- Go 100%
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
|
||
|---|---|---|
| internal/forgejo | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| README.md | ||
| tools.go | ||
| tools_test.go | ||
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/httpForgejo 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 .
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.