What version of Kimi Code is running?
0.27.0
Which open platform/subscription were you using?
Kimi Code (OAuth)
Which model were you using?
No response
What platform is your computer?
Linux 7.1.3-2-cachyos x86_64 unknown
What issue are you seeing?
当 Kimi Code 连接一个满足以下条件的 Streamable HTTP MCP 服务器时,MCP 初始化必然超时:
- HTTPS 连接通过 ALPN 协商为 HTTP/2;
- 服务器支持独立的 GET SSE 流;
- GET SSE 流在连接期间保持打开。
Kimi Code 最终报告:
ERROR mcp server unavailable server=FNS transport=http status=failed reason="Timed out after 30000ms"
服务器本身工作正常。以下客户端或实现连接同一端点时,完整握手均可在约 0.4 秒内完成:
initialize
→ notifications/initialized
→ tools/list
已验证可正常工作的客户端包括:
- curl
- node:http
- node:http2
- Codex CLI / Rust rmcp
What steps can reproduce the bug?
Kimi Code 的 HTTP MCP transport 使用官方 TypeScript MCP SDK的 StreamableHTTPClientTransport,默认调用 Node.js 全局 fetch。
初始化流程如下:
- POST /api/mcp:initialize 成功;
- POST /api/mcp:notifications/initialized 返回 202;
- 客户端打开独立的 GET /api/mcp SSE 流;
- 客户端发送 POST /api/mcp:tools/list;
- tools/list 请求一直等待,最终触发 30 秒启动超时。
该行为仅在连接实际协商为 HTTP/2 且独立 SSE 流保持打开时出现。
定位结果
通过 Undici 的 diagnostics_channel 观察到:
undici:request:create
会正常触发,说明 tools/list 请求已经创建。
但是后续:
undici:client:sendHeaders
始终没有触发,说明请求没有真正写入 HTTP/2 连接,服务端也没有收到对应的 HEADERS 帧。
此时已经存在一个长期保持打开的 GET SSE 流。后续 POST 请求被创建,但没有获得派发机会,最终一直等待至 Kimi Code 的 startupTimeoutMs 超时。
该现象与 Undici 的 HTTP/2并发派发行为高度相关,也与已有的 Undici #4143 (nodejs/undici#4143) 类似:HTTP/2 连接的有效并发派发能力可能受客户端配置或默认值限制,导致长驻 SSE 流占用唯一调度槽位。
对比实验
禁用 HTTP/2
new Agent({
allowH2: false,
});
结果:
- 使用 HTTP/1.1;
- GET SSE 保持打开;
- tools/list 仍可正常派发;
- 约 0.4 秒返回 200;
- Kimi Code 成功加载全部 26 个 MCP 工具。
启用 HTTP/2
new Agent({
allowH2: true,
});
结果:
- 通过 ALPN 协商 HTTP/2;
- GET SSE 保持打开后,后续 POST 不再派发;
- undici:request:create 触发;
- undici:client:sendHeaders 不触发;
- Kimi Code 在约 30 秒后启动超时。
排除服务端问题
使用原生 node:http2 客户端,在同一个 HTTP/2 session 上并发执行:
GET SSE
POST tools/list
两条 stream 均正常工作。
服务端发送的 HTTP/2 设置包括:
SETTINGS_MAX_CONCURRENT_STREAMS = 250
因此服务端允许多个并发 HTTP/2 stream,问题发生在 Undici 客户端派发请求之前。
最小复现
该问题可以通过一个与 MCP 服务器实现无关的最小示例复现:
- 本地 node:http2 服务器;
- 一个保持打开的 SSE GET 端点;
- 同源 POST 端点;
- Undici fetch 客户端;
- 约 50 行代码。
在 GET SSE 流保持打开期间,Undici 创建 POST 请求但不发送 HEADERS。
最小复现脚本可以随 Issue 一并提供。
预期行为
HTTP/2 允许在同一连接中并发多个 stream。
当独立 GET SSE 流保持打开时,Undici 仍应正常派发后续的 POST 请求:
HTTP/2 connection
├── GET /api/mcp → 长驻 SSE stream
└── POST /api/mcp → tools/list
长驻 SSE stream 不应阻止其他 HTTP/2 stream 创建和发送。
实际行为
HTTP/2 connection
├── GET /api/mcp → 长驻 SSE stream
└── POST /api/mcp → 请求已创建,但 HEADERS 从未发送
Kimi Code 随后因 tools/list 无法完成而触发启动超时。
建议修复
作为兼容性修复,建议为 MCP HTTP transport 使用独立的 Undici dispatcher,并显式禁用 HTTP/2:
import {
Agent,
fetch as undiciFetch,
} from "undici";
const mcpDispatcher = new Agent({
allowH2: false,
});
const mcpFetch = (input, init) =>
undiciFetch(input, {
...init,
dispatcher: mcpDispatcher,
});
const transport = new StreamableHTTPClientTransport(url, {
fetch: mcpFetch,
});
关闭 MCP transport 时,还应关闭对应 dispatcher,避免连接资源泄漏:
await mcpDispatcher.close();
如果还需要支持旧版 SSEClientTransport,建议让它使用相同的自定义 fetch。
禁用 HTTP/2 会失去多路复用并使用额外的 HTTP/1.1 连接,但对于目前 MCP 的请求规模,这是一个可接受且已验证有效的兼容性方案。
另一种可能的修复方向,是确保 Undici 的 HTTP/2 dispatcher 允许至少两个并发 stream。不过该配置在不同 Undici 版本中的行为存在差异,禁用 H2 是目前验证最充分的绕行方案。
用户侧临时绕行
临时使用一个不发布 HTTP/2 ALPN、仅提供 HTTP/1.1 的 HTTPS 入口。
只有在 MCP 后端位于 localhost 或完全可信的内部网络时,才建议直接使用纯 HTTP 地址。远程 MCP 通常携带认证信息,不应通过不可信网络明文传输。
使用 HTTP/1.1 入口后已验证:
- Kimi Code 会话日志中不再出现 MCP 启动错误;
- FNS 的 26 个工具全部正常加载。
What is the expected behavior?
No response
Additional information
No response
What version of Kimi Code is running?
0.27.0
Which open platform/subscription were you using?
Kimi Code (OAuth)
Which model were you using?
No response
What platform is your computer?
Linux 7.1.3-2-cachyos x86_64 unknown
What issue are you seeing?
当 Kimi Code 连接一个满足以下条件的 Streamable HTTP MCP 服务器时,MCP 初始化必然超时:
Kimi Code 最终报告:
ERROR mcp server unavailable server=FNS transport=http status=failed reason="Timed out after 30000ms"
服务器本身工作正常。以下客户端或实现连接同一端点时,完整握手均可在约 0.4 秒内完成:
initialize
→ notifications/initialized
→ tools/list
已验证可正常工作的客户端包括:
What steps can reproduce the bug?
Kimi Code 的 HTTP MCP transport 使用官方 TypeScript MCP SDK的 StreamableHTTPClientTransport,默认调用 Node.js 全局 fetch。
初始化流程如下:
该行为仅在连接实际协商为 HTTP/2 且独立 SSE 流保持打开时出现。
定位结果
通过 Undici 的 diagnostics_channel 观察到:
undici:request:create
会正常触发,说明 tools/list 请求已经创建。
但是后续:
undici:client:sendHeaders
始终没有触发,说明请求没有真正写入 HTTP/2 连接,服务端也没有收到对应的 HEADERS 帧。
此时已经存在一个长期保持打开的 GET SSE 流。后续 POST 请求被创建,但没有获得派发机会,最终一直等待至 Kimi Code 的 startupTimeoutMs 超时。
该现象与 Undici 的 HTTP/2并发派发行为高度相关,也与已有的 Undici #4143 (nodejs/undici#4143) 类似:HTTP/2 连接的有效并发派发能力可能受客户端配置或默认值限制,导致长驻 SSE 流占用唯一调度槽位。
对比实验
禁用 HTTP/2
new Agent({
allowH2: false,
});
结果:
启用 HTTP/2
new Agent({
allowH2: true,
});
结果:
排除服务端问题
使用原生 node:http2 客户端,在同一个 HTTP/2 session 上并发执行:
GET SSE
POST tools/list
两条 stream 均正常工作。
服务端发送的 HTTP/2 设置包括:
SETTINGS_MAX_CONCURRENT_STREAMS = 250
因此服务端允许多个并发 HTTP/2 stream,问题发生在 Undici 客户端派发请求之前。
最小复现
该问题可以通过一个与 MCP 服务器实现无关的最小示例复现:
在 GET SSE 流保持打开期间,Undici 创建 POST 请求但不发送 HEADERS。
最小复现脚本可以随 Issue 一并提供。
预期行为
HTTP/2 允许在同一连接中并发多个 stream。
当独立 GET SSE 流保持打开时,Undici 仍应正常派发后续的 POST 请求:
HTTP/2 connection
├── GET /api/mcp → 长驻 SSE stream
└── POST /api/mcp → tools/list
长驻 SSE stream 不应阻止其他 HTTP/2 stream 创建和发送。
实际行为
HTTP/2 connection
├── GET /api/mcp → 长驻 SSE stream
└── POST /api/mcp → 请求已创建,但 HEADERS 从未发送
Kimi Code 随后因 tools/list 无法完成而触发启动超时。
建议修复
作为兼容性修复,建议为 MCP HTTP transport 使用独立的 Undici dispatcher,并显式禁用 HTTP/2:
import {
Agent,
fetch as undiciFetch,
} from "undici";
const mcpDispatcher = new Agent({
allowH2: false,
});
const mcpFetch = (input, init) =>
undiciFetch(input, {
...init,
dispatcher: mcpDispatcher,
});
const transport = new StreamableHTTPClientTransport(url, {
fetch: mcpFetch,
});
关闭 MCP transport 时,还应关闭对应 dispatcher,避免连接资源泄漏:
await mcpDispatcher.close();
如果还需要支持旧版 SSEClientTransport,建议让它使用相同的自定义 fetch。
禁用 HTTP/2 会失去多路复用并使用额外的 HTTP/1.1 连接,但对于目前 MCP 的请求规模,这是一个可接受且已验证有效的兼容性方案。
另一种可能的修复方向,是确保 Undici 的 HTTP/2 dispatcher 允许至少两个并发 stream。不过该配置在不同 Undici 版本中的行为存在差异,禁用 H2 是目前验证最充分的绕行方案。
用户侧临时绕行
临时使用一个不发布 HTTP/2 ALPN、仅提供 HTTP/1.1 的 HTTPS 入口。
只有在 MCP 后端位于 localhost 或完全可信的内部网络时,才建议直接使用纯 HTTP 地址。远程 MCP 通常携带认证信息,不应通过不可信网络明文传输。
使用 HTTP/1.1 入口后已验证:
What is the expected behavior?
No response
Additional information
No response