MCP 资源与提示:工具之外的上下文暴露


文档摘要

MCP 资源与提示:工具之外的上下文暴露 本节摘要:Tools 拿走了 MCP 90% 的注意力,但另外两个服务端原语解决的是不同问题。Resources 暴露供读取的数据;Prompts 暴露可复用模板(斜杠命令)。许多服务端本该用 resource,却把读取包成工具;本该用 prompt,却在客户端提示里硬编码工作流。本节给出决策规则,并走查 与 的消息流。 学习目标 阅读完本节,你应当能够: 针对给定领域,在把能力暴露为 tool、resource 还是 prompt 之间做决策。 实现 、 、 ,并处理 。 实现带参数模板的 与 。 识别 host 何时把 prompts 呈现为斜杠命令、何时自动注入上下文。

MCP 资源与提示:工具之外的上下文暴露

本节摘要:Tools 拿走了 MCP 90% 的注意力,但另外两个服务端原语解决的是不同问题。Resources 暴露供读取的数据;Prompts 暴露可复用模板(斜杠命令)。许多服务端本该用 resource,却把读取包成工具;本该用 prompt,却在客户端提示里硬编码工作流。本节给出决策规则,并走查 resources/*prompts/* 的消息流。

学习目标

阅读完本节,你应当能够:

  1. 针对给定领域,在把能力暴露为 tool、resource 还是 prompt 之间做决策。
  2. 实现 resources/listresources/readresources/subscribe,并处理 notifications/resources/updated
  3. 实现带参数模板的 prompts/listprompts/get
  4. 识别 host 何时把 prompts 呈现为斜杠命令、何时自动注入上下文。

一、问题与直觉

一个天真的笔记 MCP 服务端把一切都暴露成工具:notes_readnotes_listnotes_search。这把每次数据访问都包成一次模型驱动的工具调用。后果:

  • 模型得为每个可能受益于上下文的查询,决定是否调 notes_read
  • 只读内容无法被订阅或流到 host 的侧栏。
  • 客户端 UI(Claude Desktop 的资源附件面板、Cursor 的「Include file」选择器)无法呈现这些数据。

正确的划分:数据暴露成 resource,变更或计算动作暴露成 tool,可复用的多步工作流暴露成 prompt。每个原语有自己的 UX 形态与访问模式。

二、从零实现

Tools vs resources vs prompts——决策规则

能力 原语
用户想搜索、过滤或变换数据 tool
用户想让 host 把这些数据作为上下文 resource
用户想要可重跑的模板化工作流 prompt

💡 心法:模型会在每个相关查询上受益于调用它的,是 tool;用户会受益于把它附加到对话的,是 resource;用户想复用的整个多步工作流单元,是 prompt。

Resources

resources/list 返回 {resources:[{uri, name, mimeType, description?}]}resources/read{uri},返回 {contents:[{uri, mimeType, text | blob}]}

URI 可以是任何可寻址的东西:

  • file:///Users/alice/notes/mcp.md
  • postgres://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 外被编辑后重新把文件拉进上下文。

资源模板(2025-11-25 新增)

resourceTemplates 让你暴露一个参数化 URI 模式:notes://{id},其中 id 作为补全目标。客户端可在资源选择器里自动补全 id。

Prompts

prompts/list 返回 {prompts:[{name, description, arguments?}]}prompts/get{name, arguments},返回 {description, messages:[{role, content}]}

一个 prompt 是一个模板,填好后变成一串消息,host 把它喂给自己的模型。例如 code_review prompt 取一个 file_path 参数,返回三消息序列:系统消息、含文件正文的用户消息、带推理模板的助手开场。

Host 与 prompts

Claude Desktop、VS Code、Cursor 把 prompts 呈现为聊天 UI 里的斜杠命令。用户键入 /code_review 并从表单选参数。服务端的 prompt 是「用户快捷方式」与「发给模型的完整提示」之间的契约。

⚠️ 并非所有客户端都支持 prompts——查能力协商。一个声明了 prompt 能力的服务端,面对一个不支持 prompt 的客户端,只是看不到斜杠命令而已。

「list changed」通知

resources 与 prompts 在集合变动时都发 notifications/list_changed。一个刚导入 20 条新笔记的笔记服务端发 notifications/resources/list_changed;客户端重调 resources/list 拾取新增。

内容类型约定

  • 文本:mimeTypetext/plaintext/markdownapplication/json
  • 二进制:image/pngapplication/pdf,加 blob 字段。
  • MCP Apps(第 14 节):text/html;profile=mcp-app,在 ui:// URI 里。

动态资源

资源 URI 不必对应静态文件。notes://recent 每次读都返回最近五条笔记;db://query/users/active 执行一个参数化查询。服务端可自由动态计算内容。

规则:若客户端可按 URI 缓存,URI 必须稳定;若计算是一次性的,URI 应含时间戳或 nonce,免得客户端缓存过期。

订阅 vs 轮询

支持订阅的客户端通过 notifications/resources/updated 收服务端推送。不支持订阅的客户端(预订阅时代或 host 不支持)靠重读轮询。两者都合规——服务端的能力声明告诉客户端它支持哪种。

订阅的代价:服务端的每会话状态(谁订阅了什么)。保持订阅集有界,断连的客户端应超时清理。

Prompts vs 系统 prompts

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 看完整流程。

五、练习

  1. 观察订阅:运行 code/main.py,看初始资源列表,再触发一次笔记编辑,验证 notifications/resources/updated 事件被触发。

  2. 加 list_changed:加一个 resources/list_changed 发射器,新建笔记时发通知让客户端重新发现。

  3. 设计 GitHub prompts:为一个 GitHub MCP 服务端设计三个 prompts:summarize_prtriage_issuerelease_notes,各带参数 schema,prompt 正文无需再编辑即可运行。

  4. 重新分类:取第 07 节服务端里的一个现有工具,判断它该继续是工具,还是该拆成 resource + tool 对,一句话给出理由。

  5. 读规范补字段:读规范的 server/resourcesserver/prompts 节,找出 resources/read 里一个很少填、但规范支持的字段。(提示:看资源内容的 _meta。)

本节要点回顾

  1. 决策规则:搜索/过滤/变换 → tool;附加为上下文的只读数据 → resource;可复用多步工作流 → prompt。
  2. Resources:resources/list + resources/read,URI 寻址(file:///postgres:///自定义),支持文本与二进制(blob)。
  3. 订阅:声明 subscribe:true,客户端 resources/subscribe,服务端 notifications/resources/updated 推送。
  4. 资源模板:2025-11-25 新增 resourceTemplates,参数化 URI 配补全。
  5. Prompts:prompts/get 渲染成消息列表;host 呈现为斜杠命令;不支持 prompts 的客户端只是看不到命令。
  6. list_changed 通知:集合变动时发,客户端重新发现。
  7. 动态资源:URI 可动态计算内容;可缓存的 URI 必须稳定,一次性的加 nonce。
  8. 订阅 vs 轮询:都合规,靠能力声明区分;订阅有每会话状态代价。
  9. Prompts ≠ 系统 prompts:并存分层,客户端绝不许服务端 prompt 覆盖自己的系统提示。

下一节,我们进入 sampling——服务端如何反过来请求客户端的 LLM 跑补全,以及这为何能在不给服务端 API key 的前提下启用 Agent 循环。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U