# MCP servers

OpenCode connects to [Model Context Protocol](https://modelcontextprotocol.io/) servers and exposes capabilities such as tools, prompts, resources, and instructions. MCP tools consume model context, so add only the servers you need.

## Setup

Add a remote server from the project that should use it, then check its connection:

```sh
opencode mcp add context7 --url https://mcp.context7.com/mcp
opencode mcp list
```

The command writes the server to the project [configuration](/config). Add `--global` to make it available in every project:

```sh
opencode mcp add context7 --global --url https://mcp.context7.com/mcp
```

Remote servers use OAuth by default. If the list shows `needs authentication`, open OpenCode, run `/mcps`, select the server, and sign in. A connected server is ready for an agent to use:

```text
✓ context7  connected
```

## Config

To configure a server by hand, give it a unique name under `mcp.servers`. V2 does not place server names directly under `mcp`.

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "my-server": {
        "type": "local",
        "command": ["npx", "-y", "example-mcp-server"],
      },
    },
  },
}
```

Servers connect automatically. Use `disabled`, not an `enabled` field, to keep one configured without connecting it:

```jsonc
{
  "mcp": {
    "servers": {
      "my-server": {
        "type": "local",
        "command": ["npx", "-y", "example-mcp-server"],
        "disabled": true,
      },
    },
  },
}
```

A higher-precedence project config replaces the entire server object with the same name. Use different names for separate connections or accounts; otherwise repeat every required field in the override:

```jsonc title="opencode.jsonc"
{
  "mcp": {
    "servers": {
      "my-server": {
        "type": "remote",
        "url": "https://mcp.example.com/mcp",
      },
    },
  },
}
```

## Local

A local server is a command that OpenCode starts over the MCP stdio transport. Add one with a command after `--`:

```sh
opencode mcp add everything -- npx -y @modelcontextprotocol/server-everything
```

Pass environment variables with repeated `--env` flags:

```sh
opencode mcp add everything --env LOG_LEVEL=info -- npx -y @modelcontextprotocol/server-everything
```

Use configuration for other process options such as a working directory:

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "everything": {
        "type": "local",
        "command": ["npx", "-y", "@modelcontextprotocol/server-everything"],
        "cwd": ".",
        "environment": {
          "LOG_LEVEL": "info",
          "MCP_API_KEY": "{env:MCP_API_KEY}",
        },
      },
    },
  },
}
```

| Field | Required | Description |
| --- | --- | --- |
| `type` | Yes | Must be `"local"`. |
| `command` | Yes | Executable followed by its arguments. |
| `cwd` | No | Process directory. Relative paths resolve from the workspace, which is also the default. |
| `environment` | No | String variables added to OpenCode's inherited process environment. |
| `disabled` | No | Prevents connection when `true`. Defaults to `false`. |
| `codemode` | No | Set to `false` to expose tools directly instead of through Code Mode. Defaults to `true`. |
| `timeout` | No | Per-server timeout overrides. |
| `protocol` | No | `legacy` (default), `auto`, or `2026-07-28`. See [Protocol version](#protocol-version). |
| `pool` | No | Share one connection across directories. Defaults to `false`. See [Shared connections](#shared-connections). |

Use `{env:NAME}` for environment substitution. Shell expressions such as `$NAME` are not expanded in JSON strings:

```jsonc
{
  "environment": {
    "MCP_API_KEY": "{env:MCP_API_KEY}",
  },
}
```

## Remote

A remote server uses the MCP Streamable HTTP transport and requires an absolute URL:

```sh
opencode mcp add context7 --url https://mcp.context7.com/mcp
```

Pass request headers with repeated `--header` flags. Quote `{env:NAME}` references so the shell writes them literally:

```sh
opencode mcp add context7 --url https://mcp.context7.com/mcp --header 'CONTEXT7_API_KEY={env:CONTEXT7_API_KEY}'
```

Use configuration for other options. Store secrets in environment variables rather than in the file:

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "context7": {
        "type": "remote",
        "url": "https://mcp.context7.com/mcp",
        "oauth": false,
        "headers": {
          "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}",
        },
      },
    },
  },
}
```

| Field | Required | Description |
| --- | --- | --- |
| `type` | Yes | Must be `"remote"`. |
| `url` | Yes | Absolute Streamable HTTP endpoint. |
| `headers` | No | String HTTP headers sent to the endpoint. |
| `oauth` | No | OAuth settings, or `false` to disable OAuth. |
| `disabled` | No | Prevents connection when `true`. Defaults to `false`. |
| `codemode` | No | Set to `false` to expose tools directly instead of through Code Mode. Defaults to `true`. |
| `timeout` | No | Per-server timeout overrides. |
| `protocol` | No | `legacy` (default), `auto`, or `2026-07-28`. See [Protocol version](#protocol-version). |
| `pool` | No | Share one connection across directories. Defaults to `false`. See [Shared connections](#shared-connections). |

Use `oauth: false` only when the server exclusively uses an API key or another header credential:

```jsonc
{
  "type": "remote",
  "url": "https://mcp.example.com/mcp",
  "oauth": false,
  "headers": { "Authorization": "Bearer {env:MCP_API_KEY}" },
}
```

## OAuth

OAuth is enabled for remote servers unless `oauth` is `false`. OpenCode discovers the authorization server, signs in with PKCE, and refreshes tokens. Credentials are stored outside project configuration.

Most servers need only the URL:

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "sentry": {
        "type": "remote",
        "url": "https://mcp.sentry.dev/mcp",
      },
    },
  },
}
```

