Tailored Forgejo MCP server (Go): issues, comments, labels, and Actions runs/jobs/logs over stdio
Find a file
Hans Hübner bd4c5e1f0e tools: add asset upload, label create, and full comment bodies
Add three capabilities the herold workflow previously needed raw curl for:

- issue_asset_upload: multipart POST to
  /repos/{owner}/{repo}/issues/{index}/assets, reads a local file_path and
  uploads it as an attachment, returning the created asset JSON (including
  browser_download_url) so screenshots can be embedded in issue bodies/comments.
- label_create: POST /repos/{owner}/{repo}/labels; color is normalized to the
  leading-'#' form Forgejo expects (verified against the instance swagger,
  example "#00aabb").
- Full comment bodies: issue_comments_list gains a full=true flag that returns
  untruncated bodies, and a new issue_comment_get fetches one comment
  (GET /repos/{owner}/{repo}/issues/comments/{id}) with its complete body.

The multipart upload reuses the shared http client via a new Client.PostMultipart
helper, so the token auth and CheckRedirect=ErrUseLastResponse redirect-refusal
policy match the JSON write helpers exactly. The common request dispatch is
factored into Client.send.

gofmt, go vet, go build, and go test ./... all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbNidCHZtavpYBcQD2kBss
2026-07-11 18:19:03 +02:00
internal/forgejo tools: add asset upload, label create, and full comment bodies 2026-07-11 18:19:03 +02:00
.gitignore forgejo-mcp: typed MCP server over the Forgejo REST API 2026-06-27 21:38:12 +02:00
go.mod forgejo-mcp: typed MCP server over the Forgejo REST API 2026-06-27 21:38:12 +02:00
go.sum forgejo-mcp: typed MCP server over the Forgejo REST API 2026-06-27 21:38:12 +02:00
main.go forgejo-mcp: typed MCP server over the Forgejo REST API 2026-06-27 21:38:12 +02:00
README.md tools: add asset upload, label create, and full comment bodies 2026-07-11 18:19:03 +02:00
tools.go tools: add asset upload, label create, and full comment bodies 2026-07-11 18:19:03 +02:00
tools_test.go tools: add asset upload, label create, and full comment bodies 2026-07-11 18:19:03 +02:00

forgejo-mcp

A tailored Model Context Protocol server that exposes a clean, typed tool surface over the Forgejo/Gitea REST API (/api/v1). It lets an agent perform issue, comment, label, and CI (Actions) operations without driving the fj CLI or raw curl.

Built for the Forgejo instance at code.netzhansa.com (Forgejo 15.x), but works against any API-compatible Forgejo or Gitea host.

  • Single static, pure-Go binary (forgejo-mcp), no CGO.
  • MCP over stdio, using the official MCP Go SDK (github.com/modelcontextprotocol/go-sdk).
  • A thin net/http Forgejo client that centralizes host + token and surfaces the HTTP status and response body on any non-2xx.

Configuration

All configuration is via environment variables. No token is hardcoded and no keychain is read.

Variable Required Meaning
FORGEJO_HOST yes Base URL, e.g. https://code.netzhansa.com (no /api/v1).
FORGEJO_TOKEN yes API token, sent as Authorization: token <TOKEN>.
FORGEJO_OWNER no Default repository owner when a tool omits owner.
FORGEJO_REPO no Default repository name when a tool omits repo.

Every tool accepts owner and repo arguments. They may be omitted when FORGEJO_OWNER / FORGEJO_REPO are set.

Build

go build -o forgejo-mcp .

Tools

Issues:

Tool REST mapping
issue_list GET /repos/{owner}/{repo}/issues (state, labels, page)
issue_get GET /repos/{owner}/{repo}/issues/{index}
issue_create POST /repos/{owner}/{repo}/issues (title, body, labels[])
issue_edit PATCH /repos/{owner}/{repo}/issues/{index} (title/body/state)
issue_set_state PATCH /repos/{owner}/{repo}/issues/{index} (open|closed)
issue_labels_add POST /repos/{owner}/{repo}/issues/{index}/labels (id/name)
issue_labels_remove DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id}
issue_comment_create POST /repos/{owner}/{repo}/issues/{index}/comments
issue_comments_list GET /repos/{owner}/{repo}/issues/{index}/comments (full)
issue_comment_get GET /repos/{owner}/{repo}/issues/comments/{id}
issue_comment_edit PATCH /repos/{owner}/{repo}/issues/comments/{id}
issue_comment_delete DELETE /repos/{owner}/{repo}/issues/comments/{id}
issue_asset_upload POST /repos/{owner}/{repo}/issues/{index}/assets (multipart)

issue_comments_list truncates each body to ~280 chars for a compact listing; pass full=true for untruncated bodies, or issue_comment_get to read one comment in full. issue_asset_upload reads a local file (file_path) and uploads it as an attachment, returning the created asset JSON including browser_download_url to embed in Markdown as ![alt](browser_download_url).

Labels:

Tool REST mapping
repo_labels_list GET /repos/{owner}/{repo}/labels
label_create POST /repos/{owner}/{repo}/labels

Actions (CI):

Tool REST mapping
actions_runs_list GET /repos/{owner}/{repo}/actions/runs (page)
actions_run_get GET /repos/{owner}/{repo}/actions/runs/{run_id}
actions_run_jobs GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs *
actions_job_logs GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs *
actions_run_logs GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs *

* These three routes only exist on patched or newer Forgejo builds. When the running instance does not expose them the API returns 404; the tools translate that into a clear message rather than a bare error.

actions_job_logs returns the log text directly. actions_run_logs returns a zip archive, so the tool reports its byte size and content type instead of inlining the binary; use actions_run_jobs + actions_job_logs to read individual job logs as text.

Redirects are never followed

The HTTP client refuses to follow 3xx redirects. When a repository is renamed or transferred to another owner, Forgejo 301-redirects the old path to the new one. Go's default redirect policy would turn a POST/PATCH into a GET on the new location and drop the request body, so a write to a stale FORGEJO_OWNER/FORGEJO_REPO would silently succeed-as-a-read: it returns 200 and the wrong payload while creating nothing. Instead a redirect surfaces as an error naming the new Location, so a stale owner/repo is fixed rather than hidden.

Register with Claude Code

claude mcp add forgejo \
  --env FORGEJO_HOST=https://code.netzhansa.com \
  --env FORGEJO_TOKEN=your_token_here \
  --env FORGEJO_OWNER=hanshuebner \
  --env FORGEJO_REPO=herold \
  -- /absolute/path/to/forgejo-mcp

FORGEJO_OWNER / FORGEJO_REPO are optional; drop them to require explicit owner/repo on every call.