MCP Resources vs Tools(vs Prompts):区别与选择指南
MCP Resources 和 Tools 有什么区别?讲清三种原语分别由谁控制、何时用 Resource、Tool 或 Prompt,附 TypeScript 与 Python 示例和各客户端的真实支持情况。
在 MCP 中,Tools(工具) 是由模型决定何时调用的函数(模型控制),而 Resources(资源) 是以 URI 标识的只读数据,由客户端应用决定是否作为上下文附加进来(应用控制)。第三种原语 Prompts(提示模板) 是由用户显式选择的模板,通常以斜杠命令出现(用户控制)。简单的判断规则是:模型需要执行操作或计算时用 Tool,宿主应用需要把数据作为上下文加载时用 Resource,由人发起一个固定工作流时用 Prompt。
这个一句话答案掩盖了实际开发中真正关键的决定:应该由谁来负责把数据拉进来?你要支持的客户端到底支不支持?本文两点都会讲到,并给出当前 TypeScript 和 Python SDK(两者都是 v2,实现 2026-07-28 版规范)的代码。
控制模型:谁来决定?
MCP Server 规范 按照控制方对三种 Server 原语进行了划分:
| 原语 | 控制方 | 典型界面 | 示例 |
|---|---|---|---|
| Prompts | 用户控制 | 斜杠命令、菜单项 | /review-code、"总结这个 PR" |
| Resources | 应用控制 | @ 提及、附加 / 上下文选择器、自动纳入 | 文件内容、数据库 schema、git 历史 |
| Tools | 模型控制 | 由 LLM 调用,最好经过用户确认 | 创建 issue、执行查询、发送邮件 |
"控制"指的是由谁决定何时使用该原语,而不是由谁编写它。三者都由 Server 编写。规范还指出这些只是设计意图:客户端可以按任何方式呈现任意原语。实际上,正是这种灵活性导致各客户端的支持程度差异很大(下文详述)。
Tools:操作与计算
一个 Tool 包含名称、描述、inputSchema(JSON Schema),以及可选的 outputSchema。客户端通过 tools/list 列出工具,模型通过 tools/call 请求调用。以下情况适合用 Tool:
- 操作有副作用:写文件、开 PR、发消息、扣款。
- 结果取决于模型在运行时选择的参数:搜索词、过滤条件、日期范围。
- 工作内容是计算而不是查找:执行 SQL、渲染图表、调用外部 API。
- 需要模型在任务进行中自己判断需要某样东西。
Tool 的结果可以包含文本、图片、音频、结构化 JSON(structuredContent),而且值得注意的是还可以包含资源:要么是 resource_link(客户端可以读取的 URI),要么是内嵌内容的 resource。这正是两种原语之间的桥梁。
TypeScript(@modelcontextprotocol/server v2):
import { McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
const server = new McpServer({ name: 'catalog', version: '1.0.0' });
server.registerTool(
'search_products',
{
description: 'Search the product catalog by name',
inputSchema: z.object({
query: z.string().describe('Substring to match against product names'),
limit: z.number().int().max(50).optional(),
}),
},
async ({ query, limit }) => {
const hits = await db.searchProducts(query, limit ?? 10);
return {
content: [{ type: 'text', text: hits.map((h) => h.name).join('\n') }],
};
}
);
Python(mcp v2,MCPServer):
from mcp.server import MCPServer
mcp = MCPServer("catalog")
@mcp.tool()
def search_products(query: str, limit: int = 10) -> list[str]:
"""Search the product catalog by name."""
return [p.name for p in db.search_products(query, limit)]
在 Python 中,类型注解就是 schema,docstring 就是模型看到的描述。
Resources:由应用附加的数据
Resource 是带有 URI、名称和 MIME 类型的只读内容。客户端通过 resources/list 发现资源,通过 resources/read 读取。以下情况适合用 Resource:
- 数据是上下文而不是操作:配置文件、schema、文档、报表。
- 应该由用户或宿主应用选择放进上下文的内容("附加文件"模式)。
- 同一份内容会在多轮对话或多个会话中复用,并且可以缓存。
- 你希望内容可寻址,让 Tool 结果或 Prompt 能通过 URI 指向它。
TypeScript,静态资源:
server.registerResource(
'db-schema',
'schema://main',
{
title: 'Database schema',
description: 'DDL for the main database',
mimeType: 'text/plain',
},
async (uri) => ({
contents: [{ uri: uri.href, text: await db.dumpSchema() }],
})
);
Python:
@mcp.resource("schema://main", mime_type="text/plain")
def db_schema() -> str:
"""DDL for the main database."""
return db.dump_schema()
读取回调返回一个 contents 数组。每一项都回显 URI,并携带 text 或 base64 编码的 blob,所以 PNG、PDF 等二进制文件也能处理。
Resource 模板
大部分真实数据都是参数化的。Resource 模板用 RFC 6570 URI 模板 暴露一整类 URI,客户端通过 resources/templates/list 发现它们。参数可以通过 completion API 自动补全。
import { ResourceTemplate } from '@modelcontextprotocol/server';
server.registerResource(
'user-profile',
new ResourceTemplate('users://{userId}/profile', { list: undefined }),
{ title: 'User profile', mimeType: 'application/json' },
async (uri, { userId }) => ({
contents: [
{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify(await getUser(userId)),
},
],
})
);
@mcp.resource("users://{user_id}/profile")
def user_profile(user_id: str) -> str:
"""Profile data for one user."""
return json.dumps(get_user(user_id))
在 TypeScript SDK 中 list 是必填键。实例无法枚举时传 undefined;如果希望它们出现在 resources/list 中,就传一个返回具体 URI 的回调。
订阅与变更通知
Resource 是唯一支持逐项变更追踪的原语,而这一点在最新规范中有变化:
notifications/resources/list_changed告诉客户端资源集合发生了变化(需声明listChanged: true)。TS SDK 在你注册或移除资源时会自动发送。- 单个资源更新(需声明
subscribe: true)通过notifications/resources/updated告诉客户端某个 URI 的内容变了。在 2025 年版本的连接上,客户端用resources/subscribe订阅。在 2026-07-28 版本的连接上,这个方法已被移除:客户端在subscriptions/listen请求的resourceSubscriptions过滤器中列出资源 URI,更新通知从这条流上送达。
如果你要同时支持两个版本,TypeScript SDK 文档 展示了需要做的状态管理。注意订阅需要一个比单次请求更长寿的连接,所以在无状态的旧版 HTTP 处理器后面,它不会投递任何更新。
Prompts:由用户发起的工作流
Prompt 是一个具名、带参数的模板,返回一组消息(prompts/list、prompts/get)。当应该由人来触发一个可重复的工作流时使用它,例如"按照我们的代码规范审查这个 diff"或"为 X 版本起草发布说明"。Prompt 消息可以内嵌资源,所以一个 Prompt 可以通过 URI 把代码规范文档拉进来。
server.registerPrompt(
'review-code',
{
title: 'Code review',
description: 'Review code against our conventions',
argsSchema: z.object({ code: z.string().describe('The code to review') }),
},
({ code }) => ({
messages: [
{
role: 'user',
content: { type: 'text', text: `Review this code:\n\n${code}` },
},
],
})
);
@mcp.prompt(title="Code review")
def review_code(code: str) -> str:
"""Review code against our conventions."""
return f"Review this code:\n\n{code}"
Resources、Tools、Prompts 对比
| Tools | Resources | Prompts | |
|---|---|---|---|
| 控制方 | 模型 | 应用 / 宿主 | 用户 |
| 标识方式 | 名称 + 输入 schema | URI(或 URI 模板) | 名称 + 参数 |
| 副作用 | 允许(应经用户确认) | 无,只读 | 无(返回消息) |
| 发现 | tools/list | resources/list、resources/templates/list | prompts/list |
| 使用 | tools/call | resources/read | prompts/get |
| 变更追踪 | list_changed | list_changed + 单个 URI 更新 | list_changed |
| 能否引用资源 | 能(resource_link、内嵌) | 不适用 | 能(内嵌、链接) |
| 最适合 | 操作、带运行时参数的查询 | 上下文、文档、schema | 可重复的、由人发起的工作流 |
客户端支持:教程常常跳过的部分
规范只是决策的一半。设计得再好的 Resource,在一个忽略 Resource 的客户端里也是不可见的。截至 2026 年 10 月:
| 客户端 | Tools | Resources | Prompts |
|---|---|---|---|
| Claude Code | 支持 | 支持:@server:protocol://path 提及,另有内置工具用于列出和读取资源 | 支持:/mcp__server__prompt 斜杠命令 |
| Cursor | 支持 | 支持 | 支持 |
| VS Code(Copilot) | 支持 | 支持:Add Context → MCP Resources、MCP: Browse Resources | 支持:聊天中的斜杠命令 |
| Claude API MCP connector | 支持 | 不支持(改用 SDK 的客户端辅助函数) | 不支持(同上) |
| OpenAI API / ChatGPT | 支持(以 search、fetch 等工具为核心) | 仅用于应用的 UI 资源 | 未见文档说明 |
由此得出两个实际结论:
- 如果模型必须自己找到数据,就提供一个 Tool。 即便在支持 Resource 的客户端里,大多数也是把资源呈现给用户(通过选择器或 @ 提及),而不是让模型自由浏览。Claude Code 是个例外,因为它给模型提供了列出 / 读取资源的辅助工具。
- 重要数据双重暴露。 常见做法是提供一个
read_doc或get_schemaTool,返回与你注册的 Resource 相同 URI 的resource_link或内嵌资源。只支持 Tool 的客户端照样能用,支持 Resource 的客户端则能享受缓存、选择器和订阅。
关于客户端如何选择工具、以及工具列表过长为什么有害,参见 MCP 工具发现。如果你在 Resource 和检索流水线之间权衡,MCP vs RAG 讲的就是这个取舍。
决策清单
- 会改变 Server 之外的任何东西吗?→ Tool。
- 模型需要在运行时选择参数(查询词、任务中途发现的 ID)吗?→ Tool,可选地返回
resource_link。 - 是用户会"附加"的稳定上下文(文档、schema、文件)吗?→ Resource,参数化的话用模板。
- 客户端需要知道它何时变化吗?→ 带
subscribe/listChanged的 Resource。 - 是一个由人发起、只需少量输入的工作流吗?→ Prompt,按需内嵌资源。
- 你的目标客户端只支持 Tool(API connector、许多 Agent 框架)吗?→ 同时用 Tool 暴露这些数据。
常见错误
- 只读数据只做成 Tool。 这样哪里都能用,但会失去缓存和用户主动附加的能力,而且每次查找都要消耗一轮模型调用。再加一个 Resource。
- 在 Resource 里偷偷写数据。
resources/read不能有副作用,因为客户端可能随时预取或重复读取。 - 模板变量不做校验。 像
file:///{path}这样的模板必须解析并限制路径范围。规范要求 Server 校验 URI,并对文件路径做清理以防目录穿越。 - 资源不存在时返回空的
contents数组。 在 2026-07-28 规范下应返回 JSON-RPC-32602错误(客户端仍应兼容旧的-32002)。 - Prompt 假设了特定客户端。 并非所有客户端都会渲染 Prompt,所以底层能力也要能通过 Tool 或 Resource 访问到。
用 MCP Inspector 并排测试三种原语。它为 Tools、Resources(包括模板)和 Prompts 分别提供了标签页,是查看客户端实际会收到什么的最快方法。
下一步
看看现有 Server 是如何划分接口的。例如在 Inspector 中对比 Filesystem、GitHub 和 Postgres 这几个 Server,看它们分别把哪些数据暴露为 Tool、哪些暴露为 Resource。然后浏览 MCP.so Server 目录 找到适合你技术栈的 Server,并参考 Claude Code MCP 配置,在一个三种原语都支持的客户端里试试 @ 资源和 / 提示模板。
常见问题
MCP 的 Resources 和 Tools 有什么区别?
- Tools 是由模型决定调用的函数,可以有副作用;Resources 是以 URI 标识的只读数据,由客户端应用决定是否作为上下文加载。简单说,Tools 由模型控制,Resources 由应用控制。
什么时候应该用 MCP Resource 而不是 Tool?
- 对于文件、schema、文档这类稳定的只读上下文,并且希望用户或宿主应用能够附加和缓存时,用 Resource。模型需要在运行时选择参数、做计算或修改状态时,用 Tool。
MCP Prompts 是做什么用的?
- Prompts 是由用户控制的模板,返回一组消息,通常以斜杠命令的形式出现。它们把代码审查这类可重复的工作流打包起来,并可以内嵌资源作为上下文。
所有 MCP 客户端都支持 Resources 和 Prompts 吗?
- 不是。Claude Code、Cursor 和 VS Code 支持 Tools、Resources 和 Prompts,但 Claude API 的 MCP connector 这类 API 级集成只使用 Tools。如果要让模型在所有客户端里都能拿到数据,就同时通过 Tool 暴露。