MCP 接入:连接外部工具并处理故障
用 mcp.json 把外部工具服务器接进 Pi,讲清曝光策略、连接时机、诊断手段和资源释放路径。
MCP 接入:连接外部工具并处理故障
MCP(Model Context Protocol)给 Pi 一条接外部能力的通道:Issue 系统、企业文档站、内部 API,都可以作为 MCP 服务器接进来,工具注册成 mcp__<服务器>__<工具> 后供模型调用。
接进来只是第一步。真正会卡住人的是后面三件事:工具怎么暴露给模型(曝光策略)、服务器什么时候连上(连接时机)、连不上或退出时怎么收干净(诊断与释放)。这三件事各有一套独立规则,混在一起看就会得出错误结论。
这篇把 MCP 的配置格式、曝光策略、连接时序和失败处理讲透。读它需要会写 JSON 配置,以及对 Pi 的工具机制有一个基本印象(模型提出工具请求,程序校验参数后执行)。
Pi 迭代很快,接口可能变化。动手前先记录 pi --version,本文依据 Pi 官方文档 与 官方仓库 编写。
一、配置文件在哪,写什么
格式和大多数 MCP 客户端一致:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
"description": "读取项目目录下的文件"
},
"docs": {
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ${DOCS_TOKEN}" },
"description": "搜索并读取产品文档"
}
}
}两种运输方式的字段:
两种类型都支持这些通用字段:
timeout:单次请求超时秒数,默认 60。进度通知会重置它。enabled: false:保留条目但不连接。exposure和toolExposure:控制工具怎么够到模型,见下一节。description:一句话说明这个服务器提供什么。它会进系统提示的服务器列表,工具检索按它排序,codemode 的describeNamespace()也会返回它。
description 不写会怎样?服务器连上之后,用它的说明文字第一行代替。所以能写就写,别让模型猜。
几条容易记错的规则
- 工具名是
mcp__<服务器>__<工具>,非字母数字下划线的字符全部换成_。 - 服务器名只能包含字母、数字、
_和-。 只靠-和_区分的两个名字算同一个服务器,第二个会被拒绝。 - 无效的条目会被报告并跳过,不影响其它服务器连接。 一个写错的条目不会拖垮整份配置。
- 项目条目只写
enabled、exposure、toolExposure时,只覆盖用户级同名服务器的这三个字段,其余(包括env、headers)保留。这样可以在某个项目里关掉一个用户级服务器:
{
"mcpServers": {
"internal-tools": { "enabled": false }
}
}命令行也能改配置:
# 往用户级 mcp.json 添加一个 stdio 服务器;-- 之后的参数原样传给服务器进程
pi mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem .
# 列出所有服务器并连一遍:能区分"配置写错了"和"服务器连不上",退出码非 0 表示有问题
pi mcp list
# 移除一个服务器条目
pi mcp remove filesystempi mcp add 默认写用户级,加 --local 或 -l 写项目级。
pi mcp 系列 shell 命令不加载扩展,它们只看得到文件配置的服务器。扩展自己注册的服务器(pi.registerMcpServer())在 /mcp 里能看到,但 pi mcp list 看不到。
会话内用 /mcp 查看和调整:看连接状态、工具数量、曝光方式和配置来源,重新连接,登录登出,切换曝光,或启用禁用。在会话外改完配置,回到会话里要 /reload 才生效。
OAuth 认证
远程服务器用 OAuth 时,mcp.json 里不需要凭据,只要一个 URL:
{
"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:
{
"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 设成别的名字:
{
"mcpServers": {
"figma": { "url": "https://mcp.figma.com/mcp", "oauth": { "clientName": "Claude Code" } }
}
}这个名字只在注册新客户端时发送。想用新名字重新注册,先登出。
二、曝光策略决定模型怎么用 MCP 工具
服务器的 exposure 决定工具到达模型的路径。这是 MCP 里最容易配错的一项。
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 宿主里尤其重要,后面「你现在能做什么」一节会给出正确写法。
四、诊断手段
# 单独诊断 MCP 连接:会真的连一遍每个启用的服务器,打印状态、工具数和错误详情
# 条目无效或服务器没连上时退出码为 1,可以直接拿来做 CI 检查
pi mcp list它会连接每一个启用的服务器并打印状态、工具和错误。每个服务器一行,格式是 <名字>: <状态> (<曝光方式>, <作用域>),下面跟着传输方式、工具列表和错误详情。
条目无效或启用的服务器没连上时,退出码是 1。 这条命令不加载扩展,只看文件配置的服务器——所以它能把"配置问题"和"装配问题"分开。
会话内用 /mcp 看完整连接错误和失败 stdio 服务器的 stderr 尾部。
服务器日志通知追加到 ~/.pi/agent/mcp.log,格式是 <时间> [<服务器>] <级别> <日志器>: <消息>。文件超过 5 MB 后移到 mcp.log.1。
Pi 在启动后报告一次配置错误、连接失败和需要登录的服务器——只有一次,不是每次提示都报。所以别指望反复触发提示来刷出错误信息,直接用 /mcp 或 pi mcp list。
五、可观察结果对照表
你现在能做什么
- 在
~/.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 连接被释放并重建,而不是残留僵尸子进程。