程云来 / 杭州
Back to Blog·MCP
·About 17 min

MCP 是什么:让 AI 连接工具之前,先看懂这条协议

从一个 AI 助手无法访问业务数据的现场出发,拆开 MCP 的 Host、Client、Server、Tools、Resources 和 Prompts,再用一次完整调用解释它与函数调用、Agent、REST API 的区别。


适用版本:MCP 官方规范 2026-07-28(资料核对:2026-08-31)。文中的 TypeScript 代码用于说明协议形状,不绑定某个 SDK。

AI 说“我做不到”时,缺的是什么

让 AI 助手总结一篇文章,它可以直接生成文字;让它回答“我在 GitHub 上还有哪些未处理的 issue”,情况就不同了。

模型不知道你的仓库地址,也没有 GitHub token,更不会凭空访问公司的数据库。

应用当然可以为它单独写一个 GitHub 插件,再为另一个模型客户端重写一遍。插件越来越多以后,连接方式、参数描述和权限确认都会各自长成一套。

MCP(Model Context Protocol)要解决的,正是这段连接关系:让 AI 应用用一套公开协议发现外部能力,并按统一消息格式获取上下文或调用工具。1

先把三类参与者摆在桌面上

MCP 不是“模型直接连数据库”。一次连接至少包含三个角色:

正在绘制图表…
查看 Mermaid 源码
flowchart LR
    user[用户] --> host[MCP Host<br/>AI 应用]
    host --> model[模型]
    host --> client1[MCP Client]
    host --> client2[MCP Client]
    client1 --> server1[MCP Server<br/>GitHub]
    client2 --> server2[MCP Server<br/>业务数据库]
    server1 --> github[GitHub API]
    server2 --> db[(数据库)]

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

  1. Host:Claude Desktop、Claude Code 或你自己写的 AI 应用,负责协调模型、用户界面和一个或多个 MCP Client。
  2. Client:由 Host 创建,每个 Client 只维护与一个 Server 的连接,并代为发送 MCP 请求。
  3. Server:接近真实系统的一侧,可以读取文件、调用 API、查询数据库,或者执行一段计算。

模型只提出“我想调用 search_issues”,真正发出 MCP 请求的是 Host 中的 Client;Server 不直接读取模型状态。

从三个角色到一条消息:MCP 规定了什么

上一节回答了“谁和谁连接”,但还没有回答“连接时说什么”。MCP 规定的是角色之间的共同消息语言:数据层定义 JSON-RPC 消息、能力发现和结果形状。2

传输层只负责把这些消息送到目标进程或网络端点。Server 内部可以使用 Python、TypeScript 或 Go,消息仍然可以通过本地 stdio 或远程 Streamable HTTP 传递。

把一次最小交互画出来,三个角色就能和具体方法对上:

正在绘制图表…
查看 Mermaid 源码
sequenceDiagram
    participant H as Host
    participant C as MCP Client
    participant S as MCP Server
    H->>C: initialize
    C->>S: tools/list
    S-->>C: 工具名称与输入 schema
    C->>S: tools/call
    S-->>C: CallToolResult

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

initialize 协商协议版本和双方能力;握手完成后,Client 用 tools/list 发现工具,再用 tools/call 发起调用。Server 返回 MCP 规定的内容块,而不是某个厂商的私有对象。

Server 能声明的能力,正是围绕这组消息组织起来的:

能力发现与使用的消息谁驱动返回或产生什么
Toolstools/listtools/call模型通过 Host 请求一次计算或操作的 CallToolResult
Resourcesresources/listresources/readHost / 应用决定读取带 URI 的文件、schema 或文档内容
Promptsprompts/listprompts/get用户选择模板一组可传参的消息模板

“Server 暴露能力”具体指三组可发现、可调用的方法。下一节先追踪 Tools 的完整调用;Resources 和 Prompts 使用相同的发现思路,但由不同的方法承载。3 4 5

跟着一次 search_issues 调用走一遍

假设用户问:“帮我找出 rhapsody-site 里本周还没关闭的 issue。”一次调用大致会经过下面的顺序:

