---
title: MCP Servers
description: The Model Context Protocol servers wired into the OpenCode config, and a sanitised opencode.json example.
sidebar:
  order: 6
---

The OpenCode setup wires in a set of [Model Context Protocol](https://modelcontextprotocol.io) servers. They give agents documentation lookups, code search, browser control, system data, and dev-server management without shelling out for everything.

:::note[Where the real config lives]
Unlike the agents, commands, skills, and plugins, the stowed harness `opencode.json` that declares these servers is **not** published. It lives only in the private overlay (`~/.config/dotfiles-private/agents/.config/opencode/opencode.json`), because the same file carries machine-specific repository paths in its permission allow-lists. The repo root `opencode.json` is a public, repo-local session config and may declare project-specific servers, but it is not the generated harness config described here. The servers themselves hold no secrets: the two that need credentials read them from the environment. This page documents the servers and a sanitised config so the setup can be reproduced.
:::

## Servers in use

| Server | Transport | Purpose |
| --- | --- | --- |
| `answer-overflow` | remote | Searchable archive of programming Discord help channels. Community troubleshooting context. |
| `astro-docs` | remote | Search the official Astro framework docs. Loaded only in Astro projects (an `astro.config.*` marker, see below). |
| `chrome-devtools` | local | Drive Chrome DevTools for frontend debugging: page snapshots, console, network, Lighthouse, performance traces. Loaded only in frontend projects (an `index.html` or web-framework/bundler config marker, see below). |
| `context` | local | Branch and tech-stack context for the current repository (`git_context`, `stack_context`, `command_help`). Runs `context mcp` from the `context-git` package installed by `dot init`. |
| `context7` | remote | Up-to-date library and framework documentation with code examples. Needs an API key. |
| `convex` | local | Convex backend tooling: query data, run functions, inspect deployments. Loaded only in Convex projects (a `convex.json` or `convex/` marker, see below). |
| `github` | remote | Read-only GitHub access: issues, PRs, Actions, code scanning, Dependabot, discussions, projects. Needs a bearer token. |
| `grep` | remote | Search real-world code across public GitHub repos via [grep.app](https://grep.app). |
| `notes` | local | Repository notes vault (`note_read`, `note_list`, `note_write`, `note_delete`) and notes context. Runs `notes mcp` from the `repo-notes-git` package installed by `dot init`. |
| `pitchfork` | local | Manage long-running dev-server daemons: start, stop, restart, status, logs. Loaded only in repos with a `pitchfork.toml` (see below). |
| `system-bridge` | local | System Bridge: system info modules (cpu, memory, disks, media) and media control. |

Remote servers connect over HTTP to a hosted endpoint. Local servers are launched on demand over stdio (`opencode x`, `npx`, or an installed binary).

Chrome DevTools tools can execute browser-context JavaScript. The `subagent-chrome-devtools-guard` plugin keeps them available only in top-level sessions, so delegated subagents cannot use DevTools as a research path around web, GitHub, or repository-reading tools.

The `system-bridge` server runs the `system-bridge-mcp` binary from the `system-bridge-git` AUR package. That package is listed in the public `.dot-public-packages` manifest and installed by `dot init`, so the MCP command is available on a fully set-up machine without a separate install step.

The `context` server runs `context mcp` from the `context-git` AUR package (also in `.dot-public-packages`). Repository context tools and schemas live entirely in the standalone [`context`](https://context.timmo.dev) project. See [Context Integration](/git/context/) for how OpenCode plugins consume its JSON output.

The `notes` server runs `notes mcp` from the `repo-notes-git` package (also in `.dot-public-packages`). Repository note commands use it for vault reads, writes, and deletes; the `repo-notes` OpenCode plugin supplies the current repository's note context. New note content omits `date`, which `note_write` inserts or refreshes. Updates read the note first and pass its SHA-256 revision as `expectedHash`; a mismatch means the note changed concurrently and must be reread before reconciling the update. Deletion requires explicit confirmation and remains permission-gated.

:::note[Credentials]
`context7` and `github` need credentials, supplied through the environment and referenced from `opencode.json` with `{env:...}` so no secret ever lands in the config file. The GitHub server reads `DOT_GH_MCP_BEARER`; the `opencode`/`cursor` shell wrappers and `opencode-server` obtain it from `gh auth token` only for the launched harness process, rather than exporting it to every shell child. `dot doctor` warns when `gh` is missing or has no token.
:::

## Generated from one spec

Every harness's MCP config is generated from a single private spec, `~/.config/dotfiles-private/mcp.yml`, by `dot mcp-sync`. One edit to the spec regenerates each harness's native config in the stowed source tree, so the harnesses stay aligned instead of drifting apart by hand.

Active harnesses (generated):

- **OpenCode** (`agents/.config/opencode/opencode.json`, `mcp` block)
- **Cursor** (`agents/.cursor/mcp.json`, `mcpServers`)
- **VS Code** (`agents/.config/Code/User/mcp.json`, `servers`)
- **Copilot CLI** (`agents/.copilot/mcp-config.json`, `mcpServers`)

The spec defines each server once and enables it for every active harness, so all four expose the same set. Enablement is written out as an explicit per-harness matrix, so any single cell can be flipped later. A canonical `{env:VAR}` reference is rewritten to each harness's native syntax (`{env:VAR}`, `${env:VAR}`, or `${VAR}`), and commands that differ by harness (such as the OpenCode `opencode x` runner for `chrome-devtools`) are carried as per-harness overrides.

The OpenCode config is written as a full catalogue: every server appears with an explicit `enabled: true` or `enabled: false`, so a server can be turned off in place while staying documented. The other harnesses list only their enabled servers.

Two more harnesses are kept as documented stubs, not generated:

- **Gemini CLI** would need an `mcpServers` block in `~/.gemini/settings.json` using `$VAR` env syntax.
- **Claude Code** is special: `~/.claude.json` is a live runtime state file Claude rewrites, so it is wired with `claude mcp add`, not file generation.

Run `dot mcp-sync` after editing the spec, then `dot stow`. `dot update` runs it automatically before re-stowing.

## Context gating

Some servers are only useful in repos with a matching marker. The `mcp-repo-gate` plugin prunes them from the merged config at startup when none of their markers are present, so their tools do not load where they are irrelevant. Each server maps to one or more markers (files or directories); any one, found in the project directory, an ancestor, or a nearby descendant (up to two levels down, so a monorepo whose project sits in a subdirectory still counts), keeps the server. For example, `pitchfork` is dropped in any repo without a `pitchfork.toml`, `convex` outside Convex projects, `astro-docs` outside Astro projects, and `chrome-devtools` (~27 tools, ~4.3k tokens of schema) outside frontend projects:

```ts
const REPO_REQUIRED_MARKERS: Record<string, readonly string[]> = {
  pitchfork: ["pitchfork.toml"],
  convex: ["convex.json", "convex"],
  "astro-docs": [
    "astro.config.mjs",
    "astro.config.ts",
    "astro.config.mts",
    "astro.config.js",
    "astro.config.cjs",
  ],
  // Any one frontend signal keeps chrome-devtools loaded (static entry point,
  // web-framework config, bundler config, task runner, or Storybook setup).
  // Attach it from a non-frontend directory by restarting OpenCode there.
  "chrome-devtools": [
    "index.html",
    "astro.config.mjs",
    "vite.config.ts",
    "next.config.js",
    "nuxt.config.ts",
    "svelte.config.js",
    "vue.config.js",
    "angular.json",
    "webpack.config.js",
    "rspack.config.ts",
    "rollup.config.mjs",
    "gulpfile.js",
    ".storybook",
    // ...plus the remaining astro/vite/next/bundler config extensions
  ],
};
```

The plugin is deliberately conservative: only servers listed in that map are ever removed, and only when none of their markers are found upward to the filesystem root or in a short downward scan (which skips hidden and build/vendor directories). Every other server stays on everywhere, so no tool is hidden where it might be wanted.

## Example config

A trimmed, sanitised `opencode.json`. Machine-specific repository paths are removed from the permission allow-lists, and secrets are `{env:...}` references with generic `FOO`/`BAR` placeholders standing in for real variable names. Adjust the `permission` allow-lists to your own trusted paths. In the real generated file every server also carries an explicit `"enabled": true` (or `false`), which is omitted here for brevity.

```json
{
  "$schema": "https://opencode.ai/config.json",
  "autoupdate": false,
  "shell": "/bin/zsh -l",
  "permission": {
    "external_directory": {
      "~/.config/dotfiles/**": "allow",
      "/tmp/**": "allow",
      "**/.ssh/**": "deny",
      "**/.aws/**": "deny",
      "**/.gnupg/**": "deny",
      "**/.kube/**": "deny",
      "~/.docker/config.json": "deny",
      "~/.config/gh/hosts.yml": "deny",
      "~/.local/share/opencode/mcp-auth.json": "deny"
    },
    "read": {
      "/tmp/**": "allow",
      "**/.env.example": "allow",
      "**/.env": "deny",
      "**/.env.*": "deny",
      "*.pem": "deny",
      "*.key": "deny",
      "**/.ssh/**": "deny",
      "**/.gnupg/**": "deny"
    },
    "bash": {
      "git commit": "deny",
      "git commit *": "deny",
      "git commit --amend": "ask",
      "dot": "allow",
      "dot *": "allow"
    },
    "lsp": "allow",
    "webfetch": "allow",
    "websearch": "allow"
  },
  "lsp": true,
  "mcp": {
    "answer-overflow": {
      "type": "remote",
      "url": "https://www.answeroverflow.com/mcp"
    },
    "astro-docs": {
      "type": "remote",
      "url": "https://mcp.docs.astro.build/mcp"
    },
    "chrome-devtools": {
      "type": "local",
      "command": ["opencode", "x", "chrome-devtools-mcp@latest"]
    },
    "context": {
      "type": "local",
      "command": ["context", "mcp"]
    },
    "context7": {
      "type": "remote",
      "url": "https://mcp.context7.com/mcp",
      "headers": {
        "CONTEXT7_API_KEY": "{env:FOO}"
      }
    },
    "convex": {
      "type": "local",
      "command": ["npx", "-y", "convex@latest", "mcp", "start"]
    },
    "notes": {
      "type": "local",
      "command": ["notes", "mcp"]
    },
    "github": {
      "type": "remote",
      "url": "https://api.githubcopilot.com/mcp/x/all/readonly",
      "oauth": false,
      "headers": {
        "Authorization": "Bearer {env:BAR}"
      }
    },
    "grep": {
      "type": "remote",
      "url": "https://mcp.grep.app"
    },
    "pitchfork": {
      "type": "local",
      "command": ["pitchfork", "mcp"]
    },
    "system-bridge": {
      "type": "local",
      "command": ["system-bridge-mcp"]
    }
  }
}
```
