Issues: Attachments not visible #106

Closed
opened 2026-04-21 17:41:57 +00:00 by heathen711 · 8 comments
heathen711 commented 2026-04-21 17:41:57 +00:00 (Migrated from codeberg.org)

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:

  > do you see the pdf attachment?

  ✦ I don't see any PDF attachment in the issue data returned from the API. The issue only contains:

     - Title and body text
     - Metadata (author, assignee, state, dates)
     - No labels or milestones
     - 0 comments

    The Forgejo API response doesn't include any attachments field.
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: ``` > do you see the pdf attachment? ✦ I don't see any PDF attachment in the issue data returned from the API. The issue only contains: - Title and body text - Metadata (author, assignee, state, dates) - No labels or milestones - 0 comments The Forgejo API response doesn't include any attachments field. ```
goern commented 2026-04-21 20:32:19 +00:00 (Migrated from codeberg.org)

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!

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](https://codeberg.org/goern/forgejo-mcp/src/branch/main/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!
heathen711 commented 2026-04-23 05:40:00 +00:00 (Migrated from codeberg.org)

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

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
goern commented 2026-04-24 14:16:02 +00:00 (Migrated from codeberg.org)

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 in EmbeddedResource — 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 in docs/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 grep if it had a path. That's a real UX bug.

Proposed amendment to the spec:

  1. Add an optional save_to_path parameter on download_*_attachment. When supplied, the tool writes the bytes to that path and returns the path + metadata. When absent, it returns base64 inline (current behavior).
  2. Lower the inline cap from 10 MiB to ~1 MiB. Always include browser_download_url in the response so the client can choose.
  3. Default download_*_attachment responses to metadata + URL; require inline: 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?

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 in `EmbeddedResource` — 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 in [`docs/plans/streamable-http-transport.md`](https://codeberg.org/goern/forgejo-mcp/src/branch/main/docs/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 `grep` if it had a path. That's a real UX bug. **Proposed amendment to the spec:** 1. Add an optional `save_to_path` parameter on `download_*_attachment`. When supplied, the tool writes the bytes to that path and returns the path + metadata. When absent, it returns base64 inline (current behavior). 2. Lower the inline cap from 10 MiB to ~1 MiB. Always include `browser_download_url` in the response so the client can choose. 3. Default `download_*_attachment` responses to metadata + URL; require `inline: 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?
heathen711 commented 2026-04-24 15:21:44 +00:00 (Migrated from codeberg.org)

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?

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?
goern commented 2026-04-26 10:43:01 +00:00 (Migrated from codeberg.org)

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_path would 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:

  1. Lower MaxInlineDownloadBytes from 10 MiB → 1 MiB.
  2. Always include browser_download_url in download_*_attachment responses (so files over the cap fall through cleanly to curl with 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 the browser_download_url route 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} with Accept: application/octet-stream) which definitely does.

Tracking the amendment as forgejo-mcp-zhi blocking the main implementation issue. Thanks for the careful read — this is a much better design than what I would have shipped.

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_path` would 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:** 1. Lower `MaxInlineDownloadBytes` from 10 MiB → 1 MiB. 2. Always include `browser_download_url` in `download_*_attachment` responses (so files over the cap fall through cleanly to `curl` with 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 the `browser_download_url` route for private-repo attachments ([Open Question #2](https://codeberg.org/goern/forgejo-mcp/src/branch/main/docs/plans/issue-attachments.md#open-questions)). 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}` with `Accept: application/octet-stream`) which definitely does. Tracking the amendment as [`forgejo-mcp-zhi`](https://codeberg.org/goern/forgejo-mcp/src/branch/main/.beads/issues.jsonl) blocking the main implementation issue. Thanks for the careful read — this is a much better design than what I would have shipped.
goern commented 2026-04-26 11:28:36 +00:00 (Migrated from codeberg.org)

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:

  • 12 new tools: list/get/download/create/edit/delete × issue + comment
  • 1 MiB inline cap on download_*_attachment
  • browser_download_url always included in download responses, so files at or above the cap fall through to a direct authenticated curl fetch with your token
  • All 12 tools registered under the attachment CLI 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 + curl fall-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.

Hi @heathen711 — the attachment tools are out as a pre-release: **[v2.19.0-alpha.1](https://codeberg.org/goern/forgejo-mcp/releases/tag/v2.19.0-alpha.1)** ([PR #109](https://codeberg.org/goern/forgejo-mcp/pulls/109)). The behaviour matches what we agreed in this thread: - 12 new tools: `list/get/download/create/edit/delete` × `issue` + `comment` - 1 MiB inline cap on `download_*_attachment` - `browser_download_url` always included in download responses, so files at or above the cap fall through to a direct authenticated `curl` fetch with your token - All 12 tools registered under the `attachment` CLI domain (`./forgejo-mcp --cli list_issue_attachments --args '{...}'`) Demos with real captured output: [`demos/issue-attachments.md`](https://codeberg.org/goern/forgejo-mcp/src/branch/main/demos/issue-attachments.md), [`demos/comment-attachments.md`](https://codeberg.org/goern/forgejo-mcp/src/branch/main/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 + `curl` fall-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.
heathen711 commented 2026-04-26 15:46:34 +00:00 (Migrated from codeberg.org)
  1. Reading the issue with the existing mcp does not include any hint to the LLM that there are attachments/assets, wonder if we can improve that for discovery?
  2. ✦ 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 PDF
  3. Got a response:
  ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
  │ ✓  download_issue_attachment (forgejo MCP Server) {"attachment_id":1,"index":11,"owner":"redacted","repo":"redacted-"}                                                                       │
  │                                                                                                                                                                                              │
  │    {"attachment":{"id":1,"name":"Qwen 3.5 27B:35BA3B Tool Calling Issues: Why It Breaks \u0026 How I Fixed It :                                                                              │
  │    r:Vllm.pdf","size":89179,"download_count":2,"created_at":"2026-04-20T16:23:01Z","uuid":"a0d019c4-6bfe-4776-86fb-2c1031c79dd5","browser_download_url":"https://redacted.redacted-/attachme │
  │    nts/a0d019c4-6bfe-4776-86fb-2c1031c79dd5"},"inline":true,"bytes_included":89179}                                                                                                          │
  │    [Tool 'download_issue_attachment' provided the following embedded resource with mime-type: application/pdf]                                                                               │
  │    [application/pdf]                                                                                                                                                                         │
  ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
  1. The LLM failed to read the base64 for some reason, doesn't even mention it until I asked:
  ✦ You're absolutely right! Looking back at the tool output, it says "inline":true,"bytes_included":26 and "inline":true,"bytes_included":42 - which means the file content was included inline in
    the response. However, the MCP tool response format appears to have the content embedded in a way that's not being displayed in the output I received.

    Let me check if there's a way to access the raw bytes from the download result. The issue is that the Forgejo MCP server is returning the file content but it's not being properly surfaced in
     the tool response.
  1. Download via url gets a 404
1. Reading the issue with the existing mcp does not include any hint to the LLM that there are attachments/assets, wonder if we can improve that for discovery? 2. `✦ 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 PDF 3. Got a response: ``` ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ ✓ download_issue_attachment (forgejo MCP Server) {"attachment_id":1,"index":11,"owner":"redacted","repo":"redacted-"} │ │ │ │ {"attachment":{"id":1,"name":"Qwen 3.5 27B:35BA3B Tool Calling Issues: Why It Breaks \u0026 How I Fixed It : │ │ r:Vllm.pdf","size":89179,"download_count":2,"created_at":"2026-04-20T16:23:01Z","uuid":"a0d019c4-6bfe-4776-86fb-2c1031c79dd5","browser_download_url":"https://redacted.redacted-/attachme │ │ nts/a0d019c4-6bfe-4776-86fb-2c1031c79dd5"},"inline":true,"bytes_included":89179} │ │ [Tool 'download_issue_attachment' provided the following embedded resource with mime-type: application/pdf] │ │ [application/pdf] │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` 4. The LLM failed to read the base64 for some reason, doesn't even mention it until I asked: ``` ✦ You're absolutely right! Looking back at the tool output, it says "inline":true,"bytes_included":26 and "inline":true,"bytes_included":42 - which means the file content was included inline in the response. However, the MCP tool response format appears to have the content embedded in a way that's not being displayed in the output I received. Let me check if there's a way to access the raw bytes from the download result. The issue is that the Forgejo MCP server is returning the file content but it's not being properly surfaced in the tool response. ``` 5. Download via url gets a 404
goern commented 2026-04-27 07:00:41 +00:00 (Migrated from codeberg.org)

FYI — I'm using test/e2e/attachments.sh as 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:

  1. create_issue → fresh scratch issue
  2. create_issue_attachment → upload base64 payload
  3. list_issue_attachments → assert the upload is visible (this is the bit that was broken in #106)
  4. get_issue_attachment → assert metadata + browser_download_url present
  5. curl -H "Authorization: token …" $browser_download_url → sha256-verify bytes (the over-cap fall-through path)
  6. download_issue_attachment → sha256-verify the inline BlobResourceContents (the under-cap path)

Cleanup runs via trap cleanup EXIT, so partial-failure runs still close the test issue + delete the attachment.

Run with:

FORGEJO_URL=https://codeberg.org \
FORGEJO_ACCESS_TOKEN=... \
OWNER=<your-org> REPO=<your-repo> \
  ./test/e2e/attachments.sh

If you have a moment, could you run this against your Docker-remote setup? Two specific things I'd value feedback on:

  • Does it pass against your real-world repo (especially if you can point it at a private repo to exercise the auth path beyond the public happy-path I tested against)?
  • Is the script structure useful as a template, or are there assertions you'd add for your workflow?

Either way, your green light is what I'm waiting on before promoting 2.19.0-alpha.12.19.0 stable.

(edit: link branch fixed — script lives on alpha until the release lands on main)

FYI — I'm using [`test/e2e/attachments.sh`](https://codeberg.org/goern/forgejo-mcp/src/branch/alpha/test/e2e/attachments.sh) as 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: 1. `create_issue` → fresh scratch issue 2. `create_issue_attachment` → upload base64 payload 3. `list_issue_attachments` → assert the upload is visible (this is the bit that was broken in #106) 4. `get_issue_attachment` → assert metadata + `browser_download_url` present 5. `curl -H "Authorization: token …" $browser_download_url` → sha256-verify bytes (the over-cap fall-through path) 6. `download_issue_attachment` → sha256-verify the inline `BlobResourceContents` (the under-cap path) Cleanup runs via `trap cleanup EXIT`, so partial-failure runs still close the test issue + delete the attachment. Run with: ```bash FORGEJO_URL=https://codeberg.org \ FORGEJO_ACCESS_TOKEN=... \ OWNER=<your-org> REPO=<your-repo> \ ./test/e2e/attachments.sh ``` If you have a moment, could you run this against your Docker-remote setup? Two specific things I'd value feedback on: - Does it pass against your real-world repo (especially if you can point it at a *private* repo to exercise the auth path beyond the public happy-path I tested against)? - Is the script structure useful as a template, or are there assertions you'd add for your workflow? Either way, your green light is what I'm waiting on before promoting `2.19.0-alpha.1` → `2.19.0` stable. *(edit: link branch fixed — script lives on `alpha` until the release lands on `main`)*
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
agentic-forges/forgejo-mcp#106
No description provided.