图解 Pi AgentPi Agent, Illustratedv1.1.0
包Packages· mcp
演示Slides
Extensions已完成Donepackages/mcp · v1.1.0

mcp@earendil-works/pi-mcp

独立的 Model Context Protocol 客户端。Standalone Model Context Protocol client for pi and other applications.

1.1.0
版本version
18
源文件src files
3,243
源码行src LOC
5
测试文件test files
42
测试用例test cases
3
导出路径export paths
依赖Depends on无内部依赖no internal deps被依赖Used bycoding-agent

这个包是什么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.

01Extensions

模块结构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.

三组模块、四个导入路径与 pi 中的使用者Three module groups, the import paths and the user inside pi
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
pi-mcp modules (3,243 lines of TypeScript)客户端与协议Client & protocol传输TransportsOAuth(入口 ./oauth)OAuth (entry ./oauth)导入路径、依赖与使用者Import paths, dependencies and users615client.tsMcpClient:握手 · 请求 · 分页McpClient: handshake · requests · paging113protocol/jsonrpc.tsJSON-RPC 2.0 消息 · 错误JSON-RPC 2.0 messages · errors131protocol/types.ts协议版本 · 能力 · Toolversions · capabilities · Tool117protocol/content.ts内容块 · toLlmContent()content blocks · toLlmContent()20auth-provider.tsAuthProvider 接口the AuthProvider interface71index.ts包入口 "."package entry "."55transports/transport.tsMcpTransport · TransportEvents216transports/stdio.ts子进程 · 按行 JSONchild process · line-delimited JSON505transports/streamable-http.tsPOST + SSE · 会话 · 重连POST + SSE · sessions · reconnect51transports/in-memory.ts测试用的一对传输a linked pair for tests1testing/index.ts入口 "./testing"entry "./testing"481oauth/flow.tsauthorizeMcp · PKCE · 刷新authorizeMcp · PKCE · refresh215oauth/discovery.ts.well-known 元数据发现.well-known metadata discovery209oauth/types.ts元数据 · 令牌 · 客户端类型metadata · token · client types168oauth/provider.tsMcpOAuthProvider · 状态存储McpOAuthProvider · state store164oauth/callback.ts本地回调服务器loopback callback server55oauth/errors.tsOAuthError 等 5 个类OAuthError and 4 more@earendil-works/pi-mcp@earendil-works/pi-mcp/oauth@earendil-works/pi-mcp/testingcross-spawn 7.0.6coding-agent · extensions/mcp (4,240)运行时依赖:runtime dep:不依赖任何 pi 包;pi 内唯一的使用者是 coding-agent 的内置 MCP 扩展。Depends on no pi package; inside pi its only user is coding-agent's built-in MCP extension.标签 = 行数tag = lines of code客户端 · 协议client · protocol传输transportsOAuth

要点Key points

  • 最大的文件是 client.ts;其次是 transports/streamable-http.ts——HTTP 传输要处理会话、SSE 解析、认证重试和断线续传。The largest file is client.ts, followed by transports/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 the McpTransport interface: start / send / close plus three listeners (message, error, close). Swapping transports never touches the client.
  • 认证通过一个很小的 AuthProvider 接口注入:token() 与可选的 onUnauthorized()。OAuth 只是它的一种实现(adaptOAuthProvider)。Auth is injected through a tiny AuthProvider interface: token() and an optional onUnauthorized(). OAuth is just one implementation of it (adaptOAuthProvider).

工作原理How it works

  1. 创建 StdioTransport({ command, args }) 或 StreamableHttpTransport({ url, authProvider })。Create a StdioTransport({ command, args }) or a StreamableHttpTransport({ url, authProvider }).
  2. new McpClient({ name, version }),再 await client.connect(transport)。new McpClient({ name, version }), then await client.connect(transport).
  3. listTools() / callTool() / readResource();把结果交给 toLlmContent() 变成模型能读的内容。listTools() / callTool() / readResource(); pass results to toLlmContent() to get model-ready content.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/mcp/src/index.tsL2export …包入口package entry
packages/mcp/src/client.tsL153McpClient客户端the client
packages/mcp/src/transports/transport.tsL9McpTransport传输接口transport interface
packages/mcp/src/auth-provider.tsL13AuthProvider认证接口auth interface
packages/mcp/src/oauth/index.tsL26./oauthOAuth 入口OAuth entry
02Extensions

公共 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.jsOAuth:发现、注册、授权流程、回调服务器与 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

package.json · exportsL8

常用导出Key exports

