Chapter 01

概念与架构:谁是 client,谁说了算

起点页给了一张拓扑图:host 里的 client 连 server,server 向下暴露三类 primitive,还有一条反向的红箭头。这一章把图上每个框拆开,建立 MCP 的词汇表,并钉死两个最容易搞反的点:client 不是 AI 应用,以及三类 primitive 按"控制权归谁"划分。

本章你将建立的 schema

  • MCP 解决的是 M×N 集成爆炸——降成 M+N,骨架借自 LSP
  • host / client / server 三个角色:client 在 host 内、与 server 严格 1:1
  • 沙箱隔离:server 看不到完整对话,也看不到别的 server
  • tools / resources / prompts 三类 primitive,按"谁发起调用"分(模型 / 应用 / 用户)

贯穿全章用同一个场景:你在给一个 AI 编程助手(host,比如 Cursor 或 Claude Desktop)接上一个 GitHub server,让助手能读 issue、查 PR、提交评论。下面每个概念,都用这个场景走一遍。

1.1为什么是 MCP:M×N 集成爆炸

MCP 是一套标准协议,让任意 LLM 应用与任意外部能力之间用同一种方式对接——把 M 个应用 × N 个工具的私有集成,降成 M+N 的协议实现。

为什么需要它

在 MCP 之前,每个 LLM 应用要接每个数据源 / 工具,都得写一份私有适配:Cursor 接 GitHub 一份、Claude Desktop 接 GitHub 又一份、接数据库再各写一份。M 个应用 × N 个工具 = M×N 份互不复用的胶水代码。加一个工具,所有应用都要改;换一个应用,所有集成重写一遍。这是组合爆炸。

底层机制(比文档深一层):MCP 的解法是插一层协议——工具方实现一次 server,应用方实现一次 client,两边都遵守同一套消息格式。于是 GitHub 写一个 server,所有支持 MCP 的 host 都能用;Cursor 实现一次 MCP client,就能接上所有 MCP server。集成数从 M×N 降到 M+N。

这个降维不是 MCP 发明的思路。它直接借自微软的 Language Server Protocol(LSP):编辑器世界曾有同样的爆炸——M 个编辑器 × N 种语言 = M×N 个语言插件;LSP 让每种语言写一个 language server、每个编辑器实现一次 LSP client,降成 M+N。MCP 的作者把 LSP 的三件套——JSON-RPC 消息、能力协商、双向请求——整套搬到了"AI 应用 × 外部能力"上。所以若你熟悉 LSP,MCP 的许多设计会眼熟:这不是巧合,是同一副骨架。这一点 Anthropic 的发布博客没写,只在作者访谈里讲过——也正是它,解释了后面 02 章为什么是 JSON-RPC、为什么要握手协商。

MCP 之前 MCP 之后 应用 1 应用 2 应用 3 工具 1 工具 2 工具 3 M × N 份私有集成 应用 1 应用 2 应用 3 MCP 协议 工具 1 工具 2 工具 3 M + N 份协议实现
图 1.1同一组 3 应用 × 3 工具:左边每对都要一份私有集成(9 条),右边各方只对接 MCP 一次(3+3 条)。注意:左边那团乱线就是重点——它是 M×N 的组合爆炸;MCP 把每一方"对接所有人"变成"对接协议一次"。这正是 LSP 当年对编辑器和语言做的事。

1.2host / client / server:三个角色

host 是宿主应用(持有 LLM 与对话),client 是 host 内为每个 server 开的连接器,server 是提供能力的独立进程;一个 client 恰好连一个 server。

为什么需要它

一个 host 通常要同时连多个 server——GitHub、文件系统、数据库。host 不亲自跟每个 server 对话,而是为每个 server 开一个独立的 client,每个 client 维护一条独立、隔离的会话。这样多个 server 互不干扰,host 还能站在中间统一管控授权与上下文。

底层机制(比文档深一层):这里有个最容易搞反的点——MCP 里的 "client" 不是你以为的那个 AI 应用。日常语境里 client 常指"用户面对的程序"。但在 MCP 里,用户面对的应用是 host(Cursor、Claude Desktop);client 是 host 内部的一个瘦连接器,每个 server 配一个。三者关系是 host : client : server = 1 : N : N,且 client 与 server 严格 1:1。要接 GitHub 和文件系统两个 server,host 就开两个 client,不是用一个 client 多路复用。把这层搞混,等到 03 章读 sampling(server 向 client 发请求)时,箭头方向会全反。

