---
title: "MCP 三大原语：Tools、Resources、Prompts"
wiki: mcp
category: "协议规范"
slug: mcp-primitives
url: https://learnagent.wiki/mcp/cards/mcp-primitives
tags: ["MCP", "原语", "Tools", "Resources", "Prompts", "Primitives"]
last_updated: 2026-04-11
reading_time: 14
---

> 如果说 MCP 架构讲的是"谁和谁连接"，那原语（Primitives）讲的就是"连接之后能传递什么"。

# MCP 三大原语：Tools、Resources、Prompts

## 基础概念

如果说 MCP 架构讲的是"谁和谁连接"，那原语（Primitives）讲的就是"连接之后能传递什么"。

MCP 把 Server 能提供的所有东西归成三类，官方叫它们**原语**——你可以理解成三块标准化的积木，所有 MCP Server 都是用这三块积木拼出来的：

| 原语 | 一句话 | 控制方 |
|------|--------|--------|
| **Tools**（工具） | 让模型"做事"——执行函数、调 API、写文件 | 模型控制（model-controlled）：模型自己决定要不要调 |
| **Resources**（资源） | 让模型"读数据"——文件内容、数据库表结构、API 返回体 | 应用控制（application-controlled）：Host 决定怎么塞进上下文 |
| **Prompts**（提示词模板） | 让用户"选模板"——预定义好的提示词，用户手动触发 | 用户控制（user-controlled）：用户在 UI 上选择调用 |

三者的核心区别在于**谁来触发**：Tools 由模型自主决定调用，Resources 由应用程序自动管理，Prompts 由用户手动选取。

用一个类比来记：**Tools 是菜刀（动手做菜），Resources 是食材（只读、提供原料），Prompts 是菜谱（预设好的操作模板，用户翻开才用）。**

### 决策流程图

不确定该用哪种原语？按这个思路判断：

```mermaid
graph TD
    Start["我想让 Server 提供一种能力"] --> Q1{"这个能力会产生<br/>副作用吗？"}
    Q1 -->|"会（写文件、发请求、改数据）"| Tools["用 Tools"]
    Q1 -->|"不会，只是提供数据"| Q2{"数据应该由谁决定<br/>什么时候读取？"}
    Q2 -->|"应用程序自动决定"| Resources["用 Resources"]
    Q2 -->|"用户手动选择"| Q3{"需要预设好<br/>完整的提示词吗？"}
    Q3 -->|"是"| Prompts["用 Prompts"]
    Q3 -->|"不需要，只是给数据"| Resources
```

### Tools 详解

Tools 是最常用的原语。当模型在对话中判断"我需要调用一个外部功能"时，它从 Host 汇总的工具列表里选一个 Tool，发起调用。

**发现工具**——Client 先问 Server 有哪些工具可用：

```json
// 请求
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }

// 响应
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "tools": [{
      "name": "get_weather",
      "title": "Weather Information Provider",
      "description": "Get current weather for a location",
      "inputSchema": {
        "type": "object",
        "properties": {
          "location": { "type": "string", "description": "City name or zip code" }
        },
        "required": ["location"]
      }
    }]
  }
}
```

**调用工具**——模型选好工具和参数，Client 发起调用：

```json
// 请求
{
  "jsonrpc": "2.0", "id": 2,
  "method": "tools/call",
  "params": { "name": "get_weather", "arguments": { "location": "Beijing" } }
}

// 响应
{
  "jsonrpc": "2.0", "id": 2,
  "result": {
    "content": [{ "type": "text", "text": "北京当前：28°C，晴，东风 3 级" }],
    "isError": false
  }
}
```

几个关键细节：

- **`inputSchema`**：用 JSON Schema 描述参数格式，模型会自动按这个格式填参数
- **`isError`**：如果工具执行出错（比如 API 限流），Server 在结果里把 `isError` 设成 `true`，而不是抛 JSON-RPC 错误。这样模型可以"看到"错误信息并自行决定如何处理
- **返回内容可以是多种类型**：文本（`text`）、图片（`image`）、音频（`audio`）、资源链接（`resource_link`）或嵌入资源（`resource`）
- **`outputSchema`**（可选）：Server 可以声明工具返回的结构化数据格式，方便 Client 做校验
- **工具列表可以动态变化**：Server 发一条 `notifications/tools/list_changed` 通知，Client 就会重新拉取

### Resources 详解

Resources 是"只读数据提供者"。和 Tools 的最大区别：**Resources 不产生副作用**，它只是把数据暴露给 Host，由 Host 或应用程序决定是否把这些数据塞进模型的上下文窗口。