模块Module常用导出Key exports用途Use
packages/mcp/src/client.tsMcpClient一个服务器连接:握手、请求、分页one server connection: handshake, requests, paging
packages/mcp/src/transports/stdio.tsStdioTransport本地子进程a local child process
packages/mcp/src/transports/streamable-http.tsStreamableHttpTransport McpAuthRequiredError远程 HTTP + SSEremote HTTP + SSE
packages/mcp/src/transports/transport.tsMcpTransport自定义传输custom transports
packages/mcp/src/protocol/content.tstoLlmContent结果 → 模型内容results → model content
packages/mcp/src/protocol/types.tsLATEST_PROTOCOL_VERSION SUPPORTED_PROTOCOL_VERSIONS协议版本protocol versions
packages/mcp/src/protocol/jsonrpc.tsMcpErrorJSON-RPC 错误JSON-RPC errors

./oauth 的常用导出Key ./oauth exports

模块Module常用导出Key exports用途Use
packages/mcp/src/oauth/flow.tsauthorizeMcp完整的授权流程the full authorization flow
packages/mcp/src/oauth/provider.tsMcpOAuthProvider凭据与状态credentials and state
packages/mcp/src/oauth/callback.tsOAuthCallbackServer本地回调服务器local callback server
packages/mcp/src/oauth/discovery.tsdiscoverOAuthServerInfo元数据发现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.tsMcpClientMcpClientOptions McpRequestOptions
packages/mcp/src/protocol/content.tstoLlmContentAudioContent BlobResourceContents CallToolResult ContentAnnotations ContentBlock EmbeddedResourceContent ImageContent LlmContent ResourceLinkContent TextContent +1
packages/mcp/src/protocol/jsonrpc.tsisJsonRpcNotification isJsonRpcRequest isJsonRpcResponse JSON_RPC_ERROR_CODES McpAbortError McpConnectionClosedError McpError McpTimeoutError parseJsonRpcMessageJsonRpcErrorObject JsonRpcErrorResponse JsonRpcId JsonRpcMessage JsonRpcNotification JsonRpcRequest JsonRpcResponse JsonRpcSuccessResponse
packages/mcp/src/protocol/types.tsLATEST_PROTOCOL_VERSION SUPPORTED_PROTOCOL_VERSIONSCancelledNotification ClientCapabilities Implementation InitializeParams InitializeResult ListResourcesResult ListResourceTemplatesResult ListToolsResult ProgressNotification ReadResourceResult +8
packages/mcp/src/transports/stdio.tsStdioTransportStdioTransportOptions
packages/mcp/src/transports/streamable-http.tsMcpAuthRequiredError McpHttpError McpSessionExpiredError StreamableHttpTransportStreamableHttpTransportOptions
packages/mcp/src/transports/transport.ts—McpTransport McpTransportCloseListener McpTransportErrorListener McpTransportMessageListener

由构建脚本解析 export { … } from 语句生成。Generated by parsing the export { … } from statements at build time.

03Extensions

连接与握手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.

连接状态机、initialize 握手与双方能力The connection state machine, the initialize handshake and both sides' capabilities
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Connecting: the state machine and the initialize handshakeMcpClient 状态McpClient state只能 connect 一次;closed 不可逆connect() once; closed is final握手时序(client.connect)Handshake sequence (client.connect)idleconnectingconnectedclosedMcpClientMcpTransportMCP 服务器MCP server«client»ClientCapabilities+experimental+roots+sampling+elicitation«server»ServerCapabilities+experimental+logging+prompts+resources+tools+completions2025-11-252025-06-182025-03-262024-11-05connect()握手完成handshake doneclose()任一步失败 → close()any step fails → close()transport.start()stdio:启动子进程 · http:无操作stdio: spawn the process · http: no-opinitializeprotocolVersion "2025-11-25" · capabilities · clientInfoInitializeResultprotocolVersion · capabilities · serverInfo · instructions校验:版本必须是支持的 4 个之一check: the version must be one of the 4 supportedsetProtocolVersion(v)http:之后每个请求带 MCP-Protocol-Versionhttp: every later request carries MCP-Protocol-Versionnotifications/initializedhttp:此后才打开 GET 事件流http: only now is the GET stream openedinitialize 不可取消;roots 选项存在时 capabilities 自动加上 roots: {}。initialize cannot be cancelled; with a roots option, capabilities gains roots: {} automatically.支持的协议版本SUPPORTED VERSIONS第一个 = LATEST_PROTOCOL_VERSION,默认发送它the first = LATEST_PROTOCOL_VERSION, sent by default123456实线:客户端 → 服务器solid: client → server虚线:服务器 → 客户端dashed: server → client失败failure

要点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 via setProtocolVersion(): from then on the HTTP transport sends an MCP-Protocol-Version header on every request.
  • 配置了 roots 时自动声明 roots 能力,并响应服务器的 roots/list 请求;ping 永远内置。With a roots option the roots capability is declared automatically and the server's roots/list requests are answered; ping is always built in.
  • 握手结果保存在 serverInfo、serverCapabilities、instructions 与 protocolVersion 上,供宿主展示或决策。Handshake results are kept on serverInfo, serverCapabilities, instructions and protocolVersion for the host to show or act on.