Host · 宿主应用 如 Cursor / Claude Desktop · 持有 LLM 与对话 Client A 连 GitHub server Client B 连文件系统 server GitHub Server 独立进程 文件系统 Server 独立进程 1:1 会话 · JSON-RPC 1:1 会话 互不可见
图 1.2一个 host 里开两个 client,各自 1:1 连一个 server。注意三点:① client 在 host 内部,不是独立的 AI 应用;② 连两个 server 就要两个 client,没有"一个 client 接多个 server";③ 两个 server 是隔离的独立进程,彼此看不见——这条隔离边界是 04 章攻击的舞台。
想一想

你的 host 要同时接 GitHub server 和文件系统 server。是用一个 client 多路复用两个 server,还是开两个 client?(先停十秒)

展开答案(先自己答)

开两个 client。MCP 规定 client 与 server 严格 1:1——每个 client 维护一条独立、有状态、隔离的会话。这正是为什么"多接一个 server"在架构上很干净:再开一个 client 即可,已有 client 的会话不受影响。多路复用会打破隔离,也会让 02 章的能力协商失去意义(不同 server 协商出的能力不同)。

1.3沙箱:server 看得到什么

每个 server 跑在自己的进程与会话里,看不到完整对话历史,也看不到别的 server——全局只有 host 掌握。

为什么需要它

server 常常是第三方写的。若它能读到完整对话,等于把用户的全部上下文(含密钥、隐私、其他工具输出等敏感信息)交给一个任意第三方。MCP 的设计是:host 持有对话,server 只在被调用时拿到 host 显式传给它的那一点输入。信任边界画在 host 与 server 之间。

底层机制(比文档深一层):隔离是双向的。GitHub server 看不到文件系统 server 的存在,也看不到用户和模型的完整交流;它只通过自己的 client 收到 host 决定传给它的参数(比如一次 create_issue 调用的 title 和 body)。这条"默认隔离"是 MCP 安全模型的地基。但地基也是攻击面:当隔离被绕过——例如一个恶意 server 的工具描述影响了模型对另一个 server 工具的调用(04 章的 shadowing),或在你还没调用任何工具时就往上下文注入了指令(line jumping)——隔离的假设就破了。先记住"默认隔离"这个出厂设定,04 章专门看它怎么被攻破。

1.4三大 server primitive

server 通过三种 primitive 暴露能力:tools(可执行函数)、resources(可读数据)、prompts(预设模板)。

回到 GitHub server 场景,三类各举一例:

  • Tools(工具):可执行的函数,带 JSON Schema 描述参数。例如 create_issue(title, body)、search_code(query)。调用会产生动作或副作用。
  • Resources(资源):结构化数据或内容,作为上下文喂给模型,用 URI 标识。例如 repo://owner/name/README.md 的文件内容、一段 commit 历史。读取它不产生副作用。
  • Prompts(提示模板):预设的指令 / 工作流模板,供用户主动触发。例如一个 /review-pr 模板,展开成"逐项检查这个 PR 的 diff、风格、测试"的结构化提示。

底层机制(比文档深一层):这三者最常被当成"三个大同小异的东西,反正都是 server 给的内容"。它们在内容形态上确实都能是文本。但真正的区别不在形态,而在下一节要讲的控制权——谁来决定它什么时候被用上。这条线才是 MCP 对它们分类的依据,也是面试里"tools 和 resources 有什么区别"想考的点。

1.5控制权分层:谁说了算

tools 由模型决定调用(model-controlled),resources 由应用决定何时取用(application-controlled),prompts 由用户主动触发(user-controlled)。这条"谁发起"的轴,才是三大 primitive 的划分依据。

底层机制(比文档深一层):官方规范给了一张控制层级表,三类 primitive 各对应一个控制方。

  • tools = 模型控制:模型在生成时自己决定要不要调 create_issue、用什么参数。这就是你已经熟悉的 function calling——MCP 只是把工具送进模型的可选清单,调不调仍由模型定。
  • resources = 应用控制:由 host 应用决定把哪些 resource 放进上下文。比如 IDE 把"当前打开的文件"作为 resource 注入,是 IDE 的逻辑在拉,不是模型主动"调用"了它。
  • prompts = 用户控制:由用户在 UI 里主动选用,比如点一个 slash command。既不是模型自发,也不是应用自动。
Prompts 用户控制 Resources 应用控制 Tools 模型控制 用户主动触发 应用按规则注入 模型自主决定 谁来决定它被调用 →
图 1.3三类 primitive 在"谁发起调用"这条轴上的位置。注意:它们的真正区别不是内容形态(都能是文本),而是控制权落在用户、应用、还是模型手里。要给 server 加新能力,先问这一句,就能定位它属于哪一类。
三大 primitive 对照
primitive谁控制典型例子(GitHub server)UI 形态
Tools模型create_issue · search_code模型在回答中自发调用
Resources应用README 内容 · commit 历史(URI 标识)应用把它拼进上下文
Prompts用户/review-pr 模板用户在菜单 / 斜杠命令里选
想一想

