跳到主内容
创建时间:最后更新:作者:白川

MCP 接入:连接外部工具并处理故障

用 mcp.json 把外部工具服务器接进 Pi,讲清曝光策略、连接时机、诊断手段和资源释放路径。

MCP 接入:连接外部工具并处理故障

MCP(Model Context Protocol)给 Pi 一条接外部能力的通道:Issue 系统、企业文档站、内部 API,都可以作为 MCP 服务器接进来,工具注册成 mcp__<服务器>__<工具> 后供模型调用。

接进来只是第一步。真正会卡住人的是后面三件事:工具怎么暴露给模型(曝光策略)、服务器什么时候连上(连接时机)、连不上或退出时怎么收干净(诊断与释放)。这三件事各有一套独立规则,混在一起看就会得出错误结论。

这篇把 MCP 的配置格式、曝光策略、连接时序和失败处理讲透。读它需要会写 JSON 配置,以及对 Pi 的工具机制有一个基本印象(模型提出工具请求,程序校验参数后执行)。

Pi 迭代很快,接口可能变化。动手前先记录 pi --version,本文依据 Pi 官方文档 与 官方仓库 编写。

一、配置文件在哪,写什么

文件作用域说明
~/.pi/agent/mcp.json
用户级
所有项目可用。里面通常放个人服务器和带凭据的服务器
.pi/mcp.json
项目级
需要项目信任后才读取。放项目必需的服务器

格式和大多数 MCP 客户端一致:

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
      "description": "读取项目目录下的文件"
    },
    "docs": {
      "url": "https://example.com/mcp",
      "headers": { "Authorization": "Bearer ${DOCS_TOKEN}" },
      "description": "搜索并读取产品文档"
    }
  }
}

两种运输方式的字段:

类型字段
stdio
command、args、env、cwd。command 是一个可执行文件,不是 shell 命令字符串
streamable HTTP
url、headers、oauth。旧的 SSE 传输不支持

两种类型都支持这些通用字段:

  • timeout:单次请求超时秒数,默认 60。进度通知会重置它。
  • enabled: false:保留条目但不连接。
  • exposure 和 toolExposure:控制工具怎么够到模型,见下一节。
  • description:一句话说明这个服务器提供什么。它会进系统提示的服务器列表,工具检索按它排序,codemode 的 describeNamespace() 也会返回它。

description 不写会怎样?服务器连上之后,用它的说明文字第一行代替。所以能写就写,别让模型猜。

几条容易记错的规则

  • 工具名是 mcp__<服务器>__<工具>,非字母数字下划线的字符全部换成 _。
  • 服务器名只能包含字母、数字、_ 和 -。 只靠 - 和 _ 区分的两个名字算同一个服务器,第二个会被拒绝。
  • 无效的条目会被报告并跳过,不影响其它服务器连接。 一个写错的条目不会拖垮整份配置。
  • 项目条目只写 enabled、exposure、toolExposure 时,只覆盖用户级同名服务器的这三个字段,其余(包括 env、headers)保留。这样可以在某个项目里关掉一个用户级服务器:
json
{
  "mcpServers": {
    "internal-tools": { "enabled": false }
  }
}

命令行也能改配置:

bash
# 往用户级 mcp.json 添加一个 stdio 服务器;-- 之后的参数原样传给服务器进程
pi mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem .
# 列出所有服务器并连一遍:能区分"配置写错了"和"服务器连不上",退出码非 0 表示有问题
pi mcp list
# 移除一个服务器条目
pi mcp remove filesystem

pi mcp add 默认写用户级,加 --local 或 -l 写项目级。

pi mcp 系列 shell 命令不加载扩展,它们只看得到文件配置的服务器。扩展自己注册的服务器(pi.registerMcpServer())在 /mcp 里能看到,但 pi mcp list 看不到。

会话内用 /mcp 查看和调整:看连接状态、工具数量、曝光方式和配置来源,重新连接,登录登出,切换曝光,或启用禁用。在会话外改完配置,回到会话里要 /reload 才生效。

OAuth 认证

远程服务器用 OAuth 时,mcp.json 里不需要凭据,只要一个 URL:

json
{
  "mcpServers": {
    "sentry": { "url": "https://mcp.sentry.dev/mcp" }
  }
}

