<!-- title: 知识库MCP：Resources与Prompts双原语 -->

## 概述

本教程聚焦 MCP（Model Context Protocol）中最容易被忽视却价值极高的两个原语——**Resources（资源）** 和 **Prompts（提示词模板）**。绝大多数入门教程只讲 Tool，导致开发者把所有事情都塞进"工具"里：把只读文档也包装成 Tool、把固定提示词也写成 Tool。本教程将带你用一个真实的"运维知识库（Runbook KB）"项目，正确区分三类原语，并构建一个可被 Claude Code 直接挂载、可检索、可复用的 MCP 服务器。适合已跑通过第一个 MCP 服务器、想从"能用"迈向"设计正确"的开发者。

## 前置条件

- 已安装 **Python 3.10+** 与 **uv**（`pip install uv` 或系统包管理器）
- 已安装 **Claude Code** CLI 并完成一次登录
- 了解 MCP 三原语的基本概念（Tool / Resource / Prompt），本教程会重申
- 基本的 Python 类型注解与异步函数常识

---

## 一、为什么不是所有东西都该是 Tool

很多开发者第一次写 MCP 服务器时，会陷入一个误区：把所有能力都做成 `@mcp.tool()`。比如读取一份部署文档，明明是纯只读、无副作用的"查"操作，却被包成了一个 Tool。这会带来三个问题：

1. **语义混乱**：Tool 在 MCP 规范里被定义为"有副作用、类似 POST 的操作"（execute code / produce side effect）。把只读内容做成 Tool，等于在告诉模型"这是个会改变世界的动作"，模型在调用时会更加谨慎甚至回避。
2. **上下文利用低效**：MCP 客户端（如 Claude Code）对 Resources 有专门的**附加（attach）机制**——你可以像 `@文件` 一样把资源挂进上下文，而 Tool 只能在模型主动"调用"时才注入结果。只读文档做成 Resource，可控性强得多。
3. **复用性差**：固定的最佳实践提示词（比如"代码评审清单"）做成 Tool 后，每次都要模型重新理解参数；做成 Prompt，则直接变成一条**斜杠命令**，一键复用。

MCP 官方对三原语的定位非常清晰：

| 原语 | 类比 | 控制方 | 典型用途 |
|------|------|--------|----------|
| **Resources** | GET 端点 | 用户/应用主导 | 把数据载入上下文（文档、配置、日志） |
| **Tools** | POST 端点 | 模型主导 | 执行有副作用的操作（查询数据库、发请求） |
| **Prompts** | 可复用模板 | 用户主导 | 标准化交互模式（评审、分诊、解释） |

记住一句话：**只读的、给模型"看"的，用 Resources；有副作用的、让模型"做"的，用 Tools；固定套路、让用户一键触发的，用 Prompts。**

---

## 二、理解 Resources：把数据挂进上下文

Resources 通过 URI 暴露数据。最简单的形式是**静态资源**，URI 固定，例如 `docs://index` 永远返回同一份目录。更强大的是**模板资源（URI Template）**，URI 里带参数，例如 `docs://{path}`，模型可以传不同的 `path` 读取不同文档。

在 FastMCP（MCP Python SDK v1.x 稳定线，生产推荐）里，一个 Resource 就是一个被 `@mcp.resource(...)` 装饰的普通函数：

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

mcp = FastMCP("Demo")

@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """个性化问候"""
    return f"Hello, {name}!"
```

注意三点：
- 装饰器的字符串是 **URI 模板**，`{name}` 会自动从路径里解析成函数参数。
- 函数的**类型注解**就是参数 schema——你不需要手写 JSON Schema。
- **docstring** 会作为该资源的描述返回给客户端，决定模型/用户能否找到它。

Resources 还支持**订阅/更新通知**：当底层数据变化时，服务器可以主动通知客户端资源内容已更新（`mcp.send_resource_updated(uri)`），客户端据此刷新。这对"实时日志""配置热更新"场景非常有用。

---

## 三、理解 Prompts：可复用的斜杠命令

Prompts 是参数化的提示词模板。在 FastMCP 里，`@mcp.prompt()` 装饰一个函数，函数的返回值（字符串，或结构化消息列表）就是最终注入对话的提示词：

```python
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
    """生成一段问候提示词"""
    styles = {
        "friendly": "请写一段温暖友好的问候",
        "formal":   "请写一段正式专业的问候",
    }
    return f"{styles.get(style, styles['friendly'])}，对象是 {name}。"
