ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

深入理解 MCP(Model Context Protocol):从 JSON-RPC 到 Streamable HTTP 的实战拆解

深入理解 MCP(Model Context Protocol):从 JSON-RPC 到 Streamable HTTP 的实战拆解 1. 为什么我要自己写一个 MCP ServerMCPModel Context Protocol模型上下文协议是 Anthropic 推出的开放标准目标是让大语言模型用统一的方式连接外部数据源和工具。你可以把它理解成「AI 应用的 USB-C 接口」以前每接一个服务就要写一套适配代码现在只要服务端按 MCP 规范暴露能力任何支持 MCP 的客户端都能直接调用。它适合谁适合需要把内部系统、CLI 工具、数据库封装成 AI 可调用能力的后端开发者也适合想搞懂 Agent 工具调用底层到底怎么跑的人。我一开始也以为 MCP 就是个高级 Function Call直到自己动手写 Server 才发现真正难的不是注册工具而是通信层JSON-RPC 消息怎么组、Streamable HTTP 会话怎么建、初始化握手少了哪一步就报错。这篇就聚焦通信层从 JSON-RPC 消息格式讲到 Streamable HTTP 传输给你一份能直接跑的 MCP Server 骨架再用 curl 把握手和会话验证一遍。读完你应该能独立写出一个可被客户端连上的 MCP Server并知道每一步在协议里对应什么。2. 动手前先把 TaoToken 的接入信息准备好写 MCP Server 本身不需要模型但你要验证「模型能不能通过 MCP 调到工具」就得有一个能跑 Function Call 的模型端点。我习惯用 TaoToken 做这一步因为它同时提供 OpenAI 兼容接口和 Claude Code 的接入方式验证 MCP 工具调用链路比较顺。你需要准备两样东西一个 API Key以及对应的接入地址。API 基地址是https://taotoken.net/api注意这个地址不带任何查询参数直接作为base_url用。Key 在控制台的 API Keys 页面创建建议单独建一个用于 MCP 调试的 Key方便随时吊销。如果你只是想让模型对话验证工具描述是否合理用模型对话页面就够如果你要长期跑编码类 Agent、反复调 MCP 工具那 Coding Plan 更划算额度模型和调用方式在文档里写得很清楚。接入细节和参数说明都在接入文档里遇到 401 或模型名不对先回去对一遍文档比瞎试快得多。注意MCP Server 的通信层和模型供应商是解耦的。也就是说你完全可以把 Server 跑在本地用任意兼容端点做客户端侧的模型验证。TaoToken 在这里的角色是「提供可调用的模型端点」不是 MCP 协议的一部分别把两者混在一起理解。3. 可复制的 MCP Server 配置骨架先把工程结构定下来后面所有命令都基于这个结构。我用的目录长这样mcp-demo/ ├── config.toml ├── settings.json ├── server.py └── requirements.txtrequirements.txt只有一行核心依赖mcp1.2.0config.toml放服务端自身的运行参数比如监听地址、端口、传输方式、日志级别。这样做的目的是把「协议行为」和「业务逻辑」分开换传输方式时不用改代码[server] name demo-mcp version 1.0.0 transport streamable-http host 127.0.0.1 port 8000 path /mcp [logging] level INFOsettings.json放客户端侧的连接配置也就是客户端怎么找到这个 Server。stdio 和 HTTP 两种写法差别很大这里给 Streamable HTTP 的版本{ mcpServers: { demo: { url: http://127.0.0.1:8000/mcp, transport: streamable_http } } }server.py是核心。我用 FastMCP 封装重点看它怎么把工具注册和传输层解耦from mcp.server.fastmcp import FastMCP mcp FastMCP(demo-mcp) mcp.tool() def add(a: int, b: int) - int: 计算两个整数之和 return a b mcp.tool() def echo(text: str) - str: 原样返回输入文本用于连通性验证 return fecho: {text} if __name__ __main__: mcp.run(transportstreamable-http)启动命令pip install -r requirements.txt python server.py看到日志里出现监听127.0.0.1:8000就说明服务起来了。这里有个容易踩的点mcp.run()的transport参数取值是stdio、sse、streamable-http写错会直接抛异常别凭记忆写。4. 用 curl 验证 JSON-RPC 握手与 Streamable HTTP 会话服务起来之后别急着接客户端先用 curl 把协议层走一遍。MCP 底层是 JSON-RPC 2.0所有消息都是请求-响应或通知。第一步是初始化握手客户端发initialize服务端返回能力协商结果。curl -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: 2025-03-26, capabilities: {}, clientInfo: {name: curl-client, version: 1.0.0} } }这里有两个关键点。第一Accept头必须同时包含application/json和text/event-stream因为 Streamable HTTP 允许服务端根据情况返回普通 JSON 或 SSE 流只写一个可能被拒。第二响应头里通常会带Mcp-Session-Id这个值后面每次请求都要带上否则服务端认不出你是同一个会话。拿到 session id 后发initialized通知确认握手完成。注意通知没有id字段curl -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: 上一步返回的session id \ -d { jsonrpc: 2.0, method: notifications/initialized }接着列出服务端注册的工具验证tools/listcurl -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: session id \ -d { jsonrpc: 2.0, id: 2, method: tools/list, params: {} }正常返回里应该能看到add和echo两个工具每个都带name、description、inputSchema。最后真正调用一次工具curl -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: session id \ -d { jsonrpc: 2.0, id: 3, method: tools/call, params: { name: add, arguments: {a: 5, b: 3} } }返回结构里result.content是一个数组第一项通常是{type: text, text: 8}。走到这一步说明你的 Server 在协议层已经通了。如果返回的是 SSE 格式你会看到event: message加data:前缀的文本把data:后面的 JSON 解析出来就是同样的结果。5. 本篇常见报错排查报错一406 Not Acceptable。九成是Accept头没写全。Streamable HTTP 要求客户端声明能接受application/json和text/event-stream两种缺一个服务端就可能拒绝。补全即可。报错二400 Bad Request且提示 session 无效。检查Mcp-Session-Id是否带上以及是否在initialize之后才发后续请求。顺序错了服务端会认为你在没有会话的情况下发消息。报错三initialize返回了但tools/list报方法不存在。大概率是notifications/initialized没发。这个通知是握手的一部分少了它服务端不会进入运行阶段能力列表也就查不到。报错四curl 一直挂着不返回。如果你用了GET /mcp建 SSE 长连接它本来就是不主动断开的这是正常行为。验证请求用 POST别用 GET 等响应。报错五本地能跑换端口就 404。检查config.toml里的path和客户端settings.json里的 URL 路径是否一致。Streamable HTTP 默认路径是/mcp改成别的要两边同步改。报错六模型侧调用工具时报 schema 校验失败。这通常不是通信层问题而是工具函数的类型注解和inputSchema对不上。比如参数写了list[float]但客户端传了字符串数组校验就会挂。用tools/list把 schema 打出来对一遍最直接。6. 把链路接起来继续往下走协议层验证通过后下一步就是让真实模型通过 MCP 调你的工具。这时候你需要一个能跑 Function Call 的模型端点把settings.json里的 Server 配置接到客户端再用模型对话发一句「帮我算 5 加 3」看它会不会自动触发add工具。如果模型没调工具先检查工具描述是否清晰描述写得太模糊模型会犹豫。长期跑编码类 Agent、需要反复调 MCP 工具的场景建议直接上 Coding Plan额度模型和调用方式在文档里有完整说明。接入过程中如果遇到 401、模型名不匹配、base_url 写错这类问题先翻接入文档再对照 API Keys 页面确认 Key 状态。把通信层和模型层分开排查问题定位会快很多。
返回列表