这个坑值得记一笔。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 把会话放在请求头里,任何反向代理都可能把它吃掉,排查时先用直连确认,再逐层加代理。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。
