Skip to content

Commit bac36c1

Browse files
committed
fix: Add SHA to get_file_contents while preserving MCP behavior (#595)
Enhance get_file_contents to include SHA information without changing the existing MCP server response format. Changes: - Add Contents API call to retrieve SHA before fetching raw content - Include SHA in resourceURI (repo://owner/repo/sha/{SHA}/contents/path) - Add SHA to success messages - Update tests to verify SHA inclusion - Maintain original behavior: text files return raw text, binaries return base64 This preserves backward compatibility while providing SHA information for better file versioning support. Closes #595
1 parent d15026b commit bac36c1

File tree

2 files changed

+57
-6
lines changed

2 files changed

+57
-6
lines changed

pkg/github/repositories.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,14 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
507507
// If the path is (most likely) not to be a directory, we will
508508
// first try to get the raw content from the GitHub raw content API.
509509
if path != "" && !strings.HasSuffix(path, "/") {
510+
// First, get file info from Contents API to retrieve SHA
511+
var fileSHA string
512+
opts := &github.RepositoryContentGetOptions{Ref: ref}
513+
fileContent, _, respContents, errContents := client.Repositories.GetContents(ctx, owner, repo, path, opts)
514+
if errContents == nil && respContents.StatusCode == http.StatusOK && fileContent != nil && fileContent.SHA != nil {
515+
fileSHA = *fileContent.SHA
516+
defer func() { _ = respContents.Body.Close() }()
517+
}
510518

511519
rawClient, err := getRawClient(ctx)
512520
if err != nil {
@@ -530,6 +538,11 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
530538

531539
var resourceURI string
532540
switch {
541+
case fileSHA != "":
542+
resourceURI, err = url.JoinPath("repo://", owner, repo, "sha", fileSHA, "contents", path)
543+
if err != nil {
544+
return nil, fmt.Errorf("failed to create resource URI: %w", err)
545+
}
533546
case sha != "":
534547
resourceURI, err = url.JoinPath("repo://", owner, repo, "sha", sha, "contents", path)
535548
if err != nil {
@@ -548,18 +561,28 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
548561
}
549562

550563
if strings.HasPrefix(contentType, "application") || strings.HasPrefix(contentType, "text") {
551-
return mcp.NewToolResultResource("successfully downloaded text file", mcp.TextResourceContents{
564+
result := mcp.TextResourceContents{
552565
URI: resourceURI,
553566
Text: string(body),
554567
MIMEType: contentType,
555-
}), nil
568+
}
569+
// Include SHA in the result metadata
570+
if fileSHA != "" {
571+
return mcp.NewToolResultResource(fmt.Sprintf("successfully downloaded text file (SHA: %s)", fileSHA), result), nil
572+
}
573+
return mcp.NewToolResultResource("successfully downloaded text file", result), nil
556574
}
557575

558-
return mcp.NewToolResultResource("successfully downloaded binary file", mcp.BlobResourceContents{
576+
result := mcp.BlobResourceContents{
559577
URI: resourceURI,
560578
Blob: base64.StdEncoding.EncodeToString(body),
561579
MIMEType: contentType,
562-
}), nil
580+
}
581+
// Include SHA in the result metadata
582+
if fileSHA != "" {
583+
return mcp.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)", fileSHA), result), nil
584+
}
585+
return mcp.NewToolResultResource("successfully downloaded binary file", result), nil
563586

564587
}
565588
}

pkg/github/repositories_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ func Test_GetFileContents(t *testing.T) {
7676
_, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`))
7777
}),
7878
),
79+
mock.WithRequestMatchHandler(
80+
mock.GetReposContentsByOwnerByRepoByPath,
81+
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
82+
w.WriteHeader(http.StatusOK)
83+
fileContent := &github.RepositoryContent{
84+
Name: github.Ptr("README.md"),
85+
Path: github.Ptr("README.md"),
86+
SHA: github.Ptr("abc123"),
87+
Type: github.Ptr("file"),
88+
}
89+
contentBytes, _ := json.Marshal(fileContent)
90+
_, _ = w.Write(contentBytes)
91+
}),
92+
),
7993
mock.WithRequestMatchHandler(
8094
raw.GetRawReposContentsByOwnerByRepoByBranchByPath,
8195
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -92,7 +106,7 @@ func Test_GetFileContents(t *testing.T) {
92106
},
93107
expectError: false,
94108
expectedResult: mcp.TextResourceContents{
95-
URI: "repo://owner/repo/refs/heads/main/contents/README.md",
109+
URI: "repo://owner/repo/sha/abc123/contents/README.md",
96110
Text: "# Test Repository\n\nThis is a test repository.",
97111
MIMEType: "text/markdown",
98112
},
@@ -107,6 +121,20 @@ func Test_GetFileContents(t *testing.T) {
107121
_, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`))
108122
}),
109123
),
124+
mock.WithRequestMatchHandler(
125+
mock.GetReposContentsByOwnerByRepoByPath,
126+
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
127+
w.WriteHeader(http.StatusOK)
128+
fileContent := &github.RepositoryContent{
129+
Name: github.Ptr("test.png"),
130+
Path: github.Ptr("test.png"),
131+
SHA: github.Ptr("def456"),
132+
Type: github.Ptr("file"),
133+
}
134+
contentBytes, _ := json.Marshal(fileContent)
135+
_, _ = w.Write(contentBytes)
136+
}),
137+
),
110138
mock.WithRequestMatchHandler(
111139
raw.GetRawReposContentsByOwnerByRepoByBranchByPath,
112140
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -123,7 +151,7 @@ func Test_GetFileContents(t *testing.T) {
123151
},
124152
expectError: false,
125153
expectedResult: mcp.BlobResourceContents{
126-
URI: "repo://owner/repo/refs/heads/main/contents/test.png",
154+
URI: "repo://owner/repo/sha/def456/contents/test.png",
127155
Blob: base64.StdEncoding.EncodeToString(mockRawContent),
128156
MIMEType: "image/png",
129157
},

0 commit comments

Comments
 (0)