Skip to main content
Glama

forgejo-mcp

An MCP server for Gitea and Forgejo: manage repositories, files, branches, issues, pull requests, releases and CI from Claude, much like the GitHub MCP server does for GitHub. It speaks the /api/v1 REST API shared by Forgejo and Gitea (including Codeberg and self-hosted/private instances).

The default and recommended way to run it is locally over stdio. Your token stays on your machine and is passed only to your Forgejo instance. A Cloudflare Worker entry point also exists for hosted use (see the end), but you don't need it.

Quick start (local)

git clone https://github.com/jerdog/forgejo-mcp && cd forgejo-mcp
npm install        # also builds dist/ via the `prepare` script

Verify the connection and token permissions before wiring it into Claude:

export FORGEJO_URL=https://git.example.com
export FORGEJO_TOKEN=...            # see "Creating a Forgejo access token"
node dist/stdio.js --check --repo owner/name   # --repo is optional

--check is read-only. It reports PASS / FAIL / SKIP per probe (reachability, authentication, organizations, repository search, then repo, contents, branches, commits, issues, pull requests, labels, releases and Actions for the repo you name). A 403 names the scope that is probably missing. It never prints the token.

Claude Code

claude mcp add forgejo \
  -e FORGEJO_URL=https://git.example.com \
  -e FORGEJO_TOKEN=... \
  -e FORGEJO_READ_ONLY=true \
  -- node /absolute/path/to/forgejo-mcp/dist/stdio.js

Or commit a project .mcp.json (see .mcp.json.example; it reads FORGEJO_URL / FORGEJO_TOKEN from your shell so no secret is committed).

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "forgejo": {
      "command": "node",
      "args": ["/absolute/path/to/forgejo-mcp/dist/stdio.js"],
      "env": {
        "FORGEJO_URL": "https://git.example.com",
        "FORGEJO_TOKEN": "...",
        "FORGEJO_READ_ONLY": "true"
      }
    }
  }
}

Related MCP server: gitea-mcp

Claude Code cloud environments

To have the tools in every session of a Claude Code cloud (web) environment, register the server from the environment's setup script. That covers every repository opened in the environment, so nothing is added to those repos.

  1. In the session title bar open the cloud environment menu, choose Edit, and paste this into Setup script:

    #!/bin/bash
    # Cloud environment "Setup script": installs forgejo-mcp and registers it with Claude Code.
    export FORGEJO_URL=https://git.ontheedgefarms.com
    REF=main                      # branch or tag of jerdog/forgejo-mcp to install
    REPO=jerdog/forgejo-mcp
    DIR=/opt/forgejo-mcp
    log() { echo "[forgejo-mcp] $*"; }
    
    fetch() {
      rm -rf "$DIR"
      log "try 1: git clone via github.com"
      git clone -q --depth 1 --branch "$REF" "https://github.com/$REPO" "$DIR" && { log "ok: git clone"; return 0; }
      rm -rf "$DIR"; mkdir -p "$DIR"
      log "try 2: tarball via api.github.com"
      curl -fsSL "https://api.github.com/repos/$REPO/tarball/$REF" | tar -xz --strip-components=1 -C "$DIR" \
        && [ -f "$DIR/package.json" ] && { log "ok: tarball"; return 0; }
      if [ -n "${FORGEJO_MCP_GH_TOKEN:-}" ]; then
        rm -rf "$DIR"
        log "try 3: git clone with FORGEJO_MCP_GH_TOKEN"
        git clone -q --depth 1 --branch "$REF" "https://x-access-token:${FORGEJO_MCP_GH_TOKEN}@github.com/$REPO" "$DIR" \
          && git -C "$DIR" remote set-url origin "https://github.com/$REPO" && { log "ok: token clone"; return 0; }
      fi
      return 1
    }
    
    fetch || { log "FAILED: could not fetch $REPO@$REF by any route"; exit 1; }
    bash "$DIR/scripts/cloud-setup.sh" || { log "FAILED: cloud-setup.sh exited $?"; exit 1; }
    log "done"

    Private repo: the setup phase has no session GitHub credentials. If you configured a network secret (Bearer) for GitHub, the proxy supplies auth and try 1 or 2 succeeds; list both github.com and *.github.com in its allowed domains, since a wildcard may not match the bare host. Otherwise add a fine-grained token (Contents: Read-only, this repo only) as environment variable FORGEJO_MCP_GH_TOKEN for try 3. Once the repo is public none of this is needed.

    scripts/cloud-setup.sh builds the server, runs claude mcp add forgejo --scope user, then runs --check as an informational self-test. A 401 there is expected when the environment injects credentials only inside sessions, so it only warns; confirm authentication from within a session.

  2. Make sure Network access allows github.com (to clone) and your Forgejo host.

  3. Start a new session. MCP servers are read at session start, so existing sessions don't pick it up.

