Vectrum
Sign In
107 lines · 5.5 KB
# MCP servers

Codey is an MCP client over stdio. Servers go in the config by hand, in the
same shape Claude Code and Codex use, so an entry can be carried across from
either unchanged:

```json
{
  "mcp_servers": {
    "fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
    "db": { "command": "my-db-mcp", "env": { "DB_URL": "postgres://localhost/dev" } }
  }
}
```

Each server is started once, when the session opens, in the working root, and
every tool it lists is offered as `mcp__<name>__<tool>` — the name being the
key you gave it, not whatever the server calls itself. Subagents get the same
tools from the same processes. Every call prompts like `bash` does, with
"always this tool" to stop asking. A server that is not installed, does not
answer within thirty seconds or speaks a protocol version Codey does not is
reported at startup and left out; the session goes on without it. A server
that dies later is not restarted, and its tools say so when called.

Only tools are used: not resources, prompts or sampling, and no HTTP
transport. Pictures a tool returns are described to the model rather than
shown. A server's directory is fixed when it starts, so after `/worktree` it
is still serving the root the session began in. `-no-mcp` starts none of them
for one run — Codey's own and Claude Code's alike.

## Claude Code's servers

The servers Claude Code already knows about are started as well, read from
where Claude Code keeps them rather than copied into Codey's config:

| Where | Whose | Started |
| --- | --- | --- |
| `~/.claude.json`, under `projects["<root>"].mcpServers` | yours, for this project — what `claude mcp add` writes | always |
| `.mcp.json` at the working root | the repository's, committed | once you [trust it](#trusting-a-project) |
| `~/.claude.json`, top-level `mcpServers` | yours, for every project — `claude mcp add -s user` | always |

The shape is the one already shown above, `{"mcpServers": {name: {command,
args, env}}}`, with `"type": "stdio"` accepted and meaning the same thing. A
server with `"type": "http"` or `"sse"`, or with a `url`, is a remote server;
Codey's client speaks only stdio, so it is reported at startup and skipped.
`${VAR}` and `${VAR:-default}` are expanded in the command, the arguments and
the env values, as Claude Code expands them — so a `.mcp.json` can say
`"${DB_URL}"` and leave the secret in your environment. A variable that is
unset and has no default skips that server with a notice naming it, rather
than starting it with a hole in its command line.

When two places define a server of the same name, Codey's own `mcp_servers`
win, and after that the table is read top to bottom: your per-project servers,
then the repository's, then your global ones. That is Claude Code's own order,
so a name means the same program in both. Each clash is reported once, naming
the one that won, because a server that quietly turned out to be a different
program is not something anyone thinks to debug.

### Trusting a project

A `.mcp.json` comes from the repository. Starting a server listed there means
running a command — with your environment, your credentials, your network —
that whoever wrote the repository chose, before the agent has done anything
and outside every approval prompt. So, like Claude Code, Codey asks first. The
first interactive start in a checkout whose `.mcp.json` or shared
`.claude/settings.json` [allow rules](claude-code-settings.md) would grant
something prints, before the TUI opens:

```
Claude Code's configuration for /home/you/src/thing would:

  start these MCP servers, from .mcp.json:
    db: npx -y @acme/db-mcp --url ${DB_URL}  (env: DB_TOKEN)
  run these without asking, from .claude/settings.json:
    Bash(make:*)

These files come from the repository, not from you: trusting them lets it
choose commands this machine runs. Your answer is remembered until they change.
Trust them for this checkout? [y/N]
```

One question covers both files, because it is one question: do you trust what
this repository says about itself. The full command line is shown because that
is the thing being agreed to. The answer — yes or no — is remembered for this
checkout against a fingerprint of exactly those definitions, as written, before
any `${VAR}` is expanded. A new server, a changed argument or one more allow
rule is a different fingerprint, and the question comes back; nothing else
does. A refusal is remembered too, so saying no once is not a question at every
start.

The record is kept in `~/.config/codey/trust.json`, not in the project's
`.codey/` directory with the rest of the per-repository state. That is the
point of it: anything inside the working tree can arrive with a clone, and a
repository that shipped its own record of being trusted would be approving its
own servers. The fingerprint would be no defence there — whoever wrote the
`.mcp.json` can compute it. Only a file the repository cannot reach can say what
you decided.

Where nobody can be asked — `-p`, `codey agent stdio`, a piped stdin — an
undecided project's servers are not started and its allow rules are ignored,
with a line on stderr saying so. A checkout you have already trusted needs
nothing more. `-trust-project` says yes for one run without asking and without
recording anything, which is what a CI job against a repository you control
wants. Servers in `~/.claude.json` never ask: you put them there, as you put
Codey's own in its config. Once started, every server's tool calls go through
the approval prompts like any other, unless an allow rule such as `mcp__db`
says otherwise.