正在绘制图表…
查看 Mermaid 源码
sequenceDiagram
    participant U as 用户
    participant H as Host + 模型
    participant C as MCP Client
    participant S as GitHub MCP Server
    participant G as GitHub API
    U->>H: 查找本周未关闭的 issue
    H->>C: initialize(协商版本与能力)
    C->>S: tools/list
    S-->>C: search_issues 的名称、说明、输入 schema
    C-->>H: 可用工具清单
    H->>H: 模型选择 search_issues
    H->>C: tools/call(name, arguments)
    C->>S: JSON-RPC 请求
    S->>G: 调用 GitHub API
    G-->>S: issue 列表
    S-->>C: CallToolResult
    C-->>H: 结果进入模型上下文
    H-->>U: 汇总后的回答

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

tools/list 让 Host 在运行时发现工具;tools/call 只携带工具名和参数,Server 再决定怎样访问 GitHub。模型不需要拿到 GitHub token,也不必理解 GitHub API 的响应结构。

消息长什么样

协议层只需要一组稳定的输入和输出。下面的 TypeScript 类型刻意只保留解释调用链所需的字段:

typescript
type Tool = {
  name: string;
  description?: string;
  inputSchema: Record<string, unknown>;
};

type CallToolRequest = {
  method: "tools/call";
  params: {
    name: string;
    arguments?: Record<string, unknown>;
  };
};

type CallToolResult = {
  content: Array<{ type: "text"; text: string }>;
  isError?: boolean;
};

Tool.inputSchema 描述调用参数的形状;CallToolRequest 携带工具名和参数;CallToolResult.content 承载文本、图片或资源,isError 标记工具执行失败。

在线上传输时,这些字段会放进 JSON-RPC 2.0 的 request / response envelope。为了让调用关系容易阅读,下面省略了规范要求的请求元数据(例如版本、客户端信息和能力声明):

json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "search_issues",
    "arguments": {
      "repository": "rhapsody-site",
      "state": "open",
      "created_after": "2026-08-24"
    }
  }
}

Server 返回的 result 只需要是 MCP 规定的内容块。对文本工具来说,Host 可以把 content[0].text 放入下一次模型上下文。

对文件、图片或嵌套资源,Host 应使用对应的内容类型,而不是把所有东西拼成一段字符串。

stdio 和 Streamable HTTP 只是两条路

同一套 MCP 数据层可以放在不同传输上:

传输连接方式适合场景需要额外处理
stdioHost 启动本地 Server 子进程,JSON-RPC 走 stdin/stdout本地文件、开发工具、个人环境stdout 只能输出协议消息;日志写 stderr
Streamable HTTPClient 通过一个 HTTP endpoint 与远程 Server 通信,可按需使用 SSE团队服务、云端数据、多个客户端共享鉴权、Origin 校验、会话和断线恢复

换传输时,tools/listtools/call 的语义不变;SDK 只负责握手、序列化和连接管理。

找一个可以拆开的 Server

要把消息和源码对应起来,先选一个能看到实现、能在本机启动的 Server。

我建议先用 MCP 官方的 Everything Server

它是参考实现,不以生产部署为目标,却把 Tools、Resources、Prompts 等能力集中在一个小项目里。

它不需要 GitHub token、数据库账号或业务数据,直接用 npx 启动即可:

bash
npx -y @modelcontextprotocol/server-everything

阅读源码时,可以沿着这条线索走:

正在绘制图表…
查看 Mermaid 源码
flowchart LR
    srv[Server 源码] --> reg[注册能力]
    reg --> discover[Client 发现能力]
    discover --> invoke[Client 发起调用]
    invoke --> handle[Server 处理函数]
    handle --> output[返回执行结果]

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

例如,Client 先列出工具,再调用一个回显工具。下面的代码使用官方 TypeScript SDK 的 v1 写法;SDK v2 已拆分为新的包名,实际项目应锁定版本并按对应文档调整导入路径:

typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "npx",
  args: ["-y", "@modelcontextprotocol/server-everything"],
});

const client = new Client({ name: "learning-client", version: "0.1.0" });
await client.connect(transport);

const { tools } = await client.listTools();
console.log(tools.map((tool) => tool.name));

const result = await client.callTool({
  name: "echo",
  arguments: { message: "Hello MCP" },
});
console.log(result);

await client.close();