If the server needs authentication, run `/mcps`, select it, and complete authorization in the browser. The CLI can start the same flow:

```sh
opencode mcp auth sentry
```

### Browser on another machine

When OpenCode runs over SSH, in a container, or on a remote server, the browser cannot reach OpenCode after you sign in and shows a page that does not load. Copy that page's URL from the address bar and paste it into the sign-in prompt:

```text
http://127.0.0.1:52817/callback?code=...&state=...
```

The prompt appears in `/mcps`, in `opencode mcp auth`, and in the desktop and web app's sign-in dialog while OpenCode waits for the browser.

### Client registration

OpenCode identifies itself to the authorization server with the first method that applies:

| Method | Used when |
| --- | --- |
| Pre-registered client | `oauth.client_id` is configured. |
| [Client ID Metadata Document](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-id-metadata-documents) (CIMD) | The authorization server supports CIMD and public clients, and `oauth.redirect_uri` is not set. |
| [Dynamic client registration](https://datatracker.ietf.org/doc/html/rfc7591) (DCR) | Neither of the above applies. OpenCode registers a client and reuses it on later sign-ins. |

An authorization server opts into CIMD through its metadata:

```json
{
  "issuer": "https://auth.example.com",
  "client_id_metadata_document_supported": true,
  "token_endpoint_auth_methods_supported": ["none"]
}
```

With CIMD, OpenCode sends its metadata document URL as the `client_id` and skips registration:

```text
https://opencode.ai/oauth/opencode/client.json
```

The document allows these loopback redirects without a port:

- `http://127.0.0.1/callback`
- `http://localhost/callback`
- `http://127.0.0.1/mcp/oauth/callback` for older clients

Authorization servers accept any loopback port for these redirects, as [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3) requires, so `callback_port` works with CIMD. A custom `redirect_uri` does not, so OpenCode falls back to DCR.

If the authorization server supports neither CIMD nor DCR, configure a pre-registered client.

### Pre-registered clients

When a provider gives you client credentials, use V2's snake_case OAuth fields:

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "company-tools": {
        "type": "remote",
        "url": "https://mcp.example.com/mcp",
        "oauth": {
          "client_id": "{env:MCP_CLIENT_ID}",
          "client_secret": "{env:MCP_CLIENT_SECRET}",
          "scope": "tools:read tools:execute",
          "callback_port": 19876,
          "redirect_uri": "http://127.0.0.1:19876/callback",
        },
      },
    },
  },
}
```

| Field | Description |
| --- | --- |
| `client_id` | Pre-registered client ID. Takes priority over CIMD and DCR. |
| `client_secret` | Secret for a confidential client, sent in the token request body. Omit it for public clients. |
| `scope` | Space-delimited scopes to request. Defaults to the scope the server names when it rejects an unauthenticated request. |
| `callback_port` | Local callback port from `1` through `65535`. Defaults to the `redirect_uri` port, then an available ephemeral port. |
| `redirect_uri` | Pre-registered loopback URI whose path and port reach the local callback listener. Setting it disables CIMD. |
| `auth_server_metadata_url` | URL of the authorization server's OAuth or OpenID Connect metadata document. Set it when the MCP server does not publish protected resource metadata that names its authorization server. |

Remove stored OAuth credentials when you need to sign in again or switch accounts:

```sh
opencode mcp logout sentry
```

## Timeouts

Timeouts are positive integer milliseconds. Set defaults under `mcp.timeout`; a server's `timeout` object overrides matching defaults.

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "timeout": {
      "startup": 45000,
      "catalog": 30000,
      "execution": 600000,
    },
    "servers": {
      "slow-tools": {
        "type": "remote",
        "url": "https://mcp.example.com/mcp",
        "timeout": {
          "catalog": 60000,
        },
      },
    },
  },
}
```