Notes:

  • No token needed if the environment's proxy injects one. If the environment has a credential configured for your Forgejo host (shown as "inject FORGEJO_TOKEN" in the session's proxy notes), the proxy adds the Authorization header to requests it sees, and you can leave FORGEJO_TOKEN unset.

  • NODE_USE_ENV_PROXY=1 is required. Node's fetch ignores HTTPS_PROXY by default (needs Node 22.21+ or 24+), so without it requests bypass the proxy, no credential is injected, and you get 401. The script sets it on the server. Proxy CA trust (NODE_EXTRA_CA_CERTS) is already provided by the environment.

  • If the proxy does not inject a token, set FORGEJO_TOKEN as an environment secret and export it before the bash /opt/forgejo-mcp/scripts/cloud-setup.sh line, or create a token as described above.

  • Writes are off by default (FORGEJO_READ_ONLY=true). Export FORGEJO_READ_ONLY=false before that line to enable write tools; see Read-only mode.

  • Verify in a new session with /mcp, or run NODE_USE_ENV_PROXY=1 FORGEJO_URL=... node /opt/forgejo-mcp/dist/stdio.js --check --repo owner/name.

  • Alternatively commit a project .mcp.json to a repo (see .mcp.json.example, adding "NODE_USE_ENV_PROXY": "1" to env) if you only want the tools in that repo; you still need the setup script step to install the server.

Configuration

Variable

Description

FORGEJO_URL

Base URL of the instance, e.g. https://git.example.com (required)

FORGEJO_TOKEN

Personal access token. Required for private repos and any write

FORGEJO_READ_ONLY

true hides every write tool (see Read-only mode). Recommended until you trust your setup

Read-only mode

Set FORGEJO_READ_ONLY=true to hide every write tool: the server doesn't register them, so the model can't see or call them (no create_*, update_*, delete_*, merge_*, push_files, dispatch_workflow, and so on). Only the read tools remain. true, 1 or yes (case-insensitive) turn it on; anything else, or leaving it unset, exposes write tools. The stdio server defaults to writes enabled when the variable is unset, which is why every example here sets it explicitly.

  • It is a server-side switch, not a Forgejo permission. A token with write scopes is still able to write through anything else that uses it; read-only only limits what this MCP server offers. For a hard limit, also give the token read-only scopes (see below), or rely on a read-only credential.

  • Changes apply at the next session or restart. Tools are registered when the server starts, so restart the client (or start a new cloud session) after changing the variable.

  • Verify with a tool listing: in read-only mode there is no create_issue or merge_pull_request.

How to flip it per setup:

Setup

Read-only

Enable writes

claude mcp add

-e FORGEJO_READ_ONLY=true

drop the flag, or -e FORGEJO_READ_ONLY=false

.mcp.json / Claude Desktop

"FORGEJO_READ_ONLY": "true"

remove the key or set "false"

Cloud environment

leave the setup script as is (cloud-setup.sh defaults to true)

add export FORGEJO_READ_ONLY=false above the bash "$DIR/scripts/cloud-setup.sh" line, then start a new session

Cloudflare Worker

set FORGEJO_READ_ONLY = "true" in wrangler.toml [vars] (it ships as "false") and npm run deploy

set it to "false" and redeploy

In a cloud environment where the proxy injects the credential, that credential's permissions apply to writes, and it may be an account with broad rights. Consider enabling writes only in a separate environment, or only for the session that needs them.

Creating a Forgejo access token

