ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSE 握手 404?Claude Code 的 Base URL 按 TaoToken 通道改

SSE 握手 404?Claude Code 的 Base URL 按 TaoToken 通道改 SSE 握手 404Claude Code 的 Base URL 按 TaoToken 通道改SSE 握手阶段返回 404是 MCP 从 STDIO 切到 SSE 之后最常见的一类接入故障。它的迷惑点在于浏览器能打开页面服务端日志也显示 Uvicorn 正常监听可客户端一连/sse就直接 404。要快速定位问题先把「模型通道」和「MCP 路由」拆开——用 TaoToken 给 Claude Code 配一条可用的模型通道官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 再让 Claude Code 对照你服务端里的create_starlette_app复现一次握手就能判断 404 是出在路由侧还是 Base URL 写错。TaoToken 本身不参与/sse、/messages/的路由它只保证你有可用的 Key 和正确的 Base URL去把排障变量收敛到一个。一、先定位404 到底出在 /sse 还是模型通道原文的服务端结构是 Starlette 起两个入口一个Route(/sse, endpointhandle_sse)负责持久连接一个Mount(/messages/, appsse.handle_post_message)负责接收工具调用请求。SSE 传输层由SseServerTransport(/messages/)构造这个/messages/是客户端回传 POST 的基路径而不是握手路径。很多人排障时把这两个路径搞混于是改错了地方。一次完整的 SSE 握手客户端实际经历的是向http://host:port/sse发 GET并带上Accept: text/event-stream服务端通过sse.connect_sse(request.scope, request.receive, request._send)建立流返回read_stream和write_stream服务端先推一条event: endpointdata里是带session_id的/messages/地址客户端拿着这个地址回去 POST 工具调用再由 MCP Server 的mcp_server.run(...)消费。只要第 1 步返回 404后面三步都不会发生。此时有两种可能一种是 MCP 服务端确实没有注册/sse另一种是客户端请求的 host、port、path 拼错了——这类错误在现场表现为「服务端明明写了/sse客户端却连不上」。怎么区分用一条最笨但最有效的命令curl -i -N http://127.0.0.1:8081/sse如果返回404 Not Found说明路由不匹配问题在服务端或端口如果返回200且持续输出event:行说明/sse是通的那么客户端报 404 就只能是自己拼出来的 URL 不对。注意这里必须用127.0.0.1或容器实际映射出来的地址不要用浏览器地址栏里的域名去猜。而 Claude Code 这一侧它根本不关心你的 MCP 服务端怎么写。它要连的是模型通道。把两件事混在一条 Base URL 里排查就会越查越乱。二、TaoToken 前置先拿一个可用的 Key 再做对照实验在动 MCP 服务端之前先让 Claude Code 的模型通道跑起来。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 进入控制台创建 API Key把 Key 复制出来备用下文用YOUR_API_KEY代替。这里要强调一点TaoToken 不接管/sse也不接管/messages/。它只提供 Anthropic 兼容的模型调用入口地址是https://taotoken.net/api所以你在 Claude Code 里要改的是ANTHROPIC_BASE_URL而不是 MCP 服务端的路由。这样做的价值在于做「变量隔离」当模型通道是确定可用的时候你再让 Claude Code 去复现一次 SSE 握手任何 404 都会被精确归因到 MCP 服务端或客户端 URL 拼接而不是被「Key 不通」「Base URL 写错」这类噪音干扰。对照原文时保持create_starlette_app原样不动。它的路由是/sse加/messages/这两个路径不会因为你换了模型通道而改变。真正需要复现的是「客户端怎么拼这个 URL」from mcp.client.sse import sse_client async with sse_client(http://127.0.0.1:8081/sse) as (read, write): ...如果sse_client里写的是http://127.0.0.1:8081/messages/或者写成了http://127.0.0.1:8081/sse/甚至端口还是示例里的8080那就等着收 404。拿到 Key 之后建议先做一次纯模型请求的连通性验证把模型通道钉死。Key 的管理入口在这里https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。三、可复制配置Claude Code 的 settings.json 与 ANTHROPIC_*Claude Code 读取的配置文件通常在用户目录下的.claude/settings.json。把模型通道相关变量写进env字段示意如下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: 替换为你在模型列表页看到的实际模型 ID, ANTHROPIC_SMALL_FAST_MODEL: 替换为你在模型列表页看到的实际模型 ID } }几个容易踩坑的点第一ANTHROPIC_BASE_URL只写到/api不要再手动补/v1。客户端会自己在后面拼/v1/messages写重复了就会出现类似/api/v1/v1/messages的路径表现为 404 或者 405。第二鉴权字段用ANTHROPIC_AUTH_TOKEN。如果你同时导出了ANTHROPIC_API_KEY有两个来源的凭据会让行为变得不可预测建议只保留一个尤其是排障阶段。第三环境变量的优先级通常高于配置文件。在终端里敲env | grep ANTHROPIC看看有没有残留的旧值Windows PowerShell 用Get-ChildItem Env:ANTHROPIC*。如果 shell 里还留着指向另一个地址的ANTHROPIC_BASE_URL那么你改settings.json是无效的。临时验证可以用一次性环境变量不污染长期配置# macOS / Linux export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_AUTH_TOKENYOUR_API_KEY# Windows PowerShell $env:ANTHROPIC_BASE_URL https://taotoken.net/api $env:ANTHROPIC_AUTH_TOKEN YOUR_API_KEY配置完先别急着回到 MCP 那边先确认模型通道能通这是后面所有排障的基准点。四、验证请求与成功结果第一步验证模型通道。Anthropic 兼容端点是$ANTHROPIC_BASE_URL/v1/messages用 curl 打一发最小请求curl -sS https://taotoken.net/api/v1/messages \ -H x-api-key: YOUR_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: 替换为实际模型 ID, max_tokens: 32, messages: [{role: user, content: ping}] }成功时你会拿到结构化 JSON里面有content数组和usage字段。如果这里返回 401那是 Key 的问题返回 404先检查是不是把地址写成了/api/v1/messages/messages或者落了/api。第二步验证 MCP 的 SSE 端点是否真的挂在/sse上curl -i -N http://127.0.0.1:8081/sse成功的表现是 HTTP 头里出现content-type: text/event-stream并且连接保持不关闭随后能读到形如event: endpoint的推送data里带着/messages/?session_id...。看到这一行说明create_starlette_app里的Route(/sse, endpointhandle_sse)已经生效服务端没问题。第三步把客户端指向这个地址把sse_client的入参写成完整 URL用127.0.0.1而不是localhost。很多环境下localhost会优先解析到 IPv6 的::1而你的 Uvicorn 只监听了 IPv4 的0.0.0.0于是连接被拒或者被反向代理接管后返回 404。这一步能过说明 SSE 握手链路已经打通。第四步观察日志。Starlette 在收到未知路径时会直接返回 404而/sse命中时会进入handle_sse。对照两侧日志就能明确 404 是「路由未命中」还是「模型通道配置错」。五、本篇常见错排查围绕 SSE 握手 404下面这些是最高频的原因按顺序排查能省掉大量时间。路径拼错。/sse是握手地址/messages/是回传地址。把sse_client的入参写成了/messages/请求会落到Mount上而Mount只接受 POSTGET 自然 404。尾斜杠差异。Route(/sse)匹配的是/sse不是/sse/。Starlette 默认的redirect_slashes在这种流式请求里不一定能救回来客户端如果不会跟随 307就会直接看到失败。端口不对。示例里的默认端口是8081但很多容器或本地服务实际跑在别的端口。Docker 场景下尤其要注意EXPOSE 8081和-p 8081:8081是否都做了映射只写EXPOSE不等于对外可达。监听地址不对。uvicorn.run(app, host0.0.0.0, port8081)才能被外部访问如果写成127.0.0.1容器外或局域网内都连不上客户端得到的往往是连接失败或被中间层改写成 404。Base URL 多写了/v1。ANTHROPIC_BASE_URL应为https://taotoken.net/api不要再加后缀。这一条和 SSE 的 404 长得很像但排查的是两个完全不同的对象务必先分清。鉴权字段混用。ANTHROPIC_AUTH_TOKEN和ANTHROPIC_API_KEY同时存在时行为取决于客户端实现容易造成「明明 Key 是对的却报错」的假象。残留的代理变量。shell 里的HTTP_PROXY、HTTPS_PROXY或某些 IDE 插件注入的环境变量会把请求引到别处导致路径被重写。排障时先临时 unset 掉再看结果。配置文件位置写错。settings.json放错目录等于没改用claude启动时观察它读取的配置路径确认改动真的生效。按「先模型通道、再 SSE 端点、最后客户端 URL」的顺序走404 的归属会非常清楚。模型通道不通先解决鉴权与 Base URL模型通了但/sse返回 404那是服务端路由或监听配置curl能拿到event: endpoint而客户端不行那就是客户端拼 URL 的问题。六、把 404 归位固定你的排障顺序回到这篇的标题SSE 握手 404改的往往不是服务端路由而是 Claude Code 这一侧的模型通道配置。create_starlette_app里的Route(/sse)和Mount(/messages/)不需要因为换通道而改动TaoToken 也不参与这两个路径的转发。它做的事情是把 Claude Code 的ANTHROPIC_BASE_URL指向https://taotoken.net/api让你在排查 404 时有一个确定可用的基准从而把「路由问题」和「地址写错」彻底分开。如果你还在反复对着 404 猜原因建议先按上面的顺序把两件事各自验证一遍Key 从 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建和管理配置细节对照接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里的字段说明尤其是 Base URL 写法与鉴权头。把settings.json里的ANTHROPIC_*改对再让 Claude Code 复现一次/sse握手404 到底属于哪一侧一次就能看清。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进