Skip to content
Merged
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
4 changes: 1 addition & 3 deletions pkg/github/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 39 additions & 11 deletions pkg/github/repositories_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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",
},
},
Expand Down Expand Up @@ -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",
},
},
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 1 addition & 13 deletions pkg/github/repository_resource.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package github

import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -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
Expand Down
21 changes: 19 additions & 2 deletions pkg/github/repository_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}),
}),
Expand All @@ -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: "",
}}},
Expand Down Expand Up @@ -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:
Expand Down
Loading