<!-- title: 补全MCP三原语：资源与提示词挂载实战 -->

# 补全MCP三原语：Resources 与 Prompts 挂载实战

绝大多数「第一个 MCP 服务器」教程止步于 Tools：写几个 `@mcp.tool()` 函数，跑通 Inspector，挂到 Claude Code，收工。但 MCP 协议一共定义了**三种原语**——Tools、Resources、Prompts，它们各自回答不同的问题，在 Claude Code 里的暴露方式也完全不同。只造工具，等于只用了协议的三分之一。

本篇聚焦那两块最常被忽略的拼图：用 **Resources** 把数据像文件一样暴露出去（可被 `@` 提及），用 **Prompts** 把可复用工作流变成 `/` 斜杠命令。我们用一个可运行的「个人知识库 MCP」把三原语一次性串起来。

## 一、概述与适用人群

**学习目标**：读完本篇，你将能（1）在 30 秒内判断某个能力该做成 Tool、Resource 还是 Prompt；（2）用官方 Python SDK 的 FastMCP 在一个文件里同时实现三原语；（3）用 MCP Inspector 调试，并挂载到 Claude Code，分别用「工具调用 / @ 提及 / 斜杠命令」三种方式触发。

**适用人群**：已经写过一个 Hello-World 级别的 MCP 工具、想理解协议全貌的开发者；给团队设计内部 MCP 服务、需要把文档和标准操作流程（SOP）一并交付的架构师。

## 二、前置条件

- Python 3.10+，会使用 `venv` 与 `pip`
- 已安装 Claude Code（`claude --version` 可用）
- 看过 Tools 形态的 MCP 示例（不必精通）
- 全程在 Unix-like shell（macOS/Linux/WSL）演示

```bash
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]"
mcp --version   # 确认 MCP CLI 可用
```

## 三、核心决策：为什么工具不是全部

这是本篇最重要的一节。三个原语的根本区别在于**「谁来决定使用它」**（控制方向）和**「它有没有副作用」**：

- **Resources（资源）是 application-driven（宿主应用驱动）**：只读数据，没有副作用，幂等。宿主决定何时、是否把它纳入上下文。MCP 官方文档原话——资源是 "application-driven, with host applications determining how to incorporate context"。
- **Tools（工具）是 model-driven（模型驱动）**：可执行的动作，**有副作用**（写库、发请求、改文件）。由 LLM 在推理时自主决定调用，因此通常需要用户授权。
- **Prompts（提示词模板）是 user-driven（用户驱动）**：服务器预定义的结构化消息/工作流，由用户**显式**触发。

落到 Claude Code 里，三种原语长得完全不一样：

| 能力类型 | 该做成 | 在 Claude Code 中的形态 | 典型触发者 |
|---|---|---|---|
| 只读数据 / 文档 / 配置 | **Resource** | `@server:scheme://path` 的 @ 提及，自动注入为附件 | 宿主/你 |
| 执行动作、有副作用 | **Tool** | 模型自主调用，命中权限规则 | 模型 |
| 可复用的提示词工作流 | **Prompt** | `/mcp__server__prompt` 斜杠命令 | 你 |

一句话判断法：**「提供上下文」用 Resource，「改变世界」用 Tool，「复用打法」用 Prompt。** 把一条 SOP 文档做成 Tool 是错配——它是数据不是动作；把「代码评审流程」做成 Resource 也是错配——它是流程不是数据。

```mermaid
flowchart LR
    Q["新能力需求"] --> R1{"只读数据/文档?"}
    R1 -->|是| RES["Resource\n@server:..."]
    R1 -->|否| R2{"有副作用/执行动作?"}
    R2 -->|是| TOOL["Tool\n模型自主调用"]
    R2 -->|否| PROMPT["Prompt\n/mcp__server__cmd"]
    RES --> CC1["自动注入上下文"]
    TOOL --> CC2["需权限确认"]
    PROMPT --> CC3["用户显式触发"]
    style RES fill:#e8f5e9,stroke:#2e7d32
    style TOOL fill:#fff3e0,stroke:#ef6c00
    style PROMPT fill:#e3f2fd,stroke:#1565c0
```

## 四、实战项目：个人知识库 MCP（kb-mcp）

我们要造一个 markdown 笔记知识库服务器 `kb-mcp`，把三原语用足：

- **Resources**：把笔记目录（`notes://index`）和单条笔记（`notes://{slug}` 资源模板）作为只读数据暴露，方便 `@` 提及。
- **Tools**：`create_note` / `search_notes` / `delete_note`——会改数据、有副作用，交给模型调用。
- **Prompts**：`summarize_note` / `weekly_digest`——固定打法，做成斜杠命令。

