Simple MCP server for Forgejo instances.
  • TypeScript 92.5%
  • JavaScript 7.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-03 17:37:37 +02:00
docs Add Forgejo MCP server for issues and wiki 2026-09-27 13:42:05 +02:00
nbproject Close #1 2026-10-02 17:31:29 +02:00
scripts Add review changes 2026-09-27 16:07:56 +02:00
src Fixed issue regarding setting milestones (tool definition doesn't allow union of multiple types here) 2026-10-03 17:37:37 +02:00
test Fixed issue regarding setting milestones (tool definition doesn't allow union of multiple types here) 2026-10-03 17:37:37 +02:00
.gitignore Close #2 2026-10-02 08:09:51 +02:00
LICENSE Add Forgejo MCP server for issues and wiki 2026-09-27 13:42:05 +02:00
package-lock.json Add Forgejo MCP server for issues and wiki 2026-09-27 13:42:05 +02:00
package.json Add review changes 2026-09-27 16:07:56 +02:00
README.md Improve readme 2026-10-03 17:16:08 +02:00
tsconfig.build.json Add Forgejo MCP server for issues and wiki 2026-09-27 13:42:05 +02:00
tsconfig.json Add Forgejo MCP server for issues and wiki 2026-09-27 13:42:05 +02:00

forgejo-mcp

MCP (Model Context Protocol) server that gives coding agents access to a Forgejo instance — issues (search, read, create, edit, comments, labels, milestones) and wiki pages (read + write).

Built and verified against https://source.16reiche.de (Forgejo 16.0.5). The API spec used for development is snapshotted in docs/swagger.json.

Setup

npm install
npm run build

Requires Node ≥ 20.

To get a forgejo-mcp command on your PATH (recommended for MCP clients):

npm run install:global     # builds, then installs into ~/.local (no sudo needed)
npm run uninstall:global   # removes it again

The prefix defaults to ~/.local (override with FORGEJO_MCP_PREFIX or --prefix <dir>); the script warns if <prefix>/bin is not on your PATH. It installs from an npm-packed tarball, so the copy is self-contained (dist/ plus runtime dependencies) and independent of this checkout.

Environment variables

Variable Required Default Purpose
FORGEJO_URL yes — Instance base URL (/api/v1 is added)
FORGEJO_TOKEN for writes — API token (Settings → Applications)
FORGEJO_TIMEOUT_MS no 30000 Per-request timeout; 0 disables it

The server exits with a clear error if FORGEJO_URL is missing — set it explicitly in your MCP client configuration (see the OpenCode example below).

Without a token, public read endpoints still work (writes will return 401). owner and repo are required tool arguments on repository-scoped tools; issue_search may omit them to search across all repositories the token can see.

Use with OpenCode

Run npm run install:global once, then register the server in ~/.config/opencode/opencode.json:

{
  "mcp": {
    "forgejo": {
      "type": "local",
      "command": ["forgejo-mcp"],
      "environment": {
        "FORGEJO_URL": "https://source.16reiche.de",
        "FORGEJO_TOKEN": "{env:FORGEJO_TOKEN}"
      },
      "timeout": 30000,
      "enabled": true
    }
  }
}

OpenCode then exposes the tools as forgejo_issue_search, forgejo_wiki_get_page, … The timeout raises OpenCode's 5 s MCP default so slower API calls are not cut off.

Any other MCP client works too — the server speaks plain stdio:

forgejo-mcp        # after install:global, or from a checkout:
node dist/index.js

Tools (16)

Tool Forgejo API Notes
wiki_list_pages GET /repos/{o}/{r}/wiki/pages
wiki_get_page GET /repos/{o}/{r}/wiki/page/{name} Returns decoded markdown
wiki_create_page POST /repos/{o}/{r}/wiki/new Content sent as base64 internally
wiki_edit_page PATCH /repos/{o}/{r}/wiki/page/{name} Replaces the whole body; omitting content keeps the current body (fetched and resent)
wiki_delete_page DELETE /repos/{o}/{r}/wiki/page/{name} Destructive
issue_search GET /repos/{o}/{r}/issues or GET /repos/issues/search repo scope when owner+repo known, else cross-repo; state defaults to all
issue_get GET /repos/{o}/{r}/issues/{index} Full body
issue_create POST /repos/{o}/{r}/issues Label names resolved to ids (repository labels first, then the organization's), milestones repo-only
issue_edit PATCH /repos/{o}/{r}/issues/{index} + PUT .../labels Only provided fields change; labels replaces the label list — names (repository + organization) validated before any mutation, partial failures reported explicitly
issue_comments_list GET /repos/{o}/{r}/issues/{index}/comments
issue_comment_create POST /repos/{o}/{r}/issues/{index}/comments
issue_comment_edit PATCH /repos/{o}/{r}/issues/{index}/comments/{id}
issue_comment_delete DELETE /repos/{o}/{r}/issues/{index}/comments/{id} Destructive
label_list GET /repos/{o}/{r}/labels + GET /orgs/{o}/labels Repository labels first, then the organization's (if the owner is an org); each label carries scope of "repo" or "org". A missing organization token scope is reported as org_labels_unavailable instead of failing
milestone_list GET /repos/{o}/{r}/milestones
milestone_set POST /repos/{o}/{r}/milestones or PATCH .../{id} Omit id to create (title required), pass id to update

Tool outputs are deliberately compact (curated fields, no raw API dumps) to keep agent context small; issue_get and wiki_get_page return full content.

Scripts

npm run build             # compile to dist/
npm run typecheck         # tsc over src + tests
npm test                  # vitest (unit + in-memory MCP integration tests)
npm run install:global    # build + install forgejo-mcp into ~/.local/bin
npm run uninstall:global  # remove the global install
npm run smoke             # read-only live test against the instance
npm run inspect           # MCP Inspector for manual exploration

npm run smoke accepts SMOKE_OWNER / SMOKE_REPO to target a specific repository (defaults to thirdparty/daisy-diff) and needs FORGEJO_URL (and optionally FORGEJO_TOKEN) in the environment.

npm run inspect starts the MCP Inspector with an ad-hoc server, so the session is read-only and the server entry cannot be edited in the UI. Pass the environment to the spawned server explicitly with -e KEY=VALUE:

npm run inspect -- -e FORGEJO_URL=https://codeberg.org -e FORGEJO_TOKEN=<token>

Design notes

  • Thin hand-written fetch client (~20 endpoints) instead of a generated SDK — the spec snapshot in docs/swagger.json is the source of truth.
  • Response types are hand-derived from the spec's #/definitions.
  • Errors are returned as MCP error results (isError: true) carrying the HTTP status and Forgejo's message — never crashes the server.
  • Missing FORGEJO_TOKEN logs a warning and allows anonymous reads (verified against the live instance) instead of failing startup.