到这里,你已经能让 Agent 在自己的进程里跑工具了。但工具是"私"的——你写一个 bash tool,只有你的 Agent 能用。让任何 Agent 都能用同一份"工具集",需要的是协议。Anthropic 在 2024 年底发布了 Model Context Protocol(MCP),2026 年中已成了 de-facto 标准。本章把 MCP 的协议机制讲透。

15.1 MCP 要解决的问题

Pre-MCP 混乱:
  Cursor 自己定义一套 tool spec,跟 Claude Code 不同
  Claude Code 自己一套,跟 LangChain 不同
  你给团队写个内部 API agent 工具,要实现 3-5 遍

Post-MCP:
  ┌───────────┐                  ┌──────────────┐
  │ MCP Client │  ─── MCP ────→  │ MCP Server    │
  │ (任何 Agent)│                 │ (工具实现者)    │
  └───────────┘                  └──────────────┘
  客户端只问:"你能干啥?" → 服务端回答一组标准 schema
  客户端说:"调用 foo 工具" → 服务端返回结果

MCP = “USB-C 接口” for AI Tools。一个 server 实现一次,Any Client 都能用。

15.2 MCP 的三大原语

原语 作用 谁发起 形态
Tools 模型可调用的函数(带副作用) 模型(tool_use) JSON Schema 输入 + 任意输出
Resources 可读的"数据源"(无副作用) 客户端按需 fetch URI + mime_type + 内容
Prompts 预制的对话模板 用户主动选择 name + arguments + messages

理解差别:Tools 是模型决定调;Resources 是客户端主动读、展示给模型;Prompts 是用户主动选的快捷指令。三者职责清晰,混着实现就乱。

15.3 传输方式:stdio / SSE / WebSocket

MCP 不限定网络层,常见三种:

传输 用法 适合
stdio harness fork 一个 subprocess,stdin/stdout 走 JSON-RPC 本地工具(filesystem、git)
SSE server 跑 HTTP,长连接 server-sent events 远程 server、云端服务
WebSocket server 跑 ws://,双向 需要双向低延迟(如机器人控制)

stdio 的好处是零部署——client 多开一个 subprocess 就行。OpenCode / Claude Code 的项目内 MCP 都是 stdio。

15.4 消息结构:JSON-RPC 2.0

MCP 用 JSON-RPC 2.0 包消息。看一个完整握手序列:

// Client → Server : initialize 握手
{"jsonrpc":"2.0","id":1,"method":"initialize","params":
  {"protocolVersion":"2025-06-18",
   "clientInfo":{"name":"opencode","version":"0.x"},
   "capabilities":{}}}

// Server → Client : 返回 server 能力
{"jsonrpc":"2.0","id":1,"result":
  {"protocolVersion":"2025-06-18",
   "serverInfo":{"name":"filesystem","version":"1.0"},
   "capabilities":{"tools":{},"resources":{}}}}

// Client → Server : 列举工具
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}

// Server → Client : 工具列表
{"jsonrpc":"2.0","id":2,"result":{"tools":[
  {"name":"read_file",
   "description":"Read a file from disk",
   "inputSchema":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}}
]}}

// Client → Server : 调用工具
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":
  {"name":"read_file","arguments":{"path":"/tmp/foo.txt"}}}

// Server → Client : 工具结果
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"hello"}]}}

不再学更多——任何 MCP 客户端 / 服务端交互都是这几个原语的循环。

15.5 三大致命陷阱

15.5.1 Tools vs Resources 混淆

错误:把"读文件"做成 tool。应该是 resource:客户端按需读、缓存、显示给模型。Tools 应有副作用(写磁盘、调 API、跑命令)。

15.5.2 协议版本不匹配

MCP 协议在迭代(2024-11 → 2025-03 → 2025-06-18 …)。Client 与 Server 协议版本不一致时,老版可能缺 sampling、notifications 等能力。生产配置:显式写 protocolVersion,不要依赖自动 negotiate。

15.5.3 客户端盲信 schema

服务端的 inputSchema 是给模型看的,但客户端不应假设 schema 一定合法。加重试 + 校验,不要把一个被恶意 server 灌入的奇怪 schema 直接转发给 LLM。

15.6 一个极简 stdio MCP Server

Python 用官方 SDK 30 行能写一个:

# server.py
from mcp.server.stdio import stdio_server
from mcp.server import Server
import mcp.types as types

server = Server("hello-mcp")

@server.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="greet",
            description="给指定名字打招呼",
            inputSchema={
                "type": "object",
                "properties": {"name": {"type": "string"}},
                "required": ["name"],
            },
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "greet":
        who = arguments["name"]
        return [types.TextContent(type="text", text=f"Hello, {who}!")]
    raise ValueError(f"unknown tool: {name}")

async def main():
    async with stdio_server() as (r, w):
        await server.run(r, w, server.create_initialization_opts())

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

在 OpenCode 配置:

{
  "mcp": {
    "hello": {
      "type": "stdio",
      "command": "python",
      "args": ["server.py"]
    }
  }
}

启动后,模型就能调到 greet 工具。下一章会展开实战开发。

15.7 MCP vs Function Calling:什么关系

Function calling = 模型能力:模型能输出 {"name":"foo","arguments":{...}}。 MCP = 工具发现 + 调用协议:可跨 harness 通用,不绑死某家模型 API。

┌──────────┐                                  ┌──────────┐
│Harness/  │  has tools/list + tools/call     │MCP Server│
│LLM API   │ ──────────── JSON-RPC ─────────→ │(any)     │
└──────────┘                                  └──────────┘
   ↑
   │ function calling (provider API 自己的)
   │
┌──────────┐
│  LLM     │
└──────────┘

一句话:MCP 服务端不直接函数模型,它把 tools 暴露给 harness,harness 用 function calling 让模型做选择。MCP 是 harness 的事,不是 LLM API 的事。

15.8 何时该写 MCP

场景 写 MCP 还是写普通 tool
团队内部多处 harness 要复用 ✅ MCP
该工具/数据源会被分发给社区 ✅ MCP
仅你自己项目里临时用 ❌ 普通 tool
强 dependency on 内部业务模型 ❌ 你会反复改 protocolVersion,得不偿失
流量特别大 / 延迟极敏感 ❌ stdio 启动慢,先用普通 tool

第三篇 17 章:MCP server 开发实战 会把这决策再展开。

15.9 小结

  • MCP = USB-C for AI tools,三大原语:Tools / Resources / Prompts
  • 协议版本要显式固定,schema 不能盲信
  • 30 行 stdio server 能上手;stdio 适合本地、SSE 适合远程
  • 何时写 MCP:跨 harness 复用 / 社区分发 / 解耦业务

下一篇:《16 常用 MCP Server 全景》——别自己写之前先看看现成有什么。

Summary: MCP = 协议标准化 + 三原语 + stdio/SSE 双传输;30 行起步,慎重选场景再写。