MCP 协议核心解读:从 JSON-RPC 到 Host/Client/Server 实战
- [MCP 协议核心解读:从 JSON-RPC 到 Host/Client/Server 实战](#MCP 协议核心解读:从 JSON-RPC 到 Host/Client/Server 实战)
-
- [一、MCP 协议主要内容](#一、MCP 协议主要内容)
- [二、MCP 中的 JSON-RPC(jsonrpc)样例](#二、MCP 中的 JSON-RPC(jsonrpc)样例)
-
- 1) 获取工具清单:`tools/list` 获取工具清单:
tools/list) - 2) 调用工具:`tools/call` 调用工具:
tools/call) - 3) 失败响应示例 失败响应示例)
- 1) 获取工具清单:`tools/list` 获取工具清单:
- [三、除了 JSON-RPC,还有哪些标准通信方式?](#三、除了 JSON-RPC,还有哪些标准通信方式?)
-
- 1) `stdio` 示例(本地子进程通信)
stdio示例(本地子进程通信)) - 2) `Streamable HTTP` 示例(请求-响应)
Streamable HTTP示例(请求-响应)) - 3) `HTTP + SSE` 示例(服务端事件推送)
HTTP + SSE示例(服务端事件推送)) - 如何选型?
- 1) `stdio` 示例(本地子进程通信)
- [四、MCP 解决传统 function call 的哪些痛点?](#四、MCP 解决传统 function call 的哪些痛点?)
-
- [传统 function call 常见痛点](#传统 function call 常见痛点)
- [MCP 的改进点](#MCP 的改进点)
- [五、MCP 架构:Host、Client、Server 的职责特点](#五、MCP 架构:Host、Client、Server 的职责特点)
-
- 1) Host(宿主) Host(宿主))
- 2) Client(协议客户端) Client(协议客户端))
- 3) Server(能力服务端) Server(能力服务端))
- [六、Host/Client/Server 最小示例代码](#六、Host/Client/Server 最小示例代码)
-
- 1) MCP Server(FastAPI) MCP Server(FastAPI))
- 2) MCP Client(协议调用封装) MCP Client(协议调用封装))
- 3) Host(编排与策略控制) Host(编排与策略控制))
- 4) 运行方式 运行方式)
- [七、落地建议(从 function call 平滑迁移到 MCP)](#七、落地建议(从 function call 平滑迁移到 MCP))
MCP 协议核心解读:从 JSON-RPC 到 Host/Client/Server 实战
这篇文档聚焦四个问题:
- MCP 协议主要内容是什么?
- JSON-RPC 在 MCP 里怎么用?
- MCP 解决了传统 function call 集成中的哪些痛点?
- MCP 的 Host、Client、Server 架构有什么特点,代码上怎么体现?
一、MCP 协议主要内容
MCP(Model Context Protocol)本质是一个让模型安全访问外部能力的标准协议层。
它通常围绕三类能力组织:
tools:可执行动作(如检索、写文件、调 API)resources:可读取资源(如文档、配置、知识片段)prompts:可复用提示模板(便于统一任务行为)
协议层强调的是"统一描述 + 统一调用 + 统一错误处理",重点包括:
- 能力发现:客户端先获取服务端可用能力清单(如
tools/list) - 能力调用:按统一结构调用能力(如
tools/call) - 上下文回传:将结构化结果回传给模型继续推理
- 安全治理:权限边界、审计日志、失败可解释
二、MCP 中的 JSON-RPC(jsonrpc)样例
MCP 常基于 JSON-RPC 风格进行消息交互。下面是最小示例。
1) 获取工具清单:tools/list
请求:
json
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "tools/list",
"params": {}
}
响应:
json
{
"jsonrpc": "2.0",
"id": "req-001",
"result": {
"tools": [
{
"name": "search_docs",
"description": "在知识库文档中检索相关片段",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"topK": { "type": "integer" }
},
"required": ["query"]
}
}
]
}
}
2) 调用工具:tools/call
请求:
json
{
"jsonrpc": "2.0",
"id": "req-002",
"method": "tools/call",
"params": {
"name": "search_docs",
"arguments": {
"query": "MCP 和 function call 的区别",
"topK": 3
}
}
}
响应:
json
{
"jsonrpc": "2.0",
"id": "req-002",
"result": {
"content": [
{
"type": "text",
"text": "检索到 3 条片段:...(含来源)"
}
],
"meta": {
"latencyMs": 42
}
}
}
3) 失败响应示例
json
{
"jsonrpc": "2.0",
"id": "req-003",
"error": {
"code": -32602,
"message": "Invalid params: topK must be between 1 and 20"
}
}
三、除了 JSON-RPC,还有哪些标准通信方式?
先明确一个关键点:
JSON-RPC 是"消息语义格式",不是底层传输介质。
在 MCP 实践中,除了 JSON-RPC 消息体本身,还会结合不同传输方式。常见标准模式如下:
-
stdio(标准输入输出)适合本地进程模式(如 IDE 拉起本地 MCP Server)。
-
Streamable HTTP(流式 HTTP)适合服务化部署,便于通过网关、鉴权、负载均衡接入。
-
HTTP + SSE(Server-Sent Events)适合服务端持续推送事件(进度、通知、增量结果)。
说明:社区里也有 WebSocket 等实现,但通常属于扩展方案,不一定是每个 MCP 生态都默认支持。
1) stdio 示例(本地子进程通信)
python
# stdio_server.py
import json
import sys
def handle(req: dict) -> dict:
method = req.get("method")
req_id = req.get("id")
if method == "tools/list":
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {"tools": [{"name": "echo_text", "description": "回显文本"}]}
}
if method == "tools/call":
params = req.get("params", {})
if params.get("name") == "echo_text":
text = params.get("arguments", {}).get("text", "")
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {"content": [{"type": "text", "text": text}]}
}
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "not found"}}
for line in sys.stdin:
line = line.strip()
if not line:
continue
req = json.loads(line)
resp = handle(req)
sys.stdout.write(json.dumps(resp, ensure_ascii=False) + "\n")
sys.stdout.flush()
2) Streamable HTTP 示例(请求-响应)
python
# http_server.py
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/mcp")
async def mcp(req: Request):
body = await req.json()
method = body.get("method")
req_id = body.get("id")
if method == "tools/list":
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {"tools": [{"name": "search_docs", "description": "文档检索"}]}
}
return {
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32601, "message": f"Method not found: {method}"}
}
3) HTTP + SSE 示例(服务端事件推送)
python
# sse_server.py
import asyncio
import json
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
@app.get("/mcp/events")
async def events():
async def event_stream():
for i in range(3):
payload = {
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {"step": i + 1, "total": 3}
}
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
await asyncio.sleep(1)
return StreamingResponse(event_stream(), media_type="text/event-stream")
对应前端/客户端接收:
javascript
const es = new EventSource("http://127.0.0.1:8000/mcp/events");
es.onmessage = (evt) => {
const msg = JSON.parse(evt.data);
console.log("MCP event:", msg);
};
如何选型?
- 本地 IDE 插件或 Agent 子进程:优先
stdio - 企业服务化、跨网络调用:优先
Streamable HTTP - 需要进度回传、长任务通知:补
HTTP + SSE
四、MCP 解决传统 function call 的哪些痛点?
传统 function call 不是不能用,但在系统变复杂后会暴露明显问题。
传统 function call 常见痛点
-
接口标准不统一
每家模型、每个框架字段不完全一致,迁移成本高。
-
工具发现和描述能力弱
很多实现靠"硬编码函数定义",动态扩展能力差。
-
权限治理碎片化
常把权限逻辑散落在业务代码,难审计、难复用。
-
错误处理语义不统一
不同工具返回结构各异,模型侧很难稳定处理失败分支。
-
多系统集成耦合高
工具越来越多后,业务逻辑和工具连接逻辑缠在一起。
MCP 的改进点
- 协议标准化:统一能力发现、调用和返回结构
- 能力可组合:
tools/resources/prompts可独立演进 - 权限可治理:更容易做白名单、审批、审计链路
- 生态可迁移:替换模型或平台时,尽量不重写业务流程
- 失败可控:统一错误语义,方便模型做重试/降级/拒答
五、MCP 架构:Host、Client、Server 的职责特点
1) Host(宿主)
Host 是 Agent 运行环境(IDE、平台、应用容器)。
它负责:
- 会话生命周期管理
- 模型编排与策略控制
- 权限与审计策略执行
- 将最终结果返回用户
架构特点:治理中心、策略入口、用户交互面。
2) Client(协议客户端)
Client 是 Host 里的 MCP 适配层。
它负责:
- 与 Server 建立连接
- 能力发现(list)
- 能力调用(call)
- 错误标准化与超时控制
架构特点:协议抽象层、调用编排层、容错中枢。
3) Server(能力服务端)
Server 对外暴露工具和资源能力。
它负责:
- 声明可用能力及输入 schema
- 执行业务动作(检索、写入、查询等)
- 返回结构化结果或错误
架构特点:能力提供层、领域封装层、可独立扩展单元。
六、Host/Client/Server 最小示例代码
下面用一套极简 Python 代码展示三者关系。
示例只演示核心思路,生产环境请补鉴权、日志、重试与限流。
1) MCP Server(FastAPI)
python
# server.py
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Any, Dict
app = FastAPI()
class RpcRequest(BaseModel):
jsonrpc: str
id: str
method: str
params: Dict[str, Any] = {}
@app.post("/mcp")
def mcp(req: RpcRequest):
if req.method == "tools/list":
return {
"jsonrpc": "2.0",
"id": req.id,
"result": {
"tools": [
{
"name": "echo_text",
"description": "回显输入文本",
"inputSchema": {
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"]
}
}
]
}
}
if req.method == "tools/call":
name = req.params.get("name")
args = req.params.get("arguments", {})
if name == "echo_text":
text = args.get("text", "")
return {
"jsonrpc": "2.0",
"id": req.id,
"result": {"content": [{"type": "text", "text": text}]}
}
return {
"jsonrpc": "2.0",
"id": req.id,
"error": {"code": -32601, "message": f"Tool not found: {name}"}
}
return {
"jsonrpc": "2.0",
"id": req.id,
"error": {"code": -32601, "message": f"Method not found: {req.method}"}
}
2) MCP Client(协议调用封装)
python
# client.py
import requests
class MCPClient:
def __init__(self, endpoint: str):
self.endpoint = endpoint
def _rpc(self, method: str, params: dict, req_id: str):
payload = {
"jsonrpc": "2.0",
"id": req_id,
"method": method,
"params": params
}
resp = requests.post(self.endpoint, json=payload, timeout=15)
resp.raise_for_status()
data = resp.json()
if "error" in data:
raise RuntimeError(data["error"]["message"])
return data["result"]
def list_tools(self):
return self._rpc("tools/list", {}, "list-1")
def call_tool(self, name: str, arguments: dict):
return self._rpc("tools/call", {"name": name, "arguments": arguments}, "call-1")
3) Host(编排与策略控制)
python
# host.py
from client import MCPClient
def run_host():
client = MCPClient("http://127.0.0.1:8000/mcp")
# 1) 发现可用工具
tools = client.list_tools()["tools"]
names = {t["name"] for t in tools}
if "echo_text" not in names:
raise RuntimeError("required tool missing: echo_text")
# 2) Host 做策略判断(示例:最小权限白名单)
allowed = {"echo_text"}
tool_to_use = "echo_text"
if tool_to_use not in allowed:
return "策略拒绝:该工具不在白名单中"
# 3) 调用工具并将结果回注模型上下文(此处简化为直接打印)
result = client.call_tool("echo_text", {"text": "MCP makes tools interoperable."})
text = result["content"][0]["text"]
return f"Host 收到工具结果:{text}"
if __name__ == "__main__":
print(run_host())
4) 运行方式
bash
pip install fastapi uvicorn requests pydantic
uvicorn server:app --host 127.0.0.1 --port 8000
# 新开终端
python host.py
预期输出:
text
Host 收到工具结果:MCP makes tools interoperable.
七、落地建议(从 function call 平滑迁移到 MCP)
- 先把高频工具封成 MCP Server(如检索、文件、工单)
- 在 Host 侧补齐最小权限和审计日志
- 用 Client 统一错误处理与重试策略
- 保留旧 function call 作为 fallback,分阶段迁移
- 用真实业务评估:正确率、延迟、失败恢复率、迁移成本
这样迁移通常比"一次性推翻重做"更稳。