Describe the bug
get_file_contents double-base64-encodes every binary file it returns. The blob field on the wire contains base64(base64(bytes)) instead of base64(bytes).
The practical effect is that no image fetched via get_file_contents is usable. Clients that forward the blob to a model API get a hard rejection, because after the single (correct) decode the payload is still ASCII base64 text rather than image bytes. Against the Copilot API this surfaces as:
CAPIError: 400 The image data you provided does not represent a valid image.
Please check your input and try again with one of the supported image formats
['image/jpeg', 'image/png', 'image/gif', 'image/webp'].
In agentic clients this is worse than a one-off failure: the corrupt payload persists in conversation history, so every subsequent turn re-sends it and fails identically. The session becomes unusable and the only escape is discarding the turn. We have multiple internal users hitting this, one describing it as "you've poisoned the entire session and you can no longer move forward."
Affected version
Reproduced against the hosted server at https://api.githubcopilot.com/mcp/ on 2026-08-18. The defect is present in current main (pkg/github/repositories.go).
Steps to reproduce the behavior
Call get_file_contents on any binary file — this example uses a public 330-byte PNG:
curl -s -X POST https://api.githubcopilot.com/mcp/ \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_file_contents",
"arguments":{"owner":"anomalyco","repo":"opencode",
"path":"packages/identity/mark-512x512.png"}}}'
Returned blob (truncated):
aVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQWdBQUFBSUFBZ01BQUFDSkZqeHBB…
Decoding it:
decode once → b'iVBORw0KGgoAAAAN' ← ASCII text, not image bytes
decode twice → b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR' ← the actual PNG
The file is 330 bytes, but the blob is 588 characters — consistent with base64 being applied twice.
Expected vs actual behavior
Expected: a single base64 decode of blob yields the raw file bytes.
Actual: two decodes are required. Any spec-compliant client decoding once receives ASCII base64 text labeled image/png.
Root cause
pkg/github/repositories.go (~line 1105):
// Binary content - encode as base64 blob
blobContent := base64.StdEncoding.EncodeToString(contentBytes)
result := &mcp.ResourceContents{
URI: resourceURI,
Blob: []byte(blobContent), // ← already base64, then encoded again
MIMEType: contentType,
}
mcp.ResourceContents.Blob is typed []byte:
// modelcontextprotocol/go-sdk v1.7.0 — mcp/content.go:302
type ResourceContents struct {
URI string `json:"uri"`
MIMEType string `json:"mimeType,omitempty"`
Text string `json:"text,omitempty"`
Blob []byte `json:"blob,omitzero"`
Meta Meta `json:"_meta,omitempty"`
}
Go's encoding/json marshals a []byte field as base64 automatically, and there is no custom MarshalJSON on ResourceContents. So the manual EncodeToString is applied first, and the marshaler encodes that ASCII string a second time.
This looks like a migration artifact: under mark3labs/mcp-go, Blob was a string, so manually encoding was correct. Blob has been []byte since modelcontextprotocol/go-sdk v1.0.0, which turned the same line into double encoding.
Suggested fix
Hand the marshaler the raw bytes and let it do the single encode:
result := &mcp.ResourceContents{
URI: resourceURI,
Blob: contentBytes,
MIMEType: contentType,
}
Worth adding a regression test that round-trips a known PNG through the tool and asserts that a single decode of blob starts with the PNG magic bytes 89 50 4E 47. A test asserting only "blob is non-empty" or comparing against a re-encoded string will pass while double-encoded.
Note on a red herring
We originally chased this through a file that happens to be a git symlink (sdks/vscode/images/icon.png → ../../../packages/identity/mark-512x512.png). The symlink is not the cause. The Contents API resolves symlinks transparently, and http.DetectContentType correctly identifies the resolved PNG. Both the symlink path and the real file return byte-identical double-encoded blobs. Any binary file reproduces this.
Describe the bug
get_file_contentsdouble-base64-encodes every binary file it returns. Theblobfield on the wire containsbase64(base64(bytes))instead ofbase64(bytes).The practical effect is that no image fetched via
get_file_contentsis usable. Clients that forward the blob to a model API get a hard rejection, because after the single (correct) decode the payload is still ASCII base64 text rather than image bytes. Against the Copilot API this surfaces as:In agentic clients this is worse than a one-off failure: the corrupt payload persists in conversation history, so every subsequent turn re-sends it and fails identically. The session becomes unusable and the only escape is discarding the turn. We have multiple internal users hitting this, one describing it as "you've poisoned the entire session and you can no longer move forward."
Affected version
Reproduced against the hosted server at
https://api.githubcopilot.com/mcp/on 2026-08-18. The defect is present in currentmain(pkg/github/repositories.go).Steps to reproduce the behavior
Call
get_file_contentson any binary file — this example uses a public 330-byte PNG:Returned blob (truncated):
Decoding it:
The file is 330 bytes, but the blob is 588 characters — consistent with base64 being applied twice.
Expected vs actual behavior
Expected: a single base64 decode of
blobyields the raw file bytes.Actual: two decodes are required. Any spec-compliant client decoding once receives ASCII base64 text labeled
image/png.Root cause
pkg/github/repositories.go(~line 1105):mcp.ResourceContents.Blobis typed[]byte:Go's
encoding/jsonmarshals a[]bytefield as base64 automatically, and there is no customMarshalJSONonResourceContents. So the manualEncodeToStringis applied first, and the marshaler encodes that ASCII string a second time.This looks like a migration artifact: under
mark3labs/mcp-go,Blobwas astring, so manually encoding was correct.Blobhas been[]bytesincemodelcontextprotocol/go-sdkv1.0.0, which turned the same line into double encoding.Suggested fix
Hand the marshaler the raw bytes and let it do the single encode:
Worth adding a regression test that round-trips a known PNG through the tool and asserts that a single decode of
blobstarts with the PNG magic bytes89 50 4E 47. A test asserting only "blob is non-empty" or comparing against a re-encoded string will pass while double-encoded.Note on a red herring
We originally chased this through a file that happens to be a git symlink (
sdks/vscode/images/icon.png→../../../packages/identity/mark-512x512.png). The symlink is not the cause. The Contents API resolves symlinks transparently, andhttp.DetectContentTypecorrectly identifies the resolved PNG. Both the symlink path and the real file return byte-identical double-encoded blobs. Any binary file reproduces this.