diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 25be496457..aa0509ad46 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -1104,11 +1104,9 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool return attachIFC(utils.NewToolResultResource(fmt.Sprintf("successfully downloaded text file (SHA: %s)%s", fileSHA, successNote), result)), nil, nil } - // Binary content - encode as base64 blob - blobContent := base64.StdEncoding.EncodeToString(contentBytes) result := &mcp.ResourceContents{ URI: resourceURI, - Blob: []byte(blobContent), + Blob: contentBytes, MIMEType: contentType, } return attachIFC(utils.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)%s", fileSHA, successNote), result)), nil, nil diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index c315b41e59..10aef42d67 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -84,16 +84,13 @@ func Test_GetFileContents(t *testing.T) { GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"), GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - // Base64 encode the content as GitHub API does - encodedContent := base64.StdEncoding.EncodeToString(mockRawContent) fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Type: github.Ptr("file"), - Content: github.Ptr(encodedContent), - Size: github.Ptr(len(mockRawContent)), - Encoding: github.Ptr("base64"), + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Type: github.Ptr("file"), + Content: github.Ptr(string(mockRawContent)), + Size: github.Ptr(len(mockRawContent)), } contentBytes, _ := json.Marshal(fileContent) _, _ = w.Write(contentBytes) @@ -144,7 +141,7 @@ func Test_GetFileContents(t *testing.T) { expectError: false, expectedResult: mcp.ResourceContents{ URI: "repo://owner/repo/refs/heads/main/contents/test.png", - Blob: []byte(base64.StdEncoding.EncodeToString([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"))), + Blob: []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"), MIMEType: "image/png", }, }, @@ -180,7 +177,7 @@ func Test_GetFileContents(t *testing.T) { expectError: false, expectedResult: mcp.ResourceContents{ URI: "repo://owner/repo/refs/heads/main/contents/document.pdf", - Blob: []byte(base64.StdEncoding.EncodeToString([]byte("%PDF-1.4 fake pdf content"))), + Blob: []byte("%PDF-1.4 fake pdf content"), MIMEType: "application/pdf", }, }, @@ -449,6 +446,37 @@ func Test_GetFileContents(t *testing.T) { resource := getResourceResult(t, result) assert.Equal(t, expected, *resource) + wireBytes, err := json.Marshal(result) + require.NoError(t, err) + var wireResult struct { + Content []struct { + Type string `json:"type"` + Resource *struct { + MIMEType string `json:"mimeType"` + Text string `json:"text"` + Blob string `json:"blob"` + } `json:"resource"` + } `json:"content"` + } + require.NoError(t, json.Unmarshal(wireBytes, &wireResult)) + require.Len(t, wireResult.Content, 2) + require.Equal(t, "resource", wireResult.Content[1].Type) + require.NotNil(t, wireResult.Content[1].Resource) + wireResource := wireResult.Content[1].Resource + require.Equal(t, expected.MIMEType, wireResource.MIMEType) + + if expected.Blob != nil { + decodedBlob, err := base64.StdEncoding.DecodeString(wireResource.Blob) + require.NoError(t, err) + require.Equal(t, expected.Blob, decodedBlob) + if expected.MIMEType == "image/png" { + require.True(t, strings.HasPrefix(string(decodedBlob), "\x89PNG\r\n\x1a\n")) + } + } else { + require.Empty(t, wireResource.Blob) + require.Equal(t, expected.Text, wireResource.Text) + } + // If expectedMsg is set, verify the message text if tc.expectedMsg != "" { require.Len(t, result.Content, 2) diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index 69ce8d3f20..323cdf45d2 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -1,9 +1,7 @@ package github import ( - "bytes" "context" - "encoding/base64" "errors" "fmt" "io" @@ -225,22 +223,12 @@ func RepositoryResourceContentsHandler(resourceURITemplate *uritemplate.Template }, }, nil default: - var buf bytes.Buffer - base64Encoder := base64.NewEncoder(base64.StdEncoding, &buf) - _, err := base64Encoder.Write(content) - if err != nil { - return nil, fmt.Errorf("failed to base64 encode content: %w", err) - } - if err := base64Encoder.Close(); err != nil { - return nil, fmt.Errorf("failed to close base64 encoder: %w", err) - } - return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{ { URI: request.Params.URI, MIMEType: mimeType, - Blob: buf.Bytes(), + Blob: content, }, }, }, nil diff --git a/pkg/github/repository_resource_test.go b/pkg/github/repository_resource_test.go index cb57bae545..c1402da60d 100644 --- a/pkg/github/repository_resource_test.go +++ b/pkg/github/repository_resource_test.go @@ -2,9 +2,12 @@ package github import ( "context" + "encoding/base64" + "encoding/json" "errors" "net/http" "net/url" + "strings" "testing" "github.com/github/github-mcp-server/pkg/raw" @@ -77,7 +80,7 @@ func Test_repositoryResourceContents(t *testing.T) { mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "image/png") - _, err := w.Write([]byte("# Test Repository\n\nThis is a test repository.")) + _, err := w.Write([]byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01")) require.NoError(t, err) }), }), @@ -88,7 +91,7 @@ func Test_repositoryResourceContents(t *testing.T) { expectedResponseType: resourceResponseTypeBlob, expectedResult: &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{{ - Blob: []byte("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku"), + Blob: []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"), MIMEType: "image/png", URI: "", }}}, @@ -274,6 +277,20 @@ func Test_repositoryResourceContents(t *testing.T) { switch tc.expectedResponseType { case resourceResponseTypeBlob: require.Equal(t, tc.expectedResult.Contents[0].Blob, content.Blob) + + wireBytes, err := json.Marshal(resp) + require.NoError(t, err) + var wireResult struct { + Contents []struct { + Blob string `json:"blob"` + } `json:"contents"` + } + require.NoError(t, json.Unmarshal(wireBytes, &wireResult)) + require.Len(t, wireResult.Contents, 1) + decodedBlob, err := base64.StdEncoding.DecodeString(wireResult.Contents[0].Blob) + require.NoError(t, err) + require.Equal(t, tc.expectedResult.Contents[0].Blob, decodedBlob) + require.True(t, strings.HasPrefix(string(decodedBlob), "\x89PNG\r\n\x1a\n")) case resourceResponseTypeText: require.Equal(t, tc.expectedResult.Contents[0].Text, content.Text) default: