MCP 系列(02):协议深解——Host/Client/Server 三层模型与 JSON-RPC 通信

0 阅读5分钟

三层模型

MCP 的架构由三个角色组成,每个角色的职责严格分离:

Host(宿主)
  → 运行 LLM 推理的应用:Claude Desktop、Claude Code、自研 Agent
  → 管理与一个或多个 MCP Server 的连接
  → 决定把哪些工具/资源暴露给 LLM

Client(客户端,内嵌于 Host)
  → 与单个 MCP Server 保持一个 1:1 的连接
  → 按 JSON-RPC 2.0 协议收发消息
  → 维护会话状态(capabilities、已发现的工具列表)

Server(服务端,独立进程)
  → 暴露三类能力:Tools / Resources / Prompts
  → 通过标准输入输出(stdio)或 HTTP 与 Client 通信
  → 一个 Server 可以同时被多个 Host 连接

Host 是用户看到的 AI 应用,Client 是应用内部的协议适配层,Server 是工具的提供方。


三类能力

MCP Server 暴露三类能力:

Tools(工具)     → LLM 主动调用的功能(action)
                  → 示例:搜索 Jira、执行 SQL、发送邮件
                  → 由 LLM 在推理过程中决定何时调用

Resources(资源)→ LLM 可读取的数据源(data)
                  → 示例:当前 Sprint 状态、代码库文件树
                  → Host 决定何时注入到上下文

Prompts(提示词)→ 预定义的 Prompt 模板(template)
                  → 示例:Bug 分析报告模板、代码 Review 模板
                  → 用户或 Host 直接调用,填入参数后生成完整 Prompt

传输方式

Client 与 Server 之间的通信支持三种传输方式:

stdio(标准输入输出)
  → Server 作为子进程运行,通过 stdin/stdout 交换 JSON-RPC 消息
  → 最简单,本地开发首选
  → Claude Code 接入 MCP Server 的默认方式

HTTP + SSE(Server-Sent Events)
  → Server 作为独立 HTTP 服务运行
  → ClientServer:HTTP POST
  → ServerClient:SSE 流(支持 Server 主动推送)
  → 适合远程 Server、多 Client 共享

Streamable HTTP(新)
  → HTTP POST + 可选 SSE 流
  → 同时支持同步调用和流式推送
  → 2025 年规范更新后的推荐远程方案

本文 demo 使用 stdio 传输,最直观地展示协议本身。


完整协议交换

下面是用真实 MCP Server 跑出的 8 轮 JSON-RPC 消息,从头到尾展示一次完整的协议交互。

第 1 步:initialize(能力协商)

每次会话的第一条消息,双方交换自己支持的能力。

Client → Server(请求):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {"listChanged": true},
      "sampling": {}
    },
    "clientInfo": {
      "name": "demo-client",
      "version": "1.0.0"
    }
  }
}

Server → Client(响应):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "experimental": {},
      "prompts": {"listChanged": false},
      "resources": {"subscribe": false, "listChanged": false},
      "tools": {"listChanged": false}
    },
    "serverInfo": {
      "name": "mcp-protocol-demo",
      "version": "1.13.1"
    }
  }
}

解读:

  • protocolVersion:两端协商使用的协议版本
  • capabilities:双方各自声明支持什么。Server 这里说 tools.listChanged: false,意味着工具列表不会动态变化,Client 不需要订阅变更通知
  • id 字段:initialize 是请求-响应类型,有 id

initialize 完成后,Client 发一条通知(notification)确认就绪:

{"jsonrpc": "2.0", "method": "notifications/initialized"}

注意这条消息没有 id 字段——这是 JSON-RPC 的 Notification 类型,fire-and-forget,不等响应。


第 2 步:tools/list(工具发现)

请求:

{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}

响应:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "echo",
        "description": "Repeats the input message back. Useful for testing connectivity.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "message": {"type": "string", "description": "The message to echo back"}
          },
          "required": ["message"]
        }
      },
      {
        "name": "add",
        "description": "Adds two numbers and returns the result.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "a": {"type": "number", "description": "First number"},
            "b": {"type": "number", "description": "Second number"}
          },
          "required": ["a", "b"]
        }
      }
    ]
  }
}

解读: inputSchema 是标准 JSON Schema,Host 把它传给 LLM 作为工具定义。LLM 读这个 Schema 决定什么时候调用工具、填什么参数——和 Function Calling 的 schema 结构完全一致。


第 3-4 步:tools/call(工具调用)

echo 调用:

// 请求
{
  "jsonrpc": "2.0", "id": 3, "method": "tools/call",
  "params": {"name": "echo", "arguments": {"message": "Hello from MCP client!"}}
}

// 响应
{
  "jsonrpc": "2.0", "id": 3,
  "result": {
    "content": [{"type": "text", "text": "Echo: Hello from MCP client!"}],
    "isError": false
  }
}

add 调用:

// 请求
{
  "jsonrpc": "2.0", "id": 4, "method": "tools/call",
  "params": {"name": "add", "arguments": {"a": 42, "b": 58}}
}

// 响应
{
  "jsonrpc": "2.0", "id": 4,
  "result": {
    "content": [{"type": "text", "text": "42 + 58 = 100"}],
    "isError": false
  }
}

解读: 响应结构里有两个重要字段:

  • content:数组,支持多个内容块(text / image / resource)
  • isError:布尔值。工具执行失败时设为 true,结果里写错误信息。工具报错是正常响应result),不是 JSON-RPC 协议错误(error),让 LLM 能读到错误详情并自适应

第 5-6 步:resources/list + resources/read

列举资源:

// 响应
{
  "result": {
    "resources": [{
      "name": "Server Information",
      "uri": "info://server-info",
      "description": "Metadata about this MCP server: name, version, capabilities",
      "mimeType": "application/json"
    }]
  }
}

读取资源:

// 请求
{"method": "resources/read", "params": {"uri": "info://server-info"}}

// 响应
{
  "result": {
    "contents": [{
      "uri": "info://server-info",
      "mimeType": "text/plain",
      "text": "{\"name\": \"mcp-protocol-demo\", \"version\": \"1.0.0\", ...}"
    }]
  }
}

解读: URI scheme 是自定义的(info://),Server 自己定义格式。常见 scheme:file://(本地文件)、jira://(Jira 工单)、github://(代码仓库)。Resources 是数据层,不执行动作,只读取。


第 7-8 步:prompts/list + prompts/get

列举模板:

{
  "result": {
    "prompts": [{
      "name": "summarize",
      "description": "Summarize a piece of text concisely",
      "arguments": [
        {"name": "text", "description": "The text to summarize", "required": true},
        {"name": "max_words", "description": "Maximum words in the summary", "required": false}
      ]
    }]
  }
}

渲染模板(填入参数):

// 请求
{
  "method": "prompts/get",
  "params": {
    "name": "summarize",
    "arguments": {
      "text": "MCP defines a standard protocol for connecting AI models to tools and data sources.",
      "max_words": "20"
    }
  }
}

// 响应
{
  "result": {
    "description": "Summarization prompt",
    "messages": [{
      "role": "user",
      "content": {
        "type": "text",
        "text": "Summarize the following text in at most 20 words:\n\nMCP defines a standard protocol for connecting AI models to tools and data sources."
      }
    }]
  }
}

解读: Prompts 是服务端维护的 Prompt 模板。Host 调用 prompts/get 得到渲染后的消息列表,可以直接用于构建 LLM 的 messages。企业用途:统一维护各类业务分析模板,所有 Agent 共用同一份。


JSON-RPC 2.0 消息类型总结

MCP 用的是标准 JSON-RPC 2.0,消息分三类:

请求(Request):有 id,需要响应
  {"jsonrpc":"2.0", "id": N, "method": "...", "params": {...}}

响应(Response):有 id,与请求对应
  {"jsonrpc":"2.0", "id": N, "result": {...}}  ← 成功
  {"jsonrpc":"2.0", "id": N, "error": {...}}   ← JSON-RPC 级错误

通知(Notification):无 id,fire-and-forget
  {"jsonrpc":"2.0", "method": "notifications/initialized"}

工具执行失败使用 error 响应——那是 JSON-RPC 传输层的错误(如方法不存在)。工具执行失败通过 result.isError: true 表达,让 LLM 能读到错误内容并做出决策。


运行本文 Demo

conda activate llm_base
pip install mcp
cd llm-in-action/mcp-02-protocol

# 方式 A:命令行查看原始 JSON-RPC 消息(8 步协议交互)
python demo_protocol_client.py

# 方式 B:MCP Inspector 可视化界面(需要 Node.js)
npx @modelcontextprotocol/inspector python demo_mcp_server.py

参考资料


欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。

更多实用知识和有趣产品,欢迎访问我的个人主页