工作原理How it works

  1. 订阅传输的 message / error / close;传输错误只上报,真正让请求失败的是传输关闭。Subscribe to the transport's message / error / close; transport errors are only reported — what fails requests is the transport closing.
  2. transport.start() → initialize 请求(不可取消)→ validateInitializeResult() → 版本检查。transport.start() → the initialize request (not cancellable) → validateInitializeResult() → version check.
  3. setProtocolVersion?.(v) → notifications/initialized → state = "connected"。setProtocolVersion?.(v) → notifications/initialized → state = "connected".

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/mcp/src/client.tsL202connect()握手the handshake
packages/mcp/src/client.tsL41ClientState4 个状态four states
packages/mcp/src/protocol/types.tsL9SUPPORTED_PROTOCOL_VERSIONS支持的版本supported versions
packages/mcp/src/protocol/types.tsL23ClientCapabilities客户端能力client capabilities
packages/mcp/src/protocol/types.tsL30ServerCapabilities服务器能力server capabilities
04Extensions

请求、超时与取消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.

请求簿记、超时与取消、服务器请求与分页Request bookkeeping, timeouts and cancellation, server requests and paging
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Request bookkeeping: timeouts, progress, cancellation and paging客户端发出的请求Requests the client sendsclient.request(method, params, opts)服务器发来的请求Requests from the server分页:listAll()Paging: listAll()listTools · listResources · listResourceTemplatesid = nextRequestId++递增整数an increasing integer_meta.progressToken = id仅当传了 onProgressonly with onProgresspending.set(id, entry)resolve · reject · timer · signalarmTimeout(30,000 ms)默认值,可按请求覆盖default; per-request overridetransport.send(message)发送失败 → 直接 rejectsend fails → reject at once响应到达response arrivesresult → resolve · error → McpError(code)notifications/progress重新计时 + onProgress(…)re-arm the timeout + onProgress(…)超时timeoutMcpTimeoutError + notifications/cancelledsignal 中止signal abortedMcpAbortError + notifications/cancelled传输关闭 / close()transport closed / close()所有 pending → McpConnectionClosedErrorall pending → McpConnectionClosedErrorrequestHandlers.get(method)内置:ping → {} · roots/list(配置了 roots 时)built in: ping → {} · roots/list (when roots are set)没有处理器no handler-32601 Method not foundhandler(params, { signal })incoming.set(id, AbortController)回复replyresult ?? {} · McpError 原样 · 其他 → -32603result ?? {} · McpError as is · other → -32603notifications/cancelled{ requestId } → 中止该处理器的 signal{ requestId } → abort that handler's signalrequest(method, { cursor })第一页不带 cursorfirst page without cursoritems.push(...page)tools / resources / resourceTemplatesnextCursor?缺失、null 或 "" → 结束missing, null or "" → done重复 cursor → 抛错duplicate cursor → throw最多 1,000 页at most 1,000 pagesinitialize 例外:中止或超时只 reject,不发 notifications/cancelled(规范禁止取消 initialize)。Exception: initialize is rejected on abort/timeout but never cancelled — the spec forbids it.每条进度通知都把超时重新计时,长任务只要持续报进度就不会超时。Every progress notification re-arms the timeout, so long tasks that keep reporting never time out.未知 id 的响应只报 onError,不影响其他请求。A response with an unknown id only goes to onError; other requests are unaffected.setRequestHandler(method, fn) 可增加或替换处理器。setRequestHandler(method, fn) adds or replaces a handler.listResourcesPage(cursor) 等单页方法留给需要自己控制分页的调用方。Single-page methods such as listResourcesPage(cursor) are there for callers that page themselves.流程flow收到消息message received失败 / 取消failure / cancel

要点Key points

  • 传了 onProgress 时,请求的 _meta.progressToken 就是它的 id;每条 notifications/progress 都会重新计时——持续报进度的长任务不会超时。With onProgress, the request's _meta.progressToken is its id; every notifications/progress re-arms the timeout — long tasks that keep reporting never time out.
  • initialize 例外:规范禁止取消它,所以中止或超时只在本地 reject。initialize is 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 throws McpError returns its code and data as is, any other exception becomes -32603.
  • listTools() 等方法跟着 nextCursor 自动翻页:重复的 cursor 抛错,最多 1,000 页。listTools() and friends follow nextCursor automatically: a repeated cursor throws, and there are at most 1,000 pages.