| Timeout | Default | Applies to |
| --- | --- | --- |
| `startup` | 30 seconds | Transport connection and server initialization. |
| `catalog` | 30 seconds | Listing tools, prompts, resources, and resource templates. |
| `execution` | 12 hours | Tool calls, prompt retrieval, and resource reads. |

## Protocol version

OpenCode opens every server with the classic MCP `initialize` handshake by default. Set a server's `protocol` to talk to one built on the 2026-07-28 revision:

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "modern": {
        "type": "remote",
        "url": "https://mcp.example.com/mcp",
        "protocol": "auto",
      },
    },
  },
}
```

| Value | Behavior |
| --- | --- |
| `legacy` | Default. Sends `initialize` and speaks protocol revisions up to 2025-11-25. |
| `auto` | Probes with `server/discover` for the 2026-07-28 revision and falls back to `legacy` when the server does not support it. |
| `2026-07-28` | Requires the 2026-07-28 revision. The connection fails against older servers. |

Probing a local `legacy` server adds a short-lived extra process and can wait up to the startup timeout when the server ignores unknown requests, so keep `auto` scoped to servers that need it.

## Shared connections

One OpenCode server can have many directories open, and each one connects to every MCP server on its own. Set `pool` to `true` to let directories with the same server settings share one connection and one tool list:

```jsonc title="opencode.jsonc"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "cloudflare": {
        "type": "remote",
        "url": "https://mcp.cloudflare.com/mcp",
        "pool": true,
      },
    },
  },
}
```

- Only connections on the 2026-07-28 revision are shared. With `pool` set, an unset `protocol` means `auto`. A server that answers with an older revision, or one set to `legacy`, keeps one connection per directory.
- Directories share a connection only when everything that changes it matches: the URL, headers, OAuth settings and signed-in account for a remote server, and the command, working directory and environment for a local one. Timeouts and `protocol` must match too. The server's name doesn't need to, but sign-ins are stored per name, so an OAuth server is shared only under the same name.
- A local server is shared only between directories on this machine, not ones in a workspace. Sharing it across projects needs a fixed `cwd`, such as `"~"`. Without one it runs in each project's folder, so each project gets its own and OpenCode logs a warning.
- A shared connection has no roots. When a tool asks for input, the request appears in the directory that called it.
- The connection closes as soon as the last directory using it closes or disconnects the server.

## Names

OpenCode names a tool `<server>_<tool>`. It replaces characters other than letters, numbers, `_`, and `-` with `_`:

```text
server: context 7
tool:   resolve.library/id
name:   context_7_resolve_library_id
```

MCP prompts become commands named `<server>:<prompt>` with the same normalization. For example:

```text
/context_7:find_docs
```

Command text fills the prompt's arguments in order. Separate them with spaces and quote values that contain spaces:

```text
/context_7:find_docs react "server components"
```

Choose short server names that remain unique after normalization. Under the default Code Mode, tools are grouped by the normalized server name:

```text
tools.context_7.resolve_library_id(...)
```

## Permissions

Code Mode is the default. Set `codemode` to `false` when a server's tools must stay on the provider's native tool list:

```jsonc
{
  "mcp": {
    "servers": {
      "context7": {
        "type": "remote",
        "url": "https://mcp.context7.com/mcp",
        "codemode": false,
      },
    },
  },
}
```

Use permission actions to hide or deny tools without disconnecting their server. Match the normalized `<server>_<tool>` name:

```jsonc
{
  "permissions": [
    {
      "action": "context7_*",
      "resource": "*",
      "effect": "deny",
    },
  ],
}
```

## Resources

Agents read MCP resources through two built-in tools, `list_mcp_resources` and `read_mcp_resource`. Control them with these permission actions:

| Action | Resource |
| --- | --- |
| `opencode_list_mcp_resources` | The server name. |
| `opencode_read_mcp_resource` | `<server>:<uri>` |

For example, allow every read from the `docs` server:

```jsonc
{
  "permissions": [
    {
      "action": "opencode_read_mcp_resource",
      "resource": "docs:*",
      "effect": "allow",
    },
  ],
}
```

## Server instructions

When a server returns instructions during initialization, OpenCode adds them to the system prompt:

```text
<mcp_instructions>
  <server name="sentry">
    Search for an existing issue before creating a new one.
  </server>
