MCP.so
登录
返回博客

MCP Resources vs Tools(vs Prompts):区别与选择指南

2026年10月15日MCP.so TeamMCP.so Team

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 对比

ToolsResourcesPrompts
控制方模型应用 / 宿主用户
标识方式名称 + 输入 schemaURI(或 URI 模板)名称 + 参数
副作用允许(应经用户确认)无,只读无(返回消息)
发现tools/listresources/list、resources/templates/listprompts/list
使用tools/callresources/readprompts/get
变更追踪list_changedlist_changed + 单个 URI 更新list_changed
能否引用资源能(resource_link、内嵌)不适用能(内嵌、链接)
最适合操作、带运行时参数的查询上下文、文档、schema可重复的、由人发起的工作流

客户端支持:教程常常跳过的部分

规范只是决策的一半。设计得再好的 Resource,在一个忽略 Resource 的客户端里也是不可见的。截至 2026 年 10 月:

客户端ToolsResourcesPrompts
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 资源未见文档说明

由此得出两个实际结论:

  1. 如果模型必须自己找到数据,就提供一个 Tool。 即便在支持 Resource 的客户端里,大多数也是把资源呈现给用户(通过选择器或 @ 提及),而不是让模型自由浏览。Claude Code 是个例外,因为它给模型提供了列出 / 读取资源的辅助工具。
  2. 重要数据双重暴露。 常见做法是提供一个 read_doc 或 get_schema Tool,返回与你注册的 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 暴露。