get_file_content returns base64-encoded content instead of plain text #114
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/OpenSpec
Kind/Security
Kind/Testing
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
RFC - Request For Comments
Reviewed/Confirmed
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Status/Abandoned
Status/Blocked
Status/Need More Info
hermes-attempted
hermes-needs-clarification
hermes-ready
hermes-review
hermes-wip
human-required
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
agentic-forges/forgejo-mcp#114
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
get_file_contentcallsGetContentsfrom the Forgejo SDK, which fetches file content via the/repos/{owner}/{repo}/contents/{filepath}endpoint. That API returns aContentsResponsewith the file content base64-encoded. The MCP tool forwards this as-is, so callers receive a JSON blob containing base64 rather than the file's plain text.For LLM/MCP usage this is the wrong default — the natural form for reading a text file is plain text, not base64. The caller then has to decode it themselves, which is awkward in an agentic context.
This is the read-side counterpart to #72 (
create_file/update_filebase64 fix, merged in v2.18.0). Same SDK family, opposite direction.Proposed fix
Switch
GetFileContentFnfromGetContentstoGetFile(SDK method, hits/repos/{owner}/{repo}/raw/{filepath}), which returns[]byte— raw bytes, no base64 layer.To preserve backward compatibility for clients that depend on the current base64 response, add an optional boolean parameter
as_binary(defaultfalse):Behavior:
WHEN
get_file_contentis called withowner,repo,ref,filePath(noas_binary)→ THEN returns plain-text content via
GetFileWHEN
get_file_contentis called with the same arguments andas_binary=true→ THEN returns the base64-encoded
ContentsResponseviaGetContents(current behavior)Breaking change note: the default behavior changes. Clients that currently parse the raw
encoding: base64JSON response without settingas_binary=truewill receive plain text instead. It is not clear how widespread that dependency is. If this is unacceptable, the fallback design isas_text=trueas an opt-in parameter (base64 remains the default), but that forces every new caller to know about the flag — which seems like the wrong default for an MCP tool used by LLMs.I am happy to defer to your judgment on which approach fits best, including a potential major version bump if you prefer to treat this as a breaking API change.
Working implementation
A working implementation is available at
BrilliantKahn/forgejo-mcp, branchfeat/get-file-plain-text.Changes are confined to
operation/repo/file.goandoperation/repo/file_test.go. The branch has been running in production on an internal MCP deployment since 2026-05-05. All tests pass (5 tests total, 3 new:TestGetFileContentFn_ReturnsPlainText,TestGetFileContentFn_AsBinaryReturnsContentsResponse,TestGetFileContentFn_DefaultIsPlainText).This is a request for feedback on the approach — not a PR announcement. I want to confirm the parameter name, default choice, and breaking-change posture before opening a PR.
SDK choice rationale
GetFileoverGetFileReader:GetFilereturns[]bytedirectly, converting cleanly tostringforto.TextResult().GetFileReaderreturnsio.ReadCloserand requiresio.ReadAll+Close, adding complexity without benefit for non-streaming file reads.No binary-file detection is implemented: the caller is responsible for interpreting raw bytes when
as_binaryis omitted for binary files. This keeps the implementation simple and consistent with how other tools handle output format.Hey @BrilliantKahn, thanks for opening the issues. Would you like to open a PR too, so we can have others chime in?!
Thanks for the writeup, and double thanks for running it in production before opening the issue — that's the kind of legwork that makes this easy to say yes to.
Default plain text is the right call. Mirrors the v2.18.0 fix on the write side (#72). For an MCP tool with an LLM on the other end, base64 is the wrong default — and
as_text=trueopt-in punishes every new caller for a legacy choice we'd rather not preserve.One pushback on the parameter name: I'd not go with
as_binary. The flag isn't really about binary files.GetContentsreturns sha, encoding, links — metadata a caller might want before a follow-upupdate_file, regardless of whether the file is binary.as_binary=trueon a UTF-8 source file reads as a lie. I leanwith_metadata, withraw=falseas a fallback. Name should describe the response shape, not guess at file content. Open to your counter if you see a reason to keep it.Treating this as a minor bump (v2.20.0), same posture as #72. Release notes will flag the default flip. No v3 over a single tool.
Skipping binary-file detection is the right move too — caller's responsibility, consistent with how the rest of the tools handle output.
Open the PR against
mainfrom your branch when you're ready.feat:commit, squash. Please update the tool description so the new param surfaces in the MCP schema.Now a personal note :) I'm quite novice on developing and this was actually my first PR ever, not done more than forking before, worrying about breaking some conventions. Some developers can be harsh :)
No surprise I rely on AI for this, as you do as well I reckon. Thanks for your response anyways and your great work, really enjoying it! Take care.
Hey @BrilliantKahn, thanks for your first PR ever! Congrats on your first PR ever! I hope you are enjoying, as I want this to be an easy, not-at-all-harsh project!