第 9 章 / 共 12 章
接进真实客户端:Claude Code、Codex CLI、Claude Desktop
9.1 三个客户端,一个共同的坑
Inspector 里跑通之后,下一步是把它接进你每天真正在用的工具。三个主流客户端的配置方式各不相同,但同一个坑会绊倒所有人:路径和环境。
宿主是用它自己的进程启动你的 server 的。这意味着:你的 shell 里 export 的环境变量它没有;你的当前工作目录它不知道;你用 uv 装的虚拟环境,它也不会自动激活。
所以三个客户端的配置里,有三件事永远要做对:用绝对路径、显式传环境变量、用能自己找到依赖的启动命令。
对本教程这样的 uv 项目,可靠的启动命令形态是:
uv --directory /绝对路径/到/项目 run python server.py
--directory 让 uv 切到项目目录,它会自己找到那个项目的虚拟环境和依赖,不依赖你的当前目录。
本章后面所有配置里的 /abs/path/... 都是占位符,请换成你机器上的真实绝对路径(pwd 和 which uv 能告诉你)。

9.2 Claude Code:命令行注册,三档作用域
Claude Code 用命令注册,不需要手写配置文件:
claude mcp add notes -- uv --directory /abs/path/notes-mcp run python server.py
-- 这个分隔符不能省,官方文档专门强调过。它之后的所有内容都会原样作为启动命令传给 server。忘了它,你 server 的参数会被 Claude Code 当成自己的选项解析,报出一个跟真实原因毫无关系的错。
远程 server 用另一种形式:
claude mcp add --transport http notes https://mcp.example.com/mcp --header "Authorization: Bearer $TOKEN"
最需要想清楚的是作用域,它决定这份配置存在哪、谁能看到:
| 作用域 | 存储位置 | 生效范围 | 会不会分发给同事 |
|---|---|---|---|
local(默认) | 用户配置文件,按项目路径分区 | 仅当前项目 | 否 |
project | 项目根目录的 .mcp.json | 仅当前项目 | 是,随版本库走 |
user | 用户级配置 | 你的所有项目 | 否 |
冲突时的优先级是 local 高于 project 高于 user。
想把 server 分发给团队,用 --scope project,它会在仓库根目录生成 .mcp.json:
{
"mcpServers": {
"notes": {
"type": "stdio",
"command": "uv",
"args": ["--directory", "${CLAUDE_PROJECT_DIR}", "run", "python", "server.py"],
"env": {
"NOTES_DIR": "${NOTES_DIR:-./notes}"
}
}
}
}
这里有两个很实用的机制:${VAR} 和 ${VAR:-默认值} 形式的环境变量展开在 command、args、env、url、headers 里都生效;${CLAUDE_PROJECT_DIR} 是项目根目录,用它就不用在共享配置里写死每个人的绝对路径。
安全上有一层保护值得知道:项目级的 .mcp.json 是从版本库里来的,交互式会话首次连接时会提示你是否信任它。这也意味着仓库里的一份 .mcp.json 相当于一条待执行的命令——review 别人的 PR 时,这个文件值得多看一眼。
接进去之后,在会话里输入 /mcp 查看状态。常见状态包括已连接、需要认证、连接失败、等待批准、已禁用。接入后的第一个动作永远是 /mcp,先确认连上了,再去对话里试工具。
还有一个 server 侧的能力值得知道:如果你的某个工具无论如何都必须每次由人确认,可以在工具的 _meta 里标记 anthropic/requiresUserInteraction 为 true。标记之后,即使用户开着比较宽松的权限模式,也会每次弹出确认,而且不提供「不再询问」。对有真实副作用的工具,这是一个很好的自我约束。(需要较新版本的 Claude Code,用之前先确认。)
9.3 Codex CLI:靠键名决定传输方式
Codex 也有命令行注册:
codex mcp add notes --env NOTES_DIR=/abs/path/notes -- uv --directory /abs/path/notes-mcp run python server.py
或者直接写配置文件 ~/.codex/config.toml(项目级是 .codex/config.toml):
[mcp_servers.notes]
command = "uv"
args = ["--directory", "/abs/path/notes-mcp", "run", "python", "server.py"]
startup_timeout_sec = 20
[mcp_servers.notes.env]
NOTES_DIR = "/abs/path/notes"
Codex 和 Claude Code 有一个概念上的差别值得注意:它没有 --transport 这个概念,传输方式由哪个键存在决定——有 command 就是 stdio,有 url 就是远程 HTTP。
[mcp_servers.notes]
url = "https://mcp.example.com/mcp"
bearer_token_env_var = "NOTES_TOKEN"
两个默认值容易咬人:启动超时默认 10 秒,单个工具执行超时默认 60 秒。如果你的 server 启动时要建索引、连数据库,10 秒很可能不够——startup_timeout_sec 要往上调。这也是第 7 章那个「把准备工作放进 lifespan」的建议的另一面:放进去是对的,但要记得它会算进启动时间。
9.4 Claude Desktop:手写 JSON,改完必须完全重启
Claude Desktop 走配置文件。macOS 上的路径是:
~/Library/Application Support/Claude/claude_desktop_config.json
也可以从菜单里进:Claude → Settings → Developer → Edit Config。
{
"mcpServers": {
"notes": {
"command": "/abs/path/bin/uv",
"args": ["--directory", "/abs/path/notes-mcp", "run", "python", "server.py"],
"env": { "NOTES_DIR": "/abs/path/notes" }
}
}
}
注意 command 用的是 uv 的绝对路径。桌面应用启动子进程时的 PATH 和你终端里的 PATH 往往不一样,写 "uv" 很可能找不到。用 which uv 拿到完整路径填进去。
改完配置必须完全退出并重启应用,不是关窗口。
排错时日志在这里:
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
mcp.log 是连接总日志,mcp-server-<名字>.log 是你的 server 的 stderr——第 2.6 节和第 8.5 节写的那些日志,最后就落在这个文件里。官方给的排错顺序也很实用:完全重启 → 检查 JSON 语法 → 确认全部是绝对路径 → 看日志 → 手工在终端跑一遍启动命令。
9.5 接入后的三步验证
不管接的是哪个客户端,接完都做这三步,顺序不要变:
- 看状态。 Claude Code 用
/mcp,Desktop 看连接器列表。状态不对就别往下走,先解决连接问题。 - 看工具列表。 确认你的工具都在,名字和描述是你期望的样子。少一个工具,说明注册失败;描述不对,说明连的是旧进程。
- 在真实对话里试一次,但不要点名工具。 这一步最容易被跳过,也最有价值。不要说「用 search_notes 搜一下 MCP」,要说「我之前好像记过关于 MCP 的笔记,帮我找找」。看模型会不会自己想到调用它——如果不会,问题不在连接,在第 5 章:你的工具描述没有给出足够的触发线索。
第三步失败是最常见的情况,也最容易被误诊成「MCP 不好用」。它其实是一个可以修的描述问题。