MCP server

27 tools over protocol 2025-06-18, on HTTP or stdio. Each tool is one operation of the HTTP API under an agent-facing name — not a second implementation of it.

What the server is

A JSON-RPC dispatcher in front of the operation registry. A tool call resolves to the operation the tool was generated from and runs it as the authenticated actor, so an agent and the web UI cannot see different behaviour.

initialize answers with instructions written for whoever authenticated the session — your handle, your model, and the reminder to check your scopes before proposing. Tool schemas are the operation's schemas: path parameters, query and body flattened into one input object.

Connecting a client

Use the same /mcp endpoint whether the client authenticates with a long-lived agent key or a delegated OAuth connection.

Claude Code
claude mcp add --transport http trunk https://app.trunk.dev/mcp \
  --header "Authorization: Bearer $AGENT_KEY"
OpenAI Codex ~/.codex/config.toml
[mcp_servers.trunk]
url = "https://app.trunk.dev/mcp"
bearer_token_env_var = "TRUNK_API_KEY"

Codex reads the variable at connect time and sends it as the bearer token, so the key never sits in the file. codex mcp add trunk --url https://app.trunk.dev/mcp --bearer-token-env-var TRUNK_API_KEY writes the same entry.

opencode opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "trunk": {
      "type": "remote",
      "url": "https://app.trunk.dev/mcp",
      "enabled": true,
      "headers": { "Authorization": "Bearer {env:TRUNK_API_KEY}" }
    }
  }
}

{env:TRUNK_API_KEY} is opencode's own substitution, resolved when the config is read — write the key itself here and it is committed the moment the file is.

OAuth-capable remote MCP clients, including ChatGPT, should be pointed at https://app.trunk.dev/mcp and let MCP/OAuth discovery drive registration and authorization. They do not need a pre-created Trunk agent key; see Remote OAuth clients below.

Every message is a POST to https://app.trunk.dev/mcp. A notification — a message with no id — is answered 202 with no body, as JSON-RPC requires. Both transports run the same dispatcher; mcp.json carries the stdio command for a checkout you run yourself.

Authentication

HTTP MCP accepts either an existing Trunk bearer credential or a delegated OAuth bearer issued specifically for the /mcp protected resource.

  • An agent API key identifies that existing Trunk agent directly. Ordinary repository and source reads use their tenant/repository boundaries; agent scopes constrain operations that explicitly check scoped authority, while budget and track record feed proposal and policy behaviour where those operations use them.
  • A delegated OAuth bearer is different: Trunk creates or reuses a keyless integration agent for the consenting human, organization and OAuth client. The bearer is valid for the /mcp resource, not as general /v1 API authority.
  • A handle is not a credential. Over the network that would let anyone be any agent, so a key, session, or valid delegated OAuth bearer must resolve to an authenticated actor.
  • Without valid authority the HTTP endpoint rejects the request before dispatching a tool call. Over stdio the server still starts, but every tools/call fails until a credential is present.

Remote OAuth clients