工作原理How it works

  1. request(method, params, { signal, timeoutMs, onProgress }) → requestInternal()。request(method, params, { signal, timeoutMs, onProgress }) → requestInternal().
  2. pending.set(id, entry) → armTimeout() → transport.send();发送失败立即 reject。pending.set(id, entry) → armTimeout() → transport.send(); a failed send rejects at once.
  3. handleMessage() 按响应 / 请求 / 通知分派;handleResponse() 删除 pending 并 resolve 或 reject McpError。handleMessage() dispatches responses / requests / notifications; handleResponse() removes the pending entry and resolves or rejects with McpError.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/mcp/src/client.tsL394requestInternal()发出请求sending a request
packages/mcp/src/client.tsL548armTimeout()超时与重新计时timeouts and re-arming
packages/mcp/src/client.tsL556cancelPending()notifications/cancellednotifications/cancelled
packages/mcp/src/client.tsL487handleRequest()服务器请求server requests
packages/mcp/src/client.tsL39MAX_LIST_PAGES分页上限paging limit
packages/mcp/src/protocol/jsonrpc.tsL37JSON_RPC_ERROR_CODES错误码error codes
05Extensions

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.

启动子进程、按行分帧与分阶段关闭Spawning the child, line framing and the staged close
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
StdioTransport: a child process, line-delimited JSON and a three-step shutdownstart()启动服务器进程spawn the server分帧Framing一行 = 一条 JSON-RPC 消息one line = one JSON-RPC messageclose()规范要求的关闭顺序:先关 stdin,再发信号the spec's shutdown order: close stdin first, then signalscrossSpawn(command, args)cross-spawn:Windows 上也能跑 .cmd 包装cross-spawn: .cmd shims work on Windows tooenv = process.env + envinheritEnv: false → 只用 envinheritEnv: false → env onlydetached: true非 Windows:自己的进程组non-Windows: its own process groupawait "spawn" | "error"命令不存在 → start() rejectmissing command → start() rejectsMcpClientchild.stdinJSON.stringify(msg) + "\n"MCP 服务器MCP server子进程child processchild.stdout按 0x0a 切分 · 去掉 \rsplit at 0x0a · strip \rchild.stderr保留最后 64 KiB · onStderrkeeps the last 64 KiB · onStderr进程组记入 liveProcessGroups;宿主退出时统一 SIGTERM。Groups go into liveProcessGroups; host exit SIGTERMs them all.send()onMessage空行跳过;解析失败 → onError,继续读下一行blank lines skipped; parse failure → onError, keep reading单行超过 16 MiB → 丢弃并报错a line over 16 MiB → dropped with an errorstderr 只是日志:stderr is only a log:stderr: "inherit" 时直接给宿主终端stderr: "inherit" sends it to the host terminalt = 0 msstdin.end()让服务器自己退出let the server exit on its ownt = 500 msSIGTERM500 ms 后 · 整个进程组after 500 ms · the whole groupt = 2,500 msSIGKILL再过 2,000 ms(closeTimeoutMs)2,000 ms later (closeTimeoutMs)子进程 "close" 后:after the child's "close":再对进程组发一次 SIGTERM,SIGTERM the group once more, sonpx / uvx 包装下的真正服务器也会退出the real server under npx / uvx exits tooWindows:taskkill /pid … /T /FWindows: taskkill /pid … /T /F服务器提前退出时:残留的半行 → onError,然后 onClose(只触发一次),McpClient 拒绝所有 pending 请求。If the server exits first: a leftover partial line → onError, then onClose (fires once); McpClient rejects every pending request.客户端 → 服务器client → server服务器 → 客户端server → client日志log

要点Key points

  • 用进程组是为了 npx、uvx 这类包装命令:只杀包装进程会留下真正的服务器。Windows 上改用 taskkill /T /F。Process groups exist for wrapper commands such as npx and uvx: killing only the wrapper would leave the real server running. Windows uses taskkill /T /F instead.
  • 宿主进程退出时,退出钩子对所有仍存活的进程组发 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 to onStderr.
  • 默认继承宿主的环境变量再叠加 env;inheritEnv: false 时只用 env。By default the host's environment is inherited and env layered on top; inheritEnv: false uses env alone.

工作原理How it works

  1. start():crossSpawn() → 登记进程组 → 等待 spawn 或 error 事件。start(): crossSpawn() → register the process group → wait for spawn or error.
  2. send():写入 JSON.stringify(message) + "\n"。send(): write JSON.stringify(message) + "\n".
  3. close():stdin.end() → SIGTERM(宽限期后)→ SIGKILL(closeTimeoutMs 后);子进程 close 后再对进程组补一次 SIGTERM。close(): stdin.end() → SIGTERM (after the grace period) → SIGKILL (after closeTimeoutMs); after the child closes, the group gets one more SIGTERM.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/mcp/src/transports/stdio.tsL69StdioTransportstdio 传输the stdio transport