### Step 1：数据模型与存储

笔记存成一个本地 JSON 索引文件（`index.json`），路径由 `KB_DIR` 环境变量控制，便于隔离测试。新建 `kb_mcp.py`：

```python
import json, os, re
from pathlib import Path
from mcp.server.fastmcp import FastMCP

KB_DIR = Path(os.environ.get("KB_DIR", Path.home() / ".kb-mcp"))
KB_DIR.mkdir(parents=True, exist_ok=True)
INDEX_FILE = KB_DIR / "index.json"

mcp = FastMCP("kb-mcp")

def slugify(title: str) -> str:
    s = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")[:60]
    return s or "note"

def _load() -> dict:
    return json.loads(INDEX_FILE.read_text("utf-8")) if INDEX_FILE.exists() else {}

def _save(idx: dict) -> None:
    INDEX_FILE.write_text(json.dumps(idx, ensure_ascii=False, indent=2), "utf-8")
```

> **预期结果**：`KB_DIR` 不存在会自动创建；`index.json` 首次写入即生成。

### Step 2：实现 Tools（动作）

```python
@mcp.tool()
def create_note(title: str, body: str, tags: str = "") -> str:
    """创建一条笔记。title 标题；body 正文（Markdown）；tags 逗号分隔标签。返回新笔记 URI。"""
    idx = _load()
    slug = slugify(title)
    idx[slug] = {"title": title, "body": body,
                 "tags": [t.strip() for t in tags.split(",") if t.strip()]}
    _save(idx)
    return f"created notes://{slug}"

@mcp.tool()
def search_notes(query: str) -> str:
    """按关键词检索标题、正文、标签，返回匹配的 URI 列表。"""
    idx, q = _load(), query.lower()
    hits = [s for s, n in idx.items()
            if q in s or q in n["title"].lower() or q in n["body"].lower()
            or any(q in t.lower() for t in n["tags"])]
    return "\n".join(f"- notes://{s} — {idx[s]['title']}" for s in hits) or "无匹配笔记。"

@mcp.tool()
def delete_note(slug: str) -> str:
    """按 slug 删除笔记。"""
    idx = _load()
    if slug not in idx:
        return f"未找到 notes://{slug}"
    del idx[slug]; _save(idx)
    return f"deleted notes://{slug}"
```

每个工具的**文档字符串会变成给模型的工具说明**——这是触发准确性的关键，务必写清「做什么」和「参数含义」。

### Step 3：实现 Resources（数据暴露）

这是与 Tools 教程的分水岭。Resources 用 `@mcp.resource("URI")`，URI 是数据标识而非函数名。静态资源用一个常量 URI；**资源模板**用 RFC 6570 风格的 `{param}` 占位，函数参数名与之对应：

```python
@mcp.resource("notes://index")
def note_index() -> str:
    """知识库目录清单（所有笔记的 URI 与标题）。"""
    idx = _load()
    if not idx:
        return "（知识库为空）"
    return "\n".join(f"- notes://{s} — {n['title']} [{', '.join(n['tags'])}]"
                     for s, n in idx.items())

@mcp.resource("notes://{slug}")
def read_note(slug: str) -> str:
    """读取单条笔记全文。"""
    idx = _load()
    if slug not in idx:
        raise ValueError(f"未知笔记: {slug}")
    n = idx[slug]
    return f"# {n['title']}\n\n{n['body']}\n\n_标签: {', '.join(n['tags'])}_"

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

两个关键点：

1. **`notes://{slug}` 是资源模板**。Claude Code 在 `@` 自动补全里会模糊匹配 `slug`，命中后自动 `resources/read` 拉取全文注入上下文。
2. **资源函数每次被读取时实时求值**——索引变了，下一次 `resources/read notes://index` 立刻反映最新状态，天然「动态」。

> 关于 `list_changed`：当你的资源列表是**静态注册**（启动时固定）时，若运行期增删，应发送 `notifications/resources/list_changed` 通知让宿主刷新列表。FastMCP 这种「函数即资源」写法每次读取都重新求值，已隐含动态性；若改用预注册列表，记得补这条通知。

### Step 4：实现 Prompts（可复用工作流）

Prompts 是「带参数的预制提示词」，在 Claude Code 里变成 `/mcp__server__name` 斜杠命令：

```python
@mcp.prompt()
def summarize_note(slug: str) -> str:
    """对指定笔记生成结构化摘要：三句话概述 + 关键点 + 一条建议。"""
    return (f"请引用 @kb-mcp:notes://{slug}，"
            "输出：1) 三句话概述；2) 3-5 个关键点；3) 一条可执行建议。")

@mcp.prompt()
def weekly_digest() -> str:
    """生成知识库简报：读取目录并把笔记按标签归类。"""
    return "请读取 @kb-mcp:notes://index，把所有笔记按标签分组，挑出 3 条最重要的。"
```