**发现资源**：

```json
// 请求
{ "jsonrpc": "2.0", "id": 1, "method": "resources/list" }

// 响应
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "resources": [{
      "uri": "file:///project/src/main.rs",
      "name": "main.rs",
      "title": "Rust Application Main File",
      "description": "Primary application entry point",
      "mimeType": "text/x-rust"
    }]
  }
}
```

**读取资源**：

```json
// 请求
{
  "jsonrpc": "2.0", "id": 2,
  "method": "resources/read",
  "params": { "uri": "file:///project/src/main.rs" }
}

// 响应
{
  "jsonrpc": "2.0", "id": 2,
  "result": {
    "contents": [{
      "uri": "file:///project/src/main.rs",
      "mimeType": "text/x-rust",
      "text": "fn main() {\n    println!(\"Hello world!\");\n}"
    }]
  }
}
```

几个关键细节：

- **URI 标识**：每个 Resource 用 URI 唯一标识，支持 `file://`、`https://`、`git://` 和自定义 scheme
- **Resource Templates**：Server 可以暴露带参数的 URI 模板（例如 `file:///{path}`），Client 填入参数后读取具体资源
- **订阅更新**：Client 可以 `resources/subscribe` 订阅某个资源，当资源内容变化时 Server 发通知
- **二进制支持**：Resource 内容可以是文本（`text` 字段）或二进制（`blob` 字段，base64 编码）
- **注解（Annotations）**：Resource 可以带上 `audience`（给用户还是给模型看）、`priority`（重要程度 0-1）、`lastModified`（最后修改时间）等元数据

### Prompts 详解

Prompts 是"预设好的提示词模板"，用户在 UI 上手动选择触发。最典型的场景：在对话框里输入 `/code_review`，然后粘贴一段代码，Server 会返回一套完整的"代码评审提示词"塞进对话。

**列出可用模板**：

```json
// 请求
{ "jsonrpc": "2.0", "id": 1, "method": "prompts/list" }

// 响应
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "prompts": [{
      "name": "code_review",
      "title": "Request Code Review",
      "description": "Asks the LLM to analyze code quality and suggest improvements",
      "arguments": [
        { "name": "code", "description": "The code to review", "required": true }
      ]
    }]
  }
}
```

**获取模板内容**（传入参数）：

```json
// 请求
{
  "jsonrpc": "2.0", "id": 2,
  "method": "prompts/get",
  "params": {
    "name": "code_review",
    "arguments": { "code": "def hello():\n    print('world')" }
  }
}

// 响应
{
  "jsonrpc": "2.0", "id": 2,
  "result": {
    "description": "Code review prompt",
    "messages": [{
      "role": "user",
      "content": {
        "type": "text",
        "text": "Please review this Python code:\ndef hello():\n    print('world')"
      }
    }]
  }
}
```

关键细节：

- **返回的是完整的消息数组**：可以包含多轮对话（`role: "user"` 和 `role: "assistant"` 交替），不只是一条文本
- **消息里可以嵌入资源**：Prompt 消息的 content 可以是文本、图片、音频、嵌入资源等多种类型
- **参数化**：每个 Prompt 可以声明 `arguments`，用户填入参数后 Server 动态生成对应的消息

### 客户端原语（附加）

除了上面三种服务端原语，MCP 还定义了三种**客户端原语**——由 Client 暴露给 Server 调用：

| 客户端原语 | 作用 | 使用场景 |
|-----------|------|----------|
| **Sampling** | Server 请求 Client 的大模型生成一段回复 | Server 自己不想集成 LLM SDK，借用 Host 的模型能力 |
| **Elicitation** | Server 请求 Client 向用户提问并等待回答 | 执行敏感操作前要求用户确认 |
| **Logging** | Server 向 Client 发送日志消息 | 调试、审计、进度显示 |

这三个初学者暂时不用深入，知道有这回事就好。

## 基础用法

理解三大原语最好的办法是看一个同时用了三种原语的 Server。下面用 Python SDK 写一个"项目助手" Server，它同时提供 Tool、Resource 和 Prompt：

