> ## Documentation Index
> Fetch the complete documentation index at: https://orbit.nckrtl.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP server

> The Gateway's Model Context Protocol server: every API operation as a tool, identified by WireGuard address, with a search variant for small contexts.

# MCP server

The Gateway serves a Model Context Protocol (MCP) server, so an agent can operate Orbit without the CLI. Every Gateway API operation is one tool. A tool call runs the same API request that the matching CLI command sends. Identity, access grants, validation, redaction, and [Activity](/docs/cli/activity) therefore apply to it in the same way.

The Project's [development deploy steps](/docs/reference/deployments#development-deploy-steps) are available as `project-dev-deploy-step-list`, `project-dev-deploy-step-create`, `project-dev-deploy-step-update`, and `project-dev-deploy-step-destroy`. Supply the numeric `project`; updates and removal also take the step name as `step`. Create and update accept `required` as a JSON boolean, so `false` stays false through MCP dispatch.

`process-update` replaces an installed Process specification. Supply its numeric ID as `process` and the complete replacement fields; it keeps the Process identity and desired state. Both `process-create` and `process-update` accept Docker `environment` as an object such as `{"EMPTY":"","LABEL":"  literal  "}`, with string values preserved. Omit or set `working_directory` to null to use the Docker image default. Responses expose environment names with redacted values. [Update in place](/docs/reference/processes-and-schedules#update-in-place) defines omitted fields, refusals, and retry recovery.

`node-settings` takes `apps`, the [apps root](/docs/reference/node-settings#set-the-apps-root).

`node-wireguard-key` takes the numeric `node` ID and `wireguard_public_key`, a canonical base64 encoding of exactly 32 bytes. It dispatches `PATCH /api/v1/nodes/{node}/wireguard-key` to set an unmanaged peer's desired public key and reconverge the hub. The API refuses Gateway Nodes, managed peers, missing WireGuard addresses, and collisions with another peer or the configured hub key. Activity input is empty. A projection failure can leave the new key stored; replaying the same key retries convergence rather than skipping it. Follow [WireGuard key recovery](/docs/cli/node#orbit-nodewireguard-key) if the change interrupts the caller's management path.

The `gateway-release-rollback` tool takes the retained release selector without a `force` input. It switches code only; database restore follows [Gateway recovery](/docs/reference/gateway-recovery#roll-back).

## Endpoints

Both endpoints speak MCP over streamable HTTP and offer the same catalogue. Their JSON-RPC envelopes are limited to 15 MiB (15,728,640 bytes), checked after active-peer authorization and before parsing. An oversized envelope receives HTTP 413 without invoking a tool. Dispatched API requests still apply their own limits and Node access checks. This envelope limit admits the full 10 MiB decoded [Project Document upload](/docs/reference/project-documents#content-and-versions), including base64 and JSON-RPC overhead.

| Endpoint | Tools it lists | Use it when |
| - | - | - |
| `https://gateway.orbit/mcp` | One tool for every API operation, on one page. | The client loads tool definitions on demand, or its context has room for the full catalogue. |
| `https://gateway.orbit/mcp/search` | `search_tools` and `execute_tools`. | The client loads every listed tool into its context. |

`search_tools` returns exact tool names with their input schemas. An empty query browses the catalogue. `execute_tools` takes `{"calls": [{"name": "...", "arguments": {...}}]}`, runs up to 25 calls in order, and stops at the first error. A nested empty object in a tool argument, such as `{"options":{}}`, stays an object through a direct `tools/call` and through `execute_tools`, and an empty list stays a list. The tool result text keeps that shape. `/mcp/search` limits the output of one call to 65,536 bytes. `execute_tools` stops when the combined results pass that limit, so a large list result can fail through `/mcp/search` and still work through `/mcp`.

One MCP request has one 570-second command deadline, the same deadline as one API request. PHP-FPM ends a Gateway request after 600 seconds. All tool calls in one `execute_tools` batch share that deadline. A tool call cannot extend or reset it. Forward work stops 20 seconds before the deadline, which leaves that time for cleanup. A call that starts after the batch has used up its forward time returns the tool error `command.deadline_exceeded` (status 504) and does not run. The batch then stops at that call. Send long tools, such as `gateway-release-smoke` or `instance-deploy`, as separate requests.

## Tool list changes

A client fetches the tool list when it connects and keeps it. A release that adds, removes, or changes a tool in `tools.json`, or an extension switch, changes the list on the Gateway at once. The server declares `tools.listChanged` as `false`, because it never sends `notifications/tools/list_changed`. A connected client keeps its list until it lists the tools again, which most clients do only when they connect.

The Gateway never ends a session because the list changed. Every request is served against the current list:

* A call to a tool that still exists runs as usual.
* A call to a tool that was removed or renamed receives HTTP 400 with the JSON-RPC error `-32602` `Tool [name] not found`. The message tells the client to list the tools again. The Gateway runs nothing.
* A call whose arguments a changed operation rejects receives the operation's usual tool error, such as `validation.failed`.

To see a new tool, reconnect the MCP server or start a new session. In Claude Code, use `/mcp` and reconnect `orbit`. `/mcp/search` lists the same two tools after every change, and `search_tools` always searches the current catalogue.

## Identity and access

The caller is the Node whose WireGuard address the connection comes from. There is no token and no login.

* A connection from an address that is not an active Node receives HTTP `403` with `peer.identity_unknown`, before it can list tools.
* `gateway-desired-fleet-state` needs no access grant. Any active Node may read the [desired fleet state](/docs/reference/self-update#where-it-is-served).
* Each tool call runs as an internal API request with the caller's address. The API checks the caller and its [access grants](/docs/cli/node#orbit-nodeaccessadd) again. A missing grant returns the API's `403` with `node_access.required` as the tool error.
* The Gateway certificate chains to the Orbit root certificate authority (CA). Orbit installs that root in the system trust store only on a Node where it converges an Instance, Route, router, ingress, or custom proxy certificate. On any other machine, run `orbit gateway:trust` first, or point the client at the root CA file.

## Tools

A tool's name is the operation ID in the [API reference](/docs/api/overview), `<family>-<verb>`, such as `node-list`, `instance-show`, or `process-restart`. Its input is one object with the operation's path parameters, query parameters, and JSON body fields. An input the API validates as a boolean is a JSON `boolean` in the tool schema. The caller sends that value as a JSON boolean. The Gateway serves each tool's input schema exactly as `bin/mcp-tools` generated it in `resources/mcp/tools.json`, with empty schemas encoded as JSON objects (`{}`), not lists (`[]`). The server puts each value where the operation expects it. A `GET` tool is marked read-only, and a `DELETE` tool is marked destructive.

`project-document-storage-show` and `project-document-storage-update` expose the Gateway-wide [Project Documents storage configuration](/docs/reference/project-documents#private-s3-boundary). Both require Gateway access and take no Project selector. Update accepts `endpoint`, `region`, `bucket`, and the paired write-only fields `access_key_id` and `secret_access_key`. Omitting both credential fields preserves them. Results contain only redacted configuration and cleanup status; show does not probe the provider. Update verifies access before saving and returns `project_documents.storage_unavailable` without provider diagnostics on failure.

`tool-scan` reads the enrolled account's Homebrew formulae, casks, and Vite+ global packages for one Node. Its only input is `node_id`. The call does not create a Tool row.

`tool-adopt` records ownership of one installed package. Its inputs are `node_id`, `manager`, `package`, and optional `version_constraint`. The call does not install, update, or remove the package.

A successful call returns the API's JSON document as text, usually `{"data": ..., "meta": {"request_id": ...}}`. A failed call returns a successful MCP transport response whose tool result has `isError: true`. Its `content` carries the API error envelope with the status added; the HTTP response itself is not the API's error status:

```json theme={"dark"}
{"isError": true, "content": [{"type": "text", "text": "{\"status\":422,\"error\":{\"code\":\"validation.failed\",\"message\":\"...\",\"details\":{\"slug\":[\"...\"]}}}"}]}
```

For a disabled extension, a direct `tools/call` returns `isError: true` and content containing `status: 409` and `error.code: extension.disabled`; HTTP 409 is reserved for the underlying API request, not the MCP transport. The same tool error is returned by `execute_tools` when a stale call names a tool that was filtered from the catalogue, and execution stops at that call. The server checks the generated tool's extension metadata before catalogue filtering can turn a stale direct call into a generic not-found error.

`instance-rename` records an externally renamed development branch and can move the Instance's own Route, including a generated Route. Send `instance` and at least one of `branch` or `domain`. The Gateway checks the local checkout before changing its records; the tool never runs `git branch -m`. See [Instance rename](/docs/cli/instance#orbit-instancerename).

`instance-deploy` and `instance-rollback` stream progress over HTTP. As tools they run to completion and return their progress events in order under `events`. The last event carries the result: a `result` event whose `status` is `succeeded` or `failed`. Step output arrives as `output` events with base64 `data_base64`. Through both endpoints, these events may use half of the 65,536-byte `/mcp/search` output limit. When they would use more, the reply keeps the first 16,384 and the last 16,384 bytes of them. One `{"type": "output_truncated", "dropped_bytes": N}` event replaces the middle and gives the number of output bytes it dropped. Phase events and the result are never dropped, so a long deploy fits in an `execute_tools` reply. To read more of the output, call `instance-deployment-show`, which returns the [stored deployment log](/docs/cli/instance#orbit-instancedeploymentshow).

Machine callbacks and browser pages have no tool: channel signatures, the Node agent endpoints, live log streams, the Grafana access check, the Instance wake page, the Schedule completion report, the annotation event stream, the task agent stream, and the two GitHub App registration pages. `bin/mcp-tools` lists them.

The `tasks-*` tools belong to the Gateway-switched [tasks](/docs/reference/tasks) extension, except `tasks-status`, which is a core assistance view that remains available while tasks is off. Each `tasks-status` entry carries `assistance_kind` and `assistance_question` ([Direction requests](/docs/reference/tasks#direction-requests)). The task definition tools are `tasks-definition-list`, `tasks-definition-show`, `tasks-definition-create`, `tasks-definition-update`, and `tasks-definition-destroy`. List and show accept any authorized peer. Create, update, and destroy need Gateway access. None of them starts a task. `tasks-question-list` accepts any authorized peer. `tasks-question-close` needs Gateway access and closes a stale question as `answered` or `superseded` ([Close a question](/docs/reference/tasks#close-a-question)). While the tasks extension is off, the API refuses these operations with HTTP 409 `extension.disabled`. The `proxycli-*` tools belong to [proxycli](/docs/reference/proxycli). The `vault-*` tools belong to [Vaultwarden](/docs/reference/vaultwarden) and need Gateway access; no `vault-*` result contains a secret. The `conn-*` tools serve the [Conn layer](/docs/reference/conn); every active Node may call them without a node access edge. A disabled extension is omitted from both `tools/list` and `/mcp/search`; a direct `tools/call` or stale `execute_tools` call returns an MCP tool error with `isError: true`, content carrying `status: 409` and `error.code: extension.disabled`, and does not execute the API route. `tasks-create` requires Gateway access. `tasks-update` and the `tasks-subtask-*` tools need access to the Node that holds the task's Instance, or to the Gateway for a task without one. `tasks-create`, `tasks-subtask-create`, and `tasks-subtask-update` take each subtask's typed [deliverables](/docs/reference/tasks#deliverables) as a `deliverables` array, and `tasks-show` returns them.

`tasks-complete` takes the numeric `group` ID and requires Gateway access. It completes a settling task, or a running or reviewing task whose watched pull request has merged or closed. On the latter path it cancels open subtasks and stops their running agents and checks before workspace removal. Other tasks return `tasks.not_settling`; a failed stop returns `tasks.subtask_interrupt_failed`. The [completion contract](/docs/reference/tasks#complete-and-cleanup) defines the durable receipt and cleanup retries.

## Connect a client

Claude Code, from a Node or a WireGuard peer that trusts the Orbit CA:

```bash theme={"dark"}
claude mcp add --transport http orbit https://gateway.orbit/mcp
```

A client that takes a JSON server entry:

```json theme={"dark"}
{"mcpServers": {"orbit": {"type": "http", "url": "https://gateway.orbit/mcp/search"}}}
```

A Node.js client reads extra CAs from `NODE_EXTRA_CA_CERTS`. Point it at the Orbit root CA file when the client does not use the system trust store.

[GitHub feedback consumption](/docs/reference/tasks#trusted-github-feedback) runs in the Tasks scheduler, not through a new tool. Existing task tools return its fixup identity and findings in the ordinary subtask fields. The reviewer allowlist is Gateway configuration, not an MCP input. Feedback adds no review-submission, dismissal, re-review-request, or merge tool. Regenerating the catalogue and task-action manifest must preserve that boundary.

## Keep the catalogue current

`bin/mcp-tools` generates `apps/gateway/resources/mcp/tools.json` from `docs/openapi.json`. The same run writes `apps/gateway/resources/tasks/actions.json`, the operation ids marked `x-orbit-task-action`, and `--check` fails when either file is stale. Tool descriptions also come from the OpenAPI document. The `tasks-create` and `tasks-update` descriptions follow the [Tasks contract](/docs/reference/tasks#tasks-and-subtasks), with Project ownership and the status, subtask, deliverable, and Coder notification requirements. The `tasks-cancel` description follows [cancelling a stuck task](/docs/reference/tasks#cancel-a-stuck-task): forced workspace removal also removes an eligible pending or failed exclusive Route of a `source_resolved` workspace, while an active or shared Route still refuses. Each generated operation records its extension slug, or `null` for an untagged (core) operation. The normal MCP server and `OrbitSearchServer` filter that metadata against the Gateway enabled set before building their catalogues. The `route-create` tool accepts either `instance_id` and `domain` for an app Route or `node_id`, `domain`, and one of `upstream` or `process_id` for a custom proxy. It does not accept `project_id` or `cluster_id` on creation. After a change to a route, form request, data class, or operation description, regenerate both:

```bash theme={"dark"}
bin/docs-openapi
bin/mcp-tools
```

Continuous integration runs `bin/docs-openapi --check` and `bin/mcp-tools --check`. A Gateway test fails when an API route has no tool or a tool has no route. The inputs to `bin/docs-openapi` are on [API reference generation](/docs/reference/api-reference).

## Why it works this way

These reasons explain the design. Check them before you propose a change.

### Tools generated from the API description

More than a hundred hand-written tool classes would repeat the form requests' rules and drift from them with every API change. Generating the catalogue from the OpenAPI document keeps one source. A weak tool description is fixed in the form request, the data class, or the CLI command text that [API reference generation](/docs/reference/api-reference) copies into the operation.

### Tool calls go through the HTTP kernel

Identity, access grants, validation, redaction, and Activity live at the HTTP boundary. A tool that called Actions directly would need a second copy of each. An internal request with the caller's address gets them all.

### No token or OAuth

The API identifies a caller by WireGuard address. A second credential would create a second authorization model for the same actions.

### No MCP server in the CLI

An MCP server that wraps CLI commands keeps the process start and the CLI install on the agent's machine, and parses output that the API already returns as JSON.

### No pushed or forced tool list refresh

A pushed `notifications/tools/list_changed` needs a stream that stays open: `GET /mcp` in the `initialize` revisions, or `subscriptions/listen` in the 2026-07-28 revision. Each open stream would hold one of the Gateway's eight PHP-FPM workers, and PHP-FPM ends every request after 600 seconds. Sending the notification inside the reply to an ordinary request needs no held stream, but the stateless server would then send it on every later request of that session. Ending a session with HTTP 404 makes a spec-compliant client initialize again and list the tools, but a client that does not handle the 404 fails every call until it restarts. A stale list costs a client at most a reconnect; a failing session stops its work. The Gateway therefore serves every session and explains a missing tool in the error.

### Typed tools, not one command tool

One tool that takes a command string would discard the input schemas, which are the main benefit of MCP for an agent. A client with a small context uses `/mcp/search` instead.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.