Loading

【大模型Wiki】17 · MCP 模型上下文协议

MCP:模型上下文协议

新增篇目。MCP 是 2024 年 11 月 Anthropic 开源、2025-2026 成为行业标准的"AI 应用 USB-C 接口"。
官网:https://modelcontextprotocol.io

一、MCP 解决什么问题

MCP 之前,让模型用上外部工具/数据只有两条路,都很难规模化:

  1. Function Calling:每个应用 × 每个工具都要写胶水代码(M×N 问题)
  2. 各家私有插件:ChatGPT Plugins、Claude Tools 互不兼容

MCP(Model Context Protocol) 是一个开放协议:工具/数据的提供方实现一次 MCP Server,任何支持 MCP 的 MCP Host(Claude Desktop、Cursor、Cline、各 IDE 与 Agent 框架)都能直接用——把 M×N 变成 M+N。

MCP 架构

┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ Claude Desktop │   │    Cursor     │   │  你的 Agent    │      MCP Hosts
└──────┬────────┘   └──────┬────────┘   └──────┬────────┘
       │      MCP Client(随 Host 内置)        │
       └────────────┬───────┴───────────────────┘
                    ▼  JSON-RPC 2.0(stdio / Streamable HTTP)
       ┌────────────────────────────────┐
       │           MCP Servers          │
       │  filesystem │ github │ postgres│
       │  browser    │ 搜索    │ 自研系统 │
       └────────────────────────────────┘

1.1 为什么是"USB-C"比喻

USB-C 之前,每个设备有自己接口(充电、投屏、传输各一根线)。USB-C 统一了物理与协议,一根线通吃。MCP 同理:在它之前,每个 AI 应用想接数据库、文件系统、API,都要各自写一套适配;MCP 统一了"AI 应用 ↔ 工具/数据"的接口,一次实现、处处可用。

1.2 三角色模型(Host / Client / Server)

MCP 的架构是清晰的三层,理解这三个角色是理解整个协议的前提:

角色 是什么 职责
MCP Host 运行 AI 应用的宿主(Claude Desktop、Cursor、VS Code、你的 Agent 程序) 承载 LLM、承载用户交互、内置一个或多个 MCP Client、决定何时调哪个工具
MCP Client 随 Host 内置的协议客户端(每个 Server 对应一个 Client 实例) 与某个 Server 建立 1:1 连接、收发 JSON-RPC、做能力协商
MCP Server 提供能力的服务进程(filesystem、github、你自研的 xxx) 实现 tools/resources/prompts 等原语,真实对接后端系统

关键点:一个 Host 可连多个 Server,但 Client 与 Server 是 1:1 一对一。Host 内部为每个 Server 起一个 Client,Client 之间互不干扰。

Host (Claude Desktop)
 ├── Client A ──► Server A (filesystem)     1:1
 ├── Client B ──► Server B (github)         1:1
 └── Client C ──► Server C (postgres)       1:1

二、协议基础:JSON-RPC 2.0

MCP 的传输内容全部是 JSON-RPC 2.0 消息。它是一种极简的远程调用格式,每条消息是一个 JSON 对象。三种基本消息类型:

2.1 请求(Request)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "city": "北京" }
  }
}
  • jsonrpc: 固定 "2.0";
  • id: 请求编号,用于匹配响应(通知没有 id);
  • method: 方法名,MCP 用 / 分隔命名空间(如 tools/list、resources/read);
  • params: 参数对象(可省略)。

2.2 响应(Response)

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "北京:晴,26°C" }]
  }
}

成功时带 result,失败时带 error(不含 result):

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32602, "message": "参数 city 缺失" }
}

2.3 通知(Notification)

没有 id 的消息,单向、不需要回复。常用于进度通知、日志:

{ "jsonrpc": "2.0", "method": "notifications/message", "params": { "level": "info", "data": "开始查询" } }

2.4 MCP 的方法命名空间