这段 Client 没有导入 Everything 的内部模块,只依赖 MCP 的连接和消息。阅读 Server 源码时,可以把工具注册处对应到 listTools 的返回值,把处理函数对应到 callTool 的结果。

如果希望把练习拆成更小的台阶,官方仓库还有几个可以直接运行的 Server:

  1. Time(Python):只有时区工具。
  2. Fetch(Python):展示 URL 参数和分段读取。
  3. Filesystem(Node.js):展示目录 allow-list 与写入权限。
  4. Memory(Node.js):展示 Resource 和更新通知。

它们的源码、README 和启动命令都在同一个公开仓库中。

远程连接也有公开例子,但含义要分清。

  1. GitMCP 提供 https://gitmcp.io/{owner}/{repo},可匿名读取公开 GitHub 仓库。
  2. GitHub MCP Server 的托管地址 https://api.githubcopilot.com/mcp/ 虽然公开可访问,实际调用仍需 OAuth 或 PAT。

学习第一条连接路径时,使用本地 Everything 更容易定位问题。掌握 tools/listtools/call 后,再把 transport 换成远程 HTTP,协议层的调用语义仍然不变。

MCP 不是函数调用,也不是 Agent 框架

这几个概念经常一起出现,但解决的层次不同:

概念主要回答MCP 与它的关系
函数调用(Function Calling)模型如何产出一个函数名和参数MCP 的 Tool 可以被翻译成函数调用,但函数调用本身不规定 Server 如何发现、连接和返回结果
Agent 框架多步推理、状态、重试和人工介入如何编排Agent 可以把 MCP 当作工具来源;MCP 不负责规划循环和持久化
REST API服务之间如何设计资源和 HTTP 接口MCP Server 内部可以调用 REST API,再把能力包装成 Tools 或 Resources
MCPAI 应用如何发现并使用外部上下文和能力它位于“应用与外部能力之间”的协议层

如果只有一个后端函数和一个调用方,直接使用函数调用或普通 HTTP 往往更简单。

Server 能做事,不代表 Host 应该放行

MCP 统一了连接方式,却没有替你完成授权。delete_filecreate_ticketsend_email 都可能是合法的 Tool,但它们的风险完全不同。

一个可用的 Host 至少要把这几件事分开:

  1. 发现:Server 声明了哪些工具和参数;
  2. 授权:当前用户和当前会话是否允许调用;
  3. 确认:调用会产生外部副作用时,是否需要用户明确批准;
  4. 审计:记录谁在什么时间、以什么参数调用了哪个工具。

本地 stdio Server 需要限制子进程的文件和环境变量权限;远程 Streamable HTTP Server 需要认证、Origin 校验和会话管理。

不要因为工具出现在 tools/list 结果里,就把它当成可信操作。安全边界仍然属于 Host、Server 和业务系统的实现责任。6

什么时候值得用 MCP

可以先问三个问题:

  1. 这个能力是否会被两个以上的 AI 应用使用?
  2. 工具、资源或提示模板是否需要动态发现,而不是写死在一个 prompt 里?
  3. 是否愿意为权限确认、超时、审计和版本兼容维护一层协议边界?

三个问题大多回答“是”,MCP 值得评估;如果只是一个页面调用一个内部 API,直接使用现有接口通常更省事。

MCP 解决的是互操作性,不会自动解决数据质量、权限设计和工具本身的业务错误。

回到开头的 issue 查询:Host 创建 Client,Client 发现并调用 search_issues,Server 访问 GitHub,再把结果以统一内容块交回 Host。MCP 统一的正是这条连接关系。

参考资料

Footnotes

  1. MCP Architecture overview:MCP 聚焦 AI 应用与外部上下文之间的协议交换,不规定应用如何使用 LLM 或管理上下文。

  2. MCP Transports:规范定义 stdio 与 Streamable HTTP 等传输方式,传输承载的是同一数据层消息。

  3. MCP Tools:Tools 由 Server 暴露,供语言模型调用外部系统、API、数据库或计算能力。

  4. MCP Resources:Resources 通过 URI 暴露文件、schema 等上下文,设计为应用驱动。

  5. MCP Prompts:Prompts 是 Server 提供、用户选择并可传参的结构化提示模板。

  6. MCP Authorization 与各传输规范中的安全要求:认证、Origin 校验、权限范围和会话管理需要由实现负责。