Mcp 协议分析
整个 Mcp ,无论是 Stdio 、 Sse 、Streamable ,只要遵守对应的 JSONRPC 请求和响应格式,即使你实际上只是一个空壳,大模型依然会相信你是个 Mcp 。
Stdio
Stdio 实际上是本地执行命令。
- 举个例子,假设你本地有一个 exe 文件,路径是:C://User/test.exe (路径我乱编的,格式可能有一点点错误,补药太在意哈),那么你的 Stdio Mcp 的 Command 选项就可以填 C://User/test.exe ,至于 Args 这个看你这个 exe 要不要,其实就是 -h 那些,而 Env 这个就是环境变量,配 C 语言环境的那个环境。
- 为什么 Cherry Stdio 使用不需要我自己配环境?我没有深究过,但是基本可以确定 Cherry Stdio 提供了一个会话,这个会话中的环境变量被 Cherry Stdio 配过,所以只影响当前会话,而不会永久影响你的电脑环境配置。
- 整个过程
- 大模型通知 Cherry Stdio 我要用这个 Mcp ,包括对应的参数什么的
- Cherry Stdio 收到消息,执行 exe 并在与 exe 的标准输入中输入 JSONRPC 消息
- Exe 从标准输入流中获取信息并处理后返回一个 JSONRPC 消息,输出到标准输出里
- Cherry Stdio 拿到数据,喂给大模型
- 大模型根据新消息再吐出最终结果
参考的 JSONRPC 请求和响应数据
1 2 3
| req: {"jsonrpc":"2.0","method":"initialize","id":0,"params":{"capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"mcp-inspector","version":"0.14.0"},"protocolVersion":"2025-03-26"}}
res: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"prompts":{},"resources":{},"tools":{}},"serverInfo":{"name":"markitdown","version":"1.8.1"}}}
|
Sse
Sse 骚操作有点多,他输入和输出不是同一个接口,也就是说他由两个接口组成一个完整的输入输出,类似下面

