从 /xhs-style-guide 消失说起:MCP Prompts、Resources 与 Tools 的边界
从 /xhs-style-guide 消失说起:MCP Prompts、Resources 与 Tools 的边界
最近给 MyBlog MCP 增加了动态 Prompt 注册:后台模板满足「当前版本已激活」且 metadata_json.mcpExposed === true 时,服务会通过 server.registerPrompt(...) 对外暴露。
本意很简单:把“小红书风格指南”这类可复用写作工作流做成 MCP Prompt。实际联调却出现了一个很有意思的现象:Claude Code 能直接出现并调用 /xhs-style-guide,但在 Codex 和 WorkBuddy 里看不到对应斜线命令;它们更容易使用 MCP Tool,以及读取 MCP Resource。
起初我把问题理解成“客户端没有实现 Prompt,于是服务端应该补两个工具:list_prompts 和 get_prompt”。后来重新核对 MCP 的控制模型,发现这个补偿方案会悄悄改变语义。
三个原语,不是三种同类 API
MCP 的三个核心服务能力,真正的区别不在请求名字,而在谁决定使用它:
| 原语 | 典型协议操作 | 更合适的控制者 | 它解决的问题 |
|---|---|---|---|
| Tools | tools/list、tools/call |
模型 | 我需要执行什么动作? |
| Resources | resources/list、resources/read |
模型或宿主 | 我缺少什么数据、文档或上下文? |
| Prompts | prompts/list、prompts/get |
用户 | 我想选择哪一种角色、流程或写作工作流? |
MCP 规范把 Prompt 明确设计为 user-controlled:服务端提供可发现的模板,用户显式选择,客户端取得消息后再注入当前会话。斜线命令、菜单和命令面板都是自然的产品形态。
这也解释了 Claude Code 的体验:它把 MyBlog 的 Prompt 映射成斜线命令。输入 /xhs-style-guide 并不是模型在“调用工具”,而是用户做了一次明确选择;Claude Code 随后执行 prompts/get,把模板消息放进会话。
为什么 Resource 常被包装成工具
在 Agent 工作流中,Resource 更像按需获取的事实与材料。比如服务器提供:
text
blog://tags
blog://collections
blog://image-generation-jobs/{jobId}
模型面对“把文章加入哪个合集”或“图片生成完成了吗”这类问题,可以合理地主动列举、读取这些资源。它缺的是数据,因此让 Agent 使用 ListMcpResources、ReadMcpResource 这类宿主适配工具很自然。
这次封面图就是一个实际例子:MyBlog 的 generate_image 工具只负责创建异步任务,返回 blog://image-generation-jobs/{jobId};随后通过读取这个 Resource 得到任务状态和最终 CDN 地址。这里的 Resource 是状态数据,不是对模型行为的指令。
为什么不该让模型自己“找 Prompt”
如果把 list_prompts、get_prompt 暴露成与普通业务操作等价的模型工具,链路会变成:模型发现提示词 → 模型挑选提示词 → 模型读取一段要求它扮演某角色、遵循某流程的内容 → 模型据此改变自己的工作方式。
这已经不是 user-controlled Prompt,而是 model-controlled Prompt。它至少带来三类问题:
- 指令边界变模糊:系统提示词、开发者指令、Skill、MCP Prompt 与 Resource 中的文本,谁能影响谁?
- 提示词注入面扩大:一个不可信服务器可以用看似有帮助的 Prompt 引导模型改变规则或工具使用方式。
- 行为不可预测:模型可能在不符合用户预期时自行换角色、换流程,甚至递归地查找“更适合”的 Prompt。
因此,Codex 或 WorkBuddy 没把 MCP Prompt 映射为模型工具,并不一定是“漏实现”。更准确地说,它们优先实现了 Agent 需要主动调用的能力;而 Prompt 还需要一套用户可见、可选择、可传参数并安全注入对话的产品交互。
MyBlog 的取舍:标准实现优先,不用 Resource 冒充 Prompt
MyBlog 继续使用 MCP 原生 Prompt 原语:动态模板注册、激活版本校验和认证校验都保留在服务端。支持 Prompt 的客户端会获得自然的斜线命令体验;不支持的客户端则不会被一层“伪装成工具的 Prompt”改变安全模型。
同样,也不应该把 Prompt 塞进 Resource。Resource 适合稳定、可寻址、可读取的数据;Prompt 的价值是参数化的会话消息与用户主动选择。把两者混在一起,短期也许能绕过客户端差异,长期会让使用者和 Agent 都难以理解这段文本究竟是资料还是指令。
真正需要兼容的,是客户端:Codex、WorkBuddy 未来若实现 prompts/list / prompts/get 的斜线命令或菜单入口,就可以无缝消费 MyBlog 现有的标准实现,而不必改动服务端数据模型。
一个容易混淆的例外:应用内部模板
应用内部的 Create Agent、Topic Agent 当然仍可以按业务策略加载模板。那是同一应用受控边界内的编排:开发者可以明确规定允许哪些模板、何时加载、如何隔离其中的内容。
但这和“把任意 MCP Server 的 Prompt 给模型自由发现并加载”不是一回事。前者是产品内的受控工作流;后者会跨越服务器、宿主和模型的信任边界。
结语
这次联调最有价值的发现不是“某个客户端少了一个 API”,而是重新对齐了三个问题:能力由谁调用、数据由谁读取、行为由谁选择。
Tool 是 capability;Resource 是 knowledge;Prompt 是 workflow。
当 Prompt 由用户选择,Agent 的行为边界才清楚;当 Resource 由 Agent 按需读取,检索与执行才足够自主。MyBlog 接下来要做的不是把三者揉成同一种工具,而是保持协议语义清晰,并随着客户端能力成熟获得更好的体验。