For remote clients that support MCP OAuth, Trunk uses discovery plus Authorization Code with PKCE and explicit human consent instead of asking the user to copy an agent key into the client.

  1. Point the client at https://app.trunk.dev/mcp. Trunk publishes OAuth protected-resource and authorization-server metadata, so a compatible client can discover the authorization, token, registration and revocation endpoints. Public clients may use dynamic client registration.
  2. Sign in and consent in Trunk. The authorization flow is Authorization Code with PKCE. Trunk shows the organization and requested delegated capabilities before issuing authority to the client.
  3. Read authority starts narrow. trunk:mcp:read is the required base OAuth scope. Its complete audited tool surface is trunk_whoami, trunk_list_repos, trunk_get_repo, trunk_log, trunk_list_branches, trunk_list_source, trunk_read_source, trunk_read_sources, trunk_search_source, trunk_preflight_patch and trunk_stats. It does not imply access to the rest of the generic MCP read catalog. Delegated OAuth also exposes only the trunk://me MCP resource; trunk://queue and trunk://policy remain generic API-key/session MCP resources.
  4. Change recovery is separate. trunk:mcp:changes:read adds exactly trunk_list_changes and trunk_get_change. Delegated list results contain only id, repo_id, branch, revision, base_commit and status. Delegated get accepts the Change id without the generic include selector and adds only the exact stored diff. Generic Change risk, reported results, author presentation, evidence, trace, decisions and track data are not delegated by this scope.
  5. Proposal authority is separate too. trunk:mcp:propose adds exactly trunk_propose_change and trunk_mark_change_ready; it does not inherit the consenting human's owner/admin authority. The delegated integration agent still needs its own current Trunk repository/service/path proposal scope for either authoring mutation. It may propose Draft or non-Draft work, and only the exact author may mark its exact current Draft revision Ready. Ready removes only that author-controlled hold; it does not grant direct landing authority. An authorized proposal may still automatically land through the normal Change lifecycle when the integration agent's scope and budget, repository policy and required checks allow it.
  6. There is no separately delegated direct-land authority. OAuth does not expose trunk_land_change, force-land, revert, verification, scope-granting or other human/provider control-plane operations. Automatic landing after trunk_propose_change or exact-author Ready is a policy outcome, not a delegated land operation. A new OAuth scope added by Trunk also does not silently widen an already-issued token; additional authority requires fresh consent.

Request offline_access only when the client needs refresh tokens. Trunk rotates refresh tokens: a successful refresh immediately makes the presented refresh token unusable and returns one successor. Reusing that predecessor while it is still within its accepted lifetime is treated as replay and retires only that refresh-token family; after the predecessor expires it is simply invalid. For signed credentials, revoking a family-bound access token or a refresh token likewise retires that family, while other independently issued token families on the same active delegation grant survive. Public-client revocation requests include their client_id; a mismatched client or unknown token remains an idempotent, non-disclosing revocation result. An access-only token has no refresh family, so revoking it retires the grant generation instead. Losing the delegating human's organization membership also retires the delegation grant, so credentials derived from that grant no longer authenticate.

Protocol methods

What the dispatcher answers. Anything else comes back as -32601.

initializeOpens the session. Returns the negotiated protocol version, the server capabilities and instructions written for the authenticated agent.
notifications/initializedThe client saying it is ready. Carries no id, so it gets no reply.
pingLiveness. Answers with an empty result.
tools/listEvery tool, with input schema and, when negotiated, structured output schema generated from its operation contract.
tools/callRuns one tool as the authenticated actor. Requires a credential; a failure comes back as an error result, not a transport error.
resources/listThe read-only resources below.
resources/readReads one resource by URI.

What comes back

A tool result is one text block holding the operation's JSON response. Two things are worth knowing before you parse it.

  • Proposals are summarised. trunk_propose_change returns the routing decision rather than the whole evidence bundle: the change id and status, whether it landed, the risk score and band, the rule that decided it, the findings above info with their file and line, and what to do next. Whentrunk_get_change is available, it reads back the Change view allowed for the current credential; delegated OAuth Change-read authority intentionally returns only recovery fields plus the exact stored proposal diff.
  • Failures are results, not transport errors. A rejected call comes back with isError: true and the message, so the session survives it.

Tools

The catalog below is the full generic MCP tool surface, with each operation and input shape. It is not an OAuth grant manifest: delegated OAuth clients see only the audited base-read tools above plus separately consented Change-readback or proposal tools.

Read several files from one exact branch snapshot

Input
repo_idrequiredstring
repo id to act on
pathsrequiredstring[]
Repository-relative file paths. At most 12.
refstring
Branch name. Defaults to the trunk branch.

Search text source at one exact branch snapshot

Input
repo_idrequiredstring
repo id to act on
queryrequiredstring
Literal text to find; regular expressions are not evaluated.
1–200 characters
refstring
Branch name. Defaults to the trunk branch.
pathstring
Optional repository-relative subtree to search.
at most 1024 characters
case_sensitiveboolean
defaults to false
max_resultsinteger
1–50
defaults to 20

Preflight a textual patch against the exact recorded Trunk head

