MCP(Model Context Protocol)

MCP 是一套开放协议,用来规范“模型/Agent 如何连接外部能力”。可以把它理解成 AI 世界的 USB-C:以前每个工具、每个数据源都要为每个客户端单独写一遍对接代码(M 个模型 × N 个工具 = M×N 种适配),有了统一接口后,工具只要实现一次 MCP,就能被所有支持 MCP 的客户端使用(M+N)。

本质:MCP 不是模型,也不是某个具体工具,而是工具与模型之间的通信标准。它解决的是“接入方式碎片化”的问题,而不是“要不要用工具”的问题。

关联:工具调用(tool call)解决的是“模型能不能影响外部世界”,见 tool_calling.md。MCP 更进一步,解决“这些工具如何以统一、可复用的方式被接入”。

为什么需要 MCP

没有统一协议时的痛点:

  • 每个 Agent 框架都有自己的一套工具定义方式,工具无法跨平台复用。
  • 每接一个新数据源(数据库、文件系统、第三方 API),都要重写一遍胶水代码。
  • 工具的发现、鉴权、调用格式各不相同,难以维护。

MCP 把这些约定标准化,工具作者写一次 MCP Server,就能被 Claude、Kiro、各类 IDE 和 Agent 直接调用。

核心架构

MCP 采用 Client-Server 架构:

  • Host(宿主):用户实际使用的应用,例如 IDE、聊天客户端。它内部集成了模型。
  • MCP Client(客户端):由 Host 启动,和某个 Server 建立一对一连接,负责收发协议消息。
  • MCP Server(服务端):真正提供能力的一方,把具体功能按协议暴露出来。一个 Server 可以是本地进程,也可以是远程服务。
Host (含模型)
  └── MCP Client ──── 协议 ──── MCP Server ──── 外部能力(DB / API / 文件系统 …)

Server 能提供的三类能力

  • Tools(工具):可被模型调用的动作,例如查询数据库、发起请求。对应传统的 tool call。
  • Resources(资源):可被读取的上下文数据,例如文件内容、数据库记录,通常由应用/用户控制加载。
  • Prompts(提示模板):预置的、可复用的提示词模板,供用户主动选用。

简单记:Tools 是“能做的动作”,Resources 是“能读的数据”,Prompts 是“能套用的模板”。

传输方式(Transport)

  • stdio:Server 作为本地子进程运行,通过标准输入输出通信。适合本地工具(如访问本地文件系统)。
  • HTTP / SSE:Server 作为远程服务运行,通过网络通信。适合云端、多用户共享的服务。

与普通工具调用的关系

维度普通 tool callMCP
定位单个框架内定义并调用工具跨框架、跨应用的统一接入协议
复用工具绑定在具体代码里,难迁移一次实现,多客户端复用
内容通常只有“动作”Tools + Resources + Prompts
连接进程内函数调用Client-Server,可本地可远程

MCP 底层依然依赖 tool call 那套“模型吐出符合 schema 的 JSON”的机制,见 tool_calling.md;MCP 只是把它标准化并加上了连接、发现、鉴权等约定。

使用场景

  • 想让同一个工具在多个 Agent / IDE 里复用,而不是每个平台重写一遍。
  • 需要把本地资源(文件、数据库)安全地暴露给模型。
  • 生态化:使用社区已经写好的 MCP Server(如文档检索、浏览器操作),无需自己开发。

应用示例(具体场景)

光看协议容易抽象,下面用几个真实场景说明 MCP 解决了什么、怎么用。

场景总览:用 MCP 前 vs 用 MCP 后

场景没有 MCP(各自为政)有 MCP(一次实现,处处可用)
读本地笔记/代码每个 Agent 各自写文件读写插件一个 filesystem Server,所有客户端共用
查数据库手写 SQL + 专属接口postgres Server 暴露查询工具,模型用自然语言查
操作网页自己封装浏览器脚本playwright Server 直接让模型点击、抓取、截图
管 GitHub调 REST API 写胶水代码github Server 让模型建 issue、查 PR、读 diff
检索文档/代码库每个产品内置一套 RAG一个检索 Server,IDE 和聊天端都能接