```

最关键的实际效果：**在 Claude Code 中，每个服务器 Prompt 都会自动变成一条斜杠命令**，命名规则是 `/mcp__<服务器名>__<prompt名>`。上面这个 prompt 挂到名为 `demo` 的服务器后，你直接输入 `/mcp__demo__greet_user`，Claude Code 会弹出参数表单，填完即把生成好的提示词送入对话。

这意味着 Prompts 是**给"人"用的快捷方式**，而 Tools 是给"模型"用的。把团队约定好的提问范式沉淀成 Prompt，比每个人各自手写提示词稳定得多。

---

## 四、实战项目：运维知识库 MCP 服务器

现在动手构建一个真实可用的项目 **RunbookKB**。场景：一个 SRE 团队把所有事故 runbook（Markdown 文档）放在本地目录，希望 Claude Code 能够：读取任意 runbook、按关键词检索、并一键发起"事件分诊"。

### 4.1 目录结构

```
runbook-kb/
├── kb_server.py          # MCP 服务器主文件
├── pyproject.toml        # 依赖声明（mcp）
└── knowledge/            # 知识库根目录（纯 Markdown）
    ├── guides/
    │   ├── deploy.md
    │   └── rollback.md
    └── incidents/
        └── db-high-cpu.md
```

先建两篇示例文档。`knowledge/guides/deploy.md`：

```markdown
# 部署 runbook
1. 确认 CI 产物版本号与发布单一致
2. 灰度 10% 流量，观察 5 分钟错误率
3. 全量发布，监控 P99 延迟
回滚请参考 rollback.md
```

`knowledge/incidents/db-high-cpu.md`：

```markdown
# 数据库 CPU 飙高
- 现象：RDS CPU > 90%，慢查询增多
- 根因排查：先查 pg_stat_activity 找长事务
- 缓解：kill 慢查询，扩容只读节点
```

### 4.2 依赖与初始化

```bash
uv init runbook-kb
cd runbook-kb
uv add "mcp>=1.27,<2"      # 锁定 v1.x 稳定线
mkdir -p knowledge/guides knowledge/incidents
```

> 为什么是 `<2`？因为 MCP Python SDK **v2 是 pre-release**（2.0.0b1），它面向 **2026-07-28 规范**，稳定版计划 2026-07-27 才发布，且每个预发布都可能有不兼容变更。生产环境必须把上界钉死在 `<2`，避免 `uv`/`pip` 意外拉到不稳定的 v2。本文用稳定 v1.x 的 FastMCP API；文末会单独讲 v2 的新写法。

### 4.3 服务器主文件 `kb_server.py`

把三原语各放一个真实例子：

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

KB_ROOT = Path(__file__).parent / "knowledge"
mcp = FastMCP("RunbookKB")


# ========== Resources：只读数据 ==========

@mcp.resource("docs://index")
def list_docs() -> str:
    """列出知识库中所有 runbook 的相对路径清单。"""
    files = sorted(KB_ROOT.rglob("*.md"))
    lines = [f"- {f.relative_to(KB_ROOT)}" for f in files]
    return "# 知识库目录\n" + "\n".join(lines)


@mcp.resource("docs://{path}")
def read_doc(path: str) -> str:
    """按相对路径读取一篇 runbook 全文。"""
    target = (KB_ROOT / path).resolve()
    # 防路径穿越：解析后必须仍在 KB_ROOT 之内
    if not str(target).startswith(str(KB_ROOT.resolve())):
        return "错误：路径越界，拒绝访问。"
    if not target.exists():
        return f"错误：文档 {path} 不存在。"
    return target.read_text(encoding="utf-8")


# ========== Tools：有副作用的检索 ==========

@mcp.tool()
def search_kb(query: str, limit: int = 5) -> str:
    """在知识库全文中按关键词检索，返回命中文档片段。"""
    hits, needle = [], query.lower()
    for f in KB_ROOT.rglob("*.md"):
        text = f.read_text(encoding="utf-8")
        idx = text.lower().find(needle)
        if idx >= 0:
            start = max(0, idx - 60)
            snippet = text[start:idx + len(needle) + 120].replace("\n", " ")
            hits.append(f"[{f.relative_to(KB_ROOT)}] …{snippet}…")
        if len(hits) >= limit:
            break
    return "\n\n".join(hits) if hits else "未找到匹配文档。"


# ========== Prompts：可复用提示词 ==========

@mcp.prompt()
def incident_triage(symptom: str, severity: str = "P2") -> str:
    """生成一份生产事件分诊的标准化提示词。"""
    return (
        f"你是一名 SRE。请对以下生产事件做分诊：\n"
        f"- 现象：{symptom}\n- 级别：{severity}\n\n"
        f"请先调用 search_kb 工具检索相关 runbook，"
        f"再用 docs:// 资源读取全文，最后给出：\n"
        f"1. 可能根因（按概率排序）\n"
        f"2. 立即缓解步骤\n3. 需要通知的负责人。"
    )


if __name__ == "__main__":
    mcp.run()   # 默认 stdio 传输
```

