深入浅出 MCP 系列教程(二):三大核心原语实战
本系列共 5 篇。上一篇讲解了协议基础与架构设计,本篇深入 MCP 的三大核心原语:Resources(资源)、Tools(工具)、Prompts(提示模板),覆盖完整规范、字段、调用链路、校验规则与典型踩坑点。
一、为什么拆成三类,而不是全部打包成”函数”
MCP 刻意把 Agent 和外部交互拆成三类,这是区别于普通 function calling 的核心:
- Resources:只读上下文源——把外部世界的信息送入模型,不产生副作用;支持 URI 寻址、变更订阅;
- Tools:可执行动作——调用后可能修改外部状态;带输入 JSON Schema,高危动作需要 Host 侧人工审批;
- Prompts:预编排任务模板——封装 system prompt、参数占位、子任务编排,提供可复用的任务入口。
一个 MCP Server 可以只实现其中任意一个或多个,在 initialize 阶段通过 capabilities 声明:
1 | "capabilities": { |
只要能力没有在双方协商交集里存在,Host 就绝对不能发起对应系列的请求。
二、Resources(资源)—— 只读上下文
2.1 核心概念:Resource URI
Resources 的设计思想:任何一条可被模型消费的信息,都拥有一个全局可定位的 URI,类似浏览器的 URL,但不局限于 HTTP。
官方推荐的常见 scheme:
file://:本地文件postgres://:数据库表/记录git://:仓库提交、分支、diffnotion://:Notion 页面slack://:会话消息
URI 的所有权属于 Server:只有暴露这个资源的 Server 知道如何解析它。Host 只把 URI 当成不透明字符串,不会自己去解析、访问。Host 拿着 URI 调用 resources/read,由 Server 去读取对应内容。
2.2 Resource 对象完整字段
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
uri |
string | ✅ | 资源唯一标识 URI |
name |
string | ✅ | 人类可读名称,给用户/模型识别 |
description |
string | ❌ | 资源描述,推荐填写,辅助 LLM 判断要不要读取 |
mimeType |
string | ❌ | MIME 类型,如 text/markdown,方便 Host 做渲染、截断 |
2.3 完整调用链路
资源相关标准 method:
resources/list:列出所有可访问资源resources/read:根据 URI 读取资源内容resources/subscribe:订阅某个 URI 的变更(需要resources.subscribe: true)resources/unsubscribe:取消订阅notifications/resources/updated:服务端推送变更通知(无 id)
读取示例:
1 | { |
返回的核心是 contents 数组,一条资源可以返回多个内容块:
1 | { |
内容类型分两种:
text(文本)、blob(base64 二进制,图片、PDF)。同一个内容块不能同时存在 text 和 blob。
订阅成功之后,文件发生修改时 Server 主动推送一条通知,不需要 Client 轮询:
1 | { |
Host 收到通知后,按需再次调用 resources/read 拉取最新版本。
2.4 约束与安全边界
- 只读语义约定:协议层面约定 Resources 不应产生副作用;这是语义约束,不是强制技术锁,靠实现遵守;
- Roots 边界:如果协商开启
roots,Server 必须拒绝根以外的 URI 读取; - 超大内容处理:几十 MB 的文件直接返回会瞬间耗尽上下文窗口;通常策略是截断 + 摘要 + 分页;
- 不可信数据:资源返回的文本属于外部不可信输入,存在提示注入风险,Host 需要做隔离、标记边界;
- 订阅是会话级:会话断开订阅自动失效,重连后需要重新订阅。
2.5 典型踩坑
- 不要把动态查询塞进工具里,能用资源读就优先资源;但高频变更资源(实时日志)持续推送会压垮通道,适合分页拉取而不是订阅;
- URI 不要硬编码、不要让 LLM 自由构造不受控的 URI。
三、Tools(工具)—— 带副作用的动作
Tools 是大家最熟悉、面试最高频的原语。工具代表”执行动作”:增删改、发送消息、创建工单、执行 SQL、部署服务。
3.1 Tool 对象完整字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | ✅ | 唯一名称,LLM 依靠这个名字调用 |
description |
string | ✅ | 给 LLM 看的自然语言描述,写得差模型就不会正确调用 |
inputSchema |
object | ✅ | JSON Schema,严格定义入参结构、类型、必填项 |
注意:没有 outputSchema。协议不强制约束返回结构,返回内容由业务决定。
示例 Tool 定义:
1 | { |
3.2 调用链路
标准 method:tools/list(列出工具)、tools/call(执行工具)、notifications/tools/list_changed(工具列表动态变更推送)。
调用示例:
1 | { |
返回的 content 数组支持多种类型:text、image、resource(内嵌一条资源)。失败返回走标准 JSON-RPC error。
3.3 工具审批流(非常关键)
MCP 没有内置”审批弹窗”报文,但规范定义了协作模式:
- Server 收到
tools/call,识别这是高危操作(删除、写库、发邮件); - Server 不能直接弹出窗口,它只能正常返回结果或通过语义提示;
- 真正的审批权在 Host:Host 在发起
tools/call之前,根据工具白名单、工具名称、参数内容决定是否展示确认窗口; - 用户拒绝 → Host 直接不发起调用;用户同意 → 才发送
tools/call。
很多新手误区:以为 Server 可以通知 Host”请你弹确认框”。协议没有这个指令;审批策略是 Host 的业务安全层。
3.4 设计规范与最佳实践
- 工具粒度不要过大:拆成原子操作(创建工单、关闭工单、添加备注),方便审批、审计、权限控制;
- description 面向 LLM 写:写清楚什么时候适合调用、参数含义、限制条件;它不是给人类看的文档;
- 入参用 JSON Schema 严格约束:枚举、长度、正则,减少模型传错参数;
- 幂等优先:写类工具尽量设计成幂等,避免网络重试造成重复创建、重复发送;
- 审计埋点:所有 tools/call 都应记录调用时间、工具名、入参、返回、用户身份、是否人工确认。
3.5 Tools vs Resources 判定口诀
- 只拿信息、查询、读取,不改变外部状态 → Resources(优先)
- 新增、修改、删除、发送、触发任务 → Tools
- 查询有复杂过滤、聚合、计算,参数动态、不方便映射成静态 URI → 放到 Tool
举例:读取单条工单详情用 resources/read;按关键词、时间、状态批量搜索工单用 search_ticket tool;创建工单用 tool;读取固定配置文件用 resource。
四、Prompts(提示模板)—— 预编排任务入口
Prompts 是最容易被忽略,但在企业落地里价值很高的原语。它不是发给 LLM 的一次性 prompt,而是可被 Host 拉取的任务模板,带参数占位。
它解决的问题:很多固定工作流(代码评审、周报生成、故障复盘、安全扫描)的 system prompt 很长、编排复杂,不适合每次让 LLM 自己从头生成。把模板托管在 MCP Server,所有接入的 Agent 都可以复用。
4.1 Prompt 对象字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | ✅ | 模板唯一名称 |
description |
string | ❌ | 模板用途描述 |
arguments |
array | ❌ | 参数定义数组(name / description / required) |
4.2 调用链路
标准 method:prompts/list、prompts/get。
1 | { |
返回结果里的 messages 是一组标准化对话消息(system / user / assistant),Host 可以直接送入 LLM:
1 | { |
4.3 特点和边界
- 模板托管在 Server:更新评审模板只改 MCP Server,所有接入的 Host 自动拿到新版本;
- 返回的是消息序列,不是单纯字符串:支持多轮预设对话;
- Prompt 本身不执行推理:
prompts/get只是把模板渲染出来;真正调用 LLM 是 Host 的工作; - 可以嵌入资源引用:模板内部可引用 Server 的 Resources,拉取参考文档作为上下文;
- 适合标准化团队规范:企业把审批、巡检、复盘模板收敛在内部 MCP Server。
4.4 什么时候不要用 Prompt 原语
- 一次性、临时、用户随口输入的指令;
- 动态性极强、每次结构完全不一样的任务;
- 模板只是一行简短指令,收益很低。
五、原语组合实战:一个完整业务流程
场景:Agent 做工单复盘。
- Host 和工单 MCP Server 完成握手,协商:tools、resources、prompts、subscribe;
- Host 调用
prompts/list,找到incident_summary复盘模板; - Host 调用
prompts/get,传入工单 id,拿到预定义 system prompt; - Host 调用
tools/call,执行search_ticket,拿到工单列表; - Host 通过
resources/read,读取指定工单详情资源; - 如果开启订阅,后续工单更新会收到
resources/updated通知; - Host 把模板 + 工单上下文一起送入 LLM,生成复盘报告;
- 需要保存复盘结果时,调用
create_notetool 写回工单。
这就是 MCP 区别于普通 function calling 的核心:读上下文、执行动作、任务模板三者有独立标准,可以自由编排组合。
六、校验规则与错误模型
除了标准 JSON-RPC 错误码,三大原语有固定的业务错误场景:
- 调用
tools/call,但 Server capabilities 没有 tools → 非法请求; inputSchema校验失败(参数缺失、类型不对、枚举值非法)→ 参数错误;resources/read访问不在 roots 范围内的 URI → 拒绝;prompts/get缺失必填 argument → 返回错误;- 订阅一个不支持 subscribe 的资源 → 返回错误。
常见 JSON-RPC 标准错误码:
| code | 含义 |
|---|---|
-32601 |
Method not found:调用了不存在的方法 |
-32602 |
Invalid params:参数不符合 schema |
-32600 |
Invalid Request:消息格式非法 |
业务自定义错误码建议从
-32000开始分配,避免和 JSON-RPC 预留冲突。
七、第二篇小结与高频误区
核心复盘
- Resources:URI 寻址的只读上下文,支持订阅推送;Host 不解析 URI;
- Tools:带副作用动作,附带 JSON Schema;审批策略由 Host 控制;
- Prompts:可拉取、带参数的任务模板;模板渲染和模型推理分离;
- 三者是独立可插拔能力,靠握手协商开启。
高频误区
| 误区 | 正解 |
|---|---|
| 所有查询都做成 Tool | 优先用 Resources 承载静态、可寻址的只读数据 |
| Server 主动弹出确认窗口 | 高危操作确认权在 Host |
| Prompt 原语会直接调用模型 | 它只返回渲染后的消息,Host 负责送模型 |
| 订阅之后自动拿到新内容 | 订阅只发变更通知,读内容仍需手动 resources/read |
| inputSchema 只是文档 | Server 有义务按 schema 校验入参,不能直接透传给业务 |
下一篇:《深入浅出 MCP 系列教程(三):传输层实战》,将详解 stdio 与 Streamable HTTP 两种传输的完整握手流程、鉴权与会话管理。