你想让助手在用户每次提问时,自动带上"当前打开的文件内容"作为背景。这该做成 tool 还是 resource?(先停十秒)

展开答案(先自己答)

resource。判据是"谁决定它被用上":这里是应用(IDE)按规则每次注入,不是模型自己决定去"调用"它。若做成 tool,就把决定权交给了模型——模型可能在该带文件时没调、不该带时乱调。做成 resource,由应用稳定地注入,符合"application-controlled"的语义。这正是图 1.3 那条轴的实战用法。

1.6不只 server 暴露能力:反向的预告

client 也向 server 暴露能力——sampling、roots、elicitation——但方向相反:是 server 反过来请求 client。

底层机制(比文档深一层):到这里,一个自然的默认是"能力都是 server 给、client 用"。这恰是 MCP 最违反直觉的地方,也是它区别于普通工具 API 的核心。host 内的 client 也会声明自己支持哪些能力,最重要的是 sampling——"替 server 跑一次模型"的能力。于是 server 可以反过来请求 client:让 client 的模型补全一段文本、向用户提问、列出允许访问的目录(roots)。控制流是双向的。这里只在 schema 上钉一个钉子:能力不是单向的。完整机制——尤其是 sampling 为什么让 server 不需要自己的 API key、为什么这条招牌能力实际部署得最少——留给 03 章。

1.7综合:一张"控制权"地图

把 1.2–1.6 串起来,本章的结论是一句话:MCP 不是"server 单向喂给模型一堆东西",而是一套按控制权组织、且双向流动的能力结构。

洞察 · 为什么"控制权"是本章的钥匙

一旦把三大 primitive 按"谁发起"而非"是数据还是动作"来看,很多事就对上了:为什么 tools 走的就是普通 function calling(模型自发);为什么 resources 不叫"读取工具"(应用注入,不归模型调度);为什么 prompts 是 UI 里的斜杠命令(用户主动)。再叠加 1.6 的反向能力——server 能请求 client 的 sampling——你手里就有了一张完整的控制权地图:谁能发起什么、朝哪个方向。02 章讲这套结构在线路上如何用 JSON-RPC 跑起来,03 章把那条反向箭头展开。

§本章 self-check

先合上教程,把答案写在纸上或编辑器里。写完再点开对照——直接点开等于把这一节当再读一遍。

  1. 在 MCP 里 "client" 具体指什么?它和 host 是什么关系?为什么不能把 client 理解成"用户面对的 AI 应用"?
  2. tools / resources / prompts 各由谁决定调用?把"IDE 每次自动把当前文件喂给模型"归到其中一类,并说明判据。
  3. 为什么 server 默认看不到完整对话历史?这条边界服务于什么目标?
  4. MCP 把 M×N 的集成爆炸降成 M+N——这个降维具体怎么发生的?谁实现一次什么?
答案(先做完再展开)
  1. client 是 host 内部为每个 server 开的瘦连接器,与 server 严格 1:1。host(如 Cursor)才是用户面对的应用。把 client 当成"用户的 AI 应用"会让后面 sampling 的方向(server→client)全反。
  2. tools = 模型控制,resources = 应用控制,prompts = 用户控制。"IDE 自动喂当前文件"归 resource:决定权在应用(IDE 按规则注入),不是模型自发调用。
  3. 因为 server 常是第三方,给它完整对话等于把全部上下文交给任意第三方。边界服务于信任隔离:host 持有对话,server 只拿到被显式传入的参数。
  4. 插一层协议:工具方实现一次 server、应用方实现一次 client,两边都遵守同一套消息格式。于是每一方只需"对接协议一次",而非"对接每个对端一次",集成数从 M×N 降到 M+N。骨架借自 LSP。
进阶挑战 · 刚好够不着

一个需求,三类 primitive 各占一块

需求:用户在输入框打 /standup,助手就拉取该用户昨天的 commit、读取团队的日报模板文件、然后生成一份站会日报。把这个需求拆成 tools / resources / prompts 三类——哪部分是哪类?分别由谁控制?

提示(卡住再展开)

对每个动作问"谁发起":/standup 这个入口是用户主动打的 → prompt;"拉昨天的 commit"是一个带参数、有动作的调用,由模型在展开后的指令里决定执行 → tool;"团队日报模板文件"是一段被读入当上下文的内容,由应用按 URI 取用 → resource。一个需求里三类同时出现,正说明它们不是"三种差不多的东西",而是控制权不同的三层。这道题 06 章还会以变体重现。