```python
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("project-assistant")

# ---------- Tool：执行代码格式化（有副作用） ----------
@mcp.tool()
def format_code(code: str, language: str = "python") -> str:
    """Format code using standard style guidelines"""
    # 实际实现里会调用 black / prettier 等工具
    return f"Formatted {language} code:\n{code.strip()}"

# ---------- Resource：提供项目 README（只读） ----------
@mcp.resource("file:///project/README.md")
def get_readme() -> str:
    """Project README file"""
    with open("/project/README.md") as f:
        return f.read()

# ---------- Prompt：代码评审模板（用户手动触发） ----------
@mcp.prompt()
def code_review(code: str) -> str:
    """Ask the LLM to review code quality"""
    return f"""Please review the following code for:
1. Code quality and readability
2. Potential bugs
3. Performance issues
4. Security concerns

Code:
{code}"""

if __name__ == "__main__":
    mcp.run()
```

启动这个 Server 后：

- 模型会通过 `tools/list` 发现 `format_code` 这个工具，在需要格式化代码时自动调用
- Host 会通过 `resources/list` 发现 `file:///project/README.md`，可以自动把 README 内容塞进上下文
- 用户在 UI 里输入 `/code_review`，选择这个 Prompt 模板，粘贴代码后 Server 返回一套完整的评审提示词

## 同类工具对比

| 维度 | MCP Tools | MCP Resources | MCP Prompts | OpenAI Function Calling | LangChain Tools |
|------|-----------|---------------|-------------|------------------------|-----------------|
| 控制方 | 模型自主决定 | 应用程序 | 用户手动 | 模型自主决定 | 模型自主决定 |
| 有无副作用 | 有 | 无（只读） | 无 | 有 | 有 |
| 返回格式 | content 数组（多类型） | URI + 文本/二进制 | 完整消息数组 | JSON 字符串 | Python 对象 |
| 动态发现 | ✅ `tools/list` + 变更通知 | ✅ `resources/list` + 订阅 | ✅ `prompts/list` | ❌ 写死在 API 调用里 | ❌ 写死在代码里 |
| 跨客户端复用 | ✅ | ✅ | ✅ | ❌ 只在 OpenAI 生态 | ❌ 只在 LangChain |
| 参数校验 | JSON Schema | URI + MIME | arguments 声明 | JSON Schema | Python 类型标注 |

核心区别：

- **MCP 三原语**把"做事"、"读数据"、"用模板"三个维度分开，职责清晰。其他方案（FC / LangChain）把所有东西都叫"Tool"，没有区分只读数据和有副作用的操作
- **MCP Resources** 没有对应的概念在 Function Calling 里——FC 里如果你想给模型提供上下文数据，得自己塞进 System Prompt 或者写个假函数
- **MCP Prompts** 也没有对应——FC 和 LangChain 都没有"用户选择触发一套预设提示词"的标准化方案

## 常见误区

| 误区 | 准确理解 |
|------|----------|
| 以为 Resources 就是"只读版 Tool" | Resources 根本不是给模型"调用"的。Resources 是给 Host 用来填充上下文的数据源，由应用程序自动决定用不用；Tool 是给模型主动调用的 |
| 以为 Prompts 是 System Prompt | Prompts 是**用户手动选择的消息模板**，可以包含多轮对话和嵌入资源，和 System Prompt 是完全不同的概念 |
| 以为一个 Server 必须三种原语都提供 | 完全不必。绝大多数 Server 只暴露 Tools，少数加上 Resources，Prompts 更少。只提供其中一种也完全合法 |
| 以为 Tool 的 `isError: true` 等于 JSON-RPC 错误 | 不一样。JSON-RPC 错误是协议层面的错误（比如方法名不存在），`isError: true` 是工具执行层面的错误（比如 API 限流），模型可以看到后者的错误信息并尝试修复 |
| 以为 Resources 不能动态更新 | Resources 支持 `subscribe` + `notifications/resources/updated` 机制，Client 可以订阅某个资源，变化时自动收到通知并重新读取 |

## 优劣势分析

| 优势 | 劣势 |
|------|------|
| **职责分离清晰**：把"做事"、"读数据"、"用模板"分成三种原语，每种有明确的语义和控制方，避免所有东西都叫"Tool"导致的混乱 | **概念门槛**：新手需要理解三种原语的区别，比起 Function Calling 的"只有 Tool"更复杂 |
| **动态发现 + 变更通知**：三种原语都支持 `list` + `listChanged` 通知，工具和数据可以运行时增减，不用重启 | **Prompts 支持参差**：不是所有 Host 都实现了 Prompts 功能，比如某些客户端只支持 Tools |
| **Resources 的只读语义**：明确告诉 Host "这个数据没有副作用"，Host 可以放心地自动把它塞进上下文而不需要用户确认 | **Resources 在实际使用中不如 Tools 直观**：很多开发者倾向于把所有东西都写成 Tool，因为 Tool 的"调用"心智模型更简单 |
| **Prompts 标准化了提示词分发**：Server 作者可以把精心调优的提示词打包分发，用户不需要自己写 Prompt | **三原语的边界有时模糊**：比如"读取文件内容"到底该用 Tool 还是 Resource？需要具体分析是否有副作用 |