- sse 端点:
- 用于向客户端发消息,这个端点接口会持续存在,符合 sse 协议,持续吐消息
- messages
- 用于接收客户端的消息,一次请求为一次接收,消息处理的结果会从 sse 端点那里吐出
- 整个过程
- 请求 sse 端点, sse 端点会返回一个 endpoint 消息,里面包含了 messages 端点路径和分配的 sessionid ,至于前面的域名什么的,约定与 sse 端点的域名保持一致(http://127.0.0.1:8094)
.png)
- 向 messages 端点发送初始化消息,messages 端点只会响应一个 accepted
.png)
.png)
- 此时 sse 端点会得到 server 返回的信息
.png)
- 包括 tool call 也是一样的
.png)
.png)
Streamable
比起 sse 好一点,没那么精神分裂,非要搞两个接口,一个接口就完事了,实际上我个人感觉跟普通的 http 接口没啥区别,也就多了一些约定
约定
- 请求
- Header
- Mcp-Session-Id sessionid 存在这里,用于告诉服务端听复用之前的信息
- Accept 默认 application/json, text/event-stream
- 响应
- Header
- Mcp-Session-Id 把 服务端分配的 sessionid 交给客户端
其他跟正常 http 请求没区别,只是请求响应 json 格式有固定要求而已
1 2 3
| req: {"jsonrpc":"2.0","method":"initialize","id":0,"params":{"capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"mcp-inspector","version":"0.14.0"},"protocolVersion":"2025-03-26"}}
res: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"prompts":{},"resources":{},"tools":{}},"serverInfo":{"name":"markitdown","version":"1.8.1"}}}
|
- 过程展示
- 这里就不展示了,就是一次请求一次响应信息,首次请求会在 header 中返回 sessionid ,之后的每次请求都带上这个 sessionid 以此保持会话状态。
- 下面这个是我的 streamable 接口处理的一小部分代码,已经可以说明上面的约定了
.png)
.png)
Mcp 请求响应分析
众所周知, Mcp 的请求响应都遵守了一个约定,字段名什么的都约定好了。
请求
这里可以看出来,以下约定:
- 必须携带 jsonrpc 字段,一般是 2.0
- method :根据实际想干嘛来提供对应的 method
- id :每次请求叠加 1 ,初始为 0
- params :根据 method 的不同也不同,对应 go 中的 interface{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| { "jsonrpc": "2.0", "method": "initialize", "id": 0, "params": { "capabilities": { "roots": { "listChanged": true }, "sampling": {} }, "clientInfo": { "name": "mcp-inspector", "version": "0.14.0" }, "protocolVersion": "2025-03-26" } }
{ "jsonrpc": "2.0", "method": "tools/call", "id": 3, "params": { "_meta": { "progressToken": 3 }, "arguments": { "uri": "https://aitoken-public.qnaigc.com/test/transformer.pdf" }, "name": "convert_to_markdown" } }
{ "jsonrpc": "2.0", "method": "tools/list", "id": 4, "params": { "_meta": { "progressToken": 4 } } }
|
响应
约定
- 必须携带 jsonrpc 字段,一般是 2.0
- id :请求给啥返回啥
- result: 客户端真正想要的数据,一样是 interface{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| { "jsonrpc": "2.0", "id": 0, "result": { "protocolVersion": "2024-11-05", "capabilities": { "prompts": {}, "resources": {}, "tools": {} }, "serverInfo": { "name": "markitdown", "version": "1.8.1" } } }
{ "jsonrpc":"2.0", "id":3, "result":{ "content":[ { "type":"text", "text":"。。。。" } ] } }
{ "jsonrpc": "2.0", "id": 4, "result": { "tools": [ { "annotations": {}, "description": "Convert a resource described by an http:, https:, file: or data: URI to markdown", "inputSchema": { "properties": { "uri": { "title": "Uri", "type": "string" } }, "required": [ "uri" ], "type": "object" }, "name": "convert_to_markdown" } ] } }
|
Method
这个好像很全:https://modelcontextprotocol.info/zh-cn/
每个 method 都有对应的确认的 params 格式和 result 格式,可以看我下面提供的例子里看他的 params 和 result 长啥样。
目前我知道的 method 分为了以下几个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const ( Initialize = "initialize" Ping = "ping" ResourcesList = "resources/list" ResourcesTemplatesList = "resources/templates/list" ResourcesRead = "resources/read" ResourcesSubscribe = "resources/subscribe" ResourcesUnsubscribe = "resources/unsubscribe" PromptsList = "prompts/list" PromptsGet = "prompts/get" ToolsList = "tools/list" ToolsCall = "tools/call" LoggingSetLevel = "logging/setLevel" CompletionComplete = "completion/complete" NotificationsInitialized = "notifications/initialized" )
|
initialize
在客户端正式启动之前,需要一个激活操作,也就是客户端往服务端发送 initialize 消息,告知服务端,客户端的身份,服务端会分配一个 sessionid 给客户端,之后的聊天就依据 sessionid 知道你是谁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| req: { "jsonrpc": "2.0", "method": "initialize", "id": 0, "params": { "capabilities": { "roots": { "listChanged": true }, "sampling": {} }, "clientInfo": { "name": "mcp-inspector", "version": "0.14.0" }, "protocolVersion": "2025-03-26" } }
res: { "jsonrpc": "2.0", "id": 0, "result": { "protocolVersion": "2024-11-05", "capabilities": { "prompts": {}, "resources": {}, "tools": {} }, "serverInfo": { "name": "markitdown", "version": "1.8.1" } } }
|
ping
就是 ping 一下看你还活不活着
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| req: { "jsonrpc": "2.0", "method": "ping", "id": 1, "params": { "_meta": { "progressToken": 1 } } }
res: { "jsonrpc": "2.0", "id": 1, "result": {} }
|
resources/list
不知道能干啥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| req: { "jsonrpc": "2.0", "method": "resources/list", "id": 1, "params": { "_meta": { "progressToken": 1 } } }
res: { "jsonrpc": "2.0", "id": 1, "result": { "resources": [] } }
|
resources/templates/list
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| req: { "jsonrpc": "2.0", "method": "resources/templates/list", "id": 2, "params": { "_meta": { "progressToken": 2 } } }
res: { "jsonrpc": "2.0", "id": 2, "result": { "resourceTemplates": [] } }
|
resources/read
没用过
resources/subscribe
没用过
resources/unsubscribe
没用过
prompts/list
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| req: { "jsonrpc": "2.0", "method": "prompts/list", "id": 1, "params": { "_meta": { "progressToken": 1 } } }
res: { "jsonrpc": "2.0", "id": 1, "result": { "prompts": [] } }
|
prompts/get
没用过
列出工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| req:{ "jsonrpc": "2.0", "method": "tools/list", "id": 4, "params": { "_meta": { "progressToken": 4 } } }
res:{ "jsonrpc": "2.0", "id": 4, "result": { "tools": [ { "annotations": {}, "description": "Convert a resource described by an http:, https:, file: or data: URI to markdown", "inputSchema": { "properties": { "uri": { "title": "Uri", "type": "string" } }, "required": [ "uri" ], "type": "object" }, "name": "convert_to_markdown" } ] } }
|
执行工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| req:{ "jsonrpc": "2.0", "method": "tools/call", "id": 3, "params": { "_meta": { "progressToken": 3 }, "arguments": { "uri": "https://aitoken-public.qnaigc.com/test/transformer.pdf" }, "name": "convert_to_markdown" } }
res:{ "jsonrpc":"2.0", "id":3, "result":{ "content":[ { "type":"text", "text":"。。。。" } ] } }
|
logging/setLevel
没用过
completion/complete
没用过
notifications/initialized
这个有点东西,动不动发一个的,但是你不用回复
Mcp 与 Eino
Eino
是字节研发的一款大模型二开框架
Eino 是怎么用 Mcp 的(参考 react agent)
- 对于 Eino 而言, Mcp 被视为了一个 Tool ,所以通过 Eino 让大模型调用 Mcp 就变成了:
- Eino 告诉大模型有这些 Tool
- 大模型通过 tool_calls 通知 Eino 调用对应的 Mcp
- Eino 将 Mcp 调用结果重新“投喂”给大模型
- 大模型根据上下文再说出最终的结果
如何将 Eino 隐藏起来的对话消耗 Token 拿到手
从上面描述可以得知,Eino 最终吐出的消息在背地里是经过了 n 次对话产生的
学习资料
- mcp (server or client) go 版:
https://github.com/mark3labs/mcp-go
https://github.com/kkb-1/mcprouter
- mcp 调试工具( F12 看他的网络请求,了解 mcp 实际协议干了啥):
https://mcp-docs.cn/docs/tools/inspector
- mcp proxy(协议转换,例如 stdio 转 sse ,这个会用就行):
https://github.com/sparfenyuk/mcp-proxy
- eino (字节的 cloudwego 系列,大模型二开框架):
https://www.cloudwego.io/zh/docs/eino/ecosystem_integration/tool/tool_mcp/