MCP 配置为什么手动跑没问题、客户端一起就挂?工作目录和环境变量两个坑
配 MCP 时最让人怀疑人生的一种情况是:
你把配置里那条命令复制到终端里跑,一切正常;让客户端去拉,就是起不来。
于是你开始怀疑配置文件写错了、怀疑客户端有 bug、怀疑自己眼花。实际上,官方调试文档里明确写了两个原因,任何一个都能造成这种现象。
一、坑一:工作目录可能是未定义的
官方原话说得很直接:客户端启动 stdio 服务时,服务的工作目录可能是未定义的——在 macOS 上可能就是 /,因为客户端本身可能是从任何地方被启动的。
而你在终端里手动跑的时候,工作目录是你当前所在的目录。
这个差别的后果是:所有相对路径的含义都变了。
你写的 ./data,手动跑时指向你项目里的 data 目录,客户端拉起来时可能指向文件系统根目录下的 data——那个大概率不存在。
官方给的正确写法
配置和 .env 里一律用绝对路径。 官方给的正例:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/data"
]
}
}
}
而不是 ./data 这样的相对写法。
command 本身也建议用绝对路径。 官方在讲启动失败的「路径问题」时给的建议就是:试试给 command 用绝对路径。
为什么 command 也要绝对路径
因为 command 的解析依赖 PATH 环境变量——而这正好引出第二个坑。
二、坑二:环境变量只继承一个有限子集
官方说明:通过 stdio 启动的 MCP 服务,只会自动继承一部分环境变量,具体是哪些跟平台有关。
这句话的杀伤力很大,因为它意味着:
- 你 shell 里 export 的 API key,服务端进程可能读不到
- 你在
.zshrc里配的PATH,可能没完整传下去 - 你用 nvm、pyenv 这类版本管理工具设置的环境,可能整个不生效
于是服务端进程要么找不到命令(command 解析失败),要么起来了但初始化时因为缺 key 而抛错退出。
这两种表现在客户端侧看到的都是「连接关闭」或者「进程提前退出」,完全看不出跟环境变量有关。
官方给的做法
在配置里显式写 env 键:
{
"mcpServers": {
"myserver": {
"command": "mcp-server-myapp",
"env": {
"MYAPP_API_KEY": "some_key"
}
}
}
}
需要什么就显式写什么,别指望继承。
三、一个真实案例:npx 找不到
modelcontextprotocol/servers 仓库的 issue #1097(已关闭)标题是:
GitHub MCP Server Fails to Start: 'npx' Command Error and Connection Closed (-32000)
报错编号是 -32000 连接关闭,真因是 npx 这个命令在那个环境下跑不通。
这正是上面两个坑叠加的典型结果:npx 通常靠 PATH 找到,而 PATH 可能没完整传给服务端进程。你在终端里能跑 npx,是因为你的 shell 配置生效了;客户端拉起来的进程不一定有那份配置。
同一个仓库的 issue #891(已关闭)标题更直白:Fix 'Client Closed' Error by Correcting npm Config——也是 npm 配置层面导致的启动失败。
两条 issue 指向同一个教训:客户端报的是连接层的现象,真因在进程启动层。
四、自查方法:模拟客户端的环境
既然问题是「你的终端环境 ≠ 客户端拉起来的环境」,那自查的关键就是尽量模拟后者。
第一,用绝对路径跑一遍。
把配置里的 command 换成绝对路径试试。先找到它在哪:
which npx
(Windows:where npx)
然后用那个完整路径去跑。如果绝对路径能跑通、命令名跑不通,那就锁定是 PATH 的问题。
第二,在一个干净的环境里跑。
尽量去掉你 shell 的个性化配置,看还能不能跑。能跑,说明不依赖你的 shell 配置;不能跑,说明依赖,那就要在 env 里补齐。
第三,从一个不相干的目录跑。
cd / 然后用绝对路径跑那条命令。这最接近客户端的处境(工作目录未定义、可能是根目录)。跑通了,说明不依赖工作目录。
这三条做完,基本能覆盖本文说的两个坑。
五、官方对启动失败的三分类
官方调试文档把启动失败归成三类,排查时可以照着对:
1. 路径问题
- 服务端可执行文件路径不对
- 缺必需的文件
- 权限问题
- 建议给
command用绝对路径
2. 配置错误
- JSON 语法错误
- 缺必填字段
- 类型不匹配
3. 环境问题
- 缺环境变量
- 变量值不对
- 权限限制
第 2 类最容易自查——JSON 语法错误可以直接用工具验。如果你的系统上有 python:
python -m json.tool 你的配置文件路径 > /dev/null && echo OK
输出 OK 就是合法 JSON。这一步花几秒,能排除掉一整类问题。
六、一条排查路径
- 验 JSON 合法性(几秒钟,先排除掉配置错误这一类)
- 把所有相对路径改成绝对路径——包括
command和参数里的路径 - 需要的环境变量显式写进
env,别指望继承 - 在干净环境 + 不相干目录下手动跑一遍那条命令,模拟客户端的处境
- 完全退出客户端再打开(Claude Desktop 只关窗口不算)
- 看日志(macOS
~/Library/Logs/Claude,Windows%APPDATA%\Claude\logs),确认服务端进程有没有启动记录 - 还不行就用 MCP Inspector 单独测,把客户端这一层摘出去
前三步能解决掉大多数「手动跑没问题、客户端起不来」的情况。
七、几条能一次性避开的习惯
- 配置里永远写绝对路径,无一例外。省下的是每次换机器、换目录都要重排一遍的时间
- 需要的环境变量都写进
env,哪怕你确定 shell 里有——显式写一遍的成本是几秒,靠继承出问题的排查成本是几十分钟 - 改完配置随手验一下 JSON
- 写配置时就假设「工作目录是根目录、环境变量什么都没有」,按这个前提写出来的配置最稳
八、版本管理工具是重灾区
用 nvm、fnm、pyenv、rbenv 这类版本管理工具的人,撞上本文这两个坑的概率特别高。值得单独说。
这类工具的工作方式,基本都是在你的 shell 配置里注入一段初始化脚本,运行时改写 PATH,把当前选中的版本指到前面。
问题在于:那段初始化脚本只在你的交互式 shell 里跑。 客户端拉起 MCP 服务端进程时,走的不是你的交互式 shell——于是:
node、python可能指向系统自带的老版本- 或者干脆找不到
- 你用
npm install -g装的包,装在版本管理工具管理的目录下,那个目录不在服务端进程的PATH里
表现就是:你在终端里 node -v 显示 20.x,服务端进程那边可能是 12.x 或者根本没有。
排查方法:
which node
which npx
拿到的是不是版本管理工具目录下的路径?如果是,那就把那个绝对路径写进配置,别依赖 PATH 解析。
配置里可以直接写完整路径:
{
"mcpServers": {
"myserver": {
"command": "/绝对路径/到/node",
"args": ["/绝对路径/到/server.js"]
}
}
}
代价是换 Node 版本时要改配置——但比每次都排查半天强。
九、写配置时的一个心法
把本文的两个坑合起来,可以总结成一句写配置时的假设:
假设服务端进程启动时,工作目录是根目录,环境变量近乎为空。
按这个假设写出来的配置,具备两个性质:
- 所有路径都是绝对的,不依赖工作目录
- 需要的环境变量都显式声明,不依赖继承
这样的配置在你的机器上能跑,在同事的机器上、在换了客户端之后、在换了目录之后,也大概率能跑。
反过来,靠继承和相对路径写出来的配置,能不能跑取决于运气——取决于那台机器的 shell 怎么配的、客户端从哪启动的。它在你这里能跑,恰恰是最误导人的信号。
十、总结
- 「手动跑没问题、客户端起不来」的两个官方原因:工作目录可能未定义(macOS 上可能是
/)、环境变量只继承有限子集。 - 配置和
.env里一律用绝对路径,command本身也建议用绝对路径。 - 需要的环境变量显式写进配置的
env键。 - issue #1097 那条
-32000 Connection closed的真因是 npx 跑不通——客户端报连接层现象,真因在启动层。 - 自查靠模拟客户端环境:绝对路径 + 干净环境 + 不相干目录。
- 官方的启动失败三分类:路径问题、配置错误、环境问题。其中 JSON 语法可以几秒钟验掉。
本文所引官方内容来自 Model Context Protocol 官方调试文档,issue 编号来自 modelcontextprotocol/servers 仓库,核对日 2026-08-08。