一为什么要做这场分享
社区目前 1 万多个 MCP server,绝大多数只用了协议里最容易的那一块——工具(tools)。但 MCP 协议设计里其实有 5 个原语(3 个 server 端 + 2 个 client 端),合在一起能让 LLM 应用做出比"工具调用"丰富得多的交互。
这次演讲的目标:
- 把 5 个原语过一遍,重点讲少有人用的那几个;
- 用一个聊天 App 例子演示怎么把 5 个原语组合起来;
- 谈 MCP 下一步——从本地走向 Web。
二5 个原语的全景
最关键的区别在于"谁来发起":
| 原语 | 谁来发起 | 一句话定位 | 典型场景 |
|---|---|---|---|
| Prompts | 用户 | 预制好的提示模板,用户主动调入上下文 | slash 命令、@cmd 命令 |
| Resources | 应用 | 原始数据/内容,由 client 应用决定怎么用 | 文件式挂载、RAG embedding |
| Tools | 模型 | 可被模型自主调用的动作 | 查数据库、改文件 |
| Sampling | Server | 让 client 帮 server 做一次模型推理 | server 内部想用 LLM 但不持模型 |
| Roots | Server | 询问 client 当前开着哪些工作区/目录 | 像 IDE 这类多 workspace 的环境 |
"应该让数据出现的时机和意图,决定你用哪个原语"——这是 David 给的核心判断准则。
三Prompts:用户主动调入的"模板"
Prompts 是 MCP 里最被忽视的原语之一。 一句话:MCP server 可以暴露一组"提示模板",让用户在 client 里主动选用。
它解决了什么问题
- 你(作者)最懂自己的 server 该怎么用——把"招牌玩法"打包成 prompt,让用户开箱即上手;
- prompts 是动态执行的——背后就是代码,不只是静态模板。可以在用户调用时去远端抓数据。
例子:把 PR comments 拉进上下文
用户敲 /pr-comments,prompt 背后就去 GitHub 拉 PR 评论,直接进上下文,然后用户跟模型说"按这些 review 意见把代码改一下"。
鲜为人知的加分项:Prompt Completion
定义 prompt 时还能给参数附补全函数。用户敲完 /pr-comments 后,弹出可选 PR 列表供选择。在 TypeScript 里实现这个需求只要几行——一个 completion 函数 + 一个生成 prompt 的函数,Claude Code + Claude 4 基本能直接给你写出来。
四Resources:应用驱动的"原始数据接入"
Prompts 是给用户用的,Resources 是给应用(client)用的。
Resource 的三种消费方式
- 方式一:加入上下文窗口
- 方式二:做 embedding / RAG 召回
- 方式三:像文件一样挂在边栏
它解决了什么问题
- 应用决定怎么用这份数据——上下文/索引/UI 渲染都行;
- 是 MCP 中比较被低估的原语:很多人只想着"加进上下文",但 client 完全可以拿来做 embedding/RAG。
例子:暴露数据库 schema
用 Astra DB 的 MCP server 把库表 schema 作为 resource 暴露。在 Claude Desktop 里像挂文件一样把表挂上,然后说"画一张 ER 图",Claude 自己读 schema 自己画图。
五Tools:模型驱动的动作
第三个 server 端原语,大家都熟:模型在跑的过程中自己决定要不要调用。David 说"建过 MCP server 的人,第一次看到模型自己调用你建的工具——是最有魔力的时刻"。
六决定用哪一个:Interaction Model(交互模型)
3 个 server 原语功能上有重叠(都能往上下文里塞东西),到底用谁?协议规范里其实埋着答案,叫 interaction model:
| 原语 | 由谁触发 | 同一份数据按"何时出现"决定用谁 |
|---|---|---|
| Prompts | 用户 主动 | 用户想立刻把它推到上下文里时 |
| Resources | 应用 决定 | 应用层有自己的策略——索引、按需召回、UI 渲染 |
| Tools | 模型 自主 | 模型在解决任务时按需调用 |
七Sampling:server 反过来让 client 跑一次模型
David 抛了个具体问题:
"我想给一个 issue tracker 做 MCP server,支持「总结某 issue 的讨论」。总结需要模型——但 server 又不知道 client 用的是哪个模型,更没有 client 的 API key。怎么办?"
最朴素的做法是 server 自己装 Anthropic SDK + 自己要个 key——立刻就尴尬:
- 用户要在 server 上又配一把 API key;
- 把"哪个模型、谁付钱、谁审隐私"的控制权从 client 抢走。
Sampling 的本质
让 server 反过来请求 client 帮忙做一次推理。
它带来的两个红利:
- Client 始终掌握安全 / 隐私 / 成本——用户在 client 那里看清楚每次推理付了多少 token、走了哪个模型,不用再额外给 server 一把 key;
- 可递归 / 可链式——server A 调 server B,B 再要 sampling,请求一路冒泡到最上层的 client。任何一个 MCP server 想用模型,都向上要,永远是同一个 client 守门。
David 坦言:sampling 是最让他兴奋、但目前 client 支持最差的原语。 Anthropic 第一方产品今年内会落地。
八Roots:server 反过来问 client "你都开了哪些工作区"
第二个 client 端原语。命名 David 自己也吐槽"起得不好"。
它解决什么
想象你做一个 git MCP server——希望它在 IDE 里跑 git 命令。问题:
server 怎么知道用户的 IDE 此刻开着哪些工程目录?
Roots 就是 client 暴露给 server 的"我现在打开了这些目录"接口,server 拿到后只在这些目录里干活,不乱跑。
VS Code 目前已经支持 roots。
九把 5 个原语组合起来:一个聊天应用的 MCP server
David 给的端到端例子——给 Slack / Discord 这类聊天 App 做 MCP server:
| 原语 | 在这个 server 里怎么用 |
|---|---|
| Prompts | summarize-this-discussion、what-happened-since-yesterday 这种 slash 命令,用户直接调入 |
| Prompt Completion | 在 summarize-this-discussion 的参数里给"最近的 thread 列表"做自动补全 |
| Resources | 把"频道列表""最近 thread"暴露给 client;client 自己决定要不要 index、要不要做 RAG |
| Tools | search_messages、read_channel、read_thread 这些动作 |
| Sampling | 在 summarize_thread 内部要做总结时,反过来让 client 跑模型 |
用户、应用、模型三条交互链路都被覆盖,不再只是"等模型调工具"。
十MCP 的下一步:从本地走向 Web
社区现在 1 万多个 server,绝大多数是本地的——Docker 容器、本地可执行。下一步:让 MCP server 成为网站——client 直接连 URL 就能用。
要走这一步需要两块基础:
- Authorization(授权)——把私有数据安全地接入 LLM 应用;
- Scaling(可伸缩)——server 能像普通 Web API 一样横向扩展。
授权:OAuth 2.1
MCP 规范现在要求远程 server 走 OAuth 2.1。这个名字听着唬人,实际上就是 OAuth 2.0 + 业界已经在做的安全加固。
| 场景 | 关键收益 |
|---|---|
| 公网产品 | 用户在 client 里直接配 mcp.payment.com,OAuth 登一下,不再需要信任随便一个开发者放在本地的 Docker 镜像——信任锚回到原服务 |
| 企业内部 | 现有 SSO / 身份提供方(Okta / Azure AD)继续生效,员工早上登一次,全天调任何内部 MCP server 都自动鉴权 |
可伸缩:Streamable HTTP
新增的 Streamable HTTP 让 MCP server "像普通 REST API 一样水平扩展":
| 情形 | 行为 |
|---|---|
| 简单 tool 调用 | 直接 return application/json,连接关闭,下一次请求另一个 Lambda 接 |
| 需要流式 / 后续推送(sampling、notification 等) | 升级到 stream,server 可以在最终 return 之前来回发消息 |
结果:MCP server 可以放在 Lambda / Cloud Run / 任何 serverless 上,跟普通 API 一样部署。
十一未来几个月路线图
| 类别 | 功能 | 说明 |
|---|---|---|
| 协议规范 | Async tasks | 支持几小时级长任务 |
| Elicitation | server 向用户主动要输入 | |
| 官方 Registry | 统一发现/发布 | |
| 多模态 | 结果流式、其他媒介 | |
| 生态 | Ruby SDK | Shopify 捐赠 |
| Go SDK | Google Go 团队官方实现 |
特别提一下 Elicitation:服务器能反过来问用户拿输入。例如"我需要你确认这个邮箱地址再继续"。这在做 MCP agent 时非常关键。
十二一页速查
| 原语 | 方向 | 触发者 | 何时考虑 |
|---|---|---|---|
| Prompts | server → client | 用户 | 想给 server 配一组"招牌玩法"或 slash 命令 |
| Resources | server → client | 应用 | 想让 client 用更高级的方式(RAG / 索引)消费数据 |
| Tools | server → client | 模型 | 经典动作调用 |
| Sampling | client → server | server 反向 | server 内部要用 LLM 但不该自己持模型/key |
| Roots | client → server | server 反向 | server 需要知道 client 当前的 workspace 范围 |
一句话外带:大多数人把 MCP 当成"暴露工具的协议",实际上 MCP 是一份关于"AI 应用 / 用户 / 模型三方协作"的完整协议——5 个原语恰好覆盖了 3 方所有方向的交互。要做出比工具调用更丰富的体验,先把 prompts / resources / sampling / roots 这四个被冷落的原语用起来。