Forgejo MCP Server
📦 This project has moved. Development, issues, releases, and container images now live at https://git.b4mad.industries/agentic-forges/forgejo-mcp. The Codeberg repository at
codeberg.org/goern/forgejo-mcpremains only as a read-only mirror, and its container registry no longer publishes images — pull fromgit.b4mad.industries/agentic-forges/forgejo-mcpinstead. Issue numbers are preserved across the move. Contributions from humans and AI agents are welcome at the new location.git clone https://git.b4mad.industries/agentic-forges/forgejo-mcp.gitIf you have a remote or a bookmark pointing at
forgejo.b4mad.net, that is the same forge under its previous name — renamed 2026-07-29, not moved again. The old name still resolves and serves port 2222 with the same host key, so existing clones keep working; repoint them at your leisure:git remote set-url origin \ ssh://[email protected]:2222/agentic-forges/forgejo-mcp.git ssh-keyscan -p 2222 git.b4mad.industries >> ~/.ssh/known_hosts
Connect your AI assistant to Forgejo repositories. Manage issues, pull requests, files, and more through natural language.
What It Does
Forgejo MCP Server is an integration plugin that connects Forgejo with Model Context Protocol (MCP) systems. Once configured, you can interact with your Forgejo repositories through any MCP-compatible AI assistant like Claude, Cursor, or VS Code extensions.
Example commands you can use:
- "List all my repositories"
- "Create an issue titled 'Bug in login page'"
- "Show me open pull requests in my-org/my-repo"
- "Get the contents of README.md from the main branch"
- "Show me the latest Actions workflow runs in agentic-forges/forgejo-mcp"
Quick Start
1. Install
Option A: Using Go (Recommended)
git clone https://git.b4mad.industries/agentic-forges/forgejo-mcp.git
cd forgejo-mcp
go install .
Ensure $GOPATH/bin (typically ~/go/bin) is in your PATH.
Note: you can also install straight from the module path, without cloning:
go install git.b4mad.industries/agentic-forges/forgejo-mcp/v3@latest
Option B: Download Binary
Download the latest release from the releases page.
For Arch Linux, use your favorite AUR helper:
yay -S forgejo-mcp # builds from source
yay -S forgejo-mcp-bin # uses pre-built binary
Option C: Nix / NixOS
You can run the server directly using the Nix package manager:
nix-shell -p forgejo-mcp
Or using Flakes:
nix run nixpkgs#forgejo-mcp
Note:
forgejo-mcpis currently only available in theunstablechannel and is not yet part of the 25.11 stable release.
Option D: Container image
A signed multi-stage OCI image is published on every release to
git.b4mad.industries/agentic-forges/forgejo-mcp. Run the server without building from source:
# Latest release
podman run --rm -i \
-e FORGEJO_ACCESS_TOKEN="<your personal access token>" \
git.b4mad.industries/agentic-forges/forgejo-mcp:latest \
--transport stdio --url https://your-forgejo-instance.org
# Or pin a specific version
podman run --rm -i git.b4mad.industries/agentic-forges/forgejo-mcp:v3.1.0 --help
The image is single-arch (linux/amd64), signed with cosign, and carries an
attached CycloneDX SBOM. See Verify the container image
to check the signature and provenance before running.
2. Get Your Access Token
- Log into your Forgejo instance
- Go to Settings → Applications → Access Tokens
- Create a new token with the permissions you need (repo, issue, etc.)
3. Configure Your AI Assistant
Add this to your MCP configuration file:
For stdio mode (most common):
{
"mcpServers": {
"forgejo": {
"command": "forgejo-mcp",
"args": [
"--transport", "stdio",
"--url", "https://your-forgejo-instance.org"
],
"env": {
"FORGEJO_ACCESS_TOKEN": "<your personal access token>",
"FORGEJO_USER_AGENT": "forgejo-mcp/1.0.0"
}
}
}
}
For streamable HTTP mode (recommended for remote/Claude.ai):
{
"mcpServers": {
"forgejo": {
"url": "http://localhost:8080/mcp"
}
}
}
When using streamable HTTP mode, start the server first:
forgejo-mcp --transport http --url https://your-forgejo-instance.org --token <your-token>
Multi-tenant HTTP mode (optional):
You can run a single centralized forgejo-mcp instance and let each client provide its own token via the standard HTTP Authorization header. This enables serving multiple users or agents from one server.
- Start the server (optionally without any global token):
forgejo-mcp --transport http --url https://your-forgejo-instance.org - Clients include their specific token in each request:
Authorization: token <token>(Forgejo style)Authorization: Bearer <token>(OAuth2/MCP style)- Note: The scheme (
tokenorBearer) is case-insensitive.
See demos/multi-tenant-http.md for a copy-pasteable walkthrough.
Exposing the server to a network
By default the sse and http transports listen on loopback only, so nothing outside
this machine can reach them. This follows the Model Context Protocol's guidance for
locally-run servers, and it is a change in behaviour: earlier versions listened on
every network interface. The default, localhost, binds both loopback families, so a
client that resolves localhost to either 127.0.0.1 or ::1 connects. If the port is
already taken on either family the server refuses to start, rather than serve on the
other family while some clients reach whatever holds the port. A family the machine
cannot use at all — IPv6 disabled, say — is skipped, and the startup log says so.
Pass an address rather than a name — --host 127.0.0.1 or --host ::1 — to bind that
family alone. Use it when the other family is unusable on this machine in a way the
server cannot recognise as "absent", so that it would otherwise refuse to start.
If you run the server in a container, or serve remote clients, you must now say so explicitly. The default will not accept connections from outside the machine — or, in a container, from outside the container:
- set
--hostto an address the network can reach (0.0.0.0for all interfaces, which is the usual choice inside a container); - and set
--allowed-hoststo the host names your clients use. This is required, not optional: the server refuses to start on a network-reachable address without it, rather than starting and rejecting every request.
Requests are refused with 403 Forbidden when their Host header is not one you
declared. Loopback names are always accepted on a loopback listener, so declaring a
proxy's hostname does not stop you connecting directly from the same machine.
Browser clients. A request carrying an Origin header is refused unless that
origin is listed in --allowed-origins, which is empty by default. Origins are
compared in full — scheme, host and port — because an Origin's port belongs to the
page making the request, not to this server. Requests with no Origin header at all
are unaffected, which is the normal case: an MCP client is not a browser.
Authentication on sse and http
On these transports every request must carry its own Authorization header. A request
without one is refused with 401 Unauthorized, rather than being served using the
server's own configured token. This is what the multi-tenant setup above expects
anyway, and it is why that setup is safe to expose.
On stdio the configured token still stands in for an absent header exactly as
before. Nothing about stdio changes.
If you run a single-user deployment over sse or http and want the old behaviour,
--allow-operator-token-fallback restores it. Understand what it means before you use
it: any client that can reach the port acts as the identity behind your token, without
presenting a credential. The startup log says so, loudly, whenever it is on.
For SSE mode (legacy HTTP-based):
{
"mcpServers": {
"forgejo": {
"url": "http://localhost:8080/sse"
}
}
}
When using SSE mode, start the server first:
forgejo-mcp --transport sse --url https://your-forgejo-instance.org --token <your-token>
Notification streams and the heartbeat
On sse and http the server holds a stream open to deliver its own messages
(notifications, progress, server requests). While a session is idle that stream
carries nothing, and clients, proxies and NAT devices drop a stream that stays
silent long enough; one MCP client aborts after six minutes. The server therefore
writes a JSON-RPC ping into the stream every --heartbeat interval, 30s by
default. Set --heartbeat 0 to restore the previous, silent behaviour.
Remote operation as an OAuth resource server
With --auth-mode resource-server, the http transport becomes an OAuth 2.0
resource server as the MCP authorization specification defines it. Clients no
longer send a Forgejo token. They log in at an OpenID Connect provider you choose
and send its JWT access token. The server validates that token, signs a JWT that
lives five minutes for the caller, and presents it to Forgejo through a Forgejo 16
Authorized Integration. No forge token is configured anywhere, and the caller's
access token never reaches Forgejo.
The mode needs:
- Forgejo 16.0 or newer;
- an OpenID Connect provider that issues JWT access tokens and can add a per-user
claim (default
forgejo_aud) holding the audience of that user's Authorized Integration; - a public HTTPS origin for the server, because Forgejo fetches the server's issuer documents from it;
- a signing key file: EC P-256 or P-384, Ed25519, or RSA of at least 2048 bits.
forgejo-mcp --transport http --url https://forgejo.example.org \
--host 0.0.0.0 --allowed-hosts mcp.example.org \
--auth-mode resource-server \
--authorization-server https://id.example.org \
--resource https://mcp.example.org/mcp \
--forgejo-jwt-issuer https://mcp.example.org/issuer \
--forgejo-jwt-signing-key-file /run/credentials/forgejo-mcp.service/signing-key
The mode is opt-in: without --auth-mode, everything above behaves as before. In
this mode the server answers only /mcp, the protected resource metadata, and the
discovery document and key set under the issuer path. It refuses to start on a
configuration it cannot serve safely, and the refusal names the setting to fix.
The signing key acts for every user whose integration trusts the issuer, so guard
it like the forge token it replaces.
- Operator guide: identity provider requirements, deployment order, proxy rules, key rotation, and a worked Zitadel example.
- User guide: creating the Authorized
Integration, the mandatory
subclaim rule, and configuring an MCP client.
4. Start Using It
Open your MCP-compatible AI assistant and try:
List all my repositories
Available Tools
Resources
MCP resource templates expose Forgejo entities as URI-addressable resources using the forgejo:// scheme. The URI scheme is instance-portable — the same URI form works against any Forgejo instance — and does not collide with Forgejo web links. Clients that support resources/templates/list and resources/read (Claude Code, Claude Desktop, Codex, Cursor) can resolve these URIs directly. Clients without resource-template support continue to use the tools above — no functionality is removed.
Resources do NOT replace any MCP tool — every existing list/get tool stays available; resources are an additive, URI-addressable read surface intended for auto-resolution from LLM context and for content-addressable caching of immutable entities like commits.
Resources that embed a list (issue, pr) cap the embedded array at 30 items. When truncated, the JSON payload includes a sentinel naming the corresponding list_* tool the caller should invoke for the full list.
When to use resources vs tools: prefer a resource when you have a specific sha or index in hand; prefer a tool when listing or searching.
Slash-separated titles such as Guides/Setup are useful as a subpage naming convention,
but Forgejo stores the pages in a flat list: it neither creates Guides automatically nor
records a parent-child relationship. Create the parent separately when readers need it, and
always address subsequent calls with the normalized page_name returned by create or list.
The wiki REST behavior documented here was live-tested against Forgejo
15.0.4+gitea-1.22.0; the complete MCP demo was replayed successfully against
16.0.0+gitea-1.22.0. This is the tested range, not a guarantee for every intermediate
or differently proxied deployment.
Client Compatibility
Demos
End-to-end, copy-pasteable walkthroughs of the tools above — grouped by
topic (labels, attachments, time tracking, notifications, orgs, bounded
I/O code review, transport) — live in demos/. Each
demo pairs real ./forgejo-mcp --cli invocations with the output they
produced against codeberg.org.
CLI Mode
You can invoke any tool directly from the command line without running an MCP server. This is useful for shell scripts, CI/CD pipelines, and Claude Code skills.
# List all available tools (grouped by domain)
forgejo-mcp --cli list
# Invoke a tool with JSON arguments
forgejo-mcp --cli get_issue_by_index --args '{"owner":"goern","repo":"forgejo-mcp","index":1}'
# Pipe JSON arguments via stdin
echo '{"owner":"goern","repo":"forgejo-mcp"}' | forgejo-mcp --cli list_repo_issues
# List recent workflow runs (text output)
forgejo-mcp --cli list_workflow_runs \
--args '{"owner":"goern","repo":"forgejo-mcp"}' \
--output=text
# List only failed runs
forgejo-mcp --cli list_workflow_runs \
--args '{"owner":"goern","repo":"forgejo-mcp","status":"failure"}' \
--output=text
# Run-scoped tools (list_action_run_jobs, cancel_workflow_run, ...) take the
# database run ID printed by list_workflow_runs (e.g. #2671), not the per-repo run
# number in the web URL (.../actions/runs/222). To map a web-URL number to a
# run ID, add "run_number":222 to the list_workflow_runs args.
# List jobs and inspect the tail of a failed job (Forgejo v16+)
forgejo-mcp --cli list_action_run_jobs \
--args '{"owner":"goern","repo":"forgejo-mcp","run_id":123}' \
--output=text
forgejo-mcp --cli get_action_job_logs \
--args '{"owner":"goern","repo":"forgejo-mcp","job_id":456,"max_bytes":32768}' \
--output=text
# Forgejo's run-wide ZIP log endpoint has no Range support. Enumerate jobs and
# fetch their bounded plaintext logs instead.
# List artifacts of a run, then read one artifact's metadata (no zip download)
forgejo-mcp --cli list_action_run_artifacts \
--args '{"owner":"goern","repo":"forgejo-mcp","run_id":123,"limit":30}' \
--output=text
forgejo-mcp --cli get_action_artifact \
--args '{"owner":"goern","repo":"forgejo-mcp","artifact_id":789}' \
--output=text
# List package versions for an owner, then inspect one version's files
forgejo-mcp --cli list_packages \
--args '{"owner":"OWNER","type":"container","limit":30}' \
--output=text
forgejo-mcp --cli get_package \
--args '{"owner":"OWNER","type":"container","name":"app","version":"1.0.0"}' \
--output=text
forgejo-mcp --cli list_package_files \
--args '{"owner":"OWNER","type":"container","name":"app","version":"1.0.0"}' \
--output=text
# delete_package removes one version. Do not invoke it against a registry you do not own.
# cancel_workflow_run is 204 even when the run already finished.
# delete_workflow_run only succeeds for a completed run; a live run is an error.
# Show a tool's parameters
forgejo-mcp --cli create_issue --help
# Control output format (json or text)
forgejo-mcp --cli list --output=json
forgejo-mcp --cli get_my_user_info --args '{}' --output=text
CLI mode requires the same FORGEJO_URL and FORGEJO_ACCESS_TOKEN configuration as MCP server mode. Tool results are written as JSON to stdout by default; errors go to stderr with a non-zero exit code.
Configuration Options
You can configure the server using command-line arguments or environment variables:
Command-line arguments take priority over environment variables.
The resource-server settings are refused in passthrough mode, so a configuration that sets them but forgets --auth-mode resource-server does not start.
Uploading attachments from the host filesystem
create_issue_attachment, create_comment_attachment, and
create_release_attachment accept either base64 content or a file_path on
the machine running forgejo-mcp. The path form avoids base64-expanding a large
release artifact through the MCP transport.
It is off by default, because it hands whatever drives the MCP client the
ability to read any file the server process can read — a prompt-injected agent
could upload ~/.ssh/id_ed25519 as a public release asset. Turn it on
deliberately, and prefer confining it:
export FORGEJO_MCP_ALLOW_FILE_PATH_UPLOAD=1
export FORGEJO_MCP_UPLOAD_ROOT=/home/you/build/dist # optional but recommended
With FORGEJO_MCP_UPLOAD_ROOT set, a path that resolves outside that directory
— by being absolute, by .., or through a symlink — is rejected before anything
is read. Base64 content uploads are unaffected by either variable.
Verifying Releases
Release archives are accompanied by a checksums.txt file and an optional
checksums.txt.sig produced by cosign
with the project's release keypair. Verifying both files lets you confirm
that the binary you downloaded was built by the project's release pipeline
and has not been tampered with in transit.
Heads up: cosign signing was introduced mid-2026. Tags released before signing was wired up ship without a
.sigfile — verification applies fromv2.23.xonward only, and only when theCOSIGN_PRIVATE_KEYsecret was configured at release time.
1. Install cosign
Follow the upstream cosign installation guide for your platform. Quick paths:
# Linux/macOS — pinned binary
COSIGN_VERSION=v2.4.1
curl -sSfL -o /usr/local/bin/cosign \
"https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64"
chmod +x /usr/local/bin/cosign
# macOS via Homebrew
brew install cosign
# Arch Linux
sudo pacman -S cosign
Confirm:
cosign version
2. Fetch the public key
The normative source for the cosign public key is the
op1st-emea-b4mad
GitOps repo — same source of truth that provisions the
cosign-signing-key-artifacts Secret in the op1st-pipelines namespace
where the release pipeline runs. This is the artifact-signing key that
signs release blobs (checksums.txt.sig); it is distinct from the
image-signing key cosign-signing-key-images.pub used in §5–§6 below.
Two ways to fetch it:
Branch-tip (live, follows future key rotations):
curl -sSfL -o cosign.pub \
https://codeberg.org/operate-first/op1st-emea-b4mad/raw/branch/main/manifests/applications/op1st-pipelines-tokens/cosign-signing-key-artifacts.pub
Commit-pinned (tamper-evident, recommended for CI/scripts):
curl -sSfL -o cosign.pub \
https://codeberg.org/operate-first/op1st-emea-b4mad/raw/commit/cd3715fa8283a2069a2e3e299744a7b55b1b0260/manifests/applications/op1st-pipelines-tokens/cosign-signing-key-artifacts.pub
The commit-pinned permalink hashes its content into the URL — if anyone ever rewrites the file at that commit, your download fails or mismatches. Pin to the latest commit that you trust before adopting the key in automation.
3. Download the release artifacts
Pick the tag you installed (e.g. v3.1.0) and grab the checksum file,
its signature, and the binary archive:
TAG=v3.1.0
VERSION="${TAG#v}"
BASE="https://git.b4mad.industries/agentic-forges/forgejo-mcp/releases/download/${TAG}"
curl -sSfLO "${BASE}/forgejo-mcp_${VERSION}_checksums.txt"
curl -sSfLO "${BASE}/forgejo-mcp_${VERSION}_checksums.txt.sig"
curl -sSfLO "${BASE}/forgejo-mcp_${VERSION}_linux_amd64.tar.gz" # adjust os/arch
4. Verify the signature, then the checksum
Cosign verifies that checksums.txt was signed by the holder of the
private key matching cosign.pub. Once the checksum file is trusted, a
plain sha256sum -c check confirms the archive's integrity.
# Verify checksums.txt against the signature.
cosign verify-blob \
--key cosign.pub \
--signature "forgejo-mcp_${VERSION}_checksums.txt.sig" \
"forgejo-mcp_${VERSION}_checksums.txt"
# Expected: "Verified OK"
# Verify the downloaded archive against the (now-trusted) checksums.
sha256sum --ignore-missing -c "forgejo-mcp_${VERSION}_checksums.txt"
# Expected: "<archive>: OK"
The checksum chain transitively covers the SBOMs and other per-archive
assets — verifying checksums.txt once is sufficient for everything
listed inside it.
5. Verify SLSA provenance for the release-tools image
The release-tools container image (used internally by the Tekton release pipeline) carries SLSA v1.0 provenance generated by Tekton Chains. This attestation binds the image digest to the exact PipelineRun, git commit, and builder identity that produced it — providing supply-chain provenance beyond what the cosign signature alone can attest.
Fetch the cosign-signing-key-images public key (a separate key from the
artifact-signing key above):
curl -sSfL -o cosign-images.pub \
https://codeberg.org/operate-first/op1st-emea-b4mad/raw/branch/main/manifests/applications/op1st-pipelines-tokens/cosign-signing-key-images.pub
Verify the attestation against a specific image tag:
IMAGE_TAG=v1.0.0 # substitute the release-tools tag you want to verify
cosign verify-attestation \
--type slsaprovenance \
--key cosign-images.pub \
"codeberg.org/operate-first/release-tools:${IMAGE_TAG}" \
| jq .
A successful run prints the decoded in-toto statement (JSON). Check that
predicate.buildDefinition.externalParameters.runSpec.params references the
expected git revision, and predicate.runDetails.builder.id shows the
Tekton Chains builder.
Note: SLSA provenance attestations are available from releases built after forgejo-mcp-46j (Tekton Chains support) landed. Earlier image tags carry only the cosign signature; they have no
verify-attestationpayload.
6. Verify the container image
The git.b4mad.industries/agentic-forges/forgejo-mcp application image (Option D)
is signed by the same cosign-signing-key-images key as the release-tools
image, carries an attached CycloneDX SBOM, and gets SLSA v1.0 provenance from
Tekton Chains. Reuse the cosign-images.pub key fetched above.
Verify the signature:
IMAGE_TAG=v3.1.0 # substitute the release you are pulling
cosign verify \
--key cosign-images.pub \
"git.b4mad.industries/agentic-forges/forgejo-mcp:${IMAGE_TAG}" \
| jq .
Verify the SLSA provenance attestation:
cosign verify-attestation \
--type slsaprovenance \
--key cosign-images.pub \
"git.b4mad.industries/agentic-forges/forgejo-mcp:${IMAGE_TAG}" \
| jq .
Verify and download the signed CycloneDX SBOM attestation:
cosign verify-attestation \
--type cyclonedx \
--key cosign-images.pub \
"git.b4mad.industries/agentic-forges/forgejo-mcp:${IMAGE_TAG}" \
| jq -r '.payload | @base64d | fromjson | .predicate' > forgejo-mcp.cdx.json
The SBOM is now a signed in-toto attestation (
cosign attest), not an unsignedattach sbomartifact.cosign download sbomno longer applies.
Because the publish pipeline pushes by digest and only promotes the
vX.Y.Z / latest tags after signing and SBOM attachment succeed, any tag
you can pull is guaranteed to be signed.
Troubleshooting verification
Error: no matching signatures— the.sigfile is from a different release, orcosign.pubis the wrong key. Re-download both from the same tag.Error: cannot read file: checksums.txt.sig— release predates cosign signing, or signing was skipped that run because the secret was unset. Fall back to the checksum-only check (sha256sum -c), which still detects in-transit corruption but not tampering.- Mismatch between
cosign.puband the signature — confirm you fetched the public key from a commit that includes the key in use at the time of the release. If in doubt, fetch frombranch/main.
Troubleshooting
Enable debug mode to see detailed logs:
forgejo-mcp --transport sse --url <url> --token <token> --debug
Or set the environment variable:
export FORGEJO_DEBUG=true
Custom User-Agent: If your Forgejo instance or proxy blocks the default go-http-client user agent, set a custom one:
# Via environment variable
export FORGEJO_USER_AGENT="forgejo-mcp/1.0.0"
# Or via CLI flag
forgejo-mcp --user-agent "forgejo-mcp/1.0.0" --transport sse --url <url> --token <token>
Getting Help
- Report issues — bugs, questions, feature requests
- Found a security problem? Do not use the issue tracker. See SECURITY.md for how to report it privately.
- View source code
- Chat on Matrix —
#forgejo-mcp:b4mad.net
This repository is also mirrored on Radicle — a peer-to-peer code collaboration network. Clone via:
rad clone rad:z4PdPpsH9iJQcWfqTbxpFcWaZ9zPL
For Developers
See DEVELOPER.md for build instructions, architecture overview, and contribution guidelines.
Known Issues
-
Installing from the old Codeberg module path —
go install codeberg.org/goern/forgejo-mcp/v2@lateststill resolves, against the read-only mirror, but that mirror lags behind the current release. Usegit.b4mad.industries/agentic-forges/forgejo-mcp/v3@latestinstead.This entry used to say that the new path was not installable until a release carried the renamed
go.mod. That release has happened —v3.0.0onwards declaremodule git.b4mad.industries/agentic-forges/forgejo-mcp/v3— so the new path installs normally. The earlierreplace-directive blocker (#67) is also gone;go.modno longer contains one.
Contributors
forgejo-mcp is shaped by everyone who files issues, writes code, reviews PRs, and pushes the project forward. Thank you all. 🙏
Code contributors
Community contributors
Issue reporters and discussion participants who shaped the direction of the project:
Cyborg contributors
This project also received contributions from AI coding agents — submitted as regular PRs, reviewed by humans:
Want to contribute? Open an issue or pull request — all are welcome.
License
This project is open source. See the repository for license details.