Skip to content

Commit 08f0b27

Browse files
fix: reduce modern MCP file content payloads
1 parent fac6d74 commit 08f0b27

2 files changed

Lines changed: 100 additions & 2 deletions

File tree

‎pkg/github/repositories.go‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool
10241024
InputSchema: schema,
10251025
},
10261026
scopes.PublicRead(scopes.Repo),
1027-
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input GetFileContentsInput) (*mcp.CallToolResult, *RepositoryContentsOutput, error) {
1027+
func(ctx context.Context, deps ToolDependencies, request *mcp.CallToolRequest, input GetFileContentsInput) (*mcp.CallToolResult, *RepositoryContentsOutput, error) {
10281028
owner := input.Owner
10291029
if owner == "" {
10301030
return utils.NewToolResultError("missing required parameter: owner"), nil, nil
@@ -1101,7 +1101,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool
11011101
if fallbackUsed {
11021102
successNote = fmt.Sprintf(" Note: the provided ref '%s' does not exist, default branch '%s' was used instead.", originalRef, rawOpts.Ref)
11031103
}
1104-
const maxContentSize = 1024 * 1024 // 1MB
1104+
maxContentSize := repositoryContentSizeLimit(request)
11051105

11061106
read, respInspect, err := inspectRepositoryFile(ctx, client, owner, repo, ref, path, fileContent)
11071107
if err != nil {
@@ -1232,6 +1232,24 @@ func recordDirContentsFieldsUsage(ctx context.Context, deps ToolDependencies, fu
12321232
recordFieldsUsageFor(ctx, deps, "get_file_contents", full, filtered, sentBytes)
12331233
}
12341234

1235+
const (
1236+
legacyRepositoryContentSizeLimit = 1024 * 1024
1237+
modernRepositoryContentSizeLimit = 448 * 1024
1238+
)
1239+
1240+
// repositoryContentSizeLimit accounts for modern typed output mirroring file
1241+
// content in both MCP content blocks and structuredContent.
1242+
func repositoryContentSizeLimit(request *mcp.CallToolRequest) int {
1243+
if request == nil || request.Params == nil {
1244+
return legacyRepositoryContentSizeLimit
1245+
}
1246+
version, _ := request.Params.Meta[mcp.MetaKeyProtocolVersion].(string)
1247+
if inventory.ProtocolEraForVersion(version) == inventory.ProtocolEraModern {
1248+
return modernRepositoryContentSizeLimit
1249+
}
1250+
return legacyRepositoryContentSizeLimit
1251+
}
1252+
12351253
// ForkRepository creates a tool to fork a repository.
12361254
func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool {
12371255
return NewTool[ForkRepositoryInput, *ForkRepositoryOutput](
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"strings"
8+
"testing"
9+
10+
"github.com/github/github-mcp-server/v2/pkg/inventory"
11+
"github.com/github/github-mcp-server/v2/pkg/translations"
12+
"github.com/google/go-github/v92/github"
13+
"github.com/modelcontextprotocol/go-sdk/mcp"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
func TestGetFileContentsModernProtocolUsesTransportSafeLimit(t *testing.T) {
18+
const fileName = "medium-file.txt"
19+
fileSHA := strings.Repeat("b", 40)
20+
21+
mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
22+
GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"),
23+
GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
24+
"GET /repos/owner/repo/git/trees/refs/heads/main": mockResponse(t, http.StatusOK, &github.Tree{
25+
Entries: []*github.TreeEntry{{
26+
Path: new(fileName),
27+
Mode: new("100644"),
28+
Type: new("blob"),
29+
SHA: new(fileSHA),
30+
}},
31+
}),
32+
GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
33+
w.WriteHeader(http.StatusOK)
34+
fileContent := map[string]any{
35+
"name": fileName,
36+
"path": fileName,
37+
"sha": fileSHA,
38+
"type": "file",
39+
"size": 500 * 1024,
40+
"download_url": "https://raw.githubusercontent.com/owner/repo/main/" + fileName,
41+
}
42+
contentBytes, err := json.Marshal(fileContent)
43+
require.NoError(t, err)
44+
_, err = w.Write(contentBytes)
45+
require.NoError(t, err)
46+
},
47+
})
48+
49+
deps := BaseDeps{Client: mustNewGHClient(t, mockedClient)}
50+
tool := GetFileContents(translations.NullTranslationHelper)
51+
request := createMCPRequest(map[string]any{
52+
"owner": "owner",
53+
"repo": "repo",
54+
"path": fileName,
55+
"ref": "refs/heads/main",
56+
})
57+
request.Params.Meta = mcp.Meta{mcp.MetaKeyProtocolVersion: inventory.ProtocolVersionMultiRoundTrip}
58+
59+
result, err := tool.Handler(deps)(ContextWithDeps(context.Background(), deps), &request)
60+
require.NoError(t, err)
61+
require.False(t, result.IsError)
62+
require.Len(t, result.Content, 2)
63+
64+
resourceLink, ok := result.Content[1].(*mcp.ResourceLink)
65+
require.True(t, ok, "modern clients should receive a ResourceLink before the 1 MiB wire limit")
66+
require.Equal(t, "repo://owner/repo/refs/heads/main/contents/"+fileName, resourceLink.URI)
67+
require.Equal(t, fileName, resourceLink.Name)
68+
}
69+
70+
func TestRepositoryContentSizeLimit(t *testing.T) {
71+
modernRequest := createMCPRequest(map[string]any{})
72+
modernRequest.Params.Meta = mcp.Meta{mcp.MetaKeyProtocolVersion: inventory.ProtocolVersionMultiRoundTrip}
73+
74+
legacyRequest := createMCPRequest(map[string]any{})
75+
legacyRequest.Params.Meta = mcp.Meta{mcp.MetaKeyProtocolVersion: "2025-11-25"}
76+
77+
require.Equal(t, modernRepositoryContentSizeLimit, repositoryContentSizeLimit(&modernRequest))
78+
require.Equal(t, legacyRepositoryContentSizeLimit, repositoryContentSizeLimit(&legacyRequest))
79+
require.Equal(t, legacyRepositoryContentSizeLimit, repositoryContentSizeLimit(nil))
80+
}

0 commit comments

Comments
 (0)