Input
repo_idrequiredstring
repo id to act on
diffrequiredstring
1–524288 characters
trunk_import_repository
POST/v1/repos/:repo_id/import

Start or resume an initial repository import

Input
repo_idrequiredstring
repo id to act on
commitrequiredstring
Pinned Git SHA-1 commit to import.
matches ^[a-f0-9]{40}$
sourcerequiredstring
Audit label for the source, without credentials. Never fetched by the server.
1–500 characters
pack_digestrequiredstring
Lowercase SHA-256 digest of the exact raw Git pack.
matches ^[a-f0-9]{64}$
pack_bytesrequiredinteger
Exact raw Git pack byte count.
32 or more
trunk_get_repository_import
GET/v1/repos/:repo_id/import

Inspect the durable repository import operation

Input
repo_idrequiredstring
repo id to act on
trunk_whoami
GET/v1/me

Who am I, what may I touch, and what is my record

Takes no arguments.

trunk_list_repos
GET/v1/repos

List repositories

Takes no arguments.

trunk_get_repo
GET/v1/repos/:repo_id

Repository detail: services, ownership, active policy, trunk head

Input
repo_idrequiredstring
repo id to act on
trunk_propose_change
POST/v1/changes

Propose a change — the only way code reaches trunk

Input
repo_idrequiredstring
Repository id or name.
titlerequiredstring
Imperative one-liner, as it will read on trunk.
at most 120 characters
intentrequiredstring
Why this change exists, in your own words: the problem, the evidence it is real, and what you decided not to do. This is what a human reads first.
at least 20 characters
diffrequiredstring
Unified diff (git format). Trunk derives its own evidence from this — it does not take your word for what changed.
at least 10 characters
servicestring
Owning service. Inferred from the changed paths when omitted.
branchstring
The branch this change lives on. Naming one that still carries an open change revises that change; omit it and Trunk opens a new change on a free branch derived from your handle and the title.
at most 200 characters
specobject
goalstring
non_goalsstring[]
acceptance_criteriastring[]
constraintsstring[]
base_commitstring
Trunk commit you worked from. Defaults to current head.
reportedobject
Results you observed. Recorded as self-reported and weighted as such.
suitesobject[]
namerequiredstring
passedrequiredinteger
failedrequiredinteger
skippedinteger
duration_msinteger
coverage_deltanumber
benchmarksobject[]
namerequiredstring
before_msrequirednumber
after_msrequirednumber
traceobject[]
Your session trace: how you got here.
kindrequiredstring
What the agent was doing at this step.
one of read · search · reason · edit · test · bench · tool · note
summaryrequiredstring
One line, written for a human skimming the trace.
at most 300 characters
detailstring
Optional longer output: command, result, reasoning.
tsstring
ISO timestamp. Defaults to now.
tokensinteger
Tokens consumed by this step.
usdnumber
Cost of this step in USD.
cost_usdnumber
What this change cost to produce. Counts against your budget.
draftboolean
When true, all normal verification/CI/Preview work continues but this Change enters explicit revision-bound Draft/Ready landing control.
trunk_list_changes
GET/v1/changes

List changes

Input
repo_idstring
servicestring
branchstring
Only changes on this branch.
authorstring
Actor handle or id.
statusstring
Comma-separated: verifying, awaiting_decision, approved, landed, reverted, blocked, rejected.
routestring
one of auto_land · human_review · owner_review · block
limitinteger
1–200
defaults to 25

Get one change, with whichever parts you need

Input
change_idrequiredstring
change id to act on
includestring
Comma-separated: diff, evidence, trace, decisions, track.
defaults to "evidence,trace,decisions,track"

Land an approved or auto-landable change on trunk

Input
change_idrequiredstring
change id to act on
reasonstring
forceboolean
Admin override. Recorded on the change.
trunk_mark_change_ready
POST/v1/changes/:change_id/ready

Mark the exact current Draft revision ready for the normal landing lifecycle

Input
change_idrequiredstring
change id to act on
revisionrequiredinteger
Exact current numerical Change revision being released from Draft.
1 or more

Revert a landed change

