From 6e8945987219d49f4fd6f07b3f9088364d72a19c Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 11 Dec 2025 18:32:33 -0800 Subject: [PATCH] Warn when large review comment payload may truncate --- pkg/github/pullrequests.go | 22 ++++++++++++-- pkg/github/pullrequests_test.go | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 661384529..20cbf9d9a 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -20,6 +20,11 @@ import ( "github.com/github/github-mcp-server/pkg/utils" ) +// Some MCP clients truncate large tool responses. This heuristic adds a leading +// warning when a marshaled payload is likely to exceed those limits so agents +// can retry with smaller pages. +const mcpLargePayloadWarningThresholdBytes = 8_000 + // PullRequestRead creates a tool to get details of a specific pull request. func PullRequestRead(getClient GetClientFn, cache *lockdown.RepoAccessCache, t translations.TranslationHelperFunc, flags FeatureFlags) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) { schema := &jsonschema.Schema{ @@ -329,12 +334,25 @@ func GetPullRequestReviewComments(ctx context.Context, client *github.Client, ca comments = filteredComments } - r, err := json.Marshal(comments) + payload, err := json.Marshal(comments) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } - return utils.NewToolResultText(string(r)), nil + if len(payload) > mcpLargePayloadWarningThresholdBytes { + warning := fmt.Sprintf( + "WARNING: pull request review comments payload is %d bytes and may be truncated by some MCP clients. If you don't see all expected comments, retry with a smaller perPage (e.g., 1–5).", + len(payload), + ) + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: warning}, + &mcp.TextContent{Text: string(payload)}, + }, + }, nil + } + + return utils.NewToolResultText(string(payload)), nil } func GetPullRequestReviews(ctx context.Context, client *github.Client, cache *lockdown.RepoAccessCache, owner, repo string, pullNumber int, ff FeatureFlags) (*mcp.CallToolResult, error) { diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index 94313d4e3..aa5de2a7b 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "net/http" + "strings" "testing" "time" @@ -12,6 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/shurcooL/githubv4" "github.com/migueleliasweb/go-github-mock/src/mock" @@ -1758,6 +1760,56 @@ func Test_GetPullRequestComments(t *testing.T) { } } +func Test_GetPullRequestReviewComments_WarnsOnLargePayload(t *testing.T) { + largeText := strings.Repeat("x", mcpLargePayloadWarningThresholdBytes+1_000) + largeComments := []*github.PullRequestComment{ + { + ID: github.Ptr(int64(999)), + Body: github.Ptr(largeText), + DiffHunk: github.Ptr(largeText), + User: &github.User{Login: github.Ptr("reviewer")}, + }, + } + + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatch( + mock.GetReposPullsCommentsByOwnerByRepoByPullNumber, + largeComments, + ), + ) + + client := github.NewClient(mockedClient) + cache := stubRepoAccessCache(githubv4.NewClient(nil), 5*time.Minute) + flags := stubFeatureFlags(map[string]bool{"lockdown-mode": false}) + _, handler := PullRequestRead(stubGetClientFn(client), cache, translations.NullTranslationHelper, flags) + + args := map[string]interface{}{ + "method": "get_review_comments", + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + } + request := createMCPRequest(args) + result, _, err := handler(context.Background(), &request, args) + + require.NoError(t, err) + require.False(t, result.IsError) + require.Len(t, result.Content, 2) + + warningContent, ok := result.Content[0].(*mcp.TextContent) + require.True(t, ok) + assert.Contains(t, warningContent.Text, "WARNING") + + dataContent, ok := result.Content[1].(*mcp.TextContent) + require.True(t, ok) + + var returnedComments []*github.PullRequestComment + err = json.Unmarshal([]byte(dataContent.Text), &returnedComments) + require.NoError(t, err) + require.Len(t, returnedComments, 1) + assert.Equal(t, largeComments[0].GetID(), returnedComments[0].GetID()) +} + func Test_GetPullRequestReviews(t *testing.T) { // Verify tool definition once mockClient := github.NewClient(nil)