packages/mcp/src/transports/stdio.tsL17killProcessTree()杀进程组killing the group
packages/mcp/src/transports/stdio.tsL10STDIN_CLOSE_GRACE_MS宽限期grace period
packages/mcp/src/transports/stdio.tsL181handleStdout()按行分帧line framing
packages/mcp/src/transports/transport.tsL3DEFAULT_MAX_MESSAGE_BYTES16 MiB16 MiB
06Extensions

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.

POST 的三种响应、错误映射与 SSE 重连The three POST responses, error mapping and SSE reconnects
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
StreamableHttpTransport: one URL, POST to send, SSE to receive每个请求的头Headers on every requestsend(message) → POST按消息类型和 Content-Type 分支branches on message kind and Content-Type错误映射Error mappingcheckResponse()SSE 流与重连SSE streams and reconnectionauthorizedFetch("POST")401/403 → 见下方401 / 403 → see below通知 / 响应notification / response202 · 丢弃 body;若是 notifications/initialized → 打开 GET 流202 · discard the body; notifications/initialized → open the GET streamapplication/json一条或一批(数组)消息 → onMessageone message or a batch (array) → onMessagetext/event-streamconsumeResponseStream(body, id):读到该 id 的响应为止consumeResponseStream(body, id): read until that id's response请求却得到 202/204,或其他类型a request answered 202/204, or another typeMcpHttpError401404othersrunGetStream()一直保持打开;405 = 服务器不提供kept open; 405 = server offers noneconsumeResponseStream()只有带事件 id 时才续传resumes only if events carry ids退避重连:Last-Event-ID 续传Back-off reconnect, resuming with Last-Event-ID延迟 = 服务器的 retry 字段,否则 min(1,000 × 2^n, 30,000) ms · 连续失败 5 次放弃delay = the server's retry field, else min(1,000 × 2^n, 30,000) ms · give up after 5 failures in a rowAcceptapplication/json, text/event-streamContent-Typeapplication/jsonMcp-Session-Id服务器在响应里分配后once the server assigned oneMCP-Protocol-Version握手协商出的版本the negotiated versionAuthorizationBearer <authProvider.token()>options.headers 先放入,上面这些覆盖它们。options.headers go first; these override them.McpAuthRequiredError带 wwwAuthenticatecarries wwwAuthenticateMcpSessionExpiredError仅当已有会话 idonly with a session idMcpHttpError(status)body 截到 8 KiBbody capped at 8 KiB401,或 403 且 error="insufficient_scope":401, or 403 with error="insufficient_scope":先交给 authProvider.onUnauthorized() 一次,再重发hand to authProvider.onUnauthorized() once, then retry可重试:408 · 429 · 5xx · 网络错误(TypeError、E*/UND_ERR_* 码);认证、会话与协议错误不重试。Retryable: 408 · 429 · 5xx · network errors (TypeError, E* / UND_ERR_* codes); auth, session and protocol errors are not.响应流断掉且无法续传:为该请求合成一条 -32603 错误响应,其余请求不受影响。A response stream that breaks and cannot resume: a synthetic -32603 error response for that request only.close():中止所有 fetch,带会话 id 发 DELETE(1 s 超时,复用上一个 token)。close(): abort every fetch, send DELETE with the session id (1 s timeout, reusing the last token).分支branch错误error流streams

要点Key points

  • 会话:服务器在响应头里给出 Mcp-Session-Id,之后每个请求都带上;带着会话 id 收到 404 → McpSessionExpiredError。Sessions: the server returns Mcp-Session-Id in 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 with error="insufficient_scope") goes to authProvider.onUnauthorized() once, then the request is retried with the new credentials; if it still fails, McpAuthRequiredError is 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 -32603 error response fails just that one request.
  • close() 带会话 id 发 DELETE(1 秒超时,复用最后一个 token,不触发刷新)。close() sends DELETE with the session id (1-second timeout, reusing the last token so no refresh is triggered).

工作原理How it works

  1. send():authorizedFetch("POST") → checkResponse() → captureSession() → 按消息类型和 Content-Type 分支。send(): authorizedFetch("POST") → checkResponse() → captureSession() → branch on message kind and Content-Type.
  2. consumeSseStream() 解析 data: / event: / id: / retry:,单个事件同样限制在 16 MiB。consumeSseStream() parses data: / event: / id: / retry:; a single event is capped at 16 MiB too.
  3. 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.tsL188StreamableHttpTransportHTTP 传输the HTTP transport
packages/mcp/src/transports/streamable-http.tsL35consumeSseStream()SSE 解析SSE parsing
packages/mcp/src/transports/streamable-http.tsL281authorizedFetch()认证重试auth retry
packages/mcp/src/transports/streamable-http.tsL366consumeResponseStream()响应流续传response-stream resumption
packages/mcp/src/transports/streamable-http.tsL413runGetStream()GET 流the GET stream
packages/mcp/src/transports/streamable-http.tsL167isTransientStatus()可重试状态retryable statuses
07Extensions

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.