In Forgejo/Gitea: Settings → Applications → Manage Access Tokens → Generate Token. Give it a name and select scopes (each category has No access / Read / Read and Write).

To use these tools

Scope

Read repos, files, branches, commits, tags, releases, pull requests, Actions runs, commit status

read:repository

Create/update/delete files, branches, releases, pull requests; merge; fork/create repos; dispatch workflows

write:repository

Read issues, comments, labels, milestones

read:issue

Create/edit issues, comments, labels

write:issue

get_me, search_users

read:user

list_organizations, listing an organization's repos

read:organization

Create repos under an organization

write:organization (plus write:repository)

Recommended first token: read:repository, read:issue, read:user, read:organization with FORGEJO_READ_ONLY=true. Add write:* scopes only when you want write tools, and drop FORGEJO_READ_ONLY at the same time.

Notes:

  • Scopes don't grant access on their own: the token only sees repositories its owner can see (membership, team or collaborator access).

  • Use --check to confirm the scopes are enough; a 403 result names the scope to add.

  • Treat the token like a password: don't commit it, and revoke it in the same settings page if it leaks.

Tools

  • Users/orgs: get_me, search_users, list_organizations

  • Repos: search_repositories, get_repository, list_repositories, create_repository, fork_repository

  • Files: get_file_contents, create_or_update_file, delete_file, push_files

  • Branches/commits: list_branches, create_branch, delete_branch, list_commits, get_commit, list_tags

  • Issues: list_issues, search_issues, get_issue, create_issue, update_issue, list_issue_comments, add_issue_comment, list_labels, create_label, add_issue_labels, remove_issue_label, list_milestones

  • Pull requests: list_pull_requests, get_pull_request, create_pull_request, update_pull_request, merge_pull_request, get_pull_request_diff, list_pull_request_files, list_pull_request_reviews, list_pull_request_review_comments, create_pull_request_review, request_pull_request_reviewers

  • Releases: list_releases, get_latest_release, get_release_by_tag, create_release

  • CI: list_action_tasks, get_commit_status, list_workflows, dispatch_workflow (Actions endpoints depend on the instance version)

Write tools carry readOnlyHint: false; deletes and merges set destructiveHint.

Development

npm run typecheck && npm test
npm run smoke -- owner/name     # read-only end-to-end run over real MCP stdio (needs FORGEJO_URL/TOKEN)
npx @modelcontextprotocol/inspector node dist/stdio.js

Optional: hosted on Cloudflare Workers

Only needed if you want a remote HTTP endpoint instead of a local process.

# edit FORGEJO_URL in wrangler.toml, then:
npx wrangler secret put FORGEJO_TOKEN
npx wrangler secret put MCP_AUTH_TOKEN   # protects the endpoint; required if FORGEJO_TOKEN is set
npm run deploy

Endpoint: https://<worker>.workers.dev/mcp. Clients send Authorization: Bearer <MCP_AUTH_TOKEN>, or bring their own Forgejo token in X-Forgejo-Token. The Worker must be able to reach your Forgejo URL over the public internet, so it is a poor fit for a private instance. Local dev: npm run dev:worker (vars in .dev.vars).

Available Tools

49 tools
add_issue_commentComment on issue or pull requestC

Add a comment to an issue or pull request.

Inputs: body (required), repo (required), index (required), owner (required).

add_issue_labelsAdd labels to issueB

Add labels (by ID) to an issue or pull request.

Inputs: repo (required), index (required), owner (required), labels (required).

create_branchCreate branchA