注意 Prompt 的返回值本身就是给模型的指令——它可以把 Resource（`@kb-mcp:notes://...`）、Tool 调用编排进一条工作流里，这正是 Prompt 作为「打法封装」的价值。

## 五、用 MCP Inspector 调试（先别急着挂 Claude Code）

Inspector 是官方 GUI 调试器，能分别对三种原语做单点测试，强烈建议先在这里验证：

```bash
mcp dev kb_mcp.py
```

浏览器打开 Inspector 后：

- **Tools 面板**：填 `title/body/tags` 调用 `create_note`，看返回 `created notes://xxx`。
- **Resources 面板**：点 List 能看到 `notes://index` 和模板 `notes://{slug}`；点开某条资源读全文。
- **Prompts 面板**：List 出 `summarize_note` / `weekly_digest`；给 `summarize_note` 传 `slug` 参数，预览渲染后的消息。

> **常见问题**：Resources 面板空白 → 多半是没 `import` 或函数没加 `@mcp.resource`；Prompts 参数不生效 → 检查函数形参名与 Inspector 字段是否一致。

## 六、挂载到 Claude Code 并验证三种触发方式

### 挂载（stdio 传输）

注意 stdio 用法中 `--` 把 Claude 自己的选项和启动命令隔开：

```bash
# 用绝对路径，避免依赖当前工作目录
KB_DIR="$HOME/.kb-mcp" \
  claude mcp add --transport stdio --env KB_DIR="$HOME/.kb-mcp" \
  kb-mcp -- python "$PWD/kb_mcp.py"

claude mcp list          # 应看到 kb-mcp
```

进入会话后用 `/mcp` 确认连接，面板会显示该服务器暴露的工具数量。Claude Code 支持 `list_changed` 通知，资源/提示词变动会自动刷新，无需重连。

### 方式 A：触发 Tool（模型自主调用）

```text
帮我用 create_note 建一条笔记：标题 "MCP 三原语"，正文讲 Tools/Resources/Prompts 区别，标签 mcp,arch
```

模型自主决定调用 `create_note`，命中权限规则后执行；返回 `created notes://mcp-san-yuan-yu`。

### 方式 B：@ 提及 Resource（宿主注入）

在输入框敲 `@`，自动补全里会列出 `@kb-mcp:notes://index` 等：

```text
把 @kb-mcp:notes://index 读出来，告诉我知识库里都有什么
```

引用的资源会被自动 `resources/read` 并作为附件注入。这是 Resource 区别于 Tool 的核心体验——**你主动选定上下文，而不是让模型去猜**。

### 方式 C：执行 Prompt 斜杠命令（用户显式触发）

敲 `/` 能看到 `/mcp__kb-mcp__summarize_note` 与 `/mcp__kb-mcp__weekly_digest`。带参数的 prompt 用空格传参：

```text
/mcp__kb-mcp__summarize_note mcp-san-yuan-yu
```

渲染后的提示词直接进入对话，模型据此执行摘要工作流。

```mermaid
sequenceDiagram
    participant U as 你
    participant CC as Claude Code
    participant S as kb-mcp 服务器
    Note over U,S: Tool 路径 —— 模型自主调用
    U->>CC: "建一条关于 MCP 的笔记"
    CC->>S: tools/call create_note(...)
    S-->>CC: created notes://mcp-san-yuan-yu
    Note over U,S: Resource 路径 —— @ 提及注入
    U->>CC: "分析 @kb-mcp:notes://index"
    CC->>S: resources/read notes://index
    S-->>CC: 目录全文（作为附件）
    Note over U,S: Prompt 路径 —— 斜杠命令
    U->>CC: /mcp__kb-mcp__summarize_note mcp-san-yuan-yu
    CC->>S: prompts/get summarize_note{slug}
    S-->>CC: 渲染后的提示词
    CC->>CC: 据此执行摘要工作流
```

## 七、完整可运行示例

把上面四段代码拼进同一个 `kb_mcp.py`（Step 1 的导入/存储 + Step 2~4 的三原语），`python kb_mcp.py` 即为标准 stdio 服务器。配套验证脚本：

```bash
# 1. 清空环境变量起步
export KB_DIR="$(mktemp -d)"
# 2. Inspector 手测
mcp dev kb_mcp.py
# 3. 挂载（另开终端）
claude mcp add --transport stdio --env KB_DIR="$KB_DIR" kb-mcp -- python "$PWD/kb_mcp.py"
# 4. 进会话验证
claude
#   /mcp                              → 确认 kb-mcp 已连接
#   "用 create_note 建一条笔记..."    → 验证 Tool
#   "@kb-mcp:notes://index 读出来"    → 验证 Resource
#   /mcp__kb-mcp__weekly_digest       → 验证 Prompt
```

