Issues: Attachments not visible #106
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#106
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?
I created an issue manually in the web, uploaded a pdf attachment which contains the info that needs to be referenced. When using the MCP the attachments on the issue are not visible:
Hi @heathen711 — thanks for the report!
We've drafted a spec for fixing this. The attachments endpoint is separate from
GET /issues/{index}in the Forgejo API, and the SDK has no issue-attachment methods yet, so there's a bit of work involved.Spec: docs/plans/issue-attachments.md
The plan covers full CRUD (list, get, download, create, rename, delete) for both issue and comment attachments — 12 new MCP tools in total. It would be great if you could review the spec and let us know if the scope matches what you need, or if there are edge cases we've missed (e.g. large file handling, private-repo auth on the download URL).
Thanks again for filing this!
I was using the MCP today, and it transporting everything in base64 struck me as odd, it forced my LLM to do more work to get a single file then if I had just done a shallow clone. And then this base64 encode is included here. Can you elaborate on where that came from?
To frame where I am coming from; I have used other MCPs that just do the >10mb path always, and this works flawlessly for tool-enabled LLMs because they can simply grep the output instead of having the process the entire output (in my LLMs case, it had to write the output to file, use a cli to decode it into another file, then grep it).
Thank you
Fair pushback — thanks for bringing it up before we shipped the wrong default.
Where the base64 came from: it's the MCP protocol itself. Binary content in tool responses is carried as
BlobResourceContents(base64-encoded) wrapped inEmbeddedResource— the SDK we use (github.com/mark3labs/mcp-go) doesn't expose a path-return type, because paths don't translate once the server runs outside the client's filesystem (Docker, SSH, or the streamable-HTTP transport we're planning indocs/plans/streamable-http-transport.md). That's the constraint the spec was written against, not a preference.But your ergonomics point stands. For stdio-local usage — which is the dominant case today — a base64 blob at the 10 MiB cap is ~13 MiB of context for something the LLM could just
grepif it had a path. That's a real UX bug.Proposed amendment to the spec:
save_to_pathparameter ondownload_*_attachment. When supplied, the tool writes the bytes to that path and returns the path + metadata. When absent, it returns base64 inline (current behavior).browser_download_urlin the response so the client can choose.download_*_attachmentresponses to metadata + URL; requireinline: true(or a non-trivial size) to actually return bytes.One tool, both modes: ergonomic path-return for local stdio, protocol-correct base64 for remote transports. Does (1)+(2)+(3) cover your case?
I think 2 alone would be sufficient.
I am running it in docker remotely, so the path isn't ideal for my use case and I feel like I could see the LLM running wild and setting it all the time leaving a bunch of lost files somewhere. I also wonder how it would handle permissions for file system, which is probably why they send it over the pipe instead.
3 is what I envisioned but given the context of base64 I think 2 is the balance. MCP + curl isn't too much of a step for an LLM for large files.
Thoughts?
Agreed — and the second-order reasoning is sharp. tmpfile hygiene and cross-container permission boundaries are exactly the rabbit hole MCP is trying to keep us out of by standardising on pipe-based binary delivery; my
save_to_pathwould have walked us right into it. Dropping it.Also right that (3) becomes redundant once (2) is in: the size cap already discriminates metadata-vs-bytes without an explicit flag.
Final scope of the spec amendment:
MaxInlineDownloadBytesfrom 10 MiB → 1 MiB.browser_download_urlindownload_*_attachmentresponses (so files over the cap fall through cleanly tocurlwith the existing token).That's it. Two narrow changes, no new tool surface.
One thing I want to verify before implementation: the spec already flagged whether
Authorization: token …works on thebrowser_download_urlroute for private-repo attachments (Open Question #2). With your "MCP + curl for large files" pattern, that question becomes load-bearing — the fall-through only works if the URL accepts the token. We'll verify against a private-repo asset during implementation; if the auth header doesn't work on that route, the alternative is the API asset-content path (GET /repos/.../assets/{id}withAccept: application/octet-stream) which definitely does.Tracking the amendment as
forgejo-mcp-zhiblocking the main implementation issue. Thanks for the careful read — this is a much better design than what I would have shipped.Hi @heathen711 — the attachment tools are out as a pre-release: v2.19.0-alpha.1 (PR #109).
The behaviour matches what we agreed in this thread:
list/get/download/create/edit/delete×issue+commentdownload_*_attachmentbrowser_download_urlalways included in download responses, so files at or above the cap fall through to a direct authenticatedcurlfetch with your tokenattachmentCLI domain (./forgejo-mcp --cli list_issue_attachments --args '{...}')Demos with real captured output:
demos/issue-attachments.md,demos/comment-attachments.md.Could you give it a spin against your real-world use case (the PDF-on-private-issue scenario you originally reported) and let us know whether (a) the original "I don't see any PDF attachment" problem is fixed, and (b) the 1 MiB cap +
curlfall-through actually feels ergonomic from inside your LLM workflow? A 👍 or any rough edges either way would be very welcome — we'd like your green light before promoting to a stable 2.19.0.Thanks again for the back-and-forth that shaped the design.
✦ I found the PDF attachment (ID 1), now I need to download it to read its content.when prompted it was able to find the PDFFYI — I'm using
test/e2e/attachments.shas the reproducer for the original "I don't see any PDF attachment" symptom you reported. It exercises the v2.19.0-alpha.1 attachment tools end-to-end against a live Forgejo:create_issue→ fresh scratch issuecreate_issue_attachment→ upload base64 payloadlist_issue_attachments→ assert the upload is visible (this is the bit that was broken in #106)get_issue_attachment→ assert metadata +browser_download_urlpresentcurl -H "Authorization: token …" $browser_download_url→ sha256-verify bytes (the over-cap fall-through path)download_issue_attachment→ sha256-verify the inlineBlobResourceContents(the under-cap path)Cleanup runs via
trap cleanup EXIT, so partial-failure runs still close the test issue + delete the attachment.Run with:
If you have a moment, could you run this against your Docker-remote setup? Two specific things I'd value feedback on:
Either way, your green light is what I'm waiting on before promoting
2.19.0-alpha.1→2.19.0stable.(edit: link branch fixed — script lives on
alphauntil the release lands onmain)