**操作说明**：`search_kb` 是 Tool（有"检索"这个执行动作），`docs://index` 与 `docs://{path}` 是 Resource（纯只读），`incident_triage` 是 Prompt（用户一键触发的固定套路）。一个文件里三种原语各司其职，正是本教程的核心主张。

**预期结果**：服务器能以 stdio 方式被 Claude Code 拉起，对外暴露 2 个资源、1 个工具、1 个提示词。

**常见问题**：
- **`ImportError: mcp`**：依赖没装好，确认 `uv add "mcp>=1.27,<2"` 执行成功。
- **路径越界误报**：Windows 下 `startswith` 比较要小心大小写，可用 `Path.is_relative_to()`（Python 3.9+）替代，更稳妥：`if not target.is_relative_to(KB_ROOT.resolve()):`。

### 4.4 用 MCP Inspector 单机调试

在动 Claude Code 之前，先用官方 Inspector 验证三原语是否都注册成功：

```bash
cd runbook-kb
uv run mcp dev kb_server.py
```

这会启动一个本地 Web UI（默认 `http://localhost:5173`）。连接后你会看到三个标签页：

- **Resources** 标签：能看到 `docs://index`，点开返回目录清单；输入 `path=guides/deploy.md` 测试模板资源。
- **Tools** 标签：调用 `search_kb`，参数 `query=CPU`，应返回 `db-high-cpu.md` 的片段。
- **Prompts** 标签：列出 `incident_triage`，填入 `symptom=数据库CPU 90%`，能看到生成的完整提示词。

**常见问题**：Resources 列表为空——通常是 docstring 为空或函数没被装饰器正确标注，检查 `@mcp.resource` 的 URI 是否以 `协议://` 开头（如 `docs://`）。

### 4.5 挂载到 Claude Code

确认无误后，把它注册为 Claude Code 的 stdio MCP 服务器：

```bash
claude mcp add runbook-kb -- uv run --with "mcp>=1.27,<2" kb_server.py
```

> 命令中 `--` 之后是启动服务器的完整命令。用 `uv run --with` 可以在不污染全局环境的情况下临时注入 `mcp` 依赖。

验证挂载成功：

```bash
claude mcp list          # 应出现 runbook-kb
claude mcp get runbook-kb  # 查看其暴露的工具/资源/提示词
```

然后在 Claude Code 交互界面里：
1. 输入 `/mcp__runbook-kb__incident_triage` —— Prompt 变成的斜杠命令，填参数即可发起分诊。
2. 让模型"读一下 docs://index"——它会通过 Resource 把目录载入上下文。
3. 让模型"搜一下 CPU 相关的 runbook"——它会调用 `search_kb` 工具。

---

## 五、整体架构与调用流程

```mermaid
flowchart LR
    U([用户在 Claude Code]) -->|/mcp__...__incident_triage| P[Prompt 模板]
    U -->|@ docs://index 挂载| R[Resource 只读数据]
    M[(Claude 模型)] -->|主动调用| T[Tool 检索]
    P --> M
    R --> M
    T --> KB[(knowledge/*.md)]
    R --> KB
    M -->|综合输出| U
```

三条路径各不相同：**Prompt** 由用户主动触发（人主导），**Resource** 由用户/应用附加进上下文（人主导），**Tool** 由模型在推理时自主决定调用（模型主导）。三者最终都汇入模型的上下文窗口，共同支撑一次高质量的回答。

---

## 六、进阶：动态资源列表与自动补全

真实知识库里文档会增删，固定写死的资源列表不现实。FastMCP 支持用 `@mcp.resource` 配合目录动态列举资源，以及 **Completions（补全）** 能力——当用户在 URI 模板里输入 `{path}` 时，客户端可以请求服务器返回候选项。

一个轻量做法是再加一个"列举可用路径"的静态资源，并把它和模板资源配合：

```python
@mcp.resource("docs://paths.json")
def available_paths() -> str:
    """返回所有可用文档路径的 JSON，供客户端做补全提示。"""
    import json
    paths = [str(f.relative_to(KB_ROOT)) for f in KB_ROOT.rglob("*.md")]
    return json.dumps(paths, ensure_ascii=False)
```

