MCP 资源与提示:工具之外的上下文暴露 本节摘要:Tools 拿走了 MCP 90% 的注意力,但另外两个服务端原语解决的是不同问题。Resources 暴露供读取的数据;Prompts 暴露可复用模板(斜杠命令)。许多服务端本该用 resource,却把读取包成工具;本该用 prompt,却在客户端提示里硬编码工作流。本节给出决策规则,并走查 与 的消息流。 学习目标 阅读完本节,你应当能够: 针对给定领域,在把能力暴露为 tool、resource 还是 prompt 之间做决策。 实现 、 、 ,并处理 。 实现带参数模板的 与 。 识别 host 何时把 prompts 呈现为斜杠命令、何时自动注入上下文。
本节摘要:Tools 拿走了 MCP 90% 的注意力,但另外两个服务端原语解决的是不同问题。Resources 暴露供读取的数据;Prompts 暴露可复用模板(斜杠命令)。许多服务端本该用 resource,却把读取包成工具;本该用 prompt,却在客户端提示里硬编码工作流。本节给出决策规则,并走查
resources/*与prompts/*的消息流。
阅读完本节,你应当能够:
resources/list、resources/read、resources/subscribe,并处理 notifications/resources/updated。prompts/list 与 prompts/get。一个天真的笔记 MCP 服务端把一切都暴露成工具:notes_read、notes_list、notes_search。这把每次数据访问都包成一次模型驱动的工具调用。后果:
notes_read。正确的划分:数据暴露成 resource,变更或计算动作暴露成 tool,可复用的多步工作流暴露成 prompt。每个原语有自己的 UX 形态与访问模式。
| 能力 | 原语 |
|---|---|
| 用户想搜索、过滤或变换数据 | tool |
| 用户想让 host 把这些数据作为上下文 | resource |
| 用户想要可重跑的模板化工作流 | prompt |
💡 心法:模型会在每个相关查询上受益于调用它的,是 tool;用户会受益于把它附加到对话的,是 resource;用户想复用的整个多步工作流单元,是 prompt。
resources/list 返回 {resources:[{uri, name, mimeType, description?}]}。resources/read 取 {uri},返回 {contents:[{uri, mimeType, text | blob}]}。
URI 可以是任何可寻址的东西:
file:///Users/alice/notes/mcp.mdpostgres://my-db/query/SELECT ...notes://note-14(自定义 scheme)memory://session-2026-04-22/recent(服务端专属)contents[] 同时支持文本与二进制。二进制用 blob(base64 编码字符串)加 mimeType。
在 capabilities 里声明 {resources:{subscribe:true}}。客户端调 resources/subscribe {uri};资源变化时服务端发 notifications/resources/updated {uri},客户端重读。
用例:一个笔记服务端的资源是磁盘上的文件;文件 watcher 触发更新通知;Claude Desktop 在 host 外被编辑后重新把文件拉进上下文。
resourceTemplates 让你暴露一个参数化 URI 模式:notes://{id},其中 id 作为补全目标。客户端可在资源选择器里自动补全 id。
prompts/list 返回 {prompts:[{name, description, arguments?}]}。prompts/get 取 {name, arguments},返回 {description, messages:[{role, content}]}。
一个 prompt 是一个模板,填好后变成一串消息,host 把它喂给自己的模型。例如 code_review prompt 取一个 file_path 参数,返回三消息序列:系统消息、含文件正文的用户消息、带推理模板的助手开场。
Claude Desktop、VS Code、Cursor 把 prompts 呈现为聊天 UI 里的斜杠命令。用户键入 /code_review 并从表单选参数。服务端的 prompt 是「用户快捷方式」与「发给模型的完整提示」之间的契约。
⚠️ 并非所有客户端都支持 prompts——查能力协商。一个声明了 prompt 能力的服务端,面对一个不支持 prompt 的客户端,只是看不到斜杠命令而已。
resources 与 prompts 在集合变动时都发 notifications/list_changed。一个刚导入 20 条新笔记的笔记服务端发 notifications/resources/list_changed;客户端重调 resources/list 拾取新增。
mimeType 为 text/plain、text/markdown、application/json。image/png、application/pdf,加 blob 字段。text/html;profile=mcp-app,在 ui:// URI 里。资源 URI 不必对应静态文件。notes://recent 每次读都返回最近五条笔记;db://query/users/active 执行一个参数化查询。服务端可自由动态计算内容。
规则:若客户端可按 URI 缓存,URI 必须稳定;若计算是一次性的,URI 应含时间戳或 nonce,免得客户端缓存过期。
支持订阅的客户端通过 notifications/resources/updated 收服务端推送。不支持订阅的客户端(预订阅时代或 host 不支持)靠重读轮询。两者都合规——服务端的能力声明告诉客户端它支持哪种。
订阅的代价:服务端的每会话状态(谁订阅了什么)。保持订阅集有界,断连的客户端应超时清理。
MCP 里的 prompts 不是系统 prompts。host 自己的系统提示(它的操作指令)与 MCP prompts(服务端提供、用户调用的模板)并存。一个行为端正的客户端绝不让服务端 prompt 覆盖自己的系统提示,而是分层叠加。
| 维度 | Tool | Resource | Prompt |
|---|---|---|---|
| 谁触发 | 模型决定调用 | 用户附加/host 注入 | 用户键入斜杠命令 |
| 访问模式 | 函数调用 | URI 寻址,只读 | 模板填参渲染成消息 |
| 可订阅 | 否 | 是 | 否 |
| 适合 | 变更/计算/搜索 | 只读数据上下文 | 可复用多步工作流 |
💡 反模式:把只读数据包成 tool(模型每次都得决定调不调,客户端 UI 无法呈现);把多步工作流硬编码进客户端系统提示(无法跨 host 复用)。
本节产出 outputs/skill-primitive-splitter.md——给定一个拟建的 MCP 服务端,它把每个能力归类为 tool/resource/prompt,并附理由。
code/main.py 把第 07 节的笔记服务端扩展为:每条笔记一个资源(notes://note-1 等)并支持 resources/subscribe;一个 review_note prompt,渲染成三消息模板;一个文件 watcher 模拟,在笔记被改时发 notifications/resources/updated;一个 notes://recent 动态资源,始终返回最近五条。跑 demo 看完整流程。
观察订阅:运行 code/main.py,看初始资源列表,再触发一次笔记编辑,验证 notifications/resources/updated 事件被触发。
加 list_changed:加一个 resources/list_changed 发射器,新建笔记时发通知让客户端重新发现。
设计 GitHub prompts:为一个 GitHub MCP 服务端设计三个 prompts:summarize_pr、triage_issue、release_notes,各带参数 schema,prompt 正文无需再编辑即可运行。
重新分类:取第 07 节服务端里的一个现有工具,判断它该继续是工具,还是该拆成 resource + tool 对,一句话给出理由。
读规范补字段:读规范的 server/resources 与 server/prompts 节,找出 resources/read 里一个很少填、但规范支持的字段。(提示:看资源内容的 _meta。)
resources/list + resources/read,URI 寻址(file:///postgres:///自定义),支持文本与二进制(blob)。subscribe:true,客户端 resources/subscribe,服务端 notifications/resources/updated 推送。resourceTemplates,参数化 URI 配补全。prompts/get 渲染成消息列表;host 呈现为斜杠命令;不支持 prompts 的客户端只是看不到命令。下一节,我们进入 sampling——服务端如何反过来请求客户端的 LLM 跑补全,以及这为何能在不给服务端 API key 的前提下启用 Agent 循环。