</mcp_instructions>
```

For servers that use Code Mode, OpenCode adds a line that says where the agent can find the server's tools.

A server's instructions are omitted when the agent's permissions deny every one of its tools, or when they deny `execute` for a server that uses Code Mode.

## Input requests

Servers can ask for input during a call through MCP elicitation. OpenCode shows the request as a form, or as a link to open for URL requests:

```text
sentry is requesting input
```

Dismissing the form cancels the request.

## Context

For calls made on behalf of a session, OpenCode sends the session ID in `CallToolRequest.params._meta["ai.opencode/sessionID"]`. This applies to direct tools and Code Mode over stdio and Streamable HTTP:

```json
{
  "method": "tools/call",
  "params": {
    "name": "lookup",
    "arguments": { "query": "example" },
    "_meta": { "ai.opencode/sessionID": "ses_..." }
  }
}
```

The ID is request metadata, not a tool argument, so it is absent from the model-visible schema. Treat it as an opaque correlation value:

| Rule | Behavior |
| --- | --- |
| Identity | It identifies the invoking OpenCode session, not the MCP transport session. |
| Presence | It can be absent for calls without session context. |
| Security | Do not use it by itself for authentication or authorization. |
| Privacy | Remote servers receive the raw ID and may log or retain it. |

## Management

List servers and their current connection state from any project:

```sh
opencode mcp list
```

Use `/mcps` in OpenCode to view, connect, disconnect, or authenticate servers. Use the CLI to add servers and manage OAuth credentials:

```sh
opencode mcp add sentry --url https://mcp.sentry.dev/mcp
opencode mcp auth sentry
opencode mcp logout sentry
```

Run `opencode mcp auth` without a name to pick from the OAuth-capable servers:

```sh
opencode mcp auth
```

To remove a server, delete its entry from the project or global configuration where it was added:

```jsonc
{
  "mcp": {
    "servers": {},
  },
}
```

Disconnecting a server in `/mcps` does not change configuration. Edit configuration directly for OAuth client settings, timeouts, working directories, or to keep a server disconnected:

```jsonc
{
  "mcp": {
    "servers": {
      "sentry": {
        "type": "remote",
        "url": "https://mcp.sentry.dev/mcp",
        "disabled": true,
      },
    },
  },
}
```