- Python 88.3%
- Nix 11.7%
Python + FastMCP + httpx. 59 curated tools (profile, keys, repos, files, branches/tags, issues, PRs, releases, social, notifications) plus a raw `api` escape hatch for the rest of /api/v1. Instance-agnostic: base_url, token and timeout from config.toml or FORGEJO_* env vars. Transports: stdio (default), --http (streamable-http), --sse. Nix flake with nixosModules.default running a hardened systemd service. Co-Authored-By: Eva |
||
|---|---|---|
| nix | ||
| src/forgejomcp | ||
| .env.example | ||
| .gitignore | ||
| config.example.toml | ||
| flake.lock | ||
| flake.nix | ||
| pyproject.toml | ||
| README.md | ||
forgejo-mcp
A general-purpose MCP server for any Forgejo (or Gitea-compatible) instance — full control of one account over the REST API. Python + FastMCP + httpx, packaged as a Nix flake. Nothing is hardcoded: instance, token and timeout all come from config or environment.
Setup
-
On your Forgejo: Settings → Applications → Generate New Token. Grant read/write on the scopes you want controlled (all of them for full account control).
-
Config:
mkdir -p ~/.config/forgejo-mcp cp config.example.toml ~/.config/forgejo-mcp/config.toml chmod 600 ~/.config/forgejo-mcp/config.toml # the token lives thereFill in
base_urlandtoken. Alternatively skip the file entirely and setFORGEJO_BASE_URL+FORGEJO_TOKENin the environment (env vars win over the file). A custom config path can be passed as the first CLI argument or viaFORGEJO_MCP_CONFIG. -
Register in Claude Code:
claude mcp add --scope user forgejo -- nix run ~/dev/eva/forgejo-mcpRestart the session; sanity check: call
whoami.
Multiple instances: register the server twice under different names, each with its own config path argument.
Transports
stdio by default. --http serves streamable-http (MCP endpoint at
http://$FASTMCP_HOST:$FASTMCP_PORT/mcp, default 127.0.0.1:8000);
--sse serves legacy SSE. Register a remote server with:
claude mcp add --transport http forgejo http://127.0.0.1:8807/mcp
NixOS module
Exposes nixosModules.default (aka nixosModules.forgejo-mcp) — runs the
server as a hardened systemd service (DynamicUser, ProtectSystem=strict,
no caps) with the streamable-http transport:
{
inputs.forgejo-mcp.url = "git+ssh://<host>/<org>/forgejo-mcp.git";
imports = [ inputs.forgejo-mcp.nixosModules.default ];
services.forgejo-mcp = {
enable = true;
baseUrl = "https://forgejo.example.com";
host = "127.0.0.1";
port = 8807;
environmentFile = "/run/secrets/forgejo-mcp.env"; # FORGEJO_TOKEN=...
};
}
Options: baseUrl, host, port, openFirewall, environmentFile,
extraEnvironment, package.
Tools
| Profile | whoami, update_profile, set_avatar, list_emails, add_email, remove_email |
| Keys | list_ssh_keys, add_ssh_key, delete_ssh_key, list_gpg_keys, add_gpg_key, delete_gpg_key |
| Repos | list_repos, search_repos, get_repo, create_repo, edit_repo (incl. topics), delete_repo (needs confirm="owner/repo"), fork_repo |
| Files | list_files, read_file, write_file, delete_file, commit_files (multi-file commit), list_commits |
| Branches & tags | list_branches, create_branch, delete_branch, list_tags, create_tag, delete_tag |
| Issues | list_issues (repo-scoped or instance-wide search), get_issue (with comments), create_issue, edit_issue, comment, edit_comment, delete_comment, list_labels, create_label, set_issue_labels |
| PRs | list_prs, get_pr (optional diff), create_pr, merge_pr, review_pr |
| Releases | list_releases, create_release, delete_release |
| Social | star, list_starred, watch, follow, list_follows, search_users |
| Notifications | notifications, mark_notifications_read |
| Orgs | list_orgs |
| Everything else | api — raw call to any /api/v1 endpoint (milestones, webhooks, wiki, actions, migrations, teams, reactions, …); see {base_url}/api/swagger |
Responses are slimmed: Forgejo's verbose payloads are trimmed to the fields that matter, so tool output stays readable and cheap.
Development
nix develop # shell with python + mcp + httpx
python -m forgejomcp.server ./config.toml
nix build # package