触发条件、授权流程六步与本地回调服务器Triggers, the six-step flow and the local callback server
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
OAuth: from a 401 to a usable access tokenrunFlow()每一步的结果都存进 providereach step's result is saved in the providerOAuthCallbackServer.listen()McpOAuthProviderMcpOAuthStateHTTP 401 · 403 insufficient_scope来自 StreamableHttpTransportfrom StreamableHttpTransportadaptOAuthProvider().onUnauthorized并发 401 共用一次 · token 已换过就直接重试concurrent 401s share one run · token already replaced → just retryauthorizeMcp(provider, options)→ "AUTHORIZED" | "REDIRECT"4 · 有 refresh_token?4 · Have a refresh_token?grant_type=refresh_tokensaveTokens → "AUTHORIZED"刷新成功;scope 缺省 = 原授权的refreshed; missing scope = the grant's5 · startAuthorization()PKCE S256 · state · resourcesaveCodeVerifier → redirectToAuthorization返回 "REDIRECT"returns "REDIRECT"传输里 → McpOAuthAuthorizationRequiredErrorin the transport → McpOAuthAuthorizationRequiredError6 · 带 authorizationCode 再调一次6 · Called again with authorizationCodeiss 校验(RFC 9207)iss check (RFC 9207)exchangeAuthorizationCode + codeVerifiersaveTokens → "AUTHORIZED"scope 缺省 = 请求的 scopemissing scope = the requested oneclientInformationtokenstokensExpireAtcodeVerifieroauthStatediscovery1 · 发现1 · DiscoverWWW-Authenticate: resource_metadata=…/.well-known/oauth-protected-resource{path}→ authorization_servers[0](缺省:服务器根)→ authorization_servers[0] (default: server root)/.well-known/oauth-authorization-server{path}/.well-known/openid-configuration{path}结果缓存为 discoveryStatecached as discoveryState2 · resource 与 scope2 · resource and scoperesource 必须与服务器 URL 匹配resource must match the server URLscope = 参数 || scopes_supportedscope = option || scopes_supported || clientMetadata.scope403 step-up:已授予 ∪ 挑战要求403 step-up: granted ∪ challenged3 · 客户端身份3 · Client identity已存的 clientInformationstored clientInformation或 Client ID Metadata Documentor a Client ID Metadata Document (https、路径非根) (https, non-root path)或动态注册 registration_endpointor dynamic registration at registration_endpoint有yes无no刷新遇到网络错误或 server_error → 落到第 5 步;其他 OAuthError 抛出,交给 authorizeMcp 的恢复逻辑。A refresh that hits a network error or server_error falls through to step 5; other OAuthErrors are thrown to authorizeMcp's recovery.错误恢复:invalid_client / unauthorized_client → 清空全部凭据重来;invalid_grant → 只清令牌重来。Recovery: invalid_client / unauthorized_client → wipe all credentials and rerun; invalid_grant → wipe tokens only and rerun.127.0.0.1 · 端口 0(随机)· 路径 /callback127.0.0.1 · port 0 (random) · path /callback浏览器带回 code · state · iss;5 分钟超时the browser brings back code · state · iss; 5-minute timeout之后用这些参数调用第 6 步step 6 then runs with these parameterspi:只有 /mcp 里的登录会走浏览器;连接本身从不弹浏览器。pi: only sign-in from /mcp opens a browser; connections never do.按精确的 serverUrl 存取:别的服务器的状态一律忽略。keyed by the exact serverUrl: state of any other server is ignored.默认 MemoryOAuthStateStore;pi 存到 mcp-auth.json。default MemoryOAuthStateStore; pi persists to mcp-auth.json.流程flow需要用户参与 / 失败needs the user / failure成功success

要点Key points

  • adaptOAuthProvider() 把它包装成传输用的 AuthProvider:并发的 401 共用一次授权;如果出错请求用的 token 已经被别人换掉了,就直接重试——轮换的 refresh token 不能用两次。adaptOAuthProvider() wraps it as the transport's AuthProvider: 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 403 insufficient_scope is a step-up: refresh is skipped and the scope requested is “granted ∪ challenged”.
  • 出错恢复:invalid_client / unauthorized_client 清空所有凭据重来一次;invalid_grant 只清令牌重来一次。Recovery: invalid_client / unauthorized_client wipe all credentials and rerun once; invalid_grant wipes 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; iss is 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

  1. 宿主实现或使用 McpOAuthProvider(可注入持久化的 McpOAuthStateStore)。The host implements or uses McpOAuthProvider (optionally with a persistent McpOAuthStateStore).
  2. 传输里用 adaptOAuthProvider(provider);需要用户登录时请求以 McpOAuthAuthorizationRequiredError 失败。Use adaptOAuthProvider(provider) in the transport; when the user must sign in, requests fail with McpOAuthAuthorizationRequiredError.
  3. 登录:OAuthCallbackServer.listen() → authorizeMcp() 返回 "REDIRECT" 并打开浏览器 → 拿到回调 → 带 authorizationCode 再调一次 authorizeMcp()。Sign-in: OAuthCallbackServer.listen() → authorizeMcp() returns "REDIRECT" and opens the browser → receive the callback → call authorizeMcp() again with authorizationCode.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/mcp/src/oauth/flow.tsL429authorizeMcp()入口与错误恢复entry and recovery
