magi-code 0.104.0

Repository-aware CLI coding agent for terminal work
Documentation
# MCP stdio and HTTP tools

[Feature docs index](README.md) · [Repository README](../../README.md)

Configure Model Context Protocol (MCP) servers so their tools appear beside magi-code's built-ins. You can diagnose setup without provider credentials.

Supported transports:

- `stdio`: local child process MCP servers.
- `http`: MCP Streamable HTTP endpoints using POST/GET/DELETE.

## Configuration and approval

Server definitions use the Claude-style `.mcp.json` convention, not a universal MCP specification. magi-code loads `CONFIG_DIR/.mcp.json`, then `cwd/.mcp.json`; `CONFIG_DIR` is the resolved `MC_HOME` (default `~/.magi-code`). It does not search parent directories. A project server replaces the entire global definition with the same name, not individual fields.

Example `.mcp.json`:

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "node",
      "args": ["${MCP_SERVER_DIR}/server.js"],
      "env": { "MCP_ROOT": "${MCP_ROOT:-/tmp/mcp-root}" }
    },
    "remote_search": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${MCP_REMOTE_SEARCH_TOKEN}",
        "X-Team": "platform"
      },
      "timeout": 30
    },
    "my_oauth_server": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "oauth": {
        "client_id": "your-client-id",
        "scopes": ["read", "tools"],
        "authorization_server": "https://auth.example.com"
      }
    }
  }
}
```

All servers are disabled until approved. An `enabled` field in `.mcp.json` cannot grant approval. In Mission Control, `/mcp` shows each server's approval status and current redacted definition. `Up`/`Down` selects a server, `PageUp`/`PageDown` scrolls its definition, `Enter` approves or revokes it, and `Esc` closes. Changes apply on the next launch, not immediately or after `/new`.

Approvals live only in global `settings.json` at `capabilities.mcp_approvals[canonical_source_path][name]`. An approved entry contains `{"version": 1, "digest": "<64 lowercase hex characters>"}`. A revoked entry is `false`. Missing, malformed, unsupported-version, and unbound boolean approvals do not authorize execution. The view distinguishes never approved, revoked, definition changed, renewal required, and invalid approval states.

The digest is an HMAC-SHA-256 commitment to the parsed, unexpanded definition, canonical source path, and server name. Its random key lives separately in `$MC_HOME/mcp-approval.key`, with owner-only `0600` permissions on Unix. Do not share or commit this key. Losing or replacing it requires approval again. Approval records contain no definitions, credentials, or expanded environment values.

The definition covers transport, command, ordered arguments, environment names and values, URL, header names and values, timeout, and all OAuth settings. Object order and document whitespace do not matter. Parser defaults, including omitted `type: stdio`, empty collections, and the 30-second timeout, normalize consistently. String contents, argument order, URL paths/queries, and environment reference names or fallback expressions remain significant. The definition's `enabled` field is excluded.

An approval action carries the displayed source and fingerprint. Saving re-reads the definition and rejects a stale action instead of approving changed contents. Failed saves refresh the view without claiming success. Historical definitions are not stored, so the view reports invalidation without a field-by-field change history. Project settings cannot approve servers. A same-name replacement in another source requires its own approval.

Loading validates approval before expanding that same definition for launch. Newly loaded definitions must pass this check before startup or reconnection, including `serve --stdio`, which never prompts for MCP approval. Existing connections and already-started calls retain their snapshot until normal shutdown.

Changing an environment value behind an unchanged reference does not invalidate approval, even when that changes the effective command or endpoint. This binds configuration, not runtime identity: executable contents, `PATH` resolution, DNS, and remote behavior are not pinned. It is not a sandbox.


| Field | Behavior |
| --- | --- |
| `type` | `stdio` (also the default when omitted) or `http`. |
| `command` / `args` / `env` | Stdio only. Command is spawned directly. The child starts with `PATH` plus the Windows startup baseline, then receives the explicit `env` overlay. |
| `url` | HTTP only. HTTPS required except explicit loopback HTTP (`localhost`, `127.0.0.0/8`, or `[::1]`). URL credentials and redirects are rejected. |
| `headers` | HTTP only. Values support environment expansion. Static `Authorization` / `Proxy-Authorization` are mutually exclusive with `oauth`. |
| `oauth.client_id` | Optional public client id. If omitted, dynamic client registration is used when advertised. |
| `oauth.scopes` | Optional requested scopes. |
| `oauth.authorization_server` | Optional authorization-server override. |
| `timeout` | Optional seconds per request; defaults to `30`, max `300`. |

Discovery parses every definition's structural fields, including server names, transport types, required fields, and JSON value types. Structural errors can block loading even for disabled servers. Only enabled entries expand environment references and validate runtime fields such as commands, URLs, headers, and timeouts. Missing environment variables in disabled entries do not block loading. Enabling through `/mcp` expands and validates the selected definition before saving approval; failure leaves the saved approval unchanged.

For enabled entries, `${VAR}` and `${VAR:-default}` expand once while loading `command`, each argument, environment values, URL, and header values. A missing variable without a default fails configuration loading. Defaults apply to unset variables; an empty variable stays empty. Expansion is not recursive or shell evaluation. Use environment references rather than storing secrets in `.mcp.json`.

Stdio servers that locate configuration or a daemon through the home directory must receive it explicitly, for example `"env": { "HOME": "${HOME}" }` for `cua-driver mcp`. Ambient `HOME` is not inherited.

HTTP `timeout` bounds each request. Cancellation is checked before and around an HTTP request, but an in-flight blocking POST/body cannot be interrupted; `notifications/initialized` uses the same path. Stdio and pending-response waits remain cancellation-responsive.

Initialization requests MCP `2025-06-18` and accepts a negotiated response of `2025-06-18` or `2025-03-26`. Other versions fail initialization. HTTP requests after initialization carry the negotiated version in `MCP-Protocol-Version`.

Calls to the same server can overlap, including calls from parallel subagents. Request IDs correlate replies; connection shutdown remains exclusive. Canceling or timing out an admitted request closes the shared connection and fails pending siblings, so later calls need a new connection. A call canceled before admission does not close it. In-flight blocking HTTP POSTs still run until completion or their timeout. OAuth token acquisition/refresh is serialized within the shared provider; tool POSTs are not.


## HTTP security rules

- Keep `.mcp.json` and `settings.json` non-secret. Use environment references for credentials.
- Remote MCP and OAuth endpoints must use HTTPS. HTTP is accepted only for explicit loopback hosts: `localhost`, `127.0.0.0/8`, and `[::1]`.
- URL userinfo is rejected. Query and fragment data are accepted for endpoint configuration but stripped from metadata/discovery URLs and diagnostics.
- MCP POST, GET/SSE, DELETE, OAuth discovery, registration, and token clients disable redirects. A redirect fails before credentials can reach another origin or downgrade to HTTP.
- Use environment references such as `"Authorization": "Bearer ${MCP_TOKEN}"` for sensitive headers. Configuration expands values once; the HTTP runtime accepts the resulting literal values without further expansion.
- Non-sensitive headers may use literal values, for example `"X-Team": "platform"`.
- All HTTP header values are redacted in debug output, errors, manager status, CLI diagnostics, sessions, and provider-visible paths.
- Diagnostics display sanitized URLs only: scheme, host, port, and path. Query strings, fragments, and URL credentials are not printed.
- HTTP error bodies are bounded before display.
- Provider auth variables are never copied into MCP HTTP headers automatically.

## OAuth HTTP authentication

OAuth is explicit and interactive:

```sh
magi-code mcp login my_oauth_server
magi-code mcp logout my_oauth_server
magi-code mcp list
```

Behavior:

- `mcp login <server>` discovers OAuth metadata, uses Authorization Code + PKCE S256, binds its callback to localhost only, and stores the resulting tokens locally.
- `mcp logout <server>` deletes the local token file only; it does not revoke remote tokens.
- `mcp list` shows OAuth status: authenticated, not authenticated, expired, refreshable, or invalid.
- Normal agent runs never open a browser. If auth is missing or expired without refresh, diagnostics tell you to run `magi-code mcp login <server>`.

Token storage:

- Tokens live under `$MC_HOME/mcp-tokens/<server>.json`.
- Token files are written with `0600` permissions on Unix.
- Tokens never belong in `settings.json`.
- Tokens, client secrets, authorization codes, PKCE verifier/state values, and bearer headers are redacted from logs, errors, Debug output, CLI diagnostics, sessions, and provider-visible tool output.

Security and limits:

- OAuth is mutually exclusive with static `Authorization` and `Proxy-Authorization` headers for the same server.
- PKCE S256 is used for all login flows.
- The callback listener binds localhost only.
- Client Credentials and Device Code grants are not supported.
- Login is an explicit CLI command, not automatic during agent/tool execution.

## Tool names

Discovered MCP tools are namespaced as:

```text
mcp__<server>__<tool>
```

Example: server `filesystem` tool `read_file` becomes `mcp__filesystem__read_file`. Qualified names must be exactly `mcp__<server>__<tool>` and no more than 64 bytes total (UTF-8; current safe components are ASCII). Both components must be non-empty and use only ASCII letters/digits/`_`/`-`; neither may contain `__`. Only server names cannot end with `_`; tool names may end with `_`. These restrictions keep the server/tool separator unambiguous.

## Tool call arguments

Direct provider calls and Code Mode share the same MCP call schema: required nonblank `intent` plus required `arguments` object. `intent` explains why the tool is needed; `arguments` contains the server's inputs. Even tools with no inputs require `arguments: {}`.

```javascript
tools.call("mcp__filesystem__read_file", {
  intent: "Read the project setup instructions",
  arguments: {path: "README.md"}
});
```

The harness validates intent before execution and forwards only `arguments` to the MCP server. Server-owned fields, including any `intent` field inside `arguments`, remain unchanged. The outer call rejects fields other than `intent` and `arguments`. Session history and hooks retain the full call. Built-ins use the same intent validation without the MCP argument wrapper; client-registered tools keep their declared schemas without an added intent requirement.

## Server messages and live tool discovery

Servers may send requests while idle or during tool calls. `ping` receives an empty result; other methods receive `-32601 Method not found`. No roots, sampling, or elicitation capabilities are advertised. Replies preserve integer or string IDs (up to 256 bytes); unusable IDs are ignored. Each server is limited to 32 replies per one-second window. Stdio writes share the existing serialized writer; HTTP uses one blocking reply worker with at most 32 queued replies and the current session, protocol, and authentication headers.

Servers advertising `capabilities.tools.listChanged: true` can send `notifications/tools/list_changed`. Notifications coalesce until the next user turn. Before preparing that turn, the worker repeats paginated `tools/list` with the normal timeout, cancellation, naming, schema conversion, and route validation. Other servers keep their startup snapshot.

An invalid or failed refresh retains the previous snapshot and schedules one retry at the next user turn. Further attempts require another notification. `/mcp` shows the retained tool count and stale status. No refresh happens during provider requests or their continuations. Calls already dispatched may finish; later calls to removed names use the normal unknown-tool path.

Each run and its children retain a frozen catalog, even if another conversation refreshes the shared manager. Refreshed tools still pass through configured permissions, task ceilings, inspection restrictions, subagent profiles, and independent direct/Code Mode exclusions. Sorted tool-definition digests avoid replacing an unchanged snapshot; the final advertised definitions contribute to the prompt-cache key.

Approval covers the canonical source, server name, and unexpanded server definition—not discovered tools. Tool changes need no re-approval. Changing the server definition still requires approval renewal and restart.

## Diagnostics

Diagnostics read the resolved configuration directory, global settings approvals, and both `.mcp.json` sources. They do not attach/create sessions or require provider auth.

```sh
magi-code mcp list
```

Prints configured servers, type, enabled/disabled state, OAuth auth status when configured, and connection result. Enabled servers are started long enough to run initialize and `tools/list`; failures are reported with phase diagnostics on stderr. HTTP servers are shown as `http <sanitized-url>`. The command exits `0` if the list command itself ran, even when individual servers fail.

```sh
magi-code mcp test remote_search
```

Starts only the selected server, runs initialize and `tools/list`, prints server name/version/protocol plus discovered tool names/descriptions, then shuts down. For HTTP servers, shutdown sends best-effort session `DELETE` when the server issued an MCP session id. It exits non-zero for missing, disabled, spawn/connect, initialize, or list failures.

### Startup policy by interface

`magi-code mcp list` and `magi-code mcp test` diagnose servers without opening a conversation. List reports individual failures without blocking other servers. Mission Control requires every enabled MCP server to connect, initialize, and discover tools before running queued prompts. If startup fails, it restores the terminal, exits nonzero, and leaves the queue unrun.

Mission Control requests cancellation before cleanup. Critical/provider worker joins are bounded at 2 s each before detach/error, but an in-flight HTTP POST/body (including `notifications/initialized`) cannot observe cancellation. The 2 s critical join may detach the worker while it remains subject to that server's configured request timeout (30 s default, 300 s maximum). Terminal restoration happens first. Pre-request cancellation and stdio/pending-response paths remain responsive; detaching a non-cooperative worker does not guarantee that it will have no side effects.

## Supported MCP surface

Supported now:

- `stdio` transport.
- Streamable HTTP transport with POST JSON-RPC, optional GET SSE notifications, and DELETE session termination.
- `initialize`, `notifications/initialized`, `tools/list`, and `tools/call`.
- HTTP `Mcp-Session-Id` tracking and `MCP-Protocol-Version` headers after initialize.
- SSE parsing for Streamable HTTP responses.
- Server-initiated `ping` and method-not-found replies on stdio and HTTP.
- Turn-boundary tool refresh for servers advertising `tools.listChanged`.

Not supported:

- Client Credentials OAuth grant.
- Device Code OAuth grant.
- SSE transport other than Streamable HTTP response parsing.
- WebSocket transport.
- MCP resources, prompts, sampling, elicitation.
- magi-code-as-MCP-server mode.

## Related docs

- [Configuration](configuration.md)
- [Tools and safety model](tools-and-safety.md)

---

[Back to feature docs](README.md) · [Back to repository README](../../README.md)