服务器拒绝未认证连接时,/mcp 会提示需要登录。执行 /mcp login sentry 或 pi mcp login sentry,Pi 打开授权页面等批准。浏览器在另一台机器上(比如 SSH 场景),把重定向后的 URL 粘进登录界面。

凭据存在 ~/.pi/agent/mcp-auth.json,按服务器名和 URL 归档。同名同 URL 的服务器在不同 mcp.json 里共享一次登录;同 URL 不同名(比如一个账号一个名字)则各自登录。访问令牌过期或被服务器拒绝时自动刷新;服务器后来追加了 scope,Pi 会要求重新登录。

OAuth 只对没有 Authorization 头的 HTTP 服务器生效。服务器不支持动态客户端注册时,要显式配 clientId:

json
{
  "mcpServers": {
    "example": {
      "url": "https://mcp.example.com/mcp",
      "oauth": {
        "clientId": "my-client",
        "clientSecret": "${EXAMPLE_SECRET}",
        "callbackPort": 8765
      }
    }
  }
}

重定向 URI 必须和注册的一致。callbackPort 对应 http://127.0.0.1:<端口>/callback;要换 URI 就设 callbackUrl,它必须用 HTTP 且 host 是 localhost、127.0.0.1 或 [::1]。

Pi 默认以 pi 这个名字注册自己。有的服务器只接受已知客户端的注册,把 clientName 设成别的名字:

json
{
  "mcpServers": {
    "figma": { "url": "https://mcp.figma.com/mcp", "oauth": { "clientName": "Claude Code" } }
  }
}

这个名字只在注册新客户端时发送。想用新名字重新注册,先登出。

二、曝光策略决定模型怎么用 MCP 工具

服务器的 exposure 决定工具到达模型的路径。这是 MCP 里最容易配错的一项。

exposure行为典型用法
codemode(默认)
可从 codemode 脚本调用,但不声明给模型,也不出现在 codemode 描述里。脚本用 searchTools()、describeTool() 或 ALL_TOOLS 找工具
通用服务器,尤其是希望脚本组合或过滤调用时
deferred
在 tool_search 为下一次模型调用加载匹配项之前,不声明给模型
工具很多、发现后希望直接调用的服务器
direct
像内置工具一样声明给模型,同时也能从 codemode 调用
少量高频工具集
hidden
注册但不可达
应当保持不可用的服务器或工具

toolExposure 可以覆盖单个工具的曝光。键是精确的工具名或带 * 的模式,精确名优先,模式之间首个匹配优先。

codemode-deferred 是 codemode 的别名。

一个容易误解的点:曝光为 codemode 或 deferred 的服务器会列进系统提示的 mcp_servers 段,并附上描述里配置的一句话,或者服务器连上后的首行说明。Pi 在提示开始时更新这一段,变化时把新段追加到对话里,而不是改工具声明,这样早先的消息还能命中缓存。

所以"服务器连上了,codemode 的描述却没变"是正常现象——默认曝光的 MCP 工具本来就 deferred,不列在描述里。服务器说明文字只在 describeNamespace() 里读得到。

三、连接是怎么发生的

Pi 在会话启动时后台连接每一个启用的服务器。 服务器的工具在连上后才出现。

第一个提示最多等 10 秒,但只等那些有 direct 工具、必须写进本次请求的服务器。其它服务器等到需要时才等:

  • codemode 脚本等它点名的服务器(mcp__<服务器>)
  • 脚本调用 searchTools() 或读 ALL_TOOLS 时等全部服务器
  • tool_search 和资源工具也等全部服务器

HTTP 网络错误和瞬时状态码(408、429、5xx)重试两次。连接掉线会显示为已断开,下次调用时重连。服务器宣布工具列表变了,新工具会被加上,被撤走的工具变为不可达。

停止一个 stdio 服务器时,Pi 依次关闭它的 stdin、发 SIGTERM、再对它的进程组发 SIGKILL。通过 npx、uvx 这类包装器启动的服务器也会被停掉。

MCP 扩展的清理挂在 session_shutdown 事件上:中止会话、关闭所有连接、清空服务器列表、发出变更事件。

这条清理路径只在会话被替换(/new、/resume、/fork)或运行时被释放时才走。 直接调 session.dispose() 不会发出 session_shutdown,MCP 连接不会走 Pi 的关闭流程——这一点在 SDK 宿主里尤其重要,后面「你现在能做什么」一节会给出正确写法。

