← All docs

MCP server

Connect an AI assistant to the CodeTrunk MCP server: tokens, client setup, tools, limits and errors.

Last updated 2026-10-27

What the MCP server is

CodeTrunk runs a Model Context Protocol server at:

https://codetrunk.eu/mcp

It lets an AI assistant work with your repositories: browse code, open issues, create commits and pull requests, and manage branches, wiki pages, milestones and organisations. Access requires a paid plan.

Create a token

  1. Sign in and open Settings, Connected apps and tokens at https://codetrunk.eu/settings/connected-apps.
  2. Under MCP tokens, give the token a name such as the tool it is for.
  3. Tick Read-only token if the assistant only needs to browse. That token can list and read repositories, issues and pull requests, but every create, change, merge and delete call is refused before it reaches Forgejo.
  4. Choose Create MCP token and copy the value. It is shown once; only its hash is stored.
  5. Paste it into your MCP client. Tokens last 90 days and can be revoked at any time from the same page.

One token works in every client. Create one token per tool if you want to revoke them separately.

What every client needs

Three settings are the same everywhere, whatever the client calls them:

Setting Value
Transport Streamable HTTP (POST only)
URL https://codetrunk.eu/mcp
Header Authorization: Bearer YOUR_TOKEN

There is no browser sign-in step by default. A personal token is the credential CodeTrunk supports everywhere. Depending on the deployment, a client can also sign in through the browser: it either registers itself (Dynamic Client Registration) or identifies itself with its own metadata URL (Client ID Metadata Documents, limited to an allowlist of vendors). If browser sign-in fails for your client, use the token below and turn off automatic OAuth where the client offers a switch (OpenCode needs "oauth": false; other clients use the header when one is configured).

Store the token in an environment variable such as CODETRUNK_MCP_TOKEN where the client supports it. That keeps it out of a config file you might commit.

Connect a client

Claude Code

Add the server for your user account, so every project can use it:

claude mcp add --transport http codetrunk https://codetrunk.eu/mcp \
  --header "Authorization: Bearer YOUR_TOKEN" \
  --scope user

Use --scope project to write a shared .mcp.json instead, or --scope local for the current project only. Check the connection with claude mcp list, or run /mcp inside a session.

You can also edit .mcp.json at the project root directly:

{
  "mcpServers": {
    "codetrunk": {
      "type": "http",
      "url": "https://codetrunk.eu/mcp",
      "headers": {
        "Authorization": "Bearer ${CODETRUNK_MCP_TOKEN}"
      }
    }
  }
}

The "type" field is required. Claude Code reads an entry without one as a stdio server and skips it, so a URL entry that matches your other client's config needs "type": "http" added.

Codex

Codex reads MCP servers from ~/.codex/config.toml, or from .codex/config.toml in a trusted project, and shares that configuration with the ChatGPT desktop app and the IDE extension:

[mcp_servers.codetrunk]
url = "https://codetrunk.eu/mcp"
bearer_token_env_var = "CODETRUNK_MCP_TOKEN"
startup_timeout_sec = 20
tool_timeout_sec = 120

Export the token before starting Codex:

export CODETRUNK_MCP_TOKEN="YOUR_TOKEN"

codex mcp add codetrunk --url https://codetrunk.eu/mcp writes the table for you, after which you add the bearer_token_env_var line. Use codex mcp list to check it, or type /mcp in the composer. If you would rather inline the header, http_headers = { Authorization = "Bearer YOUR_TOKEN" } works too, but it puts the secret in the file.

OpenCode v2

OpenCode v2 nests servers under mcp.servers. Server names placed directly under mcp are the v1 layout; v2 reads only mcp.servers.

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "codetrunk": {
        "type": "remote",
        "url": "https://codetrunk.eu/mcp",
        "oauth": false,
        "headers": {
          "Authorization": "Bearer {env:CODETRUNK_MCP_TOKEN}"
        }
      }
    }
  }
}

Put it in opencode.jsonc at the project root, or in ~/.config/opencode/opencode.json for every project. The CLI can write the entry for you:

opencode mcp add codetrunk --global \
  --url https://codetrunk.eu/mcp \
  --header 'Authorization=Bearer {env:CODETRUNK_MCP_TOKEN}'

Then run opencode mcp list or /mcps in a session. oauth: false matters here: without it OpenCode may start an OAuth flow that only completes when CodeTrunk enables registration or accepts the client's metadata URL.