Create a branch from an existing branch (default: the repo's default branch).

Inputs: repo (required), owner (required), branch (required), from_branch.

create_issueCreate issueA

Create an issue. Labels are given as numeric IDs (see list_labels).

Inputs: repo (required), owner (required), title (required), body, +4 more.

create_labelCreate labelC

Create a label in a repository.

Inputs: name (required), repo (required), color (required), owner (required), +1 more.

create_or_update_fileCreate or update fileA

Create a file, or update an existing one (then sha of the current blob is required). Creates a commit.

Inputs: path (required), repo (required), owner (required), content (required), +4 more.

create_pull_requestCreate pull requestC

Open a pull request from head into base. For cross-repo forks use head as owner:branch.

Inputs: base (required), head (required), repo (required), owner (required), +5 more.

create_pull_request_reviewReview pull requestA

Submit a review on a pull request, optionally with inline comments. Omit event to leave the review pending.

Inputs: repo (required), index (required), owner (required), body, +3 more.

create_releaseCreate releaseC

Create a release (and its tag if it does not exist yet).

Inputs: repo (required), owner (required), tag_name (required), body, +4 more.

create_repositoryCreate repositoryB

Create a repository for the authenticated user, or for an organization if org is given.

Inputs: name (required), org, private, auto_init, +2 more.

delete_branchDelete branchC
Destructive

Delete a branch.

Inputs: repo (required), owner (required), branch (required).

delete_fileDelete fileC
Destructive

Delete a file in a repository via a commit.

Inputs: sha (required), path (required), repo (required), owner (required), +2 more.

dispatch_workflowDispatch workflowC

Trigger a workflow_dispatch run (Forgejo 11+/Gitea 1.24+).

Inputs: ref (required), repo (required), owner (required), workflow (required), +1 more.

fork_repositoryFork repositoryB

Fork a repository to the authenticated user or a given organization.

Inputs: repo (required), owner (required), name, organization.

get_commitGet commitB
Read-only

Get a single commit by SHA, including changed files.

Inputs: sha (required), repo (required), owner (required).

get_commit_statusGet combined commit statusA
Read-only

Get the combined CI status (and individual statuses) for a ref/SHA.

Inputs: ref (required), repo (required), owner (required).

get_file_contentsGet file or directory contentsA
Read-only

Read a file (decoded to text) or list a directory. Use path '' for the repository root. File results include the blob sha needed to update the file.

Inputs: repo (required), owner (required), ref, path.

get_issueGet issueC
Read-only

Get an issue by number.

Inputs: repo (required), index (required), owner (required).

get_latest_releaseGet latest releaseC
Read-only

Get the latest published release.

Inputs: repo (required), owner (required).

get_meGet authenticated userA
Read-only

Get the user the configured token authenticates as. Call this first to verify the connection.

Inputs: no named parameters.

get_pull_requestGet pull requestB
Read-only

Get a pull request, including mergeability.

Inputs: repo (required), index (required), owner (required).

get_pull_request_diffGet pull request diffB
Read-only

Get the unified diff (or patch) of a pull request.

Inputs: repo (required), index (required), owner (required), format.

get_release_by_tagGet release by tagC
Read-only

Get the release for a tag name.

Inputs: tag (required), repo (required), owner (required).

get_repositoryGet repositoryC
Read-only

Get details of a repository.

Inputs: repo (required), owner (required).

list_action_tasksList Actions runsB
Read-only

List recent Actions (CI) task runs for a repository.

Inputs: repo (required), owner (required), page, limit.

list_branchesList branchesC
Read-only

List branches of a repository.

Inputs: repo (required), owner (required), page, limit.

list_commitsList commitsB
Read-only

List commits on a branch/ref, optionally limited to a file path.

Inputs: repo (required), owner (required), sha, page, +2 more.

list_issue_commentsList issue commentsB
Read-only

List comments on an issue or pull request.

Inputs: repo (required), index (required), owner (required), page, +1 more.

list_issuesList issuesB
Read-only

List or search issues in a repository (pull requests excluded).

Inputs: repo (required), owner (required), q, page, +6 more.

list_labelsList labelsB
Read-only

List labels of a repository (returns the IDs needed to label issues).

Inputs: repo (required), owner (required), page, limit.

list_milestonesList milestonesC
Read-only

List milestones of a repository.

Inputs: repo (required), owner (required), page, limit, +1 more.

list_organizationsList organizationsB
Read-only

List organizations the authenticated user belongs to.

Inputs: page, limit.

list_pull_request_filesList pull request filesB
Read-only

List files changed by a pull request.

Inputs: repo (required), index (required), owner (required), page, +1 more.

list_pull_request_review_commentsList review commentsB
Read-only

List inline comments of a specific review.

Inputs: repo (required), index (required), owner (required), review_id (required).

list_pull_request_reviewsList pull request reviewsC
Read-only

List reviews of a pull request.

Inputs: repo (required), index (required), owner (required), page, +1 more.

list_pull_requestsList pull requestsC
Read-only

List pull requests in a repository.

Inputs: repo (required), owner (required), page, sort, +3 more.

list_releasesList releasesC
Read-only

List releases of a repository.

Inputs: repo (required), owner (required), page, draft, +2 more.

list_repositoriesList repositoriesA
Read-only

List repositories owned by a user or organization. Omit owner to list the authenticated user's repositories.

Inputs: page, limit, owner, is_org.

list_tagsList tagsB
Read-only

List tags of a repository.

Inputs: repo (required), owner (required), page, limit.

list_workflowsList workflowsA
Read-only

List Actions workflows defined in a repository (Forgejo 11+/Gitea 1.24+).

Inputs: repo (required), owner (required).

merge_pull_requestMerge pull requestC
Destructive

Merge a pull request using the chosen strategy.

Inputs: repo (required), index (required), owner (required), title, +3 more.

push_filesPush multiple filesC
Destructive

Create, update or delete several files in a single commit.

Inputs: repo (required), files (required), owner (required), message (required), +2 more.

remove_issue_labelRemove label from issueB

Remove one label (by ID) from an issue or pull request.

Inputs: repo (required), index (required), owner (required), label_id (required).

request_pull_request_reviewersRequest reviewersC

Request reviews from users on a pull request.

Inputs: repo (required), index (required), owner (required), reviewers (required).

search_issuesSearch issues and pull requestsC
Read-only

Search issues/PRs across all repositories visible to the token.

Inputs: q (required), page, type, limit, +2 more.

search_repositoriesSearch repositoriesB
Read-only

Search repositories on the Forgejo/Gitea instance by keyword.

Inputs: query (required), page, limit, topic.

search_usersSearch usersC
Read-only

Search users by name.

Inputs: query (required), page, limit.

update_issueUpdate issueB

Edit an issue's title, body, state, assignees or milestone.

Inputs: repo (required), index (required), owner (required), body, +4 more.

update_pull_requestUpdate pull requestC

Edit a pull request's title, body, base branch or state.

Inputs: repo (required), index (required), owner (required), base, +4 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.9/5.0

    Scored across 49 tools

    Disambiguation4/5

    Most tools target a clear resource+action, but several list/search pairs overlap: search_issues vs list_issues, list_pull_requests vs search_issues (PRs are issues in Forgejo), and get_file_contents duplicating listing-directory semantics. Descriptions mostly disambiguate, though an agent could still misselect between list and search variants.

    Naming Consistency4/5

    Overwhelmingly consistent verb_noun pattern (list_*, get_*, create_*, update_*, merge_*, add_*, remove_*). Minor deviations like create_or_update_file and dispatch_workflow are still readable and purposeful. The 'list_pull_request_*' long forms are consistent within their family.

    Tool Count2/5

    49 tools is heavy for a single MCP server and exceeds the 25+ threshold. The surface spans repos, files, branches, commits, issues, PRs, reviews, labels, releases, milestones, and Actions CI—workable but burdened with granular tools that could be consolidated or grouped.

    Completeness4/5

    Broad, well-rounded coverage of the Forgejo/Gitea domain: full file CRUD, branches, commits, issues, PRs, reviews, labels, releases, and CI status/workflows. Some gaps remain (e.g., deleting/editing releases, managing milestones, listing PR reviewers/assignees, closing organizations), but core workflows are covered.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      Enables AI assistants to interact with Gitea repositories through intelligent tools for issue/PR management, workflow analysis, compliance checking, and content generation, plus 200+ CLI commands for complete CRUD operations.
      22
      81 npm
      6
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      A local MCP server that connects Claude directly to your Gitea instance, enabling management of repositories, issues, pull requests, files, and commit history.
      MIT
    • A
      license
      B
      quality
      C
      maintenance
      Enables AI agents to read repositories, manage issues, inspect CI/CD workflow runs, create branches, commit files, and open pull requests on a self-hosted Gitea instance.
      7
      MIT