)
1. 从一次抓包说起MCP 的 SSE 与 Streamable HTTP 到底差在哪MCPModel Context Protocol是让大模型调用外部工具的协议它本身不规定传输层只规定消息格式——JSON-RPC 2.0。真正决定你 HTTP 请求怎么写、响应怎么读的是底下那层传输机制。目前主流两种SSEServer-Sent Events和 Streamable HTTP。我最早接触时也懵同样是 POST 一个 JSON-RPC 请求为什么有的服务返回一坨 JSON有的却吐出一串event: message开头的流后来把两种都抓包对比才明白差别集中在三处请求头 Accept、会话标识session id、响应体的解析方式。先说 SSE 模式。这是 MCP 早期最常用的传输。客户端先发一个 GET 请求建立长连接服务端用text/event-stream持续推消息后续的 JSON-RPC 请求通过 POST 发到同一个 endpoint响应不一定直接回在 POST 的 body 里而是从那条 SSE 长连接里推回来。所以你会看到 POST 返回 202 或者一个空 body真正的结果在流里。这种模式对服务端友好一条连接复用但对纯 HTTP 调试不友好——你得同时维持两条通道。Streamable HTTP 是后来演进出来的。它把请求和响应收敛到单次 HTTP 往返你 POST 一个 JSON-RPC服务端可以直接用application/json回你完整结果也可以按需升级成text/event-stream流式返回。对调试者来说绝大多数情况下一次 curl 就能拿到答案不用再挂着长连接。这也是为什么现在很多 MCP 服务默认走 Streamable HTTP。理解这个差异你才能看懂为什么同一份tools/list请求在两种模式下要配不同的 Accept 头、要不要带Mcp-Session-Id、返回的 body 该按 JSON 解析还是按 SSE 逐行解析。这篇就按「先查工具列表再调工具」的顺序把两种传输的 HTTP 请求都跑一遍配置直接可复制。适合谁看想搞懂 MCP 底层原理、想用纯 HTTP 手搓调用、或者在做 MCP 客户端接入时被传输层卡住的开发者。核心检索词就三个——MCP、SSE、Streamable HTTP加上 JSON-RPC 的 tools/list 和 tools/call 两个方法。2. 前置准备用 TaoToken 统一通道拿 Key 与 Base URL在动手写请求前得先有个能稳定访问的 MCP 通道。自己搭 MCP 服务、处理鉴权和网络对只想验证协议的人来说太重。我这边用的是 TaoToken 的统一通道它把模型对话、编码 Agent、MCP 接入收敛到一套 Base URL 和 Key 上省去每个服务单独配鉴权的麻烦。先拿 Key。打开控制台登录后在 API Keys 页面创建一个新 Key。建议按用途命名比如mcp-debug方便后面区分。创建后立刻复制保存页面刷新后就看不到完整 Key 了。控制台入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite拿到 Key 后统一通道的 Base URL 是https://taotoken.net/api注意这个地址不带任何查询参数鉴权靠请求头里的Authorization: Bearer 你的Key。MCP 的 JSON-RPC 请求就 POST 到这个 Base URL 下的对应路径。这里有个容易踩的坑MCP 服务和普通 REST 不一样它的 endpoint 往往需要显式声明传输类型。在 TaoToken 通道里你可以在路径上区分比如走 Streamable HTTP 用/api/mcp走 SSE 用/api/mcp/sse。具体路径以接入文档为准别硬编码猜。接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你只是想先验证模型侧能不能通可以顺手在模型对话页发一条消息确认 Key 有效模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewriteKey 和 Base URL 都齐了下面进入正题。我会先给 Streamable HTTP 的完整配置因为它一次请求就能出结果最适合入门再给 SSE 的配置最后对比两者的请求头差异。3. 可复制配置tools/list 与 tools/call 的完整请求这一节给的是能直接粘贴运行的配置。先明确 JSON-RPC 2.0 的四个标准字段这是两种传输共用的jsonrpc: 2.0声明协议版本固定值。id请求唯一编号用来把响应和请求配对。数字或字符串都行同一会话里别重复。method要执行的方法查工具列表是tools/list调工具是tools/call。params方法参数。tools/list传空对象{}tools/call里放name和arguments。3.1 Streamable HTTP 模式配置Streamable HTTP 下一次 POST 就能拿到结果。请求头关键是Accept要同时接受 JSON 和事件流Content-Type固定application/json。curl --location --request POST https://taotoken.net/api/mcp \ --header Authorization: Bearer sk-你的Key \ --header Content-Type: application/json \ --header Accept: application/json, text/event-stream \ --data-raw { jsonrpc: 2.0, id: 1, method: tools/list, params: {} }如果服务端要求会话第一次响应头里会带Mcp-Session-Id后续请求把它带上curl --location --request POST https://taotoken.net/api/mcp \ --header Authorization: Bearer sk-你的Key \ --header Content-Type: application/json \ --header Accept: application/json, text/event-stream \ --header Mcp-Session-Id: 上一步返回的会话ID \ --data-raw { jsonrpc: 2.0, id: 3, method: tools/call, params: { name: maps_weather, arguments: { city: 北京 } } }对应的客户端配置片段以常见的 MCP 客户端 settings 为例三件套 Base URL、Key、Model ID 都要写全{ mcpServers: { taotoken-streamable: { type: streamable-http, url: https://taotoken.net/api/mcp, headers: { Authorization: Bearer sk-你的Key, Accept: application/json, text/event-stream } } } }3.2 SSE 模式配置SSE 模式要两步。先建立事件流连接再发 JSON-RPC 请求。第一步用 GETcurl --location --request GET https://taotoken.net/api/mcp/sse \ --header Authorization: Bearer sk-你的Key \ --header Accept: text/event-stream这条连接会挂住服务端会先推一个endpoint事件告诉你后续 POST 往哪发。拿到 endpoint 后再发请求curl --location --request POST https://taotoken.net/api/mcp/message?sessionIdxxx \ --header Authorization: Bearer sk-你的Key \ --header Content-Type: application/json \ --data-raw { jsonrpc: 2.0, id: 1, method: tools/list, params: {} }注意 SSE 模式下 POST 的响应通常是 202 Acceptedbody 为空真正的结果从那条 GET 事件流里推回来。所以调试时你得开两个终端一个挂着 GET一个发 POST。对应的 settings 片段{ mcpServers: { taotoken-sse: { type: sse, url: https://taotoken.net/api/mcp/sse, headers: { Authorization: Bearer sk-你的Key } } } }两种模式的请求头差异用表格对照更清楚维度Streamable HTTPSSE建连方式单次 POSTGET 建流 POST 发消息Acceptapplication/json, text/event-streamGET 用text/event-stream会话标识响应头Mcp-Session-IdURL 参数sessionId响应位置POST 的 bodyGET 事件流调试难度低一次往返高需双通道4. 验证请求从返回结果确认工具列表与调用成功配置写完得验证真的通了。先看tools/list的返回。Streamable HTTP 模式下如果服务端用 JSON 回你会拿到类似这样的结构{ jsonrpc: 2.0, id: 1, result: { tools: [ { name: maps_weather, description: 根据城市名称查询天气, inputSchema: { type: object, properties: { city: { type: string } }, required: [city] } }, { name: maps_regeocode, description: 根据经纬度反查地址, inputSchema: { type: object, properties: { location: { type: string } }, required: [location] } } ] } }看到result.tools数组就说明工具列表查通了。数组里每个元素的name就是后面tools/call要用的工具名inputSchema告诉你这个工具需要哪些参数、哪些必填。这一步很关键——很多人调工具报参数错误就是因为没先看inputSchema就瞎传。如果服务端用事件流回你会看到这样的行event: message data: {jsonrpc:2.0,id:1,result:{tools:[...]}}解析时按data:前缀逐行取把后面的 JSON 拼起来解析即可。注意 SSE 的 data 可能跨多行要按空行分隔事件块。再看tools/call的返回。调用天气工具成功后{ jsonrpc: 2.0, id: 3, result: { content: [ { type: text, text: 北京晴气温 12-24℃东南风 3 级 } ] } }result.content是数组每项有type和对应内容。文本类工具返回type: text图片类会返回type: image带 base64。判断调用成功就看有没有result字段且content非空如果返回的是error字段那就是失败了往下看排障。反查地址的工具同理传经纬度curl --location --request POST https://taotoken.net/api/mcp \ --header Authorization: Bearer sk-你的Key \ --header Content-Type: application/json \ --header Accept: application/json, text/event-stream \ --data-raw { jsonrpc: 2.0, id: 4, method: tools/call, params: { name: maps_regeocode, arguments: { location: 116.397128,39.916527 } } }返回里content[0].text就是解析出的地址。验证动作建议按这个顺序先tools/list确认工具名再挑一个参数最简单的工具tools/call最后对照inputSchema检查自己传的参数类型对不对。三步都过说明通道和协议都没问题。5. 常见报错排查401、local proxy failed 与 reading choices调试 MCP 时踩的坑基本集中在几个固定报错上逐个对照。401 Unauthorized。最常见九成是 Key 的问题。检查三处Authorization头是不是Bearer开头注意 Bearer 后有空格Key 有没有复制全前后别带空格Key 是不是被删了或过期。如果用的是 SSE 模式GET 建流和 POST 发消息两个请求都要带 Key漏一个就 401。local proxy failed / connection refused。这个报错通常出现在客户端侧不是服务端返回的。意思是客户端连不上你配的 Base URL。排查URL 是不是写成了https://taotoken.net/api/mcp而漏了协议或路径本地网络能不能正常访问该域名如果客户端走本地代理代理配置有没有把该域名排除。注意别把代理和某些网络工具混为一谈这里说的只是常规 HTTP 代理设置。reading choices / unexpected end of JSON input。这类是响应解析错误。Streamable HTTP 模式下如果服务端实际返回的是事件流而你按纯 JSON 解析就会报这个。解决办法是看响应头Content-Type是application/json就按 JSON 解析是text/event-stream就按 SSE 逐行解析。别硬套一种解析方式。OAuth / 鉴权跳转相关报错。有些 MCP 服务要求 OAuth 流程返回 302 跳转到授权页。如果你用的是统一通道的 Key 鉴权正常不会触发一旦看到跳转先确认请求头里的鉴权方式对不对别把 Key 鉴权和 OAuth 混用。tools/call 报 method not found 或 tool not found。多半是工具名写错了。回去跑一遍tools/list把name字段原样复制别自己猜。参数名也要和inputSchema.properties里的键完全一致大小写敏感。SSE 模式 POST 返回 202 但拿不到结果。这是正常的结果在 GET 流里。检查你的 GET 连接是不是还活着——有些客户端会在 POST 后误关 GET 连接导致结果推不过来。保持 GET 常驻直到收到对应id的响应再关。排查时有个通用技巧加-v参数看完整请求响应头。curl -v --location --request POST https://taotoken.net/api/mcp \ --header Authorization: Bearer sk-你的Key \ --header Content-Type: application/json \ --header Accept: application/json, text/event-stream \ --data-raw {jsonrpc:2.0,id:1,method:tools/list,params:{}}响应头里的Content-Type、Mcp-Session-Id、状态码基本能定位大部分问题。6. 把两种传输用对场景长期编码与 Agent 接入的选择搞懂两种传输后选哪个其实看场景。纯调试、写脚本、做一次性调用Streamable HTTP 更省事一次 POST 出结果不用维护长连接。做长期编码 Agent、需要服务端主动推消息、或者客户端本身支持 SSE 长连接复用那 SSE 更合适。如果你打算把 MCP 接进编码工作流比如让 Agent 长期挂着调用工具建议走 Coding Plan它把编码场景的额度和通道都配好了不用每次手动拼请求Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewriteClaude Code 这类工具接入时配置里同样要写全三件套——Base URL、Key、Model ID缺一个都连不上。Anthropic 兼容接入的说明在这里Claude Code 接入https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite最后给个实操建议调试阶段先用 Streamable HTTP 把tools/list和tools/call跑通确认工具名和参数都对等逻辑没问题了再按需切到 SSE 做长连接。别一上来就啃 SSE 的双通道容易在解析上卡半天。工具列表和调用参数这两步验证过后面接任何 MCP 客户端都是同一套 JSON-RPC 结构换汤不换药。