packages/mcp/src/oauth/flow.tsL320runFlow()完整流程the full flow
packages/mcp/src/oauth/flow.tsL451adaptOAuthProvider()接到传输上plugging into transports
packages/mcp/src/oauth/discovery.tsL148discoverOAuthServerInfo()元数据发现metadata discovery
packages/mcp/src/oauth/provider.tsL53McpOAuthProvider默认 providerdefault provider
packages/mcp/src/oauth/callback.tsL34OAuthCallbackServer本地回调loopback callback
08Extensions

内容转换与错误类型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.

结果内容到模型内容的映射与错误类型Mapping result content to model content, and the error types
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
How tool results become model input, and the error typestoLlmContent(result)CallToolResult → (text | image)[]错误类型Error types缩进 = 子类indent = subclassJSON_RPC_ERROR_CODESTextContent · "text"{ type: "text", text }ImageContent · "image"{ type: "image", data, mimeType }AudioContent · "audio""[audio <mime> omitted]"ResourceLinkContent · "resource_link""<name>: <uri>"resource · TextResourceContents{ type: "text", text }resource · Blob, mimeType image/*{ type: "image", data: blob }resource · other blob"[binary resource … omitted]"content 为空但有 structuredContent → 一条 JSON 文本(缩进 2)no content but structuredContent → one JSON text block (indent 2)McpErrorcode · data:JSON-RPC 错误响应code · data: a JSON-RPC error responseMcpTimeoutError请求超时request timed outMcpAbortErrorsignal 中止signal abortedMcpConnectionClosedError传输关闭 / 未连接transport closed / not connectedMcpHttpErrorstatus · bodyMcpAuthRequiredError401 · wwwAuthenticateMcpSessionExpiredError404:会话失效404: session expiredMcpOAuthAuthorizationRequiredError需要用户登录(./oauth)the user must sign in (./oauth)-32700 parseError-32600 invalidRequest-32601 methodNotFound-32602 invalidParams-32603 internalErrorContentBlockLlmContent原样as is原样as is占位文字placeholder名称 + URIname + URI内嵌文本embedded text内嵌图片embedded image占位文字placeholder转换converted降级为文字degraded to text子类subclass

要点Key points

  • LlmContent 的形状与 pi-ai 的 TextContent / ImageContent 一致,但 pi-mcp 并不依赖 pi-ai。LlmContent has the same shape as pi-ai's TextContent / ImageContent, yet pi-mcp does not depend on pi-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 extend Error directly.
  • McpError 携带 JSON-RPC 的 code 与 data,处理器抛出它可以向服务器返回任意错误码。McpError carries the JSON-RPC code and data; a handler that throws it can return any error code to the server.

工作原理How it works

  1. callTool() 校验结果形状,缺失的 content 补成 []。callTool() validates the result shape and defaults a missing content to [].
  2. toLlmContent(result) 逐块转换;isError 由调用方自行决定如何呈现。toLlmContent(result) converts block by block; how to present isError is up to the caller.
  3. 用 instanceof 区分错误:例如 McpSessionExpiredError 时重新连接。Tell errors apart with instanceof: e.g. reconnect on McpSessionExpiredError.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/mcp/src/protocol/content.tsL111toLlmContent()结果 → 模型内容result → model content
packages/mcp/src/protocol/content.tsL63ContentBlock5 种内容块five block kinds
packages/mcp/src/protocol/jsonrpc.tsL45McpErrorJSON-RPC 错误JSON-RPC error
packages/mcp/src/transports/streamable-http.tsL126McpHttpErrorHTTP 错误HTTP errors
packages/mcp/src/oauth/errors.tsL50McpOAuthAuthorizationRequiredError需要登录sign-in required
09Extensions

在 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.

配置来源、运行时、工具命名与四种曝光方式Config sources, the runtime, tool naming and the four exposures
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
How pi uses pi-mcp: coding-agent's built-in MCP extension服务器从哪里来Where servers come fromextensions/mcp/4,240 行4,240 lines工具名Tool namescreateMcpToolName()exposure:工具怎样出现在模型面前exposure: how tools reach the model按服务器设置,toolExposure 按工具覆盖set per server; toolExposure overrides per tool~/.pi/agent/mcp.json全局 · 可用 auth: { provider }global · may use auth: { provider }.pi/mcp.json仅受信任的项目 · 同名覆盖全局trusted projects only · same name replaces global只写 enabled/exposure 时只改这几项only enabled / exposure → just overrides thosepi.registerMcpServer()扩展注册 · mcp.json 中同名优先from extensions · mcp.json wins on a name clashcommand → StdioTransporturl → StreamableHttpTransportnew McpClient({ name, version })会话开始时后台连接connects in the background at session startOAuth:存的令牌 + 刷新OAuth: stored token + refresh<agent-dir>/mcp-auth.json · /mcp 登录<agent-dir>/mcp-auth.json · sign in with /mcp"codemode"默认:只能在 codemode 脚本里调用default: callable only from codemode scripts不进工具声明;脚本用 searchTools() 找not declared; scripts find them via searchTools()"deferred"tool_search 加载后才声明declared once tool_search loads them激活 tool_search 而不是 codemodeactivates tool_search instead of codemode"direct"立即声明给模型declared to the model right away和内置工具一样直接调用called directly like built-in tools"hidden"完全不可达unreachable/mcp 里可以改回来switch it back in /mcp格式与其他 MCP 客户端相同:{ "mcpServers": { … } }Same shape as other MCP clients: { "mcpServers": { … } }env / headers 的值可写 ${NAME} 或 !cmdenv / header values may use ${NAME} or !cmd第一次提示只等待有 direct 工具的服务器;The first prompt waits only for servers with direct tools;其他服务器在脚本或工具真正用到时再等。others are awaited when a script or tool needs them.规则RULEmcp__<server>__<tool># [^A-Za-z0-9_] → _mcp__docs__search_pagesmcp__my_server__get_issue超过 64 个字符或与另一工具重名:Longer than 64 chars, or clashing:截断 + "_" + sha256(server\0tool) 前 8 位truncate + "_" + first 8 hex of sha256(server\0tool)名字同时就是 codemode 脚本里的标识符:The name is also the identifier in codemode:await tools.mcp__docs__search_pages({…})资源走 3 个通用工具:Resources use three generic tools:list_mcp_resources · …_templates · read_mcp_resourcecodemode 曝光的服务器连上时,若 autoEnableCodemode(默认 true)就自动激活 codemode 工具。When a codemode-exposure server connects, the codemode tool is activated unless autoEnableCodemode (default true) is false.每次调用都走 pi 的工具管线:tool_call / tool_result 钩子与权限扩展同样作用于 MCP 工具。Every call runs through pi's tool pipeline: tool_call / tool_result hooks and permission extensions apply to MCP tools too.配置configMCP 运行时MCP runtimeOAuth传输transport

要点Key points

  • 四种 exposure:codemode(默认)、deferred(tool_search 加载后才声明)、direct(立即声明)、hidden;toolExposure 可以按工具覆盖。Four exposures: codemode (default), deferred (declared once tool_search loads them), direct (declared right away) and hidden; toolExposure overrides per tool.
  • 项目的 mcp.json 不能设置 auth——仓库不能决定凭据发往哪里。只写 enabled / exposure 的项目条目只覆盖全局同名服务器的这几项。A project mcp.json cannot set auth — 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, so tool_call / tool_result hooks and permission extensions apply as usual.

工作原理How it works

  1. loadMcpConfig():全局 mcp.json → 受信任项目的 .pi/mcp.json → 合并与校验。loadMcpConfig(): global mcp.json → the trusted project's .pi/mcp.json → merge and validate.
  2. runtime.ts:有 command → StdioTransport,有 url → StreamableHttpTransport;new McpClient() 并 connect()。runtime.ts: command → StdioTransport, url → StreamableHttpTransport; new McpClient() and connect().
  3. 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 through list_mcp_resources and two other tools.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/coding-agent/src/extensions/mcp/index.tsL280createMcpExtension()扩展入口extension entry
packages/coding-agent/src/extensions/mcp/config.tsL145loadMcpConfig()读取 mcp.jsonreading mcp.json
packages/coding-agent/src/extensions/mcp/runtime.tsL115StdioTransport选择传输choosing a transport
packages/coding-agent/src/extensions/mcp/tools.tsL84createMcpToolName()工具命名tool naming
packages/coding-agent/src/core/mcp-servers.tsL52McpExposure4 种 exposurefour exposures
packages/coding-agent/src/extensions/mcp/oauth.tsL147mcp-auth.json凭据存储credential storage
10Extensions

扩展点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.
11Extensions

测试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.

用例数 = 源码中 it( / test( 的出现次数(静态统计,不运行测试)。Cases = occurrences of it( / test( in the source (static count; tests were not run).

12Extensions

交叉链接Cross-links#