这个包是什么What this package ispi-mcp 是一个独立、无依赖于其他 pi 包的 Model Context Protocol 客户端:McpClient 负责 initialize 握手、请求簿记(超时、进度、取消)和分页;两种传输——本地子进程的 stdio 与远程的 Streamable HTTP(POST + SSE、会话、断线续传);./oauth 子入口实现完整的 MCP 授权流程(元数据发现、PKCE、动态注册、刷新与 step-up)。pi-mcp is a standalone Model Context Protocol client that depends on no other pi package: McpClient handles the initialize handshake, request bookkeeping (timeouts, progress, cancellation) and paging; two transports — stdio for local child processes and Streamable HTTP for remote servers (POST + SSE, sessions, resumable streams); and the ./oauth entry implements the full MCP authorization flow (metadata discovery, PKCE, dynamic registration, refresh and step-up).
共 3,243 行 TypeScript,唯一的运行时依赖是 cross-spawn。默认协议版本 2025-11-25,兼容 4 个版本。pi 内部只有 coding-agent 的内置 MCP 扩展(4,240 行)使用它。3,243 lines of TypeScript; the only runtime dependency is cross-spawn. Default protocol version 2025-11-25, 4 versions supported. Inside pi, only coding-agent's built-in MCP extension (4,240 lines) uses it.
模块结构Module structure#
一句话结论Takeaway三块:客户端与协议(client.ts 加上 protocol/ 下的 JSON-RPC、类型与内容转换)、传输(transports/ 下的统一接口与三种实现)、OAuth(oauth/,作为 ./oauth 单独导出)。./testing 只导出一对内存传输。Three blocks: client & protocol (client.ts plus JSON-RPC, types and content conversion in protocol/), transports (one interface and three implementations in transports/), and OAuth (oauth/, exported separately as ./oauth). ./testing exports only an in-memory transport pair.
要点Key points
- 最大的文件是
client.ts;其次是transports/streamable-http.ts——HTTP 传输要处理会话、SSE 解析、认证重试和断线续传。The largest file isclient.ts, followed bytransports/streamable-http.ts— the HTTP transport handles sessions, SSE parsing, auth retries and stream resumption. - 客户端只依赖
McpTransport接口:start/send/close加三个监听(message、error、close)。换传输不用改客户端。The client depends only on theMcpTransportinterface:start/send/closeplus three listeners (message, error, close). Swapping transports never touches the client. - 认证通过一个很小的
AuthProvider接口注入:token()与可选的onUnauthorized()。OAuth 只是它的一种实现(adaptOAuthProvider)。Auth is injected through a tinyAuthProviderinterface:token()and an optionalonUnauthorized(). OAuth is just one implementation of it (adaptOAuthProvider).
工作原理How it works
- 创建
StdioTransport({ command, args })或StreamableHttpTransport({ url, authProvider })。Create aStdioTransport({ command, args })or aStreamableHttpTransport({ url, authProvider }). new McpClient({ name, version }),再await client.connect(transport)。new McpClient({ name, version }), thenawait client.connect(transport).listTools()/callTool()/readResource();把结果交给toLlmContent()变成模型能读的内容。listTools()/callTool()/readResource(); pass results totoLlmContent()to get model-ready content.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/mcp/src/index.tsL2 | export … | 包入口package entry |
| packages/mcp/src/client.tsL153 | McpClient | 客户端the client |
| packages/mcp/src/transports/transport.tsL9 | McpTransport | 传输接口transport interface |
| packages/mcp/src/auth-provider.tsL13 | AuthProvider | 认证接口auth interface |
| packages/mcp/src/oauth/index.tsL26 | ./oauth | OAuth 入口OAuth entry |
公共 APIPublic API#
一句话结论Takeaway3 个导入路径。根入口从 8 个模块重新导出 18 个值和 48 个类型:McpClient、两种传输、协议类型、toLlmContent 与错误类;OAuth 在 ./oauth,内存传输在 ./testing。3 import paths. The root re-exports 18 values and 48 types from 8 modules: McpClient, the two transports, protocol types, toLlmContent and the error classes; OAuth lives in ./oauth and the in-memory transport in ./testing.
| 导入路径Import path | 目标Target | 说明Notes |
|---|---|---|
@earendil-works/pi-mcp | ./dist/index.js | 客户端、传输、协议类型、内容转换与错误client, transports, protocol types, content conversion and errors |
@earendil-works/pi-mcp/oauth | ./dist/oauth/index.js | OAuth:发现、注册、授权流程、回调服务器与 providerOAuth: discovery, registration, the flow, callback server and provider |
@earendil-works/pi-mcp/testing | ./dist/testing/index.js | 测试用:内存传输对for tests: an in-memory transport pair |
常用导出Key exports
| 模块Module | 常用导出Key exports | 用途Use |
|---|---|---|
| packages/mcp/src/client.ts | McpClient | 一个服务器连接:握手、请求、分页one server connection: handshake, requests, paging |
| packages/mcp/src/transports/stdio.ts | StdioTransport | 本地子进程a local child process |
| packages/mcp/src/transports/streamable-http.ts | StreamableHttpTransport McpAuthRequiredError | 远程 HTTP + SSEremote HTTP + SSE |
| packages/mcp/src/transports/transport.ts | McpTransport | 自定义传输custom transports |
| packages/mcp/src/protocol/content.ts | toLlmContent | 结果 → 模型内容results → model content |
| packages/mcp/src/protocol/types.ts | LATEST_PROTOCOL_VERSION SUPPORTED_PROTOCOL_VERSIONS | 协议版本protocol versions |
| packages/mcp/src/protocol/jsonrpc.ts | McpError | JSON-RPC 错误JSON-RPC errors |
./oauth 的常用导出Key ./oauth exports
| 模块Module | 常用导出Key exports | 用途Use |
|---|---|---|
| packages/mcp/src/oauth/flow.ts | authorizeMcp | 完整的授权流程the full authorization flow |
| packages/mcp/src/oauth/provider.ts | McpOAuthProvider | 凭据与状态credentials and state |
| packages/mcp/src/oauth/callback.ts | OAuthCallbackServer | 本地回调服务器local callback server |
| packages/mcp/src/oauth/discovery.ts | discoverOAuthServerInfo | 元数据发现metadata discovery |
根入口 index.ts 的全部导出(按来源模块)Every root index.ts export, by source module
展开全部 8 个来源模块Show all 8 source modules18 values48 types
| 来源模块Source module | 值导出Value exports | 类型导出Type exports |
|---|---|---|
| packages/mcp/src/auth-provider.ts | — | AuthProvider McpFetch UnauthorizedContext |
| packages/mcp/src/client.ts | McpClient | McpClientOptions McpRequestOptions |
| packages/mcp/src/protocol/content.ts | toLlmContent | AudioContent BlobResourceContents CallToolResult ContentAnnotations ContentBlock EmbeddedResourceContent ImageContent LlmContent ResourceLinkContent TextContent +1 |
| packages/mcp/src/protocol/jsonrpc.ts | isJsonRpcNotification isJsonRpcRequest isJsonRpcResponse JSON_RPC_ERROR_CODES McpAbortError McpConnectionClosedError McpError McpTimeoutError parseJsonRpcMessage | JsonRpcErrorObject JsonRpcErrorResponse JsonRpcId JsonRpcMessage JsonRpcNotification JsonRpcRequest JsonRpcResponse JsonRpcSuccessResponse |
| packages/mcp/src/protocol/types.ts | LATEST_PROTOCOL_VERSION SUPPORTED_PROTOCOL_VERSIONS | CancelledNotification ClientCapabilities Implementation InitializeParams InitializeResult ListResourcesResult ListResourceTemplatesResult ListToolsResult ProgressNotification ReadResourceResult +8 |
| packages/mcp/src/transports/stdio.ts | StdioTransport | StdioTransportOptions |
| packages/mcp/src/transports/streamable-http.ts | McpAuthRequiredError McpHttpError McpSessionExpiredError StreamableHttpTransport | StreamableHttpTransportOptions |
| packages/mcp/src/transports/transport.ts | — | McpTransport McpTransportCloseListener McpTransportErrorListener McpTransportMessageListener |
由构建脚本解析 export { … } from 语句生成。Generated by parsing the export { … } from statements at build time.
连接与握手Connecting and the handshake#
一句话结论Takeawayconnect(transport) 只能调用一次:状态从 idle 到 connecting,启动传输,发送 initialize(默认协议版本 2025-11-25),校验服务器选定的版本是否在支持列表中,再发 notifications/initialized,状态变为 connected。任何一步失败都会 close(),状态进入不可逆的 closed。connect(transport) can be called once: the state goes from idle to connecting, the transport starts, initialize is sent (default protocol version 2025-11-25), the server's chosen version is checked against the supported list, notifications/initialized is sent, and the state becomes connected. Any failure calls close() and the state becomes the final closed.
要点Key points
- 支持的版本:
2025-11-25、2025-06-18、2025-03-26、2024-11-05。服务器选了别的版本时连接直接失败,而不是“尽量兼容”。Supported versions:2025-11-25,2025-06-18,2025-03-26,2024-11-05. A server that picks another version fails the connection outright instead of a best-effort fallback. - 协商出的版本通过
setProtocolVersion()交给传输:HTTP 传输此后在每个请求上带MCP-Protocol-Version头。The negotiated version goes to the transport viasetProtocolVersion(): from then on the HTTP transport sends anMCP-Protocol-Versionheader on every request. - 配置了
roots时自动声明roots能力,并响应服务器的roots/list请求;ping永远内置。With arootsoption therootscapability is declared automatically and the server'sroots/listrequests are answered;pingis always built in. - 握手结果保存在
serverInfo、serverCapabilities、instructions与protocolVersion上,供宿主展示或决策。Handshake results are kept onserverInfo,serverCapabilities,instructionsandprotocolVersionfor the host to show or act on.
工作原理How it works
- 订阅传输的 message / error / close;传输错误只上报,真正让请求失败的是传输关闭。Subscribe to the transport's message / error / close; transport errors are only reported — what fails requests is the transport closing.
transport.start()→initialize请求(不可取消)→validateInitializeResult()→ 版本检查。transport.start()→ theinitializerequest (not cancellable) →validateInitializeResult()→ version check.setProtocolVersion?.(v)→notifications/initialized→state = "connected"。setProtocolVersion?.(v)→notifications/initialized→state = "connected".
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/mcp/src/client.tsL202 | connect() | 握手the handshake |
| packages/mcp/src/client.tsL41 | ClientState | 4 个状态four states |
| packages/mcp/src/protocol/types.tsL9 | SUPPORTED_PROTOCOL_VERSIONS | 支持的版本supported versions |
| packages/mcp/src/protocol/types.tsL23 | ClientCapabilities | 客户端能力client capabilities |
| packages/mcp/src/protocol/types.tsL30 | ServerCapabilities | 服务器能力server capabilities |
请求、超时与取消Requests, timeouts and cancellation#
一句话结论Takeaway每个请求拿到一个递增的整数 id,记进 pending 表并启动超时(默认 30,000 ms)。响应、超时、signal 中止与传输关闭是四种结局;后两者之外的取消都会通知服务器 notifications/cancelled。反方向,服务器发来的请求交给注册的处理器,每个都有自己的 AbortSignal。Each request gets an increasing integer id, an entry in the pending map and a timeout (default 30,000 ms). A response, a timeout, a signal abort and the transport closing are the four endings; timeouts and aborts also tell the server with notifications/cancelled. In the other direction, server requests go to registered handlers, each with its own AbortSignal.
要点Key points
- 传了
onProgress时,请求的_meta.progressToken就是它的 id;每条notifications/progress都会重新计时——持续报进度的长任务不会超时。WithonProgress, the request's_meta.progressTokenis its id; everynotifications/progressre-arms the timeout — long tasks that keep reporting never time out. initialize例外:规范禁止取消它,所以中止或超时只在本地 reject。initializeis the exception: the spec forbids cancelling it, so an abort or timeout only rejects locally.- 没有处理器的服务器请求得到
-32601 Method not found;处理器抛出McpError时原样返回 code 与 data,其他异常变成-32603。Server requests without a handler get-32601 Method not found; a handler that throwsMcpErrorreturns its code and data as is, any other exception becomes-32603. listTools()等方法跟着nextCursor自动翻页:重复的 cursor 抛错,最多 1,000 页。listTools()and friends follownextCursorautomatically: a repeated cursor throws, and there are at most 1,000 pages.
工作原理How it works
request(method, params, { signal, timeoutMs, onProgress })→requestInternal()。request(method, params, { signal, timeoutMs, onProgress })→requestInternal().pending.set(id, entry)→armTimeout()→transport.send();发送失败立即 reject。pending.set(id, entry)→armTimeout()→transport.send(); a failed send rejects at once.handleMessage()按响应 / 请求 / 通知分派;handleResponse()删除 pending 并 resolve 或 rejectMcpError。handleMessage()dispatches responses / requests / notifications;handleResponse()removes the pending entry and resolves or rejects withMcpError.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/mcp/src/client.tsL394 | requestInternal() | 发出请求sending a request |
| packages/mcp/src/client.tsL548 | armTimeout() | 超时与重新计时timeouts and re-arming |
| packages/mcp/src/client.tsL556 | cancelPending() | notifications/cancellednotifications/cancelled |
| packages/mcp/src/client.tsL487 | handleRequest() | 服务器请求server requests |
| packages/mcp/src/client.tsL39 | MAX_LIST_PAGES | 分页上限paging limit |
| packages/mcp/src/protocol/jsonrpc.tsL37 | JSON_RPC_ERROR_CODES | 错误码error codes |
stdio 传输The stdio transport#
一句话结论TakeawayStdioTransport 用 cross-spawn 启动服务器进程(非 Windows 上放进独立的进程组),每条消息是一行 JSON。关闭遵循规范:先关 stdin 让服务器自己退出,500 ms 后对整个进程组发 SIGTERM,再过 2 秒发 SIGKILL。StdioTransport starts the server with cross-spawn (in its own process group on non-Windows), one JSON message per line. Shutdown follows the spec: close stdin and let the server exit, SIGTERM the whole process group after 500 ms, SIGKILL two seconds later.
要点Key points
- 用进程组是为了
npx、uvx这类包装命令:只杀包装进程会留下真正的服务器。Windows 上改用taskkill /T /F。Process groups exist for wrapper commands such asnpxanduvx: killing only the wrapper would leave the real server running. Windows usestaskkill /T /Finstead. - 宿主进程退出时,退出钩子对所有仍存活的进程组发 SIGTERM——即使宿主忘了关闭传输。When the host exits, an exit hook SIGTERMs every live process group — even if the host forgot to close the transport.
- stdout 按 0x0a 切分;单行超过 16 MiB 丢弃并报错;stderr 只保留最后 64 KiB,同时回调
onStderr。stdout is split at 0x0a; a line over 16 MiB is dropped with an error; stderr keeps only its last 64 KiB and also goes toonStderr. - 默认继承宿主的环境变量再叠加
env;inheritEnv: false时只用env。By default the host's environment is inherited andenvlayered on top;inheritEnv: falseusesenvalone.
工作原理How it works
start():crossSpawn()→ 登记进程组 → 等待spawn或error事件。start():crossSpawn()→ register the process group → wait forspawnorerror.send():写入JSON.stringify(message) + "\n"。send(): writeJSON.stringify(message) + "\n".close():stdin.end()→ SIGTERM(宽限期后)→ SIGKILL(closeTimeoutMs后);子进程 close 后再对进程组补一次 SIGTERM。close():stdin.end()→ SIGTERM (after the grace period) → SIGKILL (aftercloseTimeoutMs); after the child closes, the group gets one more SIGTERM.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/mcp/src/transports/stdio.tsL69 | StdioTransport | stdio 传输the stdio transport |
| packages/mcp/src/transports/stdio.tsL17 | killProcessTree() | 杀进程组killing the group |
| packages/mcp/src/transports/stdio.tsL10 | STDIN_CLOSE_GRACE_MS | 宽限期grace period |
| packages/mcp/src/transports/stdio.tsL181 | handleStdout() | 按行分帧line framing |
| packages/mcp/src/transports/transport.tsL3 | DEFAULT_MAX_MESSAGE_BYTES | 16 MiB16 MiB |
Streamable HTTP 传输The Streamable HTTP transport#
一句话结论Takeaway所有消息都 POST 到同一个 URL。通知和响应得到 202;请求的回复要么是 JSON(单条或一批),要么是一条 SSE 流。握手完成后再打开一条 GET 的 SSE 流接收服务器主动发来的消息。两种流断开时按退避策略重连,并用 Last-Event-ID 续传。Every message is POSTed to the same URL. Notifications and responses get 202; a request is answered with JSON (one message or a batch) or an SSE stream. After the handshake a GET SSE stream is opened for messages the server initiates. Both kinds of stream reconnect with back-off when they drop, resuming with Last-Event-ID.
要点Key points
- 会话:服务器在响应头里给出
Mcp-Session-Id,之后每个请求都带上;带着会话 id 收到 404 →McpSessionExpiredError。Sessions: the server returnsMcp-Session-Idin a response header and every later request carries it; a 404 with a session id →McpSessionExpiredError. - 401(或 403 且
error="insufficient_scope")先交给authProvider.onUnauthorized()一次,然后用新凭据重发;仍失败就抛McpAuthRequiredError。A 401 (or a 403 witherror="insufficient_scope") goes toauthProvider.onUnauthorized()once, then the request is retried with the new credentials; if it still fails,McpAuthRequiredErroris thrown. - 重试只针对 408、429、5xx 与网络错误;认证、会话和协议错误不重试。响应流无法续传时,只为那一个请求合成一条
-32603错误响应。Only 408, 429, 5xx and network errors are retried; auth, session and protocol errors are not. When a response stream cannot resume, a synthetic-32603error response fails just that one request. close()带会话 id 发DELETE(1 秒超时,复用最后一个 token,不触发刷新)。close()sendsDELETEwith the session id (1-second timeout, reusing the last token so no refresh is triggered).
工作原理How it works
send():authorizedFetch("POST")→checkResponse()→captureSession()→ 按消息类型和 Content-Type 分支。send():authorizedFetch("POST")→checkResponse()→captureSession()→ branch on message kind and Content-Type.consumeSseStream()解析data:/event:/id:/retry:,单个事件同样限制在 16 MiB。consumeSseStream()parsesdata:/event:/id:/retry:; a single event is capped at 16 MiB too.runGetStream()循环:openSseStream(lastEventId)(405 = 服务器不提供)→ 读到断开 →reconnectDelay()→ 重连。runGetStream()loops:openSseStream(lastEventId)(405 = server offers none) → read until it drops →reconnectDelay()→ reconnect.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/mcp/src/transports/streamable-http.tsL188 | StreamableHttpTransport | HTTP 传输the HTTP transport |
| packages/mcp/src/transports/streamable-http.tsL35 | consumeSseStream() | SSE 解析SSE parsing |
| packages/mcp/src/transports/streamable-http.tsL281 | authorizedFetch() | 认证重试auth retry |
| packages/mcp/src/transports/streamable-http.tsL366 | consumeResponseStream() | 响应流续传response-stream resumption |
| packages/mcp/src/transports/streamable-http.tsL413 | runGetStream() | GET 流the GET stream |
| packages/mcp/src/transports/streamable-http.tsL167 | isTransientStatus() | 可重试状态retryable statuses |
OAuth 授权OAuth authorization#
一句话结论TakeawayauthorizeMcp() 是整个 MCP 授权规范的实现:从 401 的 WWW-Authenticate 出发发现受保护资源与授权服务器的元数据,确定 resource 与 scope,取得客户端身份(已存、Client ID Metadata Document 或动态注册),能刷新就刷新,否则发起 PKCE 授权跳转;回调带回 code 后再调用一次完成换取。authorizeMcp() implements the whole MCP authorization spec: starting from a 401's WWW-Authenticate, it discovers protected-resource and authorization-server metadata, settles resource and scope, obtains a client identity (stored, a Client ID Metadata Document, or dynamic registration), refreshes if it can, and otherwise starts a PKCE redirect; when the callback brings back a code, a second call completes the exchange.
要点Key points
adaptOAuthProvider()把它包装成传输用的AuthProvider:并发的 401 共用一次授权;如果出错请求用的 token 已经被别人换掉了,就直接重试——轮换的 refresh token 不能用两次。adaptOAuthProvider()wraps it as the transport'sAuthProvider: concurrent 401s share one authorization; if the failing request's token was already replaced, it simply retries — a rotating refresh token cannot be used twice.- 403
insufficient_scope是 step-up:跳过刷新,请求“已授予 ∪ 挑战要求”的 scope。A 403insufficient_scopeis a step-up: refresh is skipped and the scope requested is “granted ∪ challenged”. - 出错恢复:
invalid_client/unauthorized_client清空所有凭据重来一次;invalid_grant只清令牌重来一次。Recovery:invalid_client/unauthorized_clientwipe all credentials and rerun once;invalid_grantwipes the tokens only and reruns once. - 安全检查:授权服务器必须支持 PKCE S256;带 code 回来时校验
iss(RFC 9207);protected resource 必须与服务器 URL 匹配;状态按精确的 serverUrl 存取。Safety checks: the authorization server must support PKCE S256;issis checked when a code comes back (RFC 9207); the protected resource must match the server URL; state is stored per exact serverUrl.
工作原理How it works
- 宿主实现或使用
McpOAuthProvider(可注入持久化的McpOAuthStateStore)。The host implements or usesMcpOAuthProvider(optionally with a persistentMcpOAuthStateStore). - 传输里用
adaptOAuthProvider(provider);需要用户登录时请求以McpOAuthAuthorizationRequiredError失败。UseadaptOAuthProvider(provider)in the transport; when the user must sign in, requests fail withMcpOAuthAuthorizationRequiredError. - 登录:
OAuthCallbackServer.listen()→authorizeMcp()返回 "REDIRECT" 并打开浏览器 → 拿到回调 → 带authorizationCode再调一次authorizeMcp()。Sign-in:OAuthCallbackServer.listen()→authorizeMcp()returns "REDIRECT" and opens the browser → receive the callback → callauthorizeMcp()again withauthorizationCode.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/mcp/src/oauth/flow.tsL429 | authorizeMcp() | 入口与错误恢复entry and recovery |
| packages/mcp/src/oauth/flow.tsL320 | runFlow() | 完整流程the full flow |
| packages/mcp/src/oauth/flow.tsL451 | adaptOAuthProvider() | 接到传输上plugging into transports |
| packages/mcp/src/oauth/discovery.tsL148 | discoverOAuthServerInfo() | 元数据发现metadata discovery |
| packages/mcp/src/oauth/provider.tsL53 | McpOAuthProvider | 默认 providerdefault provider |
| packages/mcp/src/oauth/callback.tsL34 | OAuthCallbackServer | 本地回调loopback callback |
内容转换与错误类型Content conversion and error types#
一句话结论TakeawaytoLlmContent() 把 CallToolResult 变成模型 API 接受的两种块:文本与图片。文本、图片和内嵌的文本 / 图片资源原样转换;音频、资源链接和其他二进制资源变成一行占位文字;没有内容块却有 structuredContent 时输出它的 JSON。toLlmContent() turns a CallToolResult into the two block kinds model APIs accept: text and images. Text, images and embedded text / image resources convert directly; audio, resource links and other binary resources become a one-line placeholder; a result with no content blocks but structuredContent yields its JSON.
要点Key points
LlmContent的形状与pi-ai的TextContent/ImageContent一致,但pi-mcp并不依赖pi-ai。LlmContenthas the same shape aspi-ai'sTextContent/ImageContent, yetpi-mcpdoes not depend onpi-ai.- 错误按类型区分:超时、中止、连接关闭、HTTP 状态(及其两个子类:需要认证、会话过期)与需要登录。除这两个子类外都直接继承
Error。Errors are told apart by type: timeouts, aborts, closed connections, HTTP statuses (with two subclasses: auth required and session expired) and required sign-in. Apart from those two subclasses, all extendErrordirectly. McpError携带 JSON-RPC 的code与data,处理器抛出它可以向服务器返回任意错误码。McpErrorcarries the JSON-RPCcodeanddata; a handler that throws it can return any error code to the server.
工作原理How it works
callTool()校验结果形状,缺失的content补成[]。callTool()validates the result shape and defaults a missingcontentto[].toLlmContent(result)逐块转换;isError由调用方自行决定如何呈现。toLlmContent(result)converts block by block; how to presentisErroris up to the caller.- 用
instanceof区分错误:例如McpSessionExpiredError时重新连接。Tell errors apart withinstanceof: e.g. reconnect onMcpSessionExpiredError.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/mcp/src/protocol/content.tsL111 | toLlmContent() | 结果 → 模型内容result → model content |
| packages/mcp/src/protocol/content.tsL63 | ContentBlock | 5 种内容块five block kinds |
| packages/mcp/src/protocol/jsonrpc.tsL45 | McpError | JSON-RPC 错误JSON-RPC error |
| packages/mcp/src/transports/streamable-http.tsL126 | McpHttpError | HTTP 错误HTTP errors |
| packages/mcp/src/oauth/errors.tsL50 | McpOAuthAuthorizationRequiredError | 需要登录sign-in required |
在 pi 中的使用How pi uses it#
一句话结论Takeawaycoding-agent 的内置 MCP 扩展读取全局和受信任项目的 mcp.json(格式与其他 MCP 客户端相同),以及扩展用 pi.registerMcpServer() 注册的服务器,在后台连接,并把工具注册为 mcp__<server>__<tool>。默认的 exposure 是 codemode:MCP 工具不进模型的工具声明,只能在 codemode 脚本里调用。coding-agent's built-in MCP extension reads the global and trusted-project mcp.json (same shape as other MCP clients) plus servers extensions register with pi.registerMcpServer(), connects in the background, and registers tools as mcp__<server>__<tool>. The default exposure is codemode: MCP tools stay out of the model's tool declarations and are called from codemode scripts.
要点Key points
- 四种 exposure:
codemode(默认)、deferred(tool_search加载后才声明)、direct(立即声明)、hidden;toolExposure可以按工具覆盖。Four exposures:codemode(default),deferred(declared oncetool_searchloads them),direct(declared right away) andhidden;toolExposureoverrides per tool. - 项目的
mcp.json不能设置auth——仓库不能决定凭据发往哪里。只写 enabled / exposure 的项目条目只覆盖全局同名服务器的这几项。A projectmcp.jsoncannot setauth— a repository must not choose where credentials go. A project entry with only enabled / exposure just overrides those fields of the global server of the same name. - 连接从不自己弹浏览器:只用存下的 token 和 refresh token;需要登录时在
/mcp中完成,凭据存在<agent-dir>/mcp-auth.json。Connections never open a browser on their own: they use the stored token and refresh token; sign-in happens in/mcp, and credentials live in<agent-dir>/mcp-auth.json. - 每次 MCP 调用都走 pi 的工具管线,
tool_call/tool_result钩子和权限扩展照常生效。Every MCP call runs through pi's tool pipeline, sotool_call/tool_resulthooks and permission extensions apply as usual.
工作原理How it works
loadMcpConfig():全局mcp.json→ 受信任项目的.pi/mcp.json→ 合并与校验。loadMcpConfig(): globalmcp.json→ the trusted project's.pi/mcp.json→ merge and validate.runtime.ts:有command→StdioTransport,有url→StreamableHttpTransport;new McpClient()并connect()。runtime.ts:command→StdioTransport,url→StreamableHttpTransport;new McpClient()andconnect().createMcpToolName()生成工具名(超过 64 字符或冲突时加 8 位哈希);资源通过list_mcp_resources等 3 个工具访问。createMcpToolName()builds tool names (an 8-hex hash suffix when longer than 64 characters or clashing); resources go throughlist_mcp_resourcesand two other tools.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/coding-agent/src/extensions/mcp/index.tsL280 | createMcpExtension() | 扩展入口extension entry |
| packages/coding-agent/src/extensions/mcp/config.tsL145 | loadMcpConfig() | 读取 mcp.jsonreading mcp.json |
| packages/coding-agent/src/extensions/mcp/runtime.tsL115 | StdioTransport | 选择传输choosing a transport |
| packages/coding-agent/src/extensions/mcp/tools.tsL84 | createMcpToolName() | 工具命名tool naming |
| packages/coding-agent/src/core/mcp-servers.tsL52 | McpExposure | 4 种 exposurefour exposures |
| packages/coding-agent/src/extensions/mcp/oauth.tsL147 | mcp-auth.json | 凭据存储credential storage |
扩展点Extension points#
McpTransport实现 start / send / close 与三个监听即可接入新的传输。Implement start / send / close and three listeners to add a transport.AuthProvidertoken() 加可选的 onUnauthorized():任何认证方案。token() plus an optional onUnauthorized(): any auth scheme.OAuthClientProvider · McpOAuthStateStore自定义 OAuth 状态的存储与跳转方式。Customise how OAuth state is stored and how redirects happen.client.setRequestHandler(method, fn)响应服务器发来的请求(如 sampling、elicitation)。Answer requests from the server (e.g. sampling, elicitation).client.onNotification(method, fn)监听 list_changed 等通知。Listen for notifications such as list_changed.McpClientOptions.roots声明 roots 能力并提供根目录列表。Declare the roots capability and provide the roots.createInMemoryTransportPair()./testing:在同一进程里测试客户端与服务器。./testing: test a client and a server in one process.测试Tests#
一句话结论Takeaway每个模块一个测试文件:客户端(握手、请求、分页、取消)、内容转换、OAuth 流程、stdio 子进程与 Streamable HTTP;大多通过内存传输、测试用子进程或本地 HTTP 服务器端到端验证。One test file per area: the client (handshake, requests, paging, cancellation), content conversion, the OAuth flow, stdio child processes and Streamable HTTP; most run end to end over the in-memory transport, a fixture child process or a local HTTP server.
| 测试文件Test file | 用例cases |
|---|---|
| packages/mcp/test/streamable-http.test.ts | 15 |
| packages/mcp/test/client.test.ts | 12 |
| packages/mcp/test/oauth.test.ts | 12 |
| packages/mcp/test/content.test.ts | 2 |
| packages/mcp/test/stdio.test.ts | 1 |
用例数 = 源码中 it( / test( 的出现次数(静态统计,不运行测试)。Cases = occurrences of it( / test( in the source (static count; tests were not run).