Skip to content

🐛 fix(sdk): MCP 死连接自愈真正武装——onclose 改字段赋值 + 超时预算透传(#455) - #462

Merged
CavinHuang merged 4 commits into
mainfrom
fix/455-mcp-onclose-arm
Aug 22, 2026
Merged

🐛 fix(sdk): MCP 死连接自愈真正武装——onclose 改字段赋值 + 超时预算透传(#455)#462
CavinHuang merged 4 commits into
mainfrom
fix/455-mcp-onclose-arm

Conversation

@CavinHuang

Copy link
Copy Markdown
Owner

问题

PR#441 声称修复「stdio server 崩溃后 status 停留 connected」,但监听从未注册:McpClientLikeonclose 声明为注册方法 onclose?(listener: () => void),而 MCP SDK 的 Protocol.onclose 是零参回调字段onclose?: () => void_onclose() 直接调用)。于是

client.onclose?.(() => { ... })

对裸 SDK Client 恒为静默 no-op(字段初始 undefined),死连接自愈完全失效——恰是原 PR 要修的 bug 本身。

修复

核心(#455

[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 计时器

测试

新增 3 条 fake-client 单测(此前 onclose 相关测试为零,正是本 bug 溜进 main 的原因):

  1. 连接成功后 onclose 被赋值为函数(fake client 初始不带该字段,模拟裸 SDK Client),零参触发后 status 打回 failed/transport_error,下次 ensureConnected 正常重连且新 client 再次武装。
  2. 重连后旧 client 的 stale onclose 不影响新连接(守卫语义钉死)。
  3. 配置预算透传进 callTool options.timeout:显式 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 新增)✅
  • sidecar tsc --noEmit ✅(sidecar 只经 manager 层接口传 timeoutMs,不受接口形状变化影响)

Fixes #455

🤖 Generated with Claude Code

TaTaLiao and others added 4 commits August 22, 2026 22:34
核心(#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>
@CavinHuang
CavinHuang merged commit b369dcd into main Aug 22, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 [P1] MCP 死连接自愈从未武装:McpClientLike.onclose 类型错位致监听 no-op(PR#441 无效修复已进 main)

2 participants