Forgejo MCP Server
MIRROR ONLY!! This Model Context Protocol (MCP) server provides tools and resources for interacting with the Forgejo (specifically Codeberg.org) REST API.
Automated security grades and vulnerability claims are hidden while MCPSafe's reviewed-publication gate is active. Registry metadata and quality metrics remain live.
Trust Score 57
Trust is stricter than security: it combines reviewed security, MCP capability risk, supply-chain metadata, quality, popularity, freshness, and penalties for unknowns.
- • Good documentation surface
- • Solid supply-chain metadata
- • Recently scanned
- • No reviewed security score
- • MCP risk traits: Runs shell commands, Filesystem access, Network/API access
Reviewed Security Publication Pending
MCPSafe treats automated scan output as internal evidence until review. Scan dates, versions, and coverage remain visible; public security scores and vulnerability claims appear only after findings pass the reviewed-publication gate.
Security Score?
Vulnerabilities Found
Quality ScoreC
Maintenance55
Popularity31
Documentation100
Available Documentation
Compatibility72
Features
Supported Transports
READMEForgejo MCP Server
View sourceForgejo 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-mcp remains only as aread-only mirror, and its container registry no longer publishes images —
pull from git.b4mad.industries/agentic-forges/forgejo-mcp instead. Issuenumbers are preserved across the move. Contributions from humans and
AI agents are welcome at the new location.
```bash
git clone https://git.b4mad.industries/agentic-forges/forgejo-mcp.git
```
If you have a remote or a bookmark pointing at forgejo.b4mad.net, that isthe 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:
```bash
git remote set-url origin \
ssh://git@git.b4mad.industries: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: go install from the module path works from the first releaseafter the module was renamed (see Known Issues):
```bash
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 binaryOption C: Nix / NixOS
You can run the server directly using the Nix package manager:
```bash
nix-shell -p forgejo-mcp
```
Or using Flakes:
```bash
nix run nixpkgs#forgejo-mcp
```
> Note: forgejo-mcp is currently only available in the unstable channel 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:v2.24.0 --help| Tag | Meaning |
|-----------------|----------------------------------------------------------------|
| vMAJOR.MINOR.PATCH | Immutable — the exact release (e.g. v2.24.0). Use in production. |
| latest | Moving — tracks the most recent release. Convenience only. |
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):
```bash
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 (token or Bearer) 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>4. Start Using It
Open your MCP-compatible AI assistant and try:
List all my repositoriesAvailable Tools
| Tool | Description |
|------|-------------|
| User | |
| get_my_user_info | Get information about the authenticated user |
| check_notifications | Check and list user notifications |
| get_notification_thread | Get detailed info on a single notification thread |
| mark_notification_read | Mark a single notification thread as read |
| mark_all_notifications_read | Acknowledge all notifications |
| list_repo_notifications | Filter notifications scoped to a single repository |
| mark_repo_notifications_read | Mark all notifications in a specific repo as read |
| search_users | Search for users |
| Repositories | |
| list_my_repos | List all repositories you own |
| get_repo | Get a single repository by owner and name |
| create_repo | Create a new repository |
| fork_repo | Fork a repository |
| edit_repo | Edit repository settings. Only fields you pass change; omitted fields are left unchanged. Providing no fields is an error. |
| search_repos | Search for repositories |
| Topics | |
| list_repo_topics | List a repository's topics. Bounded by page (default 1) + limit (default 100); returns {topics, page, limit, count}. |
| set_repo_topics | Replace all topics. topics is a required comma-separated string; an empty string clears. Invalid names are rejected before the request. |
| add_repo_topic | Add one topic |
| delete_repo_topic | Delete one topic |
| Branches | |
| list_branches | List all branches in a repository |
| create_branch | Create a new branch |
| delete_branch | Delete a branch |
| Branch Protection | |
| list_branch_protections | List a repository's branch protection rules. Bounded by page (1-based) + limit (page size); the response echoes page/limit so callers can fetch the next page. |
| get_branch_protection | Get a single rule by rule name |
| create_branch_protection | Create a rule. Requires branch_name; status_check_contexts is a comma-separated list of required checks (e.g. "ci/build,ci/test"). |
| edit_branch_protection | Edit a rule by rule name. Only fields you pass change; omitted fields are left unchanged. |
| delete_branch_protection | Delete a rule by rule name |
| Webhooks | |
| list_repo_hooks | List repository webhooks. Bounded by page (default 1) + limit (default 30, no server-imposed ceiling); returns total_count when Forgejo reports X-Total-Count. |
| get_repo_hook | Get a single repository webhook by ID |
| create_repo_hook | Create a repository webhook. The secret is accepted but never echoed in the response. |
| edit_repo_hook | Edit a repository webhook. Only fields you pass change; omitted fields are left unchanged. |
| delete_repo_hook | Delete a repository webhook by ID |
| test_repo_hook | Trigger a test delivery for a repository webhook — WARNING: triggers a live HTTP delivery |
| Files | |
| get_file_content | Get the content of a file. Optional start_line/end_line request a 1-indexed inclusive line range (clamps to file extent; ignored when with_metadata=true). |
| create_file | Create a new file |
| update_file | Update an existing file |
| delete_file | Delete a file |
| Commits | |
| list_repo_commits | List commits in a repository |
| Issues | |
| list_repo_issues | List issues in a repository (page/limit). Optional sort orders server-side: relevance, latest, oldest, recentupdate, leastupdate, mostcomment, leastcomment, nearduedate, farduedate (the last two are the due-date directions). |
| search_issues | Search issues across every repository of one owner (page/limit); returns {issues,page,limit,count,has_next,total_count} — total_count is present only when Forgejo reports X-Total-Count |
| get_issue_by_index | Get a specific issue |
| create_issue | Create a new issue. Optional labels (comma-separated names or IDs), assignees (comma-separated usernames) and milestone (numeric ID) are applied by the same request that creates the issue. |
| add_issue_labels | Add labels to an issue. labels takes comma-separated label names or numeric IDs; an unrecognised name is an error and nothing is applied. |
| remove_issue_labels | Remove labels from an issue. labels takes comma-separated label names or numeric IDs. |
| update_issue | Update an existing issue (requires numeric milestone ID). due_date sets the deadline (RFC3339); clear_due_date=true removes it. The two are mutually exclusive — setting both is an error, and omitting both leaves the deadline unchanged. set_labels replaces the issue's entire label set (names or IDs); an empty string clears every label. |
| issue_state_change | Open or close an issue |
| list_issue_dependencies | List issues the given issue depends on. Bounded by page (1-based) + limit (page size); the response echoes page/limit so callers can fetch the next page. |
| list_issue_dependents | List issues that depend on the given issue. Bounded by page (1-based) + limit (page size); the response echoes page/limit so callers can fetch the next page. |
| add_issue_dependency | Make one issue depend on another |
| remove_issue_dependency | Remove a dependency from an issue |
| list_repo_milestones | List milestones with their IDs (use with update_issue) |
| list_repo_labels | List labels with their IDs. Merges org-level labels for org-owned repos (set include_org_labels=false to opt out). Each entry carries a scope field ("repo" or "org"). |
| list_org_labels | List organization-level labels with their IDs. The assignment tools accept label names directly, so this is for discovery and for the rare name that exists in both the repo and the org scope. |
| create_repo_label | Create a repository label (name, color as 6-digit hex, optional description). Returns the numeric id; add_issue_labels also takes the name. |
| edit_repo_label | Edit a repository label (PATCH — only supplied fields change: name, color, description). |
| delete_repo_label | Delete a repository label. Refuses by default when the label is in use (reports count); set delete_mode=force to override. |
| get_repo_label | Get a single repository label by numeric id. |
| create_org_label | Create an organization-level label. Same fields as create_repo_label. |
| edit_org_label | Edit an organization-level label (PATCH semantics). |
| delete_org_label | Delete an organization-level label. In-use guard counts across visible org repos (best-effort); delete_mode=force overrides. |
| get_org_label | Get a single organization-level label by numeric id. |
| Comments | |
| list_issue_comments | List comments on an issue or PR |
| get_issue_comment | Get a specific comment |
| create_issue_comment | Add a comment to an issue or PR |
| edit_issue_comment | Edit a comment |
| delete_issue_comment | Delete a comment |
| Pull Requests | |
| list_repo_pull_requests | List pull requests in a repository |
| get_pull_request_by_index | Get a specific pull request |
| create_pull_request | Create a new pull request |
| update_pull_request | Update an existing pull request |
| list_pull_reviews | List reviews for a pull request |
| get_pull_review | Get a specific pull request review |
| list_pull_review_comments | List comments on a pull request review |
| list_pull_request_files | List changed files in a pull request (paginated). Use the returned filenames as the file_path argument to get_pull_request_diff. |
| get_pull_request_diff | Get the unified diff of a pull request. Optional file_path returns only that file's hunks (matches on either pre- or post-rename path). |
| merge_pull_request | Merge a pull request (style: merge/rebase/rebase-merge/squash; optional title/message/delete-branch/force-merge/wait-for-checks). |
| create_pull_review | Create a review on a pull request (state: APPROVED/REQUEST_CHANGES/COMMENT) with optional inline comments. |
| Actions | |
| dispatch_workflow | Trigger a workflow run via workflow_dispatch event |
| list_workflow_runs | List workflow runs with optional filtering by status, event, or SHA |
| get_workflow_run | Get details of a specific workflow run by ID |
| list_action_run_jobs | List jobs for a Forgejo v16+ workflow run with client-side page and limit bounds |
| get_action_job_logs | Read a Forgejo v16+ job log with resumable offset and max_bytes bounds; defaults to the tail |
| cancel_workflow_run | Cancel a pending or running workflow run. Already-finished runs also return success (HTTP 204); the run is left unchanged |
| delete_workflow_run | Delete a completed workflow run. A live run is an API error. Removes the run and its job logs; Forgejo marks that run's artifacts deleted |
| list_action_run_artifacts | List artifacts of a workflow run. Server-paged via page/limit (default 30, max 50); optional name filter. Envelope {artifacts, page, limit, count, total_count?} |
| get_action_artifact | Get metadata for one Actions artifact. Does not download the zip |
| Organizations | |
| search_org_teams | Search for teams in an organization |
| Time Tracking | |
| list_issue_tracked_times | List tracked time entries on an issue or PR |
| list_repo_tracked_times | List tracked time entries across a repository |
| list_my_tracked_times | List your own tracked time entries |
| add_issue_time | Log time against an issue or PR (accepts seconds or duration like 15m) |
| reset_issue_time | Delete ALL tracked time entries on an issue or PR (destructive) |
| delete_issue_time_entry | Delete a single tracked time entry by ID |
| start_issue_stopwatch | Start a stopwatch on an issue or PR |
| stop_issue_stopwatch | Stop a running stopwatch and record the elapsed time |
| cancel_issue_stopwatch | Cancel a running stopwatch without recording |
| list_my_stopwatches | List currently running stopwatches |
| Attachments | |
| list_issue_attachments | List attachments on an issue or PR |
| get_issue_attachment | Get metadata for a single issue/PR attachment |
| download_issue_attachment | Download an issue/PR attachment (inline if < 1 MiB; metadata + URL otherwise) |
| create_issue_attachment | Upload a new attachment to an issue or PR (base64 content or a file path on the MCP host) |
| edit_issue_attachment | Rename an issue/PR attachment |
| delete_issue_attachment | Delete an issue/PR attachment |
| list_comment_attachments | List attachments on an issue/PR comment |
| get_comment_attachment | Get metadata for a single comment attachment |
| download_comment_attachment | Download a comment attachment (inline if < 1 MiB; metadata + URL otherwise) |
| create_comment_attachment | Upload a new attachment to an issue/PR comment (base64 content or a file path on the MCP host) |
| edit_comment_attachment | Rename a comment attachment |
| delete_comment_attachment | Delete a comment attachment |
| Releases | |
| list_releases | List releases for a repository (page/limit + client-side state filter: all/draft/prerelease/published) |
| get_release_by_id | Get a release by numeric ID |
| get_release_by_tag | Get a release by tag name |
| get_latest_release | Get the latest non-draft, non-prerelease release |
| create_release | Create a new release (pass target_commitish to also create the tag) |
| edit_release | Update fields of an existing release (only supplied fields are sent) |
| delete_release | Delete a release by numeric ID — destructive |
| delete_release_by_tag | Delete a release by tag name — destructive, verify tag |
| list_release_attachments | List attachments on a release (response fetched in full, sliced client-side) |
| get_release_attachment | Get metadata for a single release attachment |
| download_release_attachment | Download a release attachment (inline if < 1 MiB; metadata + URL otherwise) |
| create_release_attachment | Upload a new attachment to a release (base64 content or a file path on the MCP host) |
| edit_release_attachment | Rename a release attachment |
| delete_release_attachment | Delete a release attachment — destructive |
| Wiki | |
| list_wiki_pages | List pages using page/limit; returns has_next and, when Forgejo reports X-Total-Count, total_count. total_count counts upstream's raw wiki tree entries, so it can exceed the number of pages listed on a wiki with subdirectories — an upper bound, not an exact total. |
| get_wiki_page | Read decoded Markdown; optional start_line/end_line, always returns total_lines. |
| get_wiki_revisions | List revision history using page/limit; returns has_next and total_count, the page's total revision count as reported in the response body. |
| create_wiki_page | Create a page and return its server-normalized page_name; slash-separated titles are a flat subpage naming convention (no hierarchy and no automatic parent page), and an existing title is overwritten. |
| update_wiki_page | Update content/title by normalized page_name; last writer wins. |
| delete_wiki_page | Delete a page by normalized page_name. |
| Server | |
| get_forgejo_mcp_server_version | Get the MCP server version |
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.
| URI Template | Entity | Notes |
|---|---|---|
| forgejo://owner/{owner} | application/json | User or org profile addressed by login; resolves user first, falls back to org. |
| forgejo://repo/{owner}/{repo} | application/json | Repository overview: identity + counts, no embedded lists. |
| forgejo://repo/{owner}/{repo}/commit/{sha} | Commit metadata | Immutable per sha. Returns JSON + markdown sidecar. sha must be 40 hex chars. |
| forgejo://repo/{owner}/{repo}/commit/{sha}/status | application/json | Combined CI status for a sha: aggregate state + bounded per-context statuses (cap 30, sentinel names list tool get_commit_statuses). |
| forgejo://repo/{owner}/{repo}/issue/{index} | application/json (+ text/markdown sidecar) | Issue metadata + rendered body + bounded recent comments (cap 30, sentinel names list_issue_comments). |
| forgejo://repo/{owner}/{repo}/issues{?state,labels,page,limit} | application/json | Bounded list of issues as rows — index, title, state, author, labels, assignees, milestone, comment count, timestamps, due date — and no bodies. state ∈ {open, closed, all} (default open); labels comma-separated; cap 30, sentinel names list_repo_issues. Read the single-issue resource for a body. |
| forgejo://repo/{owner}/{repo}/{kind}/{index}/comment/{id} | application/json (+ text/markdown sidecar) | Single comment by id; kind ∈ {issue, pr}. |
| forgejo://repo/{owner}/{repo}/{kind}/{index}/comments{?page,limit} | application/json | Bounded comment thread with full bodies (the single-issue resource excerpts them at 200 chars); kind ∈ {issue, pr}; cap 30, sentinel names list_issue_comments. |
| forgejo://repo/{owner}/{repo}/pr/{index} | application/json (+ text/markdown sidecar) | PR metadata, head/base refs, mergeability, bounded recent comments (cap 30, sentinel list_issue_comments) and reviews (cap 30, sentinel list_pull_reviews). |
| forgejo://repo/{owner}/{repo}/label/{id} | application/json | Single repository label by numeric id. |
| forgejo://repo/{owner}/{repo}/labels{?page,limit} | application/json | Bounded list of repository labels (cap 30, sentinel names list_repo_labels). |
| forgejo://org/{org}/labels{?page,limit} | application/json | Bounded list of organization-level labels (cap 30, sentinel names list_org_labels). |
| forgejo://repo/{owner}/{repo}/wiki/{pageName} | application/json (+ text/markdown sidecar) | Wiki page with bounded revisions and Markdown capped at 1 MiB. Use the returned normalized page_name; encode a literal / as %2F and spaces as %20 in the URI (do not double-encode an already normalized name). |
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
| Client | resources/templates/list | resources/read |
|---|---|---|
| Claude Code | supported | supported |
| Claude Desktop | supported | supported |
| Codex | supported | supported |
| Cursor (current) | supported | supported |
| Older / minimal clients | tools only | tools only |
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
# 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
# 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=textCLI 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:
| CLI Argument | Environment Variable | Description |
|--------------|---------------------|-------------|
| --url | FORGEJO_URL | Your Forgejo instance URL |
| --token | FORGEJO_ACCESS_TOKEN | Your personal access token |
| --debug | FORGEJO_DEBUG | Enable debug mode |
| --transport | - | Transport mode: stdio, sse, or http |
| --sse-port | - | Port for SSE mode (default: 8080) |
| --http-port | - | Port for streamable HTTP mode (default: 8080) |
| --host | FORGEJO_MCP_HOST | Address the sse and http transports bind to (default: localhost, reachable from this machine only, binding both 127.0.0.1 and ::1; pass one of those addresses to bind that family alone) |
| --allowed-hosts | FORGEJO_MCP_ALLOWED_HOSTS | Comma-separated Host names this server answers to; required when --host is not loopback |
| --allowed-origins | FORGEJO_MCP_ALLOWED_ORIGINS | Comma-separated web origins allowed to send an Origin header, as full origins (https://console.example.org). Empty by default |
| --allow-operator-token-fallback | FORGEJO_MCP_ALLOW_OPERATOR_TOKEN_FALLBACK | On sse/http, serve requests with no Authorization header using this server's own token. Off by default |
| --cli | - | Enter CLI mode for direct tool invocation |
| --user-agent | FORGEJO_USER_AGENT | HTTP User-Agent header (default: forgejo-mcp/<version>) |
| - | FORGEJO_MCP_ALLOW_FILE_PATH_UPLOAD | Allow file_path attachment uploads to read the host filesystem (1/true/yes/on; off by default) |
| - | FORGEJO_MCP_UPLOAD_ROOT | Confine file_path uploads to this directory (default: anywhere the process can read) |
Command-line arguments take priority over environment variables.
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 recommendedWith 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 .sig file — verificationapplies from v2.23.x onward only, and only when theCOSIGN_PRIVATE_KEY secret was configured at release time.1. Install cosign
Follow the upstream
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 cosignConfirm:
cosign version2. Fetch the public key
The normative source for the cosign public key is the
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.pubCommit-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.pubThe 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. v2.23.1) and grab the checksum file,
its signature, and the binary archive:
TAG=v2.23.1
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/arch4. 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](https://tekton.dev/docs/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.pubVerify 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-attestation payload.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=v2.24.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.jsonThe SBOM is now a signed in-toto attestation (cosign attest), not anunsignedattach 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, or cosign.pub is 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.pub` and 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 from branch/main.
Troubleshooting
Enable debug mode to see detailed logs:
forgejo-mcp --transport sse --url <url> --token <token> --debugOr set the environment variable:
export FORGEJO_DEBUG=trueCustom 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:z4PdPpsH9iJQcWfqTbxpFcWaZ9zPLFor Developers
See DEVELOPER.md for build instructions, architecture overview, and contribution guidelines.
Known Issues
- `go install ...@latest` needs a post-rename release — The Go module path
was codeberg.org/goern/forgejo-mcp/v2 until the forge move; Go resolves
modules by the path declared in go.mod, so the
git.b4mad.industries/... path only becomes installable once a release is
tagged carrying the renamed go.mod. Until then, use the clone-and-build
workflow shown in Quick Start. The old
go install codeberg.org/goern/forgejo-mcp/v2@latest still resolves against
the read-only Codeberg mirror, but that mirror lags behind the current
release — do not rely on it. The earlier replace-directive blocker
(#67) is
gone; go.mod no 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
| Contributor | Highlights |
|-------------|------------|
| goern (Christoph Görn) | Project creator and maintainer |
| Ronmi Ren | Co-creator; SSE/HTTP transport, issue blocking, CI/CD improvements, logo, Glama spec |
| twstagg (Tristin Stagg) | User agent configuration support (PR #89) |
| mattdm (Matthew Miller) | Logging improvements, FORGEJO_* migration, README, URL refactor |
| byteflavour | check_notifications + full notification management API (PR #84, #86); stateless per-request auth for HTTP/SSE transports (PR #138); NixOS installation docs (PR #146); feature requests #80, #85 |
| jesterret | Pull request reviews and comments support (PR #51) |
| appleboy | Custom SSE port support, bug fixes |
| ignasgil | remove_issue_labels tool (PR #96) |
| dmikushin (Dmitry Mikushin) | Fix string-encoded number parameter parsing from MCP clients (PR #93) |
| jiriks74 | mcp-go v0.44.0 dependency update (PR #90) |
| th (Tomi Haapaniemi) | update_pull_request tool |
| hiifong | Early bug fixes and updates |
| Lunny Xiao | Early contributions |
| techknowlogick | Early contributions |
| yp05327 | Early contributions |
| mw75 | Owner/org support for repo creation (PR #18) |
| Dax Kelson | Issue comment management (PR #34) |
| Guruprasad Kulkarni | Arch Linux AUR installation docs (PR #69) |
| Mario Wolff | Contributions |
| Massimo Fraschetti | Contributions |
| synath (David Paul Turley) | Repository-scoped token support via ServerVersion probe (PR #112); merge status-code check (PR #113); Claude Desktop Extension (.mcpb) packaging (PR #118) |
| BrilliantKahn | get_file_content plain-text default (PR #116); list_repo_contents and get_repo_tree tools (PR #117). First-ever open source contribution — welcome aboard! 🎉 |
| pisco (Marco Pisco) | file_path uploads for issue, comment, and release attachments, with streaming multipart so large release assets no longer round-trip through base64 (PR #481) |
Community contributors
Issue reporters and discussion participants who shaped the direction of the project:
| Contributor | Contributions |
|-------------|--------------|
| byteflavour | Filed #80 (milestone/label discovery), #85 (notification API proposal); active reviewer in discussions |
| choucavalier | Filed #82 (fix skill), #70 (macOS arm64 releases), #62 (binary releases & mise support) |
| MalcolmMielle | Filed #59 (PR review tools — since implemented) |
| redbeard | Filed #60 (Actions support — since implemented) |
| c6sepl6p | Filed #72 (base64 encoding), #54 (merge pull request — since implemented) |
| malik | Filed #73 (version flag), #47 (Nix build fix) |
| a2800276 | Filed #74 (OpenAI compatibility) |
| simenandre | Filed #49 (go install support) |
| BasdP | Filed #42 (Projects support) |
| BoBeR182 | Filed #32 (wiki support) |
| ignasgil | Filed #95 (remove_issue_labels feature request) |
| Vokuar | Filed #99 (streamable HTTP transport support) |
| janbaer | Filed #98 (reply to review comment) |
| fraschm98 | Early issue reports |
| heathen711 | Filed #106 (issue/comment attachments — since implemented); shaped the 1 MiB inline cap + browser_download_url fall-through design |
| chris420 (Chris Oloff) | Filed #452 (org-wide issue search — since implemented as search_issues); design review on PR #458 that replaced the next-page probe with instance-ceiling enforcement, and caught the response envelope misreporting its own limit |
Cyborg contributors
This project also received contributions from AI coding agents — submitted as regular PRs, reviewed by humans:
| Agent | Role | Contributions |
|-------|------|---------------|
| brenner-axiom (b4-dev, B4arena) | AI dev agent | Organization management tools (PR #94); showboat demos (PR #97); list_repo_milestones, list_repo_labels tools (PR #83); race condition fix (PR #78); contributors docs (PR #87, #88); filed #76; code reviews |
| opencode | AI dev agent | Pull request reviews and comments support (PR #51) |
| claude-code | AI dev agent | get_file_content plain-text default and list_repo_contents/get_repo_tree tools, paired with BrilliantKahn (PR #116, #117) |
| b4mad-release-agent | Release automation | Automated changelog and release tagging |
| the #B4mad Renovate bot | Dependency updates | Automated dependency upgrades |
Want to contribute? Open an issue or pull request — all are welcome.
License
This project is open source. See the repository for license details.
Embed Security Badge
Add this badge to your README or documentation
[](https://mcpsafe.org/registry/forgejo-mcp-server)Server Information
- Source
- GITHUB
- Language
- other
- License
- GPL-3.0
- Transport
- STDIO
- Added
- Aug 11, 2026
- Updated
- Oct 1, 2026