
1. FastMCP 本地跑起来后为什么总在 400 和 session termination 上翻车FastMCP 是一个基于 Python 的 MCP 服务器/客户端框架能让你用很少的代码把本地工具、数据库、文件系统暴露给大模型调用uvicorn 则是把它以 ASGI 方式跑起来的常见选择。适合谁适合已经在用 Claude、Cursor 或自建 Agent想把本地能力接进模型又不想被 SSE 长连接和 session 管理折磨的开发者。我最近在把一套 FastMCP 服务从 STDIO 切到 Streamable HTTP用 uvicorn 启动后客户端调用大模型接口时频繁出现两类报错一是400 Bad Request二是Session termination failed: Server disconnected without sending a response。前者通常发生在初始化或工具调用阶段后者多出现在会话收尾。两个问题看着独立实际都指向同一件事session 的创建、复用和销毁没有被正确对齐。这篇就按我实际排查的顺序走一遍先讲清楚 FastMCP uvicorn 的启动骨架再把 TaoToken 的统一 Key 配进去然后给出可复制的config.toml/settings.json最后用 curl 和日志逐项定位 400 与 session termination。目标很明确——你照着配完能自己确认会话是正常结束的而不是被服务端提前掐断。2. 前置TaoToken 统一 Key 与 FastMCP 的接入位置FastMCP 本身不绑定某一家模型服务它负责的是 MCP 协议层的工具暴露和会话管理真正去调大模型的那一步需要一个兼容 OpenAI/Anthropic 风格的接口地址和 Key。TaoToken 在这里的角色就是统一入口一个 Key 覆盖多种模型省得你在 FastMCP 的配置里来回换 base_url 和 api_key。你需要先拿到两样东西API Key在控制台的 API Keys 页面创建地址是https://taotoken.net/api-keysdeep link 带 utm?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite。接口基址https://taotoken.net/api注意这个地址不加 UTM 参数直接作为base_url用。如果你只是想先验证模型通不通可以直接用模型对话页面发一条消息确认 Key 有效https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite。这一步能排除掉「Key 本身无效」这个最容易被忽略的变量。注意FastMCP 的 session 是服务端维护的Key 配错通常报 401而 400 更多是请求体或 session 头的问题两者别混。3. 可复制配置config.toml 与 settings.json 骨架先给 FastMCP 服务端的启动骨架。我用的是uvicorn直接跑 ASGI 应用端口 8000关键参数是--timeout-keep-alive这个后面排障会重点讲。# server.py from fastmcp import FastMCP mcp FastMCP(taotoken-demo) mcp.tool() def add(a: int, b: int) - int: 两数相加用于验证工具调用链路 return a b if __name__ __main__: mcp.run(transportstreamable-http, host0.0.0.0, port8000)启动命令uvicorn server:app --host 0.0.0.0 --port 8000 --timeout-keep-alive 300如果你的 FastMCP 版本把 ASGI app 暴露为mcp.http_app()就改成对应的对象名。--timeout-keep-alive 300是解决 session termination 的关键默认值偏短长会话容易被服务端主动断开。接下来是config.toml用于客户端侧声明 TaoToken 的统一 Key 和模型[llm] base_url https://taotoken.net/api api_key sk-你的TaoTokenKey model claude-3-5-sonnet [mcp] transport streamable-http endpoint http://127.0.0.1:8000/mcp/ timeout 300settings.json给不习惯 TOML 的场景用{ llm: { base_url: https://taotoken.net/api, api_key: sk-你的TaoTokenKey, model: claude-3-5-sonnet }, mcp: { transport: streamable-http, endpoint: http://127.0.0.1:8000/mcp/, timeout: 300 } }两个文件里最容易出错的是endpoint结尾的斜杠。少了它服务端会返回 307 重定向客户端跟随时可能丢掉 session 头进而演变成 400。4. 验证请求curl 打通初始化与工具调用配置写完别急着上客户端先用 curl 把链路走一遍。第一步是初始化拿到 session idcurl -i -X POST http://127.0.0.1:8000/mcp/ \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2024-11-05, capabilities: {}, clientInfo: {name: curl-test, version: 1.0} } }正常返回里会带Mcp-Session-Id响应头把它记下来。第二步用这个 session id 调工具curl -i -X POST http://127.0.0.1:8000/mcp/ \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -H Mcp-Session-Id: 上一步拿到的值 \ -d { jsonrpc: 2.0, id: 2, method: tools/call, params: {name: add, arguments: {a: 1, b: 2}} }成功时你会看到result里返回3并且响应头里 session 仍然有效。如果这一步返回 400先看请求体里Mcp-Session-Id有没有带、值有没有复制错。第三步是主动结束会话curl -i -X DELETE http://127.0.0.1:8000/mcp/ \ -H Mcp-Session-Id: 上一步拿到的值返回 200 或 204 都算正常结束。如果这里报Server disconnected without sending a response基本就是 keep-alive 超时或 worker 数不对下一节展开。5. 本篇常见错排查400 与 session termination 逐项定位400 Bad Request 的三种典型来源。第一endpoint少了结尾斜杠触发 307 后 session 头丢失第二请求头Accept没带text/event-streamStreamable HTTP 要求同时声明 JSON 和事件流第三多 worker 启动导致 session id 对不上。FastMCP 的 session id 由服务端维护如果你用--workers 4起 uvicorn请求可能落到没有该 session 的进程上直接 400。这也是 issue #956 里讨论的核心session id 不在客户端维护是出于安全考虑但代价就是多进程下必须做会话粘滞。session termination 的定位步骤。先看 uvicorn 启动参数里有没有--timeout-keep-alive没有就加上 300再看是不是用了多 worker如果是改回单 worker 或在前置层做粘滞最后看客户端timeout是否小于服务端 keep-alive客户端先超时也会表现为服务端断开。日志定位。启动时加--log-level debug观察 initialize 请求进来后有没有分配 session id、tools/call 有没有命中同一个 session、DELETE 有没有被处理。三段日志对得上会话就是正常结束的。现象优先检查处理307 Temporary Redirectendpoint 结尾斜杠补/mcp/400 Bad RequestAccept 头 / session id补text/event-stream核对 idsession termination failedkeep-alive / worker 数加--timeout-keep-alive 300改单 worker6. 接入与长期使用按场景选对入口排障和接入阶段重点是把 Key 和文档对齐API Keys 页面创建 Key接入文档看协议细节https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite。如果你要验证某个模型在 FastMCP 工具调用下的表现直接用模型对话发一条带工具描述的消息最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite。长期跑编码类 Agent 或需要稳定会话的场景建议走 Coding Plan把 Key 和额度统一管理避免频繁换 Key 导致 session 重建https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。控制台里可以随时看调用量和 Key 状态https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite。最后留一个我踩过的坑FastMCP 版本迭代快2.3.2 之后客户端能力整合进自身库2.6.0 加了 JWT2.9.0 加了中间件。你照着旧文档写的 client 初始化代码在新版本里可能直接报 400。遇到报错先对版本号再对配置比盲目改代码快得多。