示例 1:文件系统 MCP —— 把”我的笔记文件夹”变成模型的工具

痛点:你希望 Agent 能读你 ~/notes 下的 Markdown,但又不希望它碰全盘文件。 做法:装一个 filesystem MCP Server,只把 ~/notes 这一个目录授权给它。

配置(以常见的 mcpServers 配置为例):

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/ava/notes"]
    }
  }
}

接上后,模型就能”读 ~/notes/foo.md""把总结写回 ~/notes/bar.md”,且越界访问会被 Server 拒绝。这就是把”外部能力”安全地标准化暴露。

示例 2:浏览器 MCP(Playwright)—— 让模型真正”打开网页”

这正好接上 ../web/headless-browser.md:无头浏览器的能力,通过一个 MCP Server 就变成了模型能直接调用的工具。

端到端流程

你:"去 example.com 把首页标题和首图链接给我"
  │
  ▼
Host(模型) 决定调用 playwright MCP 的 tool:navigate / screenshot / extract
  │
  ▼
MCP Client ──协议──▶ Playwright MCP Server
                          │ 启动无头 Chromium
                          │ 打开页面、执行 JS、截图、取 DOM
                          ▼
                    返回:标题="..."、图片url="..."
  │
  ▼
模型把结果整理成你的回答

好处:模型不再只能”猜”网页内容,而是像人一样真实加载并渲染页面,拿到 JS 跑完后的结果。

示例 3:GitHub MCP —— 用自然语言管仓库

:“帮我看看昨天提的那个 PR 有没有人 review。” 背后发生的事

  1. 模型从 GitHub MCP 的 tool 清单里选 list_pull_requests(或 get_review_comments)。
  2. MCP Client 把请求发给 GitHub MCP Server。
  3. Server 用你配置的 token 调 GitHub API,拿到 PR 列表和 review 状态。
  4. 结果回传模型,模型组织成:“你的 PR #123 已有 2 条 review 评论,其中 1 条要求改 login.ts……”

关键:你作为用户只在配置时授权一次 token,之后”查 PR / 建 issue / 读 diff”都是标准工具调用,换一个支持 MCP 的客户端不用重写。

示例 4:数据库 MCP —— 用大白话查数据

:“上个月的活跃用户比这个月少多少?” 没有 MCP:你打开 DB 客户端,自己写 SELECT ... WHERE date BETWEEN ...,再心算差值。 有 MCPpostgres Server 暴露了查询工具,并可读 schema(Resources)。模型生成查询、Server 执行(带权限约束)、返回数字,模型算出差值并解释。

安全点:Server 可以限制只能跑只读查询、只能访问特定库,避免模型误执行 DROP

这些例子共同的套路

无论文件、网页、GitHub 还是数据库,模式都一样:

用户用自然语言提需求
   ▼
模型挑一个 MCP Server 暴露的 tool
   ▼
MCP Client ↔ Server(协议通信)
   ▼
Server 调用真实外部能力(文件/API/浏览器/DB)
   ▼
结果回模型 → 组织成回答/动作

你作为使用者,关注点从”怎么写对接代码”变成了”选哪个 Server、授什么权”。

动手:写一个最小 MCP Server

光看例子不过瘾,下面用 Python 的官方 SDK(mcp)写一个能跑的最小 Server:暴露一个工具(加法)+ 一个资源(问候语),走 stdio 传输。

1. 安装

pip install "mcp[cli]"   # 含 FastMCP 与命令行工具

2. 写一个 demo_server.py

from mcp.server.fastmcp import FastMCP
 
# 创建一个 Server 实例,名字随便起
mcp = FastMCP("demo")
 
# —— Tools:模型能调用的"动作" ——
@mcp.tool()
def add(a: int, b: int) -> int:
    """把两个数相加,返回结果"""
    return a + b
 