OpenCode v2 routes MCP tools through Code Mode by default, so the model writes a small script instead of calling each tool directly. Add "codemode": false to the server entry if you want the tools on the model's native tool list. Tool names appear as codetrunk_<tool>.

Pi

Pi reads user-level servers from ~/.pi/agent/mcp.json and project servers from .pi/mcp.json:

pi mcp add codetrunk --url https://codetrunk.eu/mcp \
  --bearer-token-env-var CODETRUNK_MCP_TOKEN
pi mcp list

The file form is the same shape as Claude Code and Cursor, with ${VAR} expansion:

{
  "mcpServers": {
    "codetrunk": {
      "url": "https://codetrunk.eu/mcp",
      "headers": { "Authorization": "Bearer ${CODETRUNK_MCP_TOKEN}" },
      "description": "CodeTrunk repositories, issues, pull requests and Actions"
    }
  }
}

Run /reload after editing the file outside a session, and /mcp inside a session to inspect the connection. Pi uses OAuth only when a server has no Authorization header, so the header above keeps it on bearer tokens. Pi exposes MCP tools through codemode by default; set "exposure": "direct" on the entry if you want them declared to the model, or "deferred" for tool_search. Tool names appear as mcp__codetrunk__<tool>.

Antigravity

Antigravity reads mcp_config.json from ~/.gemini/config/mcp_config.json for all projects, or from <workspace>/.agents/mcp_config.json for one workspace:

{
  "mcpServers": {
    "codetrunk": {
      "serverUrl": "https://codetrunk.eu/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    }
  }
}

url is accepted as an alias for serverUrl. Restart Antigravity, or reload the server from the MCP manager in the agent panel. Optional per-server keys are disabled, enabledTools, disabledTools and timeoutSeconds. MCP tools default to Ask mode, so the first calls need your approval.

Cursor

Cursor reads .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project:

{
  "mcpServers": {
    "codetrunk": {
      "url": "https://codetrunk.eu/mcp",
      "headers": {
        "Authorization": "Bearer ${env:CODETRUNK_MCP_TOKEN}"
      }
    }
  }
}

${env:NAME} is resolved by Cursor itself, so the variable has to be set in the environment Cursor started from, not just in a shell profile you edited later. Toggle the server under Customize in the sidebar and check MCP Logs in the Output panel if it does not connect.

Zed

Zed keeps MCP servers under context_servers in settings.json (open it with zed: open settings file):

{
  "context_servers": {
    "codetrunk": {
      "url": "https://codetrunk.eu/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    }
  }
}

Zed prompts for OAuth only when a remote server has no Authorization header, so the header above is what keeps it on your token. Check the dot next to the server under Settings, AI, MCP Servers: green means active. Remote headers are stored in plain text in your settings file, so keep that file out of version control. If the agent rarely picks the tools, mention the server by name in the prompt or give it a profile that enables only CodeTrunk tools.

Any other client

Clients that read the common mcpServers shape work with the same entry:

{
  "mcpServers": {
    "codetrunk": {
      "type": "http",
      "url": "https://codetrunk.eu/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    }
  }
}

Tools

The server exposes 39 tools. Every call acts as the token owner.

Area Tools
Repositories list_repos, search_repos, get_repo, create_repo, update_repo, delete_repo, get_file_contents
Branches and commits list_branches, create_branch, list_commits, create_commit
Issues list_issues, get_issue, create_issue, comment_issue, close_issue, reopen_issue, delete_comment
Pull requests list_pull_requests, get_pull_request, create_pull_request, close_pull_request, reopen_pull_request, merge_pull_request
Actions list_action_runs, get_action_run, list_action_run_jobs, get_action_job, get_action_job_logs, list_action_artifacts
Actions controls dispatch_workflow, cancel_action_run, rerun_action_run, rerun_action_job
Organisations Read-only: list_orgs, get_org, list_org_members
Wiki and milestones create_wiki_page, create_milestone

Clients prefix tool names with the server name, for example codetrunk_list_repos in OpenCode and mcp__codetrunk__list_repos in Pi. Ask the assistant for the action rather than memorising the prefix.

Every tool carries the MCP safety hints: the 19 read tools are marked read-only and non-destructive, write tools are not. Clients with per-tool approval, ChatGPT connectors among them, can keep the reads on auto-approve and ask you only for the writes. delete_repo, delete_comment, merge_pull_request and cancel_action_run are additionally marked destructive, so they always prompt.

create_commit creates or updates files. Forgejo's API has no multi-file commit endpoint, so each file in one call becomes its own commit. The optional operation is create, update, or omitted to detect whether the file already exists. Every file needs a content value; an explicit empty string clears the file. A call is limited to 20 files and 1 MiB of content, and every entry is checked before the first write so one bad entry cannot leave the earlier files committed. Deleting files goes through the Git history, not the MCP tool.

merge_pull_request supports merge, squash, rebase, rebase-merge and fast-forward-only. Passing head_sha makes the merge fail if the pull request head changed since it was reviewed.

Actions logs are returned as text and truncated to the last max_chars characters, default 20,000. Workflow dispatch, cancel and rerun are available; secrets, variables and runners are not exposed over MCP.

Limits

  • 5 repositories per hour per account for create_repo. The counter is per user, not per token.
  • 120 requests per minute per account.
  • One call has a bounded cost: 20 files and 1 MiB per create_commit, 20 upstream pages for a merged pull request listing, and a 5 MiB read cap on job logs.
  • 60 rejected requests per minute: invalid tokens are counted by IP, and valid tokens rejected for account state, scope or plan are counted by account. Further attempts answer 429 with Retry-After.
  • Your plan limits still apply: private repositories, storage and organisation ownership are checked before an action runs.
  • Repository creation is refused when the storage allowance is full.

Errors

Response Meaning
401 invalid_token The token is missing, expired or revoked. Create a new one.
403 mcp_access_denied The account is on a plan without MCP access. Upgrade first.
403 account_suspended The account is banned. Contact support.
403 email_not_verified Verify the e-mail address first.
403 forgejo_not_active The Forgejo account is not active yet.
403 insufficient_scope The token does not carry the mcp scope.
Tool error with Rate limit reached Wait for the hourly window to pass.
Tool error with Private repository limit reached Upgrade, or free a private repository slot.

Tool errors arrive inside the MCP response with isError: true, so the assistant can read the reason and adjust.

Security

  • The server acts as you and has exactly your permissions. It cannot reach another account's private data.
  • A personal token only carries the mcp scope, never read or write API access. A read-only token additionally refuses every write tool at the endpoint.
  • Use a read-only token for an assistant that only needs to answer questions about your code. It cannot be talked into a commit or a delete by text inside an issue or a file.
  • Tokens are stored as SHA-256 hashes and shown once. Revoking one stops access immediately.
  • Changing your password revokes every connected app and token, including MCP tokens.
  • Organisation access is read-only over MCP. Creating organisations and changing membership stays in the Forgejo interface.

Discovery

MCP clients that support OAuth discovery get an RFC 9728 challenge from /mcp pointing at:

https://codetrunk.eu/.well-known/oauth-protected-resource/mcp

The authorization server metadata is at /.well-known/oauth-authorization-server. Personal tokens do not need discovery; send the bearer header directly.

registration_endpoint appears only when CodeTrunk enables Dynamic Client Registration. A client that uses a metadata URL (Client ID Metadata Documents) can also complete the flow for an allowlisted vendor, in which case the metadata advertises client_id_metadata_document_supported: true. If neither is enabled for your client, use a header or a pre-registered client.

Troubleshooting

  • The client hangs or shows a 405: CodeTrunk answers POST only. A client that opens a GET stream before sending requests is not supported; update it or use another client.
  • The client tries to open a browser sign-in: it is using OAuth discovery instead of your header. If CodeTrunk enables registration or the client's metadata vendor is allowlisted, the sign-in can complete; otherwise configure the Authorization header and disable OAuth for that server (OpenCode needs "oauth": false).
  • The client shows no tools: confirm the URL ends in /mcp, that the header is sent on every request, and that the token has not expired. In Claude Code, check that the entry has "type": "http".
  • A tool says the repository is not found: the token owner may not have access, or the owner/repository names are wrong.
  • A commit or push is refused: the branch may be protected, or the storage allowance may be full.
  • A token stopped working after a password change: that is deliberate. Create a new token.
  • Requests are refused with 429: the per-account or per-IP limit is full. The response carries Retry-After.

Something missing or out of date? info@stennmedia.nl