四、诊断手段

bash
# 单独诊断 MCP 连接:会真的连一遍每个启用的服务器,打印状态、工具数和错误详情
# 条目无效或服务器没连上时退出码为 1,可以直接拿来做 CI 检查
pi mcp list

它会连接每一个启用的服务器并打印状态、工具和错误。每个服务器一行,格式是 <名字>: <状态> (<曝光方式>, <作用域>),下面跟着传输方式、工具列表和错误详情。

输出含义
connected, N tools
连接成功,退出码 0
needs sign-in
需要 OAuth 登录,执行 /mcp login <名字>
其它状态 + 错误行
连接失败,退出码 1
config error: ...
配置条目本身有问题,那个条目被跳过

条目无效或启用的服务器没连上时,退出码是 1。 这条命令不加载扩展,只看文件配置的服务器——所以它能把"配置问题"和"装配问题"分开。

会话内用 /mcp 看完整连接错误和失败 stdio 服务器的 stderr 尾部。

服务器日志通知追加到 ~/.pi/agent/mcp.log,格式是 <时间> [<服务器>] <级别> <日志器>: <消息>。文件超过 5 MB 后移到 mcp.log.1。

Pi 在启动后报告一次配置错误、连接失败和需要登录的服务器——只有一次,不是每次提示都报。所以别指望反复触发提示来刷出错误信息,直接用 /mcp 或 pi mcp list。

五、可观察结果对照表

场景触发方式应观察到的结果
成功
pi mcp list 全部已连接
每个服务器显示 connected 和工具数量;退出码 0
成功
会话内 /mcp
列出服务器状态、工具数、曝光方式和配置来源
失败
服务器命令不存在
pi mcp list 退出码 1;/mcp 显示 stderr 尾部;其它服务器照常连接
失败
mcp.json 条目格式非法
该条目被报告并跳过;其余服务器不受影响
失败
需要 OAuth 但未登录
状态 needs sign-in;pi mcp list 退出码 1
失败
服务器名只差 - 和 _
第二个服务器被拒绝,报名称冲突
取消
会话被替换(/new、/resume)
session_shutdown 触发,旧连接释放,新会话重新连接

你现在能做什么

  • 在 ~/.pi/agent/mcp.json 里加一个 stdio 服务器,用 pi mcp list 验证它显示 connected 且工具数大于 0。
  • 把一个服务器的 exposure 从默认的 codemode 改成 direct,重启会话后在 /mcp 里确认曝光方式变了,并观察模型这次是直接调工具还是走脚本。
  • 配置一个需要 OAuth 的远程服务器,用 /mcp login <名字> 完成登录,然后检查 ~/.pi/agent/mcp-auth.json 已经生成。
  • 用 pi mcp list 的退出码写一条 CI 检查:退出码非 0 就让流水线失败。
  • 让一个服务器故意写错命令名,验证 pi mcp list 退出码是 1、且其它服务器仍然连接成功。
  • 在会话里执行 /new,观察 MCP 连接被释放并重建,而不是残留僵尸子进程。

排障速查

现象检查点处理
服务器连不上
单独验证
pi mcp list;stdio 服务器看 stderr 尾部,日志在 ~/.pi/agent/mcp.log
工具连上了但模型看不见
exposure
默认是 codemode,不声明给模型。改 direct 或用 tool_search 发现
只有一个服务器没连上
无效条目
无效条目被跳过不影响其它服务器;看 /mcp 的错误详情
名字冲突被拒
服务器名规则
名字只能有字母数字 _ -;只差 - 和 _ 的两个名字算同一个
项目配置不生效
项目信任
.pi/mcp.json 需要先信任项目
改了配置没反应
是否需要重载
会话内 /reload;pi mcp shell 命令不加载扩展
需要登录却登录不了
OAuth 客户端注册
服务器只认已知客户端时设 oauth.clientName;需要重注册就先登出
登录回调没反应
回调地址
callbackUrl 必须是 HTTP 且 host 是 localhost/127.0.0.1/[::1]
stdio 进程没被回收
释放路径
只在 session_shutdown 时清理;会话替换或运行时释放才会走这条路

参考资料