# —— Resources:模型能读取的"数据" ——
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
    """按名字返回一句问候(name 由调用方传入)"""
    return f"你好,{name}!"
 
if __name__ == "__main__":
    mcp.run()   # 默认以 stdio 方式运行(作为子进程被 Host 拉起)

就这几行:用装饰器把普通函数标成 tool / resource,SDK 自动帮你生成协议所需的 schema、负责收发消息。你完全不用手写 JSON-RPC 细节

3. 把它接进客户端

在客户端的 mcpServers 配置里加一项,指向这个脚本:

{
  "mcpServers": {
    "demo": {
      "command": "python",
      "args": ["/绝对路径/demo_server.py"]
    }
  }
}

保存后客户端会自动拉起这个进程并通过标准输入输出通信,然后从 @mcp.tool() / @mcp.resource()发现你暴露的能力。

4. 之后怎么用

回到对话里(以本仓库的 Agent 为例):

  • 你说”算一下 3 加 5” → 模型发现 demoadd 工具 → 通过 MCP Client 调用 → Server 算出 8 → 模型回答。
  • 你说”用 demo 给我来句问候,我叫 ava” → 模型读 greeting://ava 资源 → 拿到”你好,ava!“。

运行时发生了什么

Host 启动 → 拉起 python demo_server.py(子进程)
   │
   ▼ 通过 stdin/stdout 走协议
Host 问:"你有哪些 tool / resource?"  → Server 回:add、greeting://{name}
   │
   ▼ 用户提需求时
模型决定调用 add(3,5) → Client 发包给 Server → Server 执行函数 → 回传 8

关键点:你写的只是”能力本身”(一个加法函数、一句问候),协议、发现、传输、鉴权约定全由 SDK 和客户端接管。换一个支持 MCP 的客户端,这个 Server 不用改一行就能复用——这正是 MCP 的价值。

备选:TypeScript / Node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
 
const server = new McpServer({ name: "demo", version: "1.0.0" });
server.tool("add", { a: z.number(), b: z.number() },
  async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] }));
 
const transport = new StdioServerTransport();
await server.connect(transport);

思路一样,只是用 zod 显式声明参数 schema。

工具是怎么”export”给模型的(发现 + 翻译)

前面写过最小 Server,但”模型怎么知道有哪些 tool”这件事还没拆开。它分两步:发现(discovery)翻译(translation)

1. 发现:Server 自曝能力(MCP 协议内部)

Host 拉起 Server 后,MCP Client 会主动发 JSON-RPC 请求去”问”Server 有哪些能力;Server 把能力清单回传:

Client ── tools/list ──────────▶ Server
Client ◀── [{name, description, inputSchema}, ...] ── Server

Client ── resources/list / prompts/list ──▶ Server   (同样的机制)

Server 回的每条 tool 定义,正是 schema.md 第 4 节说的 (B) 工具参数 schema(JSON Schema)

{
  "name": "add",
  "description": "把两个数相加",
  "inputSchema": {
    "type": "object",
    "properties": { "a": {"type": "integer"}, "b": {"type": "integer"} },
    "required": ["a", "b"]
  }
}

这一步对应「动手」第 4 步的”发现机制”,也是常见误区里”工具清单是运行时由 Server 连上后自报的”的底层实现——靠的就是 tools/list

2. 翻译:Client 把 MCP 格式改成模型要的格式

关键点——模型从不直接读 MCP 协议。 MCP 是 Client↔Server 之间的事;模型只认自家 API 的 function-calling 格式(如 OpenAI 的 tools 字段)。所以 Client 拿到 tools/list 的结果后,要翻译成模型 API 的格式再注入请求:

// 发给模型 API 的(以 OpenAI 兼容格式为例)
{
  "model": "gpt-4o",
  "messages": [ /* … */ ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "add",
        "description": "把两个数相加",
        "parameters": { "type":"object", "properties":{/* … */}, "required":["a","b"] }  // ← 直接从 inputSchema 搬来
      }
    }
  ]
}