当文档变化时，主动通知客户端刷新：

```python
# 例如在某次写入文件后
mcp.send_resource_updated("docs://paths.json")
```

这样客户端缓存的资源就会失效重取，保证知识库"热更新"。

---

## 七、向前看：v2 的 `MCPServer` 新写法

MCP Python SDK 正在向 v2 演进（面向 **2026-07-28 规范**，稳定版计划 **2026-07-27**）。v2 把类名从 `FastMCP` 统一为 `MCPServer`，API 更精简，"15 行一个服务器"的写法变成：

```python
from mcp.server import MCPServer

mcp = MCPServer("Demo")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"
```

装饰器语义、三原语划分与本教程完全一致，迁移成本极低。但**务必等 v2 正式稳定后再用于生产**，预发布期间每个版本都可能有破坏性变更，且要在依赖里写 `<2` 上界。本教程的 RunbookKB 只需把 `from mcp.server.fastmcp import FastMCP` 换成 `from mcp.server import MCPServer`、`FastMCP(` 换成 `MCPServer(` 即可在 v2 上跑。

---

## 实战案例小结

回顾 RunbookKB 做了什么：用 **Resources** 暴露只读的 runbook 全文与目录（语义正确、可被附加、可热更新），用 **Tool** 承担有副作用的全文检索，用 **Prompt** 把"事件分诊"沉淀成团队共享的一键斜杠命令。三者各归其位，没有把只读数据硬塞进 Tool，也没有把固定提示词写成 Tool。这就是"设计正确"的 MCP 服务器与"能跑就行"的 MCP 服务器的本质区别。

可运行的扩展方向：
- 把 `search_kb` 升级为向量检索（嵌入 + 相似度），从"关键词命中"升级为"语义命中"。
- 给 `read_doc` 增加权限维度，按用户角色过滤可见文档。
- 增加 **Elicitation**（v1.x 文档已列入 server 能力），让服务器在执行中反问用户补充信息，形成"检索→反问→精化"的闭环。

---

## 延伸阅读

- **MCP 官方规范与概念**：<https://modelcontextprotocol.io/specification/latest> ——三原语的权威定义、URI 模板、资源订阅等。
- **MCP Python SDK 文档**：<https://py.sdk.modelcontextprotocol.io/> ——v1.x 与 v2 的 Building Servers / Resources / Prompts 章节。
- **MCP Inspector**：<https://github.com/modelcontextprotocol/inspector> ——本地调试三原语的图形工具。
- **Claude Code MCP 接入文档**：<https://docs.claude.com/en/docs/claude-code/mcp> ——`claude mcp add` 的 stdio/http 两种传输配置。

---

## 训练营适用性评估

- **适合类型**：企业内部 SRE/DevOps/平台工程训练营、AI 工程化进阶营、Claude Code 深度实战营。
- **教学时长**：约 **2.5–3 小时**（含动手）。其中概念辨析 30 分钟、RunbookKB 编码 90 分钟、Inspector 调试与 Claude Code 挂载 45 分钟、v2 迁移与扩展讨论 15 分钟。
- **学员产出**：每人/每组交付一个可挂载、可检索、含至少一个 Prompt 的知识库 MCP 服务器，并能在 Claude Code 中用斜杠命令触发。
- **难度定位**：中级。要求学员已跑通过至少一个 MCP Tool 服务器，本教程的增量是"原语选型的判断力"而非"从零装环境"。

---

## 参考来源

1. [MCP Python SDK（GitHub v1.x README）](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/README.md) — 官方 Python SDK，含 FastMCP 三原语 quickstart、v2 迁移说明与 2026-07-28 规范时间线。
2. [Model Context Protocol 官方文档](https://modelcontextprotocol.io) — MCP 概念、规范、Resources/Tools/Prompts 的权威定义。
3. [MCP Tools vs Resources vs Prompts（RapidDevelopers）](https://www.rapidevelopers.com/mcp-tutorial/mcp-tools-vs-resources-vs-prompts) — 三原语选型对比与"何时用哪个"的实践指引。
4. [Complete Guide to MCP in 2026（dev.to）](https://dev.to/x4nent/complete-guide-to-mcp-model-context-protocol-in-2026-architecture-implementation-and-4a11) — 2026 年 MCP 架构、Streamable HTTP 传输与生产部署综述。
5. [MCP Inspector（GitHub）](https://github.com/modelcontextprotocol/inspector) — 用于本地调试 Resources/Tools/Prompts 的官方图形工具。

> [!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)
