SSE MCP 请求又转回 FastAPI REST?Codex 走 TaoToken 后看 webapi_mcp 日志 📅 发布时间:2026/9/19 6:43:28 👁 浏览次数: 1. webapi_mcp 日志里 SSE 请求落进 REST 路由的现场在 webapi_mcp 里用create_mcp_server_app(app, auto_mount_fastapiTrue)把 FastAPI 应用和 MCP SSE 服务绑在一起后MCP Inspector 连上 SSE 却看不到 tools日志里还能看到同一条 SSE 请求又转进内部 FastAPI RESTful API。遇到这种排障场景我先把 Codex 接到 TaoToken去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentcodex_mcp_sse 创建 Key再让 Codex 按 webapi_mcp 日志逐条对照源码。TaoToken 在这里只提供模型 Key 和兼容通道不参与 SSE 或 FastAPI 自动挂载本身。这个问题的迷惑点在于Inspector 那边可能显示连接成功也可能直接断开终端里uvicorn看起来没报错但tools/list就是空的。真正有用的线索不在 Inspector 界面而在 webapi_mcp 的访问日志、启动日志和源码里的挂载顺序。下面按“先抓日志、再配 Codex、最后拆源码”的顺序走每一步都只动本地文件和本地命令。1.1 MCP Inspector 看不到 tools 时的第一手日志先别急着改auto_mount_fastapi参数先把日志抓到。webapi_mcp 启动后在终端里会打印每个请求的路径、方法和状态码。MCP Inspector 连接时日志里通常会出现类似这样的记录INFO: 127.0.0.1:52344 - GET /sse HTTP/1.1 404 Not Found INFO: 127.0.0.1:52345 - POST /mcp HTTP/1.1 405 Method Not Allowed INFO: 127.0.0.1:52346 - GET /api/items/1 HTTP/1.1 200 OK前两行说明 SSE 或 MCP 请求没有落到预期的流式端点反而被 FastAPI 的普通 REST 路由接走了。第三行是你原本注册的业务接口它正常返回 JSON但这不代表 MCP 通道正常。判断标准不是“服务器有没有响应”而是“响应内容是不是text/event-stream”。在本地开另一个终端用curl直接打 SSE 路径能最快确认问题curl -N -H Accept: text/event-stream http://127.0.0.1:8000/sse如果返回的是 JSON 错误体比如{detail:Not Found}说明/sse没有被 MCP 的 SSE handler 接管如果返回的是 FastAPI 默认的405 Method Not Allowed说明路径存在但方法不匹配通常是 SSE 端点和 REST 路由注册到了同一个 URL。把这两条日志和curl输出保存下来后面交给 Codex 分析时比截图有用。还要注意 Inspector 里填的 URL。很多人复制的是http://127.0.0.1:8000/mcp但 webapi_mcp 实际挂载的 SSE 端点可能是http://127.0.0.1:8000/sse或者反过来。以启动日志里打印的 mount 信息为准不要凭记忆填。1.2 create_mcp_server_app 的挂载顺序为什么可疑create_mcp_server_app(app, auto_mount_fastapiTrue)这个调用里app是已经注册了一堆 REST 路由的 FastAPI 实例。auto_mount_fastapiTrue通常意味着库会把原 FastAPI 应用挂到 MCP server app 的某个子路径或者把 MCP 端点挂到原 app 上。无论哪种方向挂载顺序都会影响路由匹配。FastAPI 和 Starlette 的路由匹配是“先注册先匹配”。如果 REST 路由里存在/{path:path}这样的 catch-all或者/sse、/mcp已经被某个业务路由占用那么后注册的 MCP handler 就永远轮不到。日志里出现 SSE 请求进入 REST 路由基本可以判定为路由优先级问题而不是模型或 Key 的问题。打开 webapi_mcp 的源码找到调用create_mcp_server_app的位置看它前后有没有app.include_router(...)、app.mount(...)、app.add_api_route(...)。把这一段贴给 Codex 时不要只贴一行调用要带上上下文包括app的创建、路由注册和最终启动的是哪个变量。Codex 能根据注册顺序推断出哪个路由先命中了 SSE 请求。这里要强调Codex 不直接连接你的 FastAPI 服务也不替你执行uvicorn。它只读你贴过去的日志和源码生成本地可执行的grep、curl、python -c命令由你在本地跑再把输出贴回对话。这样既安全也能保留完整的排查链路。2. 把 Codex 接到 TaoToken 再看 webapi_mcp 日志排障时最怕上下文太长终端日志几百行源码十几个文件靠人眼很容易漏掉挂载顺序。把 Codex 接到 TaoToken 后可以让它按 webapi_mcp 日志做“日志到源码”的对照。注意TaoToken 只负责给 Codex 提供 API Key 和统一接入通道SSE 端点为什么被 FastAPI 吃掉还是要在 webapi_mcp 源码和路由表里找答案。2.1 从官网创建 Key 和确认模型 ID打开 TaoToken 注册并创建 API KeyKey 用占位符YOUR_API_KEY表示。模型 ID 不要凭感觉写去模型广场看当时列表以页面实际显示的 ID 为准。不同兼容通道支持的模型名不一样填错了 Codex 会在第一轮请求就报模型不存在反而干扰 SSE 排障。拿到 Key 后先不要改 webapi_mcp 的任何代码。把 Codex 的配置单独调通确认它自己能正常发请求、读文件、解释日志。这样后面排查 SSE 时至少能排除“AI 工具没连上”的干扰。2.2 ~/.codex/config.toml 里把 base_url 指向 https://taotoken.net/apiCodex 的配置文件在~/.codex/config.toml。这里用的是 Codex 自己的model_provider和base_url不要把 Claude Code 的ANTHROPIC_*环境变量套过来。配置写成这样model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY然后在终端里导出 Keyexport TAOTOKEN_API_KEYYOUR_API_KEY注意三个细节。第一base_url是https://taotoken.net/api末尾不要加/v1也不要加任何 UTM 参数UTM 只用于官网页面不用于接口地址。第二YOUR_MODEL_ID以 TaoToken 模型广场 当时列表为准。第三配置保存后在本地跑一条简单请求确认 Codex 能正常返回再让它读 webapi_mcp 日志。2.3 用 Codex 读日志而不是让它直连服务配置好后把 webapi_mcp 的日志文件路径告诉 Codex。比如日志在./logs/webapi_mcp.log可以这样提问这是 webapi_mcp 的启动和访问日志。现象是 MCP Inspector 连接 SSE 后看不到 tools日志里 SSE 请求进入了 FastAPI REST 路由。请按路由注册顺序分析可能原因并给出我在本地可以执行的 grep、curl 和 Python 路由打印命令。不要直接连接服务只生成命令和解释。Codex 可能会让你先跑这几条命令grep -nE sse|mcp|tools/list|mount|FastAPI|REST logs/webapi_mcp.logpython -c from webapi_mcp import mcp_app; [print(r.path, getattr(r, methods, None)) for r in mcp_app.routes]第一条从日志里捞出 SSE 和 MCP 相关请求第二条打印实际生效的路由表。你在本地执行把输出贴回对话。Codex 再根据输出判断是/sse被 REST 路由先匹配还是 mount 前缀不对。整个过程里Codex 只做代码和日志的解释器不碰你的运行环境。3. 对照 fastapi-mcp 源码拆开 SSE 又转 REST 的三条路径日志只能告诉你“请求去了哪”源码才能告诉你“为什么去了那”。create_mcp_server_app(app, auto_mount_fastapiTrue)把 FastAPI 和 MCP 绑在一起后SSE 请求转回 REST 通常有三条路径挂载前缀冲突、路由优先级错误、SSE 协议头或中间件干扰。逐条对照基本能定位。3.1 auto_mount_fastapi 把 FastAPI app 挂到哪个前缀先看create_mcp_server_app内部对auto_mount_fastapi的处理。它大概率会把传入的app挂到 MCP server app 的某个子路径比如/api或/。如果 MCP 的 SSE 端点也定义在/而 FastAPI 的 REST 路由里又有/{path:path}那么/sse会先被 FastAPI 的 catch-all 捕获返回 404 或 JSON。在源码里搜索auto_mount_fastapi、mount、include_router确认挂载顺序。如果发现先mountFastAPI再注册 MCP 的 SSE 路由就可以在本地实验把 MCP 端点注册提前或者给 FastAPI 挂载加一个明确前缀比如/rest。不要直接改库的源码先在自己的webapi_mcp初始化逻辑里调整顺序验证问题是否消失。3.2 SSE 端点与 FastAPI 路由的匹配优先级打印路由表是最直接的手段。在本地跑from webapi_mcp import mcp_app for route in mcp_app.routes: print(route.path, getattr(route, methods, None), type(route).__name__)如果输出里/sse对应的是APIRoute而不是 MCP 的流式 handler说明注册错了对象。如果/sse根本不在列表里说明 MCP 端点挂到了别的 app 或别的前缀。如果/sse在列表里但前面有/{path:path}那问题就是优先级。FastAPI 的APIRouter和Mount都有匹配顺序。Mount通常按前缀匹配APIRoute按路径和方法匹配。把 MCP 的 SSE 端点放在更具体的路径上比如/mcp/sse并确保 REST 的 catch-all 不会覆盖它。如果 REST 里确实需要 catch-all就把它放到最后注册或者把 MCP 端点注册到独立子应用再挂载。3.3 中间件、Accept 头和 Inspector 连接地址SSE 对响应头很敏感。客户端必须发送Accept: text/event-stream服务端必须返回Content-Type: text/event-stream并且不能有中间件把响应缓冲成普通 JSON。如果 webapi_mcp 里加了 CORS 中间件、GZip 中间件或自定义日志中间件它们可能提前读取了响应体导致 SSE 连接看起来像普通 REST 请求。检查 Inspector 里的连接地址是否和路由表一致。如果路由表里是/mcp/sseInspector 就不能填/sse。如果用了反向代理确认代理没有开启缓冲并且把X-Accel-Buffering: no之类的头透传。把 Inspector 的请求头和 webapi_mcp 日志里的响应头对照一遍很多“看不到 tools”的问题其实是返回了 200但内容类型不是text/event-stream。4. 改 webapi_mcp 启动方式后如何验证 tools 列表找到原因后修改webapi_mcp的启动文件或初始化顺序。验证要分三层先用curl看 SSE 端点是否真的流式返回再用 MCP Inspector 看 tools 列表最后让 Codex 对照日志确认没有 REST 路由截胡。4.1 先用 curl 检查 SSE 端点和 tools/list改完挂载顺序后重启服务。在本地执行curl -N -H Accept: text/event-stream http://127.0.0.1:8000/mcp/sse如果终端持续挂起并输出event: endpoint或类似 SSE 事件说明流式通道通了。如果立刻返回 JSON说明还是被 REST 路由接走。接着用 MCP Inspector 连接同一个地址观察 tools 列表是否出现。Inspector 里能看到 tools 后再让 Codex 分析一次日志确认请求路径是 MCP 端点而不是业务 REST 路径。4.2 MCP Inspector 正确连接与 Codex 对照日志Inspector 连接成功后webapi_mcp 日志里应该出现类似INFO: 127.0.0.1:52400 - GET /mcp/sse HTTP/1.1 200 OK INFO: 127.0.0.1:52401 - POST /mcp/messages HTTP/1.1 200 OK如果还是看到/api/...或/{path:path}的日志就把新的日志片段和路由表再贴给 Codex。让 Codex 对比修改前后的路由顺序确认 MCP 端点已经排在 REST catch-all 之前。注意不要贴生产环境的敏感路径本地日志脱敏后再用。4.3 仍然没有 tools 时的排查清单如果 SSE 通了但 tools 还是空按这个清单逐项过MCP server 是否在启动时完成了 tools 注册还是依赖首次请求动态注册create_mcp_server_app的auto_mount_fastapi是否把 FastAPI 路由重复挂载到了两个前缀Inspector 是否连接到了正确的子路径请求头是否包含Accept: text/event-stream中间件是否缓冲了流式响应反向代理是否关闭了缓冲本地uvicorn启动的是webapi_mcp:app还是webapi_mcp:mcp_app两者路由表可能不同。每排除一项就重新抓一次日志。排障最忌讳一次改五处最后不知道哪一处生效。5. 跑通后去控制台对一下这次 Codex 调用webapi_mcp 的 SSE 路由理清后Codex 的配置也顺带验证完了。回到控制台确认这次调用有没有正常记上顺便检查 Key 和模型 ID 是否长期可用。5.1 模型对话里验证同一把 Key打开 TaoToken 模型对话用YOUR_API_KEY同一把 Key 发一条测试消息确认模型 ID 和 Base URL 没填错。如果这里能通但 Codex 里报错就重点检查~/.codex/config.toml的base_url是否误加了/v1或 UTM 参数。5.2 看用量与 Coding Plan如果每天都要让 Codex 读日志、对照源码可以打开 Coding Plan 看套餐是否够用Key 在 控制台 API Keys 创建和管理。下一次再遇到 SSE 请求被 FastAPI REST 截走先把 webapi_mcp 日志丢给 Codex再对照本文的路由注册顺序检查通常比在 Inspector 里反复点重连更快。