这一步就是”export 给模型”的本质:Client 把 Server 的 inputSchema 原样塞进模型请求里的 tools[].function.parameters。模型看到的是一份”函数签名清单”,不是 MCP 报文。底层依旧是函数调用那套机制,见 tool_calling.md

3. 调用时再翻译回来

模型决定调 add(3,5) → 吐 tool_call JSON
   ▼
Client 按 schema 校验参数 → 翻译成 MCP 的 tools/call
   ▼
Server 执行真实函数 → 回 content
   ▼
Client 把结果包成 tool 消息 → 喂回模型 → 模型继续生成回答

一句话:MCP 通过 tools/list 把工具的 name + description + JSON Schema 暴露给 Client;Client 把它翻译成模型 API 的 function-calling 格式注入每次请求。模型只知道”有哪些函数、参数长啥样”,至于背后是 stdio 子进程还是远程 HTTP、用没用 MCP——模型一概不知。这正好印证常见误区:模型训练时不知道这些工具,全是运行时动态发现的。

MCP vs 自己写方法:什么时候需要配置 MCP

同一个外部能力(如浏览器、数据库)通常有两种用法,判断是否要配 MCP,看”调用方是谁”

  • 直接写代码(用库):在你的程序里 import 一个库(如 playwrightrequestspsycopg2),直接调函数。控制逻辑写在你的代码里,运行在你的进程里。
  • 配 MCP:把能力暴露成一个 MCP Server,让 AI 模型在运行时发现并调用。
调用方推荐方式原因
你自己写的脚本 / 程序直接写代码(用库)确定性强、可控、无额外进程与协议开销
AI 模型 / Agent 要灵活调用配 MCP模型运行时动态发现工具,跨客户端复用
多个 AI 客户端都要同一能力配 MCP一次实现,处处可用(M+N)
想让模型 / 非开发者自助选工具配 MCP标准化发现机制

以 Playwright 为例(两种用法不冲突)

  • 自己写方法(你之前的做法)pip install playwright → 脚本里 from playwright.sync_api import sync_playwright你当调用方,控制浏览器。不需要 MCP。
  • 配 MCP:起一个 Playwright MCP Server,把浏览器变成模型的工具,让 IDE 里的 AI 助手自己去开网页、点击、抓渲染后内容(见上文「应用示例 2」)。

一句话:MCP 是”给 AI 模型用的 USB-C 接口”;你自己写代码调库走的是普通 import,两者不互相替代。你项目从没配过 MCP,通常是因为调用方一直是”你写的代码”,而不是”模型”。

常见误区

  • “模型在训练时就知道有哪些流行的 MCP Server。” ✅ 模型训练时最多从网上文档学到”MCP 协议是什么”,但不知道你项目接了哪些 Server、暴露什么工具。工具清单是运行时由 Server 连上后”自报”的(见上文「动手」第 4 步的发现机制)。换个没见过的 Server,模型第一次也能用,因为它读的是实时返回的 schema,不是记忆里的名单。

  • “在 IDE 里『配置 MCP』是给模型上课,让它记住这些工具。” ✅ 配置只是一份静态接线清单(命令、参数、环境变量、授权 token、作用目录),告诉 IDE 启动时去拉起哪些 Server、怎么连。模型本身不”记住”任何工具;每次会话开始,IDE 按配置拉起 Server,模型再经协议动态发现可用能力。配置是”接线”,不是”训练”。

  • “MCP 让模型变聪明 / 学会新领域知识。” ✅ MCP 只负责”接上外部能力”,不往模型里塞知识。模型能不能用好一个工具,取决于它自身的推理与函数调用能力;MCP 解决的是”能不能方便地调到”这个问题。

  • “MCP 就是应用商城里的应用。” ✅ MCP 是协议/标准(类比 USB-C 规范),不是应用;“应用”对应的是 Skill,ClawHub 才是应用商店。见 skills.md

延伸阅读