🐛 fix(sdk): MCP 死连接自愈真正武装——onclose 改字段赋值 + 超时预算透传(#455) - #462
Merged
Conversation
核心(#455):McpClientLike.onclose 此前声明为注册方法形状 onclose?(listener),而 SDK Protocol.onclose 是零参回调字段 (onclose?: () => void)。client.onclose?.(listener) 对裸 SDK Client 恒为 undefined → 可选调用静默 no-op,死连接监听从未武装, stdio server 崩溃后 status 停留 connected。 - openConnection 改为 client.onclose = () => {...} 字段赋值, 保留 state.client !== client || !isCurrent() 双重入守卫; 接口声明同步改为字段形状,消除 cast 掩盖的类型错位 - 新增 fake-client 单测断言注册动作确实发生(连接后 onclose 为 函数、触发后 status 打回 failed/transport_error、下次 ensureConnected 正常重连),并钉死 stale onclose 不影响新连接 P2:SDK Protocol.request 无条件套 options?.timeout ?? 60_000, >60s 的配置预算永远被内建 60s 先爆。callTool/listResources/ readResource 现将配置预算作为 timeout 传入 options,外层 withRequestTimeout 保留为 abort 快速通道 + 兜底竞态保护。 P3:withTimeout/withRequestTimeout 计时器补 unref?(),挂起中的 预算计时器不再拖住进程退出(超时移除 abort 监听器与取消底层请求 已由既有 cleanup/controller.abort 覆盖)。 Fixes #455 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
PR#441 声称修复「stdio server 崩溃后 status 停留 connected」,但监听从未注册:
McpClientLike把onclose声明为注册方法onclose?(listener: () => void),而 MCP SDK 的Protocol.onclose是零参回调字段(onclose?: () => void,_onclose()直接调用)。于是对裸 SDK Client 恒为静默 no-op(字段初始 undefined),死连接自愈完全失效——恰是原 PR 要修的 bug 本身。
修复
核心(#455)
openConnection改为字段赋值client.onclose = () => {...},保留既有重入守卫(state.client !== client || !isCurrent()双守卫,有意 disconnect 时 SDK close() 触发的 onclose 也被正确挡住)。McpClientLike.onclose声明同步改为字段形状,消除让 typecheck 静默放行的类型错位。ensureConnected走正常重连;后续若再失败自然落入 [P2][desktop] IPC 鉴权与迁移杂项批次 #412/🐛 fix(sdk): MCP 连接栈两修复(#311/#312)——失败清理子进程 + failed 负缓存退避 #437 的指数退避。[P2] callTool 超时前提错误
SDK
Protocol.request无条件套options?.timeout ?? 60_000(已核实本仓 SDK 1.29.0 protocol.js:712),此前 options 只传{ signal },任何 >60s 的配置预算都会被内建 60s 先爆。现callTool/listResources/readResource三处将配置预算作为timeout传入 options(SDK 类型确认支持透传);外层withRequestTimeout保留为 abort 快速通道 + 兜底竞态保护。SDK 内部超时抛出的 "Request timed out" 经classifyError归类为 timeout,不会误触发断连重试。[P3] withRequestTimeout 计时器
unref?.(),挂起中的预算计时器不再拖住进程退出。cleanup()在 settle 时移除监听器并清计时器,超时路径controller.abort()会取消底层请求(【BUG】MCP manager 四缺陷:stdio 丢 cwd / 超时不取消底层 pending / getStatus 暴露活引用 / classifyError 误报 auth #226 重设计),无需再改。测试
新增 3 条 fake-client 单测(此前 onclose 相关测试为零,正是本 bug 溜进 main 的原因):
onclose被赋值为函数(fake client 初始不带该字段,模拟裸 SDK Client),零参触发后 status 打回 failed/transport_error,下次 ensureConnected 正常重连且新 client 再次武装。timeoutMs: 1234→ 1234;默认 → 30000;listResources/readResource 同样透传。红-绿验证:把赋值临时改回不武装形态 → 测试 1/2 红;去掉 timeout 透传 → 测试 3 红;恢复后全绿。
验证
bun run --filter @lume/agent-sdk typecheck✅bun test packages/sdk/src/mcp/:25 pass / 0 fail(22 既有 + 3 新增)✅tsc --noEmit✅(sidecar 只经 manager 层接口传timeoutMs,不受接口形状变化影响)Fixes #455
🤖 Generated with Claude Code