## 八、常见问题与陷阱

- **`@` 菜单里看不到我的资源**：确认 `@mcp.resource` 装饰器已加、URI scheme 合法（自定义 scheme 须符合 RFC 3986）；`/mcp` 里服务器要显示已连接。
- **斜杠命令找不到**：Prompt 在 Claude Code 里是 `/mcp__服务器名__prompt名`，注意是双下划线分隔；服务器名里的空格会被规范化成下划线。
- **工具被「藏起来」了**：Claude Code 默认开启 Tool Search，工具定义按需加载，模型靠**服务器 instructions**判断何时检索。把「这组工具处理什么任务」写进服务器说明，触发会更准（描述与 instructions 各被截断到 2KB，关键信息前置）。
- **资源返回内容很大**：默认输出超 1 万 token 会告警，默认上限 2.5 万 token。大资源可在工具 `_meta` 里声明 `anthropic/maxResultSizeChars`（上限 50 万字符）放宽阈值，或调 `MAX_MCP_OUTPUT_TOKENS`。
- **想让某些工具始终可见**：在 `.mcp.json` 给服务器加 `"alwaysLoad": true`，绕过 Tool Search 延迟加载（代价是常驻占用上下文）。
- **多机器/团队共享配置**：用 `--scope project` 把配置写进 `.mcp.json` 入版本控制，配合 `${VAR:-default}` 变量展开处理机器相关路径与密钥。

## 九、延伸阅读

- MCP 官方概念文档：Resources（`/docs/concepts/resources`）、Prompts（`/docs/concepts/prompts`）——协议层最权威的语义与 JSON-RPC 报文定义。
- Claude Code 官方 MCP 接入文档（`code.claude.com/docs/en/mcp`）：`claude mcp add` 全部选项、scope 体系、Tool Search、`maxResultSizeChars` 等服务端作者必读项。
- `modelcontextprotocol.io/docs/develop/build-server`：三原语的服务端构建总览。
- 官方 `mcp-server-dev` 插件：在 Claude Code 里 `/plugin install mcp-server-dev@claude-plugins-official` 后 `/mcp-server-dev:build-mcp-server`，让 Claude 直接脚手架一个本地或远程服务器。

## 十、训练营适用性评估

- **适配类型**：进阶训练营「AI 工具链 / Agent 工程」模块，作为「MCP 协议进阶」一节的实操课；也适合企业内训「把团队 SOP 与内部文档沉淀成可调用能力」的工作坊。
- **前置要求**：学员须先完成一个纯 Tools 的 MCP 入门练习。
- **建议时长**：讲授 45 分钟（重点放在第三节决策框架）+ 动手 60 分钟（Inspector 调试 + Claude Code 三种触发各跑一遍）+ 自由扩展 30 分钟（如：给资源加 `audience/priority/lastModified` 注解、把 Prompt 改成多轮嵌入式资源引用）。
- **可量化产出**：学员交付一个同时含 ≥2 个 Tool、≥1 个资源模板、≥1 个 Prompt 的本地 MCP 服务器，并能在 Claude Code 中分别用三种方式触发。

## 参考来源

1. [Resources — Model Context Protocol](https://modelcontextprotocol.io/docs/concepts/resources) — 资源原语：application-driven 模型、capabilities、resources/list 与 resources/read、资源模板、annotations。
2. [Prompts — Model Context Protocol](https://modelcontextprotocol.io/docs/concepts/prompts) — 提示词原语：user-controlled 模型、prompts/list 与 prompts/get、参数化与多模态内容。
3. [Connect Claude Code to tools via MCP](https://code.claude.com/docs/en/mcp) — Claude Code 侧：`claude mcp add`/scope/`.mcp.json`、`@` 引用资源、`/mcp__server__prompt` 斜杠命令、Tool Search、`maxResultSizeChars` 注解。
4. [Build an MCP server — Model Context Protocol](https://modelcontextprotocol.io/docs/develop/build-server) — 三原语（Tools/Resources/Prompts）的服务端构建总览与能力声明。
5. [How to Build Your First MCP Server for Claude (2026)](https://www.aiforanything.io/blog/how-to-build-mcp-server-claude-2026) — 2026 年度 MCP 服务器构建实战教程，含 Tools/Resources 综合示例。

---

*由「51学通信」原创出品 · 站长爱卫生（微信：gprshome201101）*

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