## 思考题

<details>
<summary>初级："读取文件内容"应该用 Tool 还是 Resource？</summary>

**参考答案：**

取决于具体场景：

- 如果你想让模型**在对话中主动决定**"我现在需要读这个文件"，然后传入文件路径，那就应该是 **Tool**。因为这个行为是模型主动发起的，而且 Tool 的 `inputSchema` 可以描述路径参数。
- 如果你想让 Host **预先把文件内容自动加载到模型上下文里**，作为背景信息，不需要模型主动请求，那就应该是 **Resource**。

实际上官方的 Filesystem Server 把 `read_file` 实现为 Tool（因为模型需要指定读哪个文件），同时可以把项目根目录的结构暴露为 Resource（方便 Host 自动加载目录树作为上下文）。两种原语可以在同一个 Server 里互补使用。

</details>

<details>
<summary>中级：为什么 MCP 要区分"协议层错误"和"工具执行错误"（isError）？</summary>

**参考答案：**

两种错误的处理链路完全不同：

**协议层错误**（JSON-RPC error）表示请求本身有问题——工具名不存在、参数格式不对、Server 内部崩溃。这种错误由 Client/Host 在协议层面处理，**模型看不到也不需要看到**，因为没有有意义的信息可以让模型修正。

**工具执行错误**（`isError: true`）表示工具跑了、但业务逻辑出了问题——API 限流了、权限不够、输入数据不合法。这种错误**需要让模型看到**，因为模型有可能根据错误信息调整策略（换个参数重试、告诉用户原因、改用别的工具）。

如果把这两种错误混在一起（像传统 RPC 那样只用错误码），模型就没法区分"这个工具根本不存在"和"这个工具存在但这次调用限流了"。前者应该放弃，后者可以等一下重试。

</details>

<details>
<summary>进阶：设计一个"数据库助手" MCP Server，说说你会怎么分配三种原语？</summary>

**参考答案：**

一个合理的设计：

**Tools（模型控制，有副作用）：**
- `execute_query`：执行 SQL 查询（SELECT / INSERT / UPDATE / DELETE）
- `create_table`：创建新表
- `alter_table`：修改表结构

**Resources（应用控制，只读）：**
- `db://schema`：整个数据库的表结构概览（Host 启动时自动加载到上下文，让模型知道有哪些表和字段）
- `db://tables/{table_name}/schema`：用 Resource Template 暴露每张表的详细 schema（Host 按需读取）
- `db://tables/{table_name}/sample`：每张表的前 10 行示例数据

**Prompts（用户控制，模板）：**
- `sql_optimization`：用户手动触发，粘贴一条慢 SQL，Server 返回一套"SQL 性能优化分析"的提示词模板
- `data_migration`：用户描述需求后，Server 返回数据迁移方案的提示词

这个分配的逻辑是：需要"做动作"的用 Tool，需要"提供背景信息"的用 Resource，需要"专业提示词模板"的用 Prompt。Resource 暴露的 schema 信息会被 Host 自动塞进上下文，这样模型在写 SQL 时就已经"知道"表结构了，不需要每次都先调 Tool 查 schema。

</details>

## 参考资料

1. 官方 Tools 文档：<https://modelcontextprotocol.io/docs/concepts/tools>（查询日期 2026-04-11）
2. 官方 Resources 文档：<https://modelcontextprotocol.io/docs/concepts/resources>
3. 官方 Prompts 文档：<https://modelcontextprotocol.io/docs/concepts/prompts>
4. 官方架构文档（原语概述）：<https://modelcontextprotocol.io/docs/concepts/architecture>
5. MCP 协议规范 - Tools：<https://modelcontextprotocol.io/specification/2025-06-18/server/tools>
6. MCP 协议规范 - Resources：<https://modelcontextprotocol.io/specification/2025-06-18/server/resources>
7. MCP 协议规范 - Prompts：<https://modelcontextprotocol.io/specification/2025-06-18/server/prompts>

---
*Source: https://learnagent.wiki/mcp/cards/mcp-primitives*
*Markdown mirror of https://learnagent.wiki, served as text/markdown for LLM ingestion.*