这个坑值得记一笔。MCP 的 TypeScript SDK 1.17 用 Streamable HTTP 连自建 server,initialize 返回 Mcp-Session-Id,紧接着 tools/list 返回 404 Session not found,绕过 Nginx 直连 3000 端口正常。

刚开始盯着「server 多副本部署,会话没有共享」,折腾了半个多小时,排掉了。

原因不止一个,得分开看。客户端没有把 session 头带回后续请求;Nginx 默认不转发带下划线的自定义头;server 重启后旧会话已经过期。

动手时先用 curl 分别带和不带头复现差异

curl -s https://mcp.example.com/mcp -H 'Content-Type: application/json' -H 'Mcp-Session-Id: $SID' -d @tools_list.json

再往下在 Nginx 显式透传会话头

python -c "print('proxy_set_header Mcp-Session-Id $http_mcp_session_id;')"

给 server 加粘性会话或把会话放进 Redis

python -c "print('enable session affinity')"

客户端持久化 session 并在 404 时重新 initialize

python mcp_client.py --retry-on-404 true

报错是间歇性的,我蹲守了半小时才抓到一次完整现场。

白天有业务在跑,能验证的部分都压到了晚上做。

怎么算真的好了,看的是同一批现象。后续请求都带同一个 Mcp-Session-Id,tools/list 返回 200 且工具齐全,重启一个副本后客户端能自动重新握手。

Streamable HTTP 把会话放在请求头里,任何反向代理都可能把它吃掉,排查时先用直连确认,再逐层加代理。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。

作者 9823189