Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions pkg/github/pullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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) {
Expand Down
52 changes: 52 additions & 0 deletions pkg/github/pullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"net/http"
"strings"
"testing"
"time"

Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down