AI入门——MCP 协议核心解读:从 JSON-RPC 到 Host/Client/Server 实战

MCP 协议核心解读:从 JSON-RPC 到 Host/Client/Server 实战

MCP 协议核心解读:从 JSON-RPC 到 Host/Client/Server 实战

这篇文档聚焦四个问题:

  1. MCP 协议主要内容是什么?
  2. JSON-RPC 在 MCP 里怎么用?
  3. MCP 解决了传统 function call 集成中的哪些痛点?
  4. MCP 的 Host、Client、Server 架构有什么特点,代码上怎么体现?

一、MCP 协议主要内容

MCP(Model Context Protocol)本质是一个让模型安全访问外部能力的标准协议层。

它通常围绕三类能力组织:

  • tools:可执行动作(如检索、写文件、调 API)
  • resources:可读取资源(如文档、配置、知识片段)
  • prompts:可复用提示模板(便于统一任务行为)

协议层强调的是"统一描述 + 统一调用 + 统一错误处理",重点包括:

  1. 能力发现:客户端先获取服务端可用能力清单(如 tools/list
  2. 能力调用:按统一结构调用能力(如 tools/call
  3. 上下文回传:将结构化结果回传给模型继续推理
  4. 安全治理:权限边界、审计日志、失败可解释

二、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 消息体本身,还会结合不同传输方式。常见标准模式如下:

  1. stdio(标准输入输出)

    适合本地进程模式(如 IDE 拉起本地 MCP Server)。

  2. Streamable HTTP(流式 HTTP)

    适合服务化部署,便于通过网关、鉴权、负载均衡接入。

  3. 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 常见痛点

  1. 接口标准不统一

    每家模型、每个框架字段不完全一致,迁移成本高。

  2. 工具发现和描述能力弱

    很多实现靠"硬编码函数定义",动态扩展能力差。

  3. 权限治理碎片化

    常把权限逻辑散落在业务代码,难审计、难复用。

  4. 错误处理语义不统一

    不同工具返回结构各异,模型侧很难稳定处理失败分支。

  5. 多系统集成耦合高

    工具越来越多后,业务逻辑和工具连接逻辑缠在一起。

MCP 的改进点

  1. 协议标准化:统一能力发现、调用和返回结构
  2. 能力可组合:tools/resources/prompts 可独立演进
  3. 权限可治理:更容易做白名单、审批、审计链路
  4. 生态可迁移:替换模型或平台时,尽量不重写业务流程
  5. 失败可控:统一错误语义,方便模型做重试/降级/拒答

五、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)

  1. 先把高频工具封成 MCP Server(如检索、文件、工单)
  2. 在 Host 侧补齐最小权限和审计日志
  3. 用 Client 统一错误处理与重试策略
  4. 保留旧 function call 作为 fallback,分阶段迁移
  5. 用真实业务评估:正确率、延迟、失败恢复率、迁移成本

这样迁移通常比"一次性推翻重做"更稳。

相关推荐
JAVA学习通2 小时前
本地知识库接入大模型时的权限隔离与安全设计
java·人工智能·安全·spring
HashTang2 小时前
用 AI 对话式驱动的开源 3D 建筑设计编辑器-Aedifex
人工智能·3d·编辑器
Yuanxl9032 小时前
论文精读-“A deep-learning-based approach for fast and robust steel”
人工智能·深度学习
飞桨PaddlePaddle2 小时前
PaddleOCR 3.5 发布:Web 端直用、文档一键转 Markdown,生态交互新体验
人工智能·开源·飞桨
沫儿笙2 小时前
KUKA库卡焊接机器人薄板焊接节气装置
人工智能·机器人
程序员cxuan2 小时前
马斯克把 Cursor 给收了
人工智能·后端·程序员
hanniuniu132 小时前
从Token吞吐到多租户隔离:F5 AI安全解决方案加速智能体AI工作流落地
人工智能·安全
ZYH101402 小时前
OpenClaw 多 Agent 与飞书机器人(AI团队)
人工智能·机器人·飞书·ai团队·小龙虾养殖·openclaw团队·openclaw接入飞书
Victor3562 小时前
MongoDB(98)如何实现MongoDB的数据归档?
后端