命名空间 用途
initialize / ping / shutdown 生命周期
tools/list tools/call 列出/调用工具
resources/list resources/read resources/templates/list 列出/读取资源
prompts/list prompts/get 列出/获取提示模板
sampling/* Server 反向请求 LLM(客户端侧原语)
logging/* notifications/* 日志与通知

三、三大核心原语

MCP Server 可以向 Host 暴露三类能力:

原语 说明 例子
Tools(工具) 模型主动调用的函数,用于产生副作用或取数 查询数据库、发邮件、执行 SQL
Resources(资源) 应用控制的只读上下文,用 URI 标识 file:///logs/app.log、db://users/schema
Prompts(提示模板) 用户主动选择的预置提示 /review-pr、/summarize-table

另有两个客户端侧原语:Sampling(Server 反过来请求 Host 的 LLM 生成)与 Roots(限定文件系统访问范围)。

3.1 Tools(工具)——模型主动调

  • 由模型决策调用:Host 把 tool 的 schema 暴露给 LLM,模型认为需要时调用;
  • 有副作用或取数:执行 SQL、调 API、写文件都算 tool;
  • 结构:name + description + inputSchema(JSON Schema)+ 返回 content(文本/图片/资源等)。

3.2 Resources(资源)——应用控制的只读上下文

  • 由应用/HOST 决定何时注入:不靠模型"调",而是 Host 把资源内容作为上下文喂给模型(例如用户打开一个文件,Host 自动把 file:// 资源读进来);
  • URI 标识:file:///logs/app.log、db://users/schema、screen://1;
  • 只读:资源不应产生副作用,是"给模型看的资料";
  • 支持模板(template):如 file:///{path} 表示一类资源,由参数实例化。

Tools 与 Resources 的区别一句话:Tool 是"让模型动手",Resource 是"给模型资料"。

3.3 Prompts(提示模板)——用户主动选择

  • 由用户触发:用户在 UI 里点一个 /review-pr 按钮,Host 套用模板生成一段提示词给模型;
  • 是预置的工作流提示,不是模型自动决策;
  • 可带参数(如 PR 编号),返回一段构造好的消息序列。

3.4 客户端侧原语:Sampling 与 Roots

  • Sampling(采样):让 Server 反过来请求 Host 的 LLM 生成文本。例如一个"翻译 Server"自身不装模型,调用 Host 的 LLM 完成翻译。需要 Host 授权,体现"Host 掌握模型"的安全边界。
  • Roots(根):客户端告诉 Server"你只允许访问这些文件系统根目录"(如 /home/user/project),Server 在限定范围内工作,是安全隔离机制。

四、传输层:stdio 与 Streamable HTTP

4.1 stdio(本地子进程)

  • Server 作为 Host 启动的子进程 运行,通过 标准输入 stdin / 标准输出 stdout 收发 JSON-RPC 行;
  • 适合本地、同机工具(文件系统、本地数据库、CLI 封装);
  • 配置简单:Host 只需知道"用什么命令启动这个 Server";
  • 无网络、无鉴权负担,进程随 Host 生命周期管理。
{ "mcpServers": { "demo": { "command": "python", "args": ["server.py"] } } }

4.2 Streamable HTTP(远程传输)

协议迭代说明:早期规范用 SSE(Server-Sent Events) 做服务端推送、用 HTTP POST 发请求(即 "SSE + HTTP" 双通道)。后续规范演进为 Streamable HTTP,把请求与流式响应统一到一个可断连重连的 HTTP 端点,更易穿透代理/负载均衡、更易扩展。本文按"stdio 与 HTTP/SSE 流式传输"两条主线讲解,具体端点形态以官方最新规范为准。

  • Server 跑在远程,通过 HTTP 通信;
  • 服务端→客户端流式推送用 SSE(或 Streamable HTTP 的流式响应),用于长任务进度、分段返回;
  • 适合远程、需鉴权、可水平扩展的 Server(SaaS 工具、团队共享服务);
  • 需处理鉴权(API Key / OAuth)、会话管理(session id)、断线重连。
维度 stdio HTTP / SSE(Streamable HTTP)
部署位置 本地子进程 远程服务
--- --- ---
鉴权 不需要(同机) 需要(API Key/OAuth)
流式推送 逐行 stdout SSE / 流式 HTTP 响应
可扩展性 单用户本地 可多用户、可负载均衡
典型场景 文件系统、本地 CLI 远程 API、团队共享服务

4.3 capability 协商(能力协商)

连接建立后,双方通过 initialize 握手声明各自支持的能力,避免"调了对方不支持的方法"。

交互流程:

// 1) Client → Server:initialize 请求,声明自己支持什么
{
  "jsonrpc": "2.0", "id": 1, "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",        // 协议版本
    "capabilities": { "tools": {}, "resources": {}, "prompts": {} },  // Client 侧能力
    "clientInfo": { "name": "my-host", "version": "1.0" }
  }
}

// 2) Server → Client:响应,声明自己能提供什么
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": {}, "resources": {}, "logging": {} },  // Server 支持 tools/logging,无 prompts
    "serverInfo": { "name": "demo-server", "version": "0.1" }
  }
}

// 3) Client → Server:initialized 通知,握手完成
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

协商的意义:Server 若未声明 prompts,Host 就不会向模型展示 prompt 功能;版本不匹配时双方可降级到共同支持的协议版本。能力协商让生态向后兼容、可渐进演进。

五、写一个 MCP Server(Python)

官方 SDK:pip install "mcp[cli]"(Python)、@modelcontextprotocol/sdk(TS)。用 FastMCP 十几行就能发布一个工具:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("demo-server")

@mcp.tool()
def get_weather(city: str) -> str:
    """查询指定城市的当前天气"""
    # 真实场景这里调天气 API
    return f"{city}:晴,26°C"

@mcp.resource("file://notes/{name}")
def read_note(name: str) -> str:
    """暴露只读资源"""
    return open(f"./notes/{name}.md", encoding="utf-8").read()

if __name__ == "__main__":
    mcp.run()          # 默认 stdio 传输

在 Claude Desktop / Cursor 的 MCP 配置里注册后重启 Host,模型即可自动发现并调用 get_weather——无需写任何 Host 侧代码。

{
  "mcpServers": {
    "demo": {
      "command": "python",
      "args": ["server.py"]
    }
  }
}

5.1 完整 Server 示例(带 tools / resources / prompts + 异步)

下面给一个更接近生产的完整例子,覆盖三类原语,并演示异步、结构化返回、错误处理。

import asyncio
import json
from mcp.server.fastmcp import FastMCP

# 1) 创建 Server 实例,命名会展示在 Host 里
mcp = FastMCP("knowledge-base")

# 2) 一个内存里的"数据库",模拟真实后端
NOTES = {
    "todo": "# 待办\n- 写 MCP 文档\n- 修复天气工具",
    "meeting": "# 会议纪要\n- 确定采用 MCP 接入内部系统",
}


# ── Tools:模型主动调用,可能产生副作用 ─────────────────────
@mcp.tool()
def search_notes(keyword: str) -> str:
    """按关键词检索笔记,返回匹配的笔记标题列表。"""
    hits = [title for title, body in NOTES.items() if keyword in body]
    return f"命中: {hits}" if hits else "无匹配"


@mcp.tool()
def add_note(title: str, content: str) -> str:
    """新增一条笔记。title 为标题,content 为正文。"""
    if not title:
        return "错误:title 不能为空"          # 简单校验,错误信息会回传给模型
    NOTES[title] = content
    return f"已保存笔记:{title}"


# ── Resources:只读上下文,由 Host 决定何时注入 ──────────────
@mcp.resource("file://notes/{name}")
def read_note(name: str) -> str:
    """按名称读取一条笔记内容(只读)。"""
    return NOTES.get(name, f"笔记不存在:{name}")


# ── Prompts:用户主动触发的预置提示模板 ─────────────────────
@mcp.prompt()
def summarize_note(name: str) -> str:
    """生成"总结某条笔记"的提示词,用户在 UI 选择后触发。"""
    body = NOTES.get(name, "")
    return f"请用不超过 3 句话总结下面这条笔记:\n\n{body}"


# 3) 入口:默认 stdio;可传 transport="sse" 走 HTTP/SSE
if __name__ == "__main__":
    mcp.run(transport="stdio")

要点讲解:

  • @mcp.tool() 装饰的函数,函数签名 + docstring 自动变成 tool schema(名称来自函数名,参数来自签名,描述来自 docstring),Host 拿去给模型看;
  • 返回值是字符串(或结构化 content),会被包成 result.content 回传;
  • Resource 用 URI 模板 file://notes/{name},{name} 由调用方填;
  • @mcp.prompt() 返回构造好的提示文本;
  • mcp.run(transport="stdio") 启动本地子进程模式。

5.2 跑一个远程 Server(HTTP / SSE)

若要让团队共享一个 Server,用 Streamable HTTP(或早期 SSE)模式启动:

# server_http.py —— 通过 http 暴露(具体端点形态参考官方最新规范)
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("remote-kb")

@mcp.tool()
def search_notes(keyword: str) -> str:
    return f"远程命中: {keyword}"

if __name__ == "__main__":
    mcp.run(transport="streamable-http")   # 或早期 transport="sse"

Host 端配置改为指向 URL 并带鉴权:

{
  "mcpServers": {
    "remote-kb": {
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer <API_KEY>" }
    }
  }
}

5.3 调试与踩坑

  • Server 启动就崩:stdio 模式下 Host 靠子进程 stdout 通信,绝不能在 Server 里往 stdout 打印调试信息(print 会污染协议流),日志必须走 stderr 或文件。
  • 工具不出现:检查装饰器是否用到(@mcp.tool() 别忘了括号)、函数是否在 mcp.run() 前定义、Host 是否真的重连了该 Server。
  • 模型不调用你的工具:多半是 description 写得太含糊,模型不知道何时该用。把"何时调用、参数是什么"写清楚。
  • 参数类型:schema 参数尽量用基础类型(str/int/bool),复杂嵌套结构模型填参易错。
  • 长任务:耗时操作建议异步 + 进度通知(notifications/*),否则 Host 可能以为 Server 卡死。
  • 权限:访问文件系统/网络的 Server 务必在最小权限下运行,避免被模型误用造成破坏。

六、生态现状(2026)

  • 官方与社区 Server:filesystem、git/github、postgres/sqlite、puppeteer/playwright(浏览器)、fetch、记忆(memory)、sequential-thinking 等数百个
  • Host 全面支持:Claude 全家桶、Cursor、Windsurf、VS Code Copilot、Cline、DeepSeek Harness 等
  • 国内模型:Qwen、GLM、DeepSeek 的官方 Agent 框架均已支持 MCP 工具接入
  • 企业落地模式:把内部系统(工单、CRM、数据仓库)封装成 MCP Server,一次投入、所有 AI 客户端通用

6.1 为什么企业喜欢 MCP

  • 一次封装,处处可用:把内部 ERP/CRM/数据仓库封装成一个 MCP Server,Claude Desktop、Cursor、自研 Agent 全部直接接入,无需每端重写胶水;
  • 安全边界清晰:Roots 限定访问范围、Sampling 让模型留在 Host 侧、Server 可独立做权限审计;
  • 可被治理:工具清单可集中登记、版本化、下线,比散落各处的私有插件好管理。

七、MCP 与 Function Calling 的关系

容易混淆的一点:MCP 不替代 Function Calling,而是建立在它之上。模型侧看到的仍然是工具 schema(Function Calling 机制);MCP 解决的是"工具从哪里来、如何标准化发现与调用":

模型 ──(Function Calling)──► Agent/Host ──(MCP 协议)──► MCP Server ──► 真实系统

即:Function Calling 是模型能力,MCP 是工具分发协议。

7.1 两者的分工(关键)

维度 Function Calling MCP
层级 模型能力(LLM API 层面) 传输/分发协议(应用架构层面)
解决 "模型如何结构化表达'我想调函数'" "工具在哪、怎么发现、怎么标准化调用"
范围 单次请求内的工具调用 跨进程、跨服务的工具/资源/提示治理
关系 是 MCP 的"最后一公里" 建立在 Function Calling 之上

通俗讲:Function Calling 让模型"会说我要调工具",MCP 让"工具真正能被找到并调到"。Host 从 MCP Server 拉来 tool schema,把它转成模型能懂的 Function Calling schema 喂给 LLM;模型返回 tool_call 后,Host 通过 MCP 协议把调用送回对应 Server 执行,再把结果回填模型。

7.2 一个端到端串联示意

┌─────────────────────────────────────────────────────┐
│ Host (如 Claude Desktop)                              │
│   1. 启动时连 MCP Server,list_tools 拿到 schema       │
│   2. 把 schema 转成 Function Calling 的 tools 列表      │
│   3. 用户提问 → 模型根据 schema 返回 tool_call          │
│   4. Host 通过 MCP 把 tool_call 发回 Server 执行        │
│   5. Server 返回 result → Host 回填模型 → 模型作答      │
└─────────────────────────────────────────────────────┘
        │  MCP (JSON-RPC over stdio/HTTP)
        ▼
   MCP Server (filesystem/github/postgres/自研)

八、协议消息全流程示例(tools/call)

把前面各层串起来,看一次完整的"查天气"调用在 MCP 里长什么样:

// [Host] Client 列出可用工具
{ "jsonrpc": "2.0", "id": 10, "method": "tools/list" }

// [Server] 返回工具清单(schema 即 Function Calling 所需)
{ "jsonrpc": "2.0", "id": 10,
  "result": { "tools": [
    { "name": "get_weather", "description": "查询城市天气",
      "inputSchema": { "type": "object",
        "properties": { "city": { "type": "string" } }, "required": ["city"] } }
  ] } }

// [Host] 模型决定调用,Client 发出 tools/call
{ "jsonrpc": "2.0", "id": 11, "method": "tools/call",
  "params": { "name": "get_weather", "arguments": { "city": "北京" } } }

// [Server] 执行后返回(content 数组支持 text/image/resource)
{ "jsonrpc": "2.0", "id": 11,
  "result": { "content": [ { "type": "text", "text": "北京:晴,26°C" } ] } }

注意 result.content 是数组,元素可多种类型——文本、图片、甚至内嵌 resource 引用。这让 MCP 工具能返回富结果(如截图、文件内容),远比单一字符串灵活。

九、安全与最佳实践

  1. 最小权限:Server 只申请它真正需要的访问范围(配合 Roots);
  2. 危险操作审批:写文件、发请求、执行命令的 tool,Host 侧应有人工确认;
  3. 日志隔离:stdio Server 的日志走 stderr/文件,绝不污染 stdout;
  4. 输入校验:tool 内部对参数做校验,错误信息清晰回传模型以便自愈;
  5. 能力按需暴露:Server 只在 initialize 声明自己真支持的原语,避免 Host 误用;
  6. 版本兼容:initialize 时协商 protocolVersion,新旧 Host/Server 才能共存;
  7. 远程 Server 鉴权:HTTP 模式必须 API Key/OAuth,不在公网裸奔。

十、参考

十一、小结

MCP 给"AI 应用 ↔ 工具/数据"提供了一套开放、标准的接口,把过去每个应用各写一套胶水的 M×N 难题,降维成 M+N。理解它要抓住三条主线:

  1. 三角色架构:Host(承载 LLM 与用户)内置 Client,与每个 Server 1:1 连接,分层清晰、互不干扰;
  2. JSON-RPC 2.0 消息 + 能力协商:所有通信是结构化 JSON,initialize 握手声明彼此支持的原语与版本,保证向后兼容;
  3. 三类原语分工:Tools(模型主动调、有副作用)、Resources(Host 注入的只读资料)、Prompts(用户触发的模板),再加 Sampling/Roots 两个客户端侧安全原语。

传输上,stdio 适合本地、HTTP/SSE 适合远程,按部署位置选择。工程落地时,MCP 不替代 Function Calling,而是站在它之上——Function Calling 是模型"表达要调工具"的能力,MCP 是"工具从哪来、怎么标准化发现与调用"的协议,二者一个在模型层、一个在架构层,互补而非互斥。

落地 MCP 的最大收益在企业内部系统接入:把工单、CRM、数据仓库封装成 Server,一次投入、所有 AI 客户端通用,且安全边界(Roots/Sampling/审批)清晰、可被集中治理。踩坑多集中在 stdio 日志污染、schema 描述不清、长任务无进度通知——按本文 5.3 与第九节的实践清单即可规避。

posted @ 2026-09-19 05:43  Stellan7  阅读(32)  评论(0)    收藏  举报