- Rust 75.5%
- Just 13.9%
- Dockerfile 7.2%
- Nushell 3.4%
|
All checks were successful
Check / fmt + clippy + build + tests (push) Successful in 1m36s
Build binary (Linux) / Build and publish forgejo-mcp binary (Linux x86_64) (push) Successful in 13m44s
Build binary (Windows) / Build and publish forgejo-mcp binary (Windows x86_64) (push) Successful in 14m14s
Build binary (Linux static musl) / Build and publish forgejo-mcp binary (Linux x86_64 static musl) (push) Successful in 14m40s
Reviewed-on: #10 |
||
|---|---|---|
| .forgejo/workflows | ||
| oci-build | ||
| src | ||
| .dockerignore | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| justfile | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| README.md | ||
forgejo-mcp
A Model Context Protocol server that exposes Forgejo operations to MCP clients. It is built on the forgejo-cli crates (fj-client, fj-core), so authentication and API calls reuse the same code path as the fj CLI rather than reimplementing them.
What it does
Speaks MCP over stdio (default) or Streamable HTTP (MCP_TRANSPORT=http, see Transports) and exposes a small set of tools:
| Tool | Arguments | Returns |
|---|---|---|
whoami |
none | The authenticated user for the configured host. |
get_repo |
owner, repo |
Repository metadata. |
list_issues |
owner, repo, state? (open/closed/all, default open) |
One page of issues. |
get_issue |
owner, repo, index |
A single issue by number. |
Each tool returns the raw forgejo-api struct as pretty JSON text.
Authentication
Credentials come from the fj CLI's keys.json (managed by directories under the Cyborus/forgejo-cli data dir). Authenticate once with the CLI:
fj auth add-key --host dev.a8n.run
The server loads keys.json on each call, so token refreshes performed by the CLI are picked up automatically. Hosts with no stored login fall back to an unauthenticated client.
Configuration
| Variable | Default | Purpose |
|---|---|---|
FORGEJO_HOST |
https://dev.a8n.run |
Base URL of the target Forgejo instance. |
RUST_LOG |
info |
Log filter (logs go to stderr; over stdio, stdout carries the JSON-RPC stream). |
MCP_TRANSPORT |
stdio |
Transport to start: stdio or http. Unset or unrecognized values use stdio. |
MCP_HTTP_ADDR |
127.0.0.1:8080 |
HTTP-mode bind address. Override to 0.0.0.0:<port> for a container or daemon. |
MCP_HTTP_TOKEN |
(none) | HTTP-mode bearer token. Required in http mode: the server refuses to start without it. |
Transports
The serve subcommand selects its transport from MCP_TRANSPORT at startup.
stdio (default)
The server reads JSON-RPC from stdin and writes it to stdout; an MCP client launches one process per session and it exits when the client disconnects. This is the mode the client registration example below uses.
Streamable HTTP
MCP_TRANSPORT=http runs a long-lived, network-reachable process that serves the Streamable HTTP MCP endpoint at POST/GET/DELETE {MCP_HTTP_ADDR}/mcp. It binds MCP_HTTP_ADDR and shuts down gracefully on SIGINT/SIGTERM.
Because the server loads stored Forgejo credentials from the fj CLI's keys.json, any reachable endpoint grants authenticated Forgejo access. The HTTP listener is therefore authenticated by default and fails closed: every request must carry Authorization: Bearer <MCP_HTTP_TOKEN> or it gets 401 Unauthorized, and the server refuses to start when MCP_HTTP_TOKEN is unset or empty. TLS is not terminated in-process; put a reverse proxy in front for a public deployment.
$env.FORGEJO_HOST = "https://dev.a8n.run"
$env.MCP_TRANSPORT = "http"
$env.MCP_HTTP_ADDR = "127.0.0.1:8080"
$env.MCP_HTTP_TOKEN = "a-long-random-secret"
./target/release/forgejo-mcp serve
Build and run
cargo build --release
$env.FORGEJO_HOST = "https://dev.a8n.run"
./target/release/forgejo-mcp serve
The server reads JSON-RPC from stdin and writes to stdout, so run it directly only for a smoke test. Normally an MCP client launches it. A bare forgejo-mcp with no subcommand prints usage and exits; the stdio server lives behind serve.
CLI
forgejo-mcp --help # usage for all subcommands
forgejo-mcp --version # forgejo-mcp <semver> (<git-hash>, built <build-date>)
forgejo-mcp serve # start the MCP stdio server
forgejo-mcp version # same banner as --version (alias: version show)
forgejo-mcp version check # ask the registry whether a newer build is published
forgejo-mcp version update # download + verify + self-replace the running binary
version update accepts --dry-run (probe + report, no download), --force (reinstall even when not newer), and --url <base> (override the baked registry URL). It verifies a published .sha256 sidecar when present (hard-fail on mismatch, skip when absent) and smoke-tests the swapped binary with --version before reporting success.
Register with an MCP client
Example claude registration (stdio transport):
claude mcp add forgejo --env FORGEJO_HOST=https://dev.a8n.run -- /absolute/path/to/forgejo-mcp/target/release/forgejo-mcp serve
For a running Streamable HTTP server, register the URL transport and pass the bearer token as a header:
claude mcp add --transport http forgejo http://127.0.0.1:8080/mcp --header "Authorization: Bearer a-long-random-secret"
Adding a tool
- Add a
#[derive(Deserialize, JsonSchema)]params struct insrc/server.rs. - Add a
#[tool]method on the#[tool_router]impl that builds the client withself.api(), calls the matchingfj_core::*wrapper, and returnsjson(&result). - If the operation does not yet exist in
fj-core, add it there first rather than callingforgejo-apidirectly, so the CLI and the MCP server stay in sync.
Development
This repo uses a justfile for the standard dev workflow. Run just (or just --list) to see every recipe.
just install-hooks # one-time per clone: install the pre-commit hook
just check # fmt + clippy + build + builder-stage docker compile
just test # cargo test
just build # release binary
just build-docker-export # extract the dynamic glibc Linux binary via the OCI build
just build-static # build the fully static musl Linux binary via Docker
just build-windows # cross-compile the Windows .exe
just create-release minor # bump version, push release branch, open the PR via fj
just pre-commit runs the same fmt + clippy + build + test steps as .forgejo/workflows/check.yml, inside the rust-builder-glibc image so the toolchain matches CI. Conventions follow the a8n-run/governance repo; see CLAUDE.md for forgejo-mcp-specific notes.
Releases
CI publishes binaries to the Forgejo Generic Packages registry on every push to main and on v* tags:
| Artifact | Target | Linking |
|---|---|---|
forgejo-mcp-linux-x86_64 |
x86_64-unknown-linux-gnu |
Dynamic glibc (needs libssl3 at runtime). |
forgejo-mcp-linux-x86_64-static |
x86_64-unknown-linux-musl |
Fully static (vendored OpenSSL/libgit2/libssh2; runs on any Linux). |
forgejo-mcp-windows-x86_64.exe |
x86_64-pc-windows-gnu |
mingw cross-compile. |
just create-release <major|minor|hotfix> opens the release PR; once merged, .forgejo/workflows/create-release.yml tags and publishes automatically.
Other implementations
Unrelated projects that also implement an MCP server for Forgejo under the same name:
License
Dual-licensed under Apache-2.0 OR MIT (LICENSE-APACHE, LICENSE-MIT), matching the forgejo-cli crates this depends on.