Skip to content

Commit c9cd0fe

Browse files
fix(repositories): return raw bytes for MCP blobs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent bf47e3e commit c9cd0fe

4 files changed

Lines changed: 60 additions & 29 deletions

File tree

pkg/github/repositories.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,11 +1104,9 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool
11041104
return attachIFC(utils.NewToolResultResource(fmt.Sprintf("successfully downloaded text file (SHA: %s)%s", fileSHA, successNote), result)), nil, nil
11051105
}
11061106

1107-
// Binary content - encode as base64 blob
1108-
blobContent := base64.StdEncoding.EncodeToString(contentBytes)
11091107
result := &mcp.ResourceContents{
11101108
URI: resourceURI,
1111-
Blob: []byte(blobContent),
1109+
Blob: contentBytes,
11121110
MIMEType: contentType,
11131111
}
11141112
return attachIFC(utils.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)%s", fileSHA, successNote), result)), nil, nil

pkg/github/repositories_test.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,13 @@ func Test_GetFileContents(t *testing.T) {
8484
GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"),
8585
GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) {
8686
w.WriteHeader(http.StatusOK)
87-
// Base64 encode the content as GitHub API does
88-
encodedContent := base64.StdEncoding.EncodeToString(mockRawContent)
8987
fileContent := &github.RepositoryContent{
90-
Name: github.Ptr("README.md"),
91-
Path: github.Ptr("README.md"),
92-
SHA: github.Ptr("abc123"),
93-
Type: github.Ptr("file"),
94-
Content: github.Ptr(encodedContent),
95-
Size: github.Ptr(len(mockRawContent)),
96-
Encoding: github.Ptr("base64"),
88+
Name: github.Ptr("README.md"),
89+
Path: github.Ptr("README.md"),
90+
SHA: github.Ptr("abc123"),
91+
Type: github.Ptr("file"),
92+
Content: github.Ptr(string(mockRawContent)),
93+
Size: github.Ptr(len(mockRawContent)),
9794
}
9895
contentBytes, _ := json.Marshal(fileContent)
9996
_, _ = w.Write(contentBytes)
@@ -144,7 +141,7 @@ func Test_GetFileContents(t *testing.T) {
144141
expectError: false,
145142
expectedResult: mcp.ResourceContents{
146143
URI: "repo://owner/repo/refs/heads/main/contents/test.png",
147-
Blob: []byte(base64.StdEncoding.EncodeToString([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"))),
144+
Blob: []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"),
148145
MIMEType: "image/png",
149146
},
150147
},
@@ -180,7 +177,7 @@ func Test_GetFileContents(t *testing.T) {
180177
expectError: false,
181178
expectedResult: mcp.ResourceContents{
182179
URI: "repo://owner/repo/refs/heads/main/contents/document.pdf",
183-
Blob: []byte(base64.StdEncoding.EncodeToString([]byte("%PDF-1.4 fake pdf content"))),
180+
Blob: []byte("%PDF-1.4 fake pdf content"),
184181
MIMEType: "application/pdf",
185182
},
186183
},
@@ -449,6 +446,37 @@ func Test_GetFileContents(t *testing.T) {
449446
resource := getResourceResult(t, result)
450447
assert.Equal(t, expected, *resource)
451448

449+
wireBytes, err := json.Marshal(result)
450+
require.NoError(t, err)
451+
var wireResult struct {
452+
Content []struct {
453+
Type string `json:"type"`
454+
Resource *struct {
455+
MIMEType string `json:"mimeType"`
456+
Text string `json:"text"`
457+
Blob string `json:"blob"`
458+
} `json:"resource"`
459+
} `json:"content"`
460+
}
461+
require.NoError(t, json.Unmarshal(wireBytes, &wireResult))
462+
require.Len(t, wireResult.Content, 2)
463+
require.Equal(t, "resource", wireResult.Content[1].Type)
464+
require.NotNil(t, wireResult.Content[1].Resource)
465+
wireResource := wireResult.Content[1].Resource
466+
require.Equal(t, expected.MIMEType, wireResource.MIMEType)
467+
468+
if expected.Blob != nil {
469+
decodedBlob, err := base64.StdEncoding.DecodeString(wireResource.Blob)
470+
require.NoError(t, err)
471+
require.Equal(t, expected.Blob, decodedBlob)
472+
if expected.MIMEType == "image/png" {
473+
require.True(t, strings.HasPrefix(string(decodedBlob), "\x89PNG\r\n\x1a\n"))
474+
}
475+
} else {
476+
require.Empty(t, wireResource.Blob)
477+
require.Equal(t, expected.Text, wireResource.Text)
478+
}
479+
452480
// If expectedMsg is set, verify the message text
453481
if tc.expectedMsg != "" {
454482
require.Len(t, result.Content, 2)

pkg/github/repository_resource.go

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package github
22

33
import (
4-
"bytes"
54
"context"
6-
"encoding/base64"
75
"errors"
86
"fmt"
97
"io"
@@ -225,22 +223,12 @@ func RepositoryResourceContentsHandler(resourceURITemplate *uritemplate.Template
225223
},
226224
}, nil
227225
default:
228-
var buf bytes.Buffer
229-
base64Encoder := base64.NewEncoder(base64.StdEncoding, &buf)
230-
_, err := base64Encoder.Write(content)
231-
if err != nil {
232-
return nil, fmt.Errorf("failed to base64 encode content: %w", err)
233-
}
234-
if err := base64Encoder.Close(); err != nil {
235-
return nil, fmt.Errorf("failed to close base64 encoder: %w", err)
236-
}
237-
238226
return &mcp.ReadResourceResult{
239227
Contents: []*mcp.ResourceContents{
240228
{
241229
URI: request.Params.URI,
242230
MIMEType: mimeType,
243-
Blob: buf.Bytes(),
231+
Blob: content,
244232
},
245233
},
246234
}, nil

pkg/github/repository_resource_test.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package github
22

33
import (
44
"context"
5+
"encoding/base64"
6+
"encoding/json"
57
"errors"
68
"net/http"
79
"net/url"
10+
"strings"
811
"testing"
912

1013
"github.com/github/github-mcp-server/pkg/raw"
@@ -77,7 +80,7 @@ func Test_repositoryResourceContents(t *testing.T) {
7780
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
7881
GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
7982
w.Header().Set("Content-Type", "image/png")
80-
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
83+
_, err := w.Write([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"))
8184
require.NoError(t, err)
8285
}),
8386
}),
@@ -88,7 +91,7 @@ func Test_repositoryResourceContents(t *testing.T) {
8891
expectedResponseType: resourceResponseTypeBlob,
8992
expectedResult: &mcp.ReadResourceResult{
9093
Contents: []*mcp.ResourceContents{{
91-
Blob: []byte("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku"),
94+
Blob: []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"),
9295
MIMEType: "image/png",
9396
URI: "",
9497
}}},
@@ -274,6 +277,20 @@ func Test_repositoryResourceContents(t *testing.T) {
274277
switch tc.expectedResponseType {
275278
case resourceResponseTypeBlob:
276279
require.Equal(t, tc.expectedResult.Contents[0].Blob, content.Blob)
280+
281+
wireBytes, err := json.Marshal(resp)
282+
require.NoError(t, err)
283+
var wireResult struct {
284+
Contents []struct {
285+
Blob string `json:"blob"`
286+
} `json:"contents"`
287+
}
288+
require.NoError(t, json.Unmarshal(wireBytes, &wireResult))
289+
require.Len(t, wireResult.Contents, 1)
290+
decodedBlob, err := base64.StdEncoding.DecodeString(wireResult.Contents[0].Blob)
291+
require.NoError(t, err)
292+
require.Equal(t, tc.expectedResult.Contents[0].Blob, decodedBlob)
293+
require.True(t, strings.HasPrefix(string(decodedBlob), "\x89PNG\r\n\x1a\n"))
277294
case resourceResponseTypeText:
278295
require.Equal(t, tc.expectedResult.Contents[0].Text, content.Text)
279296
default:

0 commit comments

Comments
 (0)