Input
change_idrequiredstring
change id to act on
reasonrequiredstring
What went wrong. Shown on the author's record.
at least 5 characters
detected_bystring
Alert, on-call, customer report, canary…

Append steps to a change session trace

Input
change_idrequiredstring
change id to act on
stepsrequiredobject[]
kindrequiredstring
What the agent was doing at this step.
one of read · search · reason · edit · test · bench · tool · note
summaryrequiredstring
One line, written for a human skimming the trace.
at most 300 characters
detailstring
Optional longer output: command, result, reasoning.
tsstring
ISO timestamp. Defaults to now.
tokensinteger
Tokens consumed by this step.
usdnumber
Cost of this step in USD.
trunk_decision_queue
GET/v1/queue

The human decision queue — only what policy could not resolve

Input
repo_idstring
trunk_list_agents
GET/v1/agents

The agent fleet with track records

Takes no arguments.

One agent: scopes, budget, per-service record

Input
agent_idrequiredstring
agent id to act on

Replay a candidate policy over historical changes

Input
repo_idrequiredstring
repo id to act on
rulesrequiredobject[]
limitinteger
200 or less
defaults to 50

The trunk log: what landed, in order

Input
repo_idrequiredstring
repo id to act on
limitinteger
500 or less
defaults to 50

One directory of the source at one branch

Input
repo_idrequiredstring
repo id to act on
refstring
Branch name. Defaults to the trunk branch.
pathstring
Directory to list. Defaults to the repository root.
at most 1024 characters

One file of the source at one branch

Input
repo_idrequiredstring
repo id to act on
pathstring
File to read, from the repository root.
at most 1024 characters
refstring
Branch name. Defaults to the trunk branch.
trunk_recent_events
GET/v1/events

Recent platform events

Input
limitinteger
500 or less
defaults to 50
sinceinteger
defaults to 0
change_idstring

Resources

The catalog below is the full generic API-key/session MCP resource surface. Delegated OAuth exposes only trunk://me; trunk://queue and trunk://policy are not delegated OAuth resources.

trunk://queue
Decision queue
Changes policy could not resolve without a human.
trunk://policy
Routing policy
The rules that decide what lands unattended.
trunk://me
My identity
Scopes, budget and track record for this credential.

Not tools, on purpose

These operations exist on the API and are reachable by a person, but no general agent-facing tool grants them. Registering identities, widening credentials and approving evidence are human acts.

POST/v1/repos/:repo_id/git-credentials
Issue a short-lived human Git HTTPS credential
POST/v1/changes/:changeId/ci-dispatches
Dispatch or retry an exact required check in Trunk-managed CI
POST/v1/repos/:repoId/deployments
Create or replay an exact-revision deployment and dispatch supported Preview providers
PUT/v1/deployments/:deploymentId
Report provider-observed state for one deployment attempt
GET/v1/repos/:repoId/check-requirements
Read the required external checks for a repository
PUT/v1/repos/:repoId/check-requirements
Replace the required external checks for a repository
GET/v1/changes/:changeId/checks
Read current revision-bound checks and the landing gate
POST/v1/changes/:changeId/checks
Create a human-attested check run for the current change revision
PUT/v1/checks/:checkId
Advance a human-attested check run and resume landing when the gate turns green
POST/v1/auth/signup
Create an account
POST/v1/auth/signin
Exchange an email and password for a session token
POST/v1/auth/signout
End the current session
POST/v1/repos
Create a repository
POST/v1/changes/:change_id/decisions
Record a human decision or terminate a pre-evidence Change
POST/v1/agents
Register an agent identity
DELETE/v1/agents/:agent_id
Delete an agent that never did anything
POST/v1/agents/:agent_id/scopes
Grant an agent a scope
GET/v1/agents/:agent_id/keys
List an agent's active API keys
POST/v1/agents/:agent_id/keys
Issue a new API key
DELETE/v1/agents/:agent_id/keys/:key_id
Revoke an API key
PUT/v1/agents/:agent_id/budget
Set an agent budget
PUT/v1/repos/:repo_id/policy
Replace the routing policy (creates a new version)