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. 用真实业务评估:正确率、延迟、失败恢复率、迁移成本

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

相关推荐
Leo.yuan几秒前
数据目录和数据字典有什么区别?一文讲清
大数据·数据库·人工智能
fuquxiaoguang2 分钟前
合规的“智能化”拐点:当金融监管遇到AI中间件
人工智能·金融监管·金融合规
phltxy4 分钟前
LangChain从模型调用到应用编排
人工智能·深度学习·语言模型·langchain
武子康7 分钟前
LingBot-World 2.0 深度解析:可交互视频为何被称为世界模型
人工智能·llm
skywalk816312 分钟前
Dumate中碰到任务自动起监控进程(另一个监控任务),但是因为沙箱损坏,永远也拿不到预期的文件,导致监控任务永远不会停的问题
人工智能·沙箱
minhuan15 分钟前
AI Agent量化评估:LLM-as-Judge、人工标注、A/B测试全解,智能体多维评测实践21.0
人工智能·大模型应用·llm-as-judge·ai agent量化评估·智能体多维评测
枫叶丹417 分钟前
OpenClaw Agent 搭建全流程:从本机自托管到可用、可控、可审计的个人 AI 助手
人工智能·agent
AI导出鸭PC端18 分钟前
预览即导出:告别AI文档格式崩塌,网页预览效果1:1落地Word
人工智能·pdf·word·流程图·豆包·ai导出鸭
阿pin19 分钟前
Android随笔-要怎样与AI相处
android·人工智能·ai
猫先生Mr.Mao20 分钟前
具身智能之π0.5:如何用异构数据训练开放世界 VLA
ai·论文解读·具身智能·vla·机器人泛化