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、为什么要握手协商。
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 要同时接 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。既不是模型自发,也不是应用自动。
| 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
先合上教程,把答案写在纸上或编辑器里。写完再点开对照——直接点开等于把这一节当再读一遍。
- 在 MCP 里 "client" 具体指什么?它和 host 是什么关系?为什么不能把 client 理解成"用户面对的 AI 应用"?
- tools / resources / prompts 各由谁决定调用?把"IDE 每次自动把当前文件喂给模型"归到其中一类,并说明判据。
- 为什么 server 默认看不到完整对话历史?这条边界服务于什么目标?
- MCP 把 M×N 的集成爆炸降成 M+N——这个降维具体怎么发生的?谁实现一次什么?
答案(先做完再展开)
- client 是 host 内部为每个 server 开的瘦连接器,与 server 严格 1:1。host(如 Cursor)才是用户面对的应用。把 client 当成"用户的 AI 应用"会让后面 sampling 的方向(server→client)全反。
- tools = 模型控制,resources = 应用控制,prompts = 用户控制。"IDE 自动喂当前文件"归 resource:决定权在应用(IDE 按规则注入),不是模型自发调用。
- 因为 server 常是第三方,给它完整对话等于把全部上下文交给任意第三方。边界服务于信任隔离:host 持有对话,server 只拿到被显式传入的参数。
- 插一层协议:工具方实现一次 server、应用方实现一次 client,两边都遵守同一套消息格式。于是每一方只需"对接协议一次",而非"对接每个对端一次",集成数从 M×N 降到 M+N。骨架借自 LSP。
一个需求,三类 primitive 各占一块
需求:用户在输入框打 /standup,助手就拉取该用户昨天的 commit、读取团队的日报模板文件、然后生成一份站会日报。把这个需求拆成 tools / resources / prompts 三类——哪部分是哪类?分别由谁控制?
提示(卡住再展开)
对每个动作问"谁发起":/standup 这个入口是用户主动打的 → prompt;"拉昨天的 commit"是一个带参数、有动作的调用,由模型在展开后的指令里决定执行 → tool;"团队日报模板文件"是一段被读入当上下文的内容,由应用按 URI 取用 → resource。一个需求里三类同时出现,正说明它们不是"三种差不多的东西",而是控制权不同的三层。这道题 06 章还会以变体重现。