<!-- title: 造会借宿主大脑的MCP服务器 -->

# 造会借宿主大脑的MCP服务器：MCP 采样与根目录原语实战，附 SEP-2577 弃用迁移

## 概述

大多数 MCP 教程只讲一个方向：宿主（client）调用服务器暴露的 `tools/resources/prompts`。本教程聚焦被冷落的**反向原语**——服务器反过来向宿主发起请求。你将掌握两个反向原语：**Sampling（采样）** 让服务器"借用宿主大脑"完成总结、分类、评审等 LLM 推理；**Roots（根目录）** 让服务器知道它被允许操作哪些目录。教程最后给出 2026 年 6 月的**重大变化**：MCP 官方 SEP-2577 已正式弃用 Roots、Sampling、Logging 三个原语，并提供一年迁移窗口。我们将构建一个完整的"代码评审小犬"服务器，再把它迁移到不会被弃用的等价实现。

**适用人群**：已能写基本 MCP 服务器（Tools/Resources/Prompts）、想进阶到协议高级能力、并希望写出"面向未来"代码的开发者。

## 前置条件

- Python 3.11+，已会写普通 FastMCP 服务器（`@mcp.tool()` 基本用法）
- 安装：`pip install "mcp>=1.28" anthropic`
- 了解 JSON-RPC 的 request/response 概念
- 一个可用的 LLM API Key（用于迁移章节直接调用，也用于自定义测试 client 回答采样请求）
- 已安装 MCP Inspector：`npx @modelcontextprotocol/inspector`

## 核心概念：MCP 的双向请求

MCP 建立在 JSON-RPC 之上，而 JSON-RPC 天然支持**双向请求**。客户端能调用服务器方法，服务器也能反过来调用客户端方法。这三类原语恰好分两个方向：

```mermaid
flowchart LR
    subgraph 客户端["宿主 / Client（如 Claude Code、Inspector）"]
        C["MCP Host\n+ 用户 + LLM"]
    end
    subgraph 服务器["MCP Server（你写的工具）"]
        S["FastMCP 服务"]
    end

    C -- "正向：tools/call、resources/read、prompts/get" --> S
    S -. "反向：roots/list（你在哪）、\nsampling/createMessage（帮我想想）、\nelicitation/create（问用户）" .-> C
```

- **正向原语**（client → server）：`Tools`、`Resources`、`Prompts`。这是绝大多数教程和实际项目的主力。
- **反向原语**（server → client）：`Roots`、`Sampling`、`Elicitation`。它们让服务器"反客为主"，向宿主要信息或要算力。

理解反向原语的关键在于能力协商：客户端必须在初始化握手时声明"我支持 sampling/roots"，否则服务器发起这些请求会被拒绝。这也是它们**采用率低、最终被弃用**的根源之一——能完整实现这套反向握手的客户端不多。

## 环境准备

新建目录并安装依赖：

```bash
mkdir mcp-reverse && cd mcp-reverse
python -m venv .venv && source .venv/bin/activate
pip install "mcp>=1.28" anthropic
export ANTHROPIC_API_KEY=sk-ant-xxxxx   # 迁移章节与自定义 client 需要
```

确认版本后，我们先把"采样"这一最性感的能力跑通。

## 步骤一：理解 Sampling（采样）

采样的协议报文很简洁。服务器通过 `sampling/createMessage` 向客户端要一次 LLM 生成：

**服务器 → 客户端的请求：**

```json
{
  "jsonrpc": "2.0", "id": 1, "method": "sampling/createMessage",
  "params": {
    "messages": [{ "role": "user", "content": { "type": "text", "text": "总结下面这段文字" } }],
    "systemPrompt": "你是简洁的助手。",
    "maxTokens": 100,
    "modelPreferences": { "hints": [{ "name": "claude" }], "intelligencePriority": 0.8 }
  }
}
```

**客户端 → 服务器的响应：**

```json
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "role": "assistant",
    "content": { "type": "text", "text": "（生成结果）" },
    "model": "claude-sonnet-4-6",
    "stopReason": "endTurn"
  }
}
```

它的精妙之处在于**控制权仍在宿主**：宿主可在转发给真正的 LLM 前，让用户审查、修改、甚至拒绝这次采样请求（规范要求 SHOULD 始终保留 human-in-the-loop）。服务器因此**不需要自带 API Key**，零密钥就能获得 LLM 能力。

```mermaid
sequenceDiagram
    participant S as 服务器(tool 执行中)
    participant C as 客户端/宿主
    participant U as 用户
    participant L as LLM
    S->>C: sampling/createMessage
    C->>U: 展示请求，请求确认/修改
    U-->>C: 批准
    C->>L: 转发为真实模型调用
    L-->>C: 生成文本
    C-->>S: CreateMessageResult
```

## 步骤二：构建第一个采样服务器

用 FastMCP（v1 稳定版）写一个工具：把传入的文本交给宿主大模型总结。注意采样要拿到 `ctx.session`，再调用 `create_message`。

```python
# sampler_min.py
from mcp.server.fastmcp import FastMCP, Context
from mcp.types import TextContent, SamplingMessage

mcp = FastMCP("sampler-min")


@mcp.tool()
async def summarize(text: str, ctx: Context) -> str:
    """让宿主大模型用一句话总结 text。"""
    result = await ctx.session.create_message(
        messages=[
            SamplingMessage(
                role="user",
                content=TextContent(text=f"用一句中文总结：\n\n{text}"),
            )
        ],
        system_prompt="你是一句话总结器，不超过 30 字。",
        max_tokens=120,
    )
    assert isinstance(result.content, TextContent)
    return result.content.text


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

**关键点解读：**

- `ctx: Context` 是 FastMCP 自动注入的请求上下文，标注 `Context` 类型即可获得。
- `ctx.session.create_message(...)` 是发起采样的入口。`messages` 必须是 `SamplingMessage` 列表，`content` 用 `TextContent`（也支持 `ImageContent`、`AudioContent`）。
- 返回的 `result.content` 可能是文本或图像，因此 `isinstance` 断言后再取 `.text` 最稳妥。
- `max_tokens` 是必填项——采样没有默认上限，必须显式给出。

**常见问题：**

| 报错现象 | 原因 | 解决 |
|---|---|---|
| `McpError: client does not support sampling` | 宿主没声明 `sampling` 能力 | 换支持采样的客户端（见步骤五） |
| 工具一直转圈不返回 | 宿主弹了确认框等用户操作 | 在宿主 UI 里批准该采样请求 |
| 返回空或被截断 | `max_tokens` 太小 | 调大 `max_tokens` |
| `pyright: reportDeprecated` 提示 | 这是 SEP-2577 弃用标记（见后文） | 暂时忽略，或按迁移章节改造 |

## 步骤三：理解 Roots（根目录）

Roots 解决的是"服务器应该操作哪些目录"的问题。客户端声明 `roots` 能力后，服务器用 `roots/list` 取回一个 `file://` URI 列表，并在列表变化时收到 `notifications/roots/list_changed`。

```json
// 服务器 -> 客户端
{ "jsonrpc": "2.0", "id": 2, "method": "roots/list" }
// 客户端 -> 服务器
{ "jsonrpc": "2.0", "id": 2,
  "result": { "roots": [
    { "uri": "file:///home/u/myproject", "name": "myproject" }
  ]}}
```

注意规范的**模糊语义**：Roots 是"信息性指引"——服务器**不强制**遵守。这既是它灵活之处，也是它最终被弃用的原因之一（语义太弱）。一个负责任的服务器应当把 roots 当作软边界，配合路径规范化防止 `../` 越界，但不要把它当成安全防线。

## 步骤四：给服务器加上 Roots 感知

下面把 roots 接入文件操作，做"目录边界检查"：

```python
# roots_guard.py
from pathlib import Path
from urllib.parse import urlparse, unquote
from mcp.server.fastmcp import FastMCP, Context

mcp = FastMCP("roots-guard")


def _uri_to_path(uri: str) -> Path:
    # file:///home/u/proj  ->  /home/u/proj
    return Path(unquote(urlparse(uri).path))


def _within(candidate: Path, roots: list[Path]) -> bool:
    real = candidate.resolve()
    for root in roots:
        try:
            real.relative_to(root)   # 能算出相对路径 → 在该根内
            return True
        except ValueError:
            continue
    return False


@mcp.tool()
async def read_file(rel_path: str, ctx: Context) -> str:
    """读取 roots 第一个根目录下的相对文件；越界则拒绝。"""
    roots_result = await ctx.session.list_roots()
    roots = [_uri_to_path(r.uri) for r in roots_result.roots]
    if not roots:
        return "❌ 宿主未暴露任何 roots。"

    candidate = (roots[0] / rel_path).resolve()
    if not _within(candidate, roots):
        return f"❌ 拒绝：{rel_path} 不在允许的根目录范围内。"
    return candidate.read_text(encoding="utf-8", errors="replace")
```

**常见问题：**

- `roots/list` 报 "not supported"：宿主未声明 roots 能力。Claude Code 默认会把当前工作目录作为 root 暴露；纯 stdio 测试时需在自定义 client 里提供。
- 路径判断不准：务必先 `.resolve()` 再比较，避免符号链接或 `../` 绕过。
- 把 roots 当安全机制：错。真正的隔离要靠进程权限、容器或沙箱，roots 只是"礼貌的提示"。

## 实战案例：代码评审小犬（Sampling + Roots 合体）

把两个反向原语组合成一个真实可用的小工具：它先取 roots 锁定项目目录，读取指定文件，再用采样让宿主大模型审阅。

```python
# review_puppy.py —— 代码评审小犬
from pathlib import Path
from urllib.parse import urlparse, unquote

from mcp.server.fastmcp import FastMCP, Context
from mcp.types import TextContent, SamplingMessage

mcp = FastMCP("review-puppy")


def _uri_to_path(uri: str) -> Path:
    return Path(unquote(urlparse(uri).path))


def _within(candidate: Path, roots: list[Path]) -> bool:
    real = candidate.resolve()
    return any(_safe_relative(real, r) for r in roots)


def _safe_relative(real: Path, root: Path) -> bool:
    try:
        real.relative_to(root)
        return True
    except ValueError:
        return False


@mcp.tool()
async def review_diff(diff_text: str, ctx: Context) -> str:
    """让宿主大模型审阅一段 git diff，返回三条中文意见。"""
    prompt = (
        "你是严格的代码审查者。审阅下面的 diff，"
        "用三句话指出最值得修改的问题，每句以『- 』开头：\n\n"
        f"{diff_text}"
    )
    result = await ctx.session.create_message(
        messages=[SamplingMessage(role="user", content=TextContent(text=prompt))],
        system_prompt="你是资深工程师，评审简短、具体、可执行。",
        max_tokens=500,
    )
    assert isinstance(result.content, TextContent)
    return result.content.text


@mcp.tool()
async def review_file(rel_path: str, ctx: Context) -> str:
    """在 roots 范围内读取文件，并对其内容做评审；越界拒绝。"""
    roots_result = await ctx.session.list_roots()
    roots = [_uri_to_path(r.uri) for r in roots_result.roots]
    if not roots:
        return "❌ 宿主未暴露任何 roots。"

    candidate = (roots[0] / rel_path).resolve()
    if not _within(candidate, roots):
        return f"❌ 拒绝：{rel_path} 越出允许的根目录。"

    text = candidate.read_text(encoding="utf-8", errors="replace")
    return await review_diff(f"（单文件 {candidate.name}）\n{text}", ctx)


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

这个服务器把"知道在哪（roots）"和"会思考（sampling）"组合起来：`review_file` 先做边界检查（安全），再做 LLM 评审（智能）。但它有个现实问题——**谁来回答采样请求？** 这正是反向原语最棘手的一环。

## 步骤五：如何验证（自定义 client 是关键）

采样的硬伤在于：**服务器发起的请求，必须有一个声明了 sampling 能力的客户端来回答。** 截止 2026 年中，Claude Code 等宿主对采样的支持参差不齐（这也是它被弃用的直接原因）。最可靠的验证途径有两条。

**途径 A：MCP Inspector**

```bash
npx @modelcontextprotocol/inspector python review_puppy.py
```

Inspector 是带 UI 的调试台，能列出工具、手动触发调用、查看 JSON-RPC 报文。它对采样的支持取决于版本，遇到 `not supported` 时改用途径 B。

**途径 B：自定义 client（推荐）**

自己写一个客户端，声明 sampling + roots 能力，并用一个真实 LLM 回答服务器的 `createMessage`。这是反向原语唯一的"端到端"测法：

```python
# test_client.py
import asyncio
from mcp import ClientSession, types
from mcp.client.stdio import stdio_client, StdioServerParameters
from anthropic import Anthropic

llm = Anthropic()


async def on_sampling(context, params: types.CreateMessageRequestParams):
    """回答服务器的 sampling/createMessage。"""
    user_text = params.messages[-1].content.text
    rsp = llm.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=params.max_tokens,
        system=params.system_prompt or None,
        messages=[{"role": "user", "content": user_text}],
    )
    return types.CreateMessageResult(
        role="assistant",
        content=types.TextContent(type="text", text=rsp.content[0].text),
        model=rsp.model,
        stopReason="endTurn",
    )


async def list_roots(context):
    """向服务器暴露工作目录作为 root。"""
    return types.ListRootsResult(
        roots=[types.Root(uri="file:///home/u/myproject", name="myproject")]
    )


async def main():
    params = StdioServerParameters(command="python", args=["review_puppy.py"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(
            read, write,
            sampling_callback=on_sampling,
            list_roots_callback=list_roots,
        ) as session:
            await session.initialize()
            res = await session.call_tool(
                "review_file",
                {"rel_path": "src/utils.py"},
            )
            print(res.content[0].text)


asyncio.run(main())
```

**预期输出**（针对某个 `utils.py`）类似：

```
- 函数缺少类型注解与 docstring，可读性差。
- 异常被裸 except 吞掉，调试时难以定位。
- 用字符串拼接构造路径，存在跨平台风险。
```

`sampling_callback` 与 `list_roots_callback` 是 `ClientSession` 构造参数：宿主正是通过它们把"反向请求"的实现委托给你的代码。至此，双向握手的两侧你都掌握了。

## 重大变化：SEP-2577 弃用通知（2026 年 6 月）

**这是本教程最重要的部分。** 2026 年 4 月 14 日，MCP 核心维护者 Kurtis Van Gent 提交了 SEP-2577《Deprecate Roots, Sampling, and Logging》，状态已为 **Final**。它正式弃用我们刚学的三个能力：

- **Roots**：`roots/list`、`notifications/roots/list_changed`
- **Sampling**：`sampling/createMessage`、`ClientCapabilities.tasks.requests.sampling`
- **Logging**：`logging/setLevel`、`notifications/message`

**时间线（务必记住）：**

- 弃用自**包含本 SEP 的规范版本**起生效（预期 2026 年 6 月）。
- 这些功能在该版本发布后**一年内仍完全可用**（约到 2027 年 6 月）。
- 弃用期内**线缆协议行为不变**：没有类型被删除、能力协商不变、现有实现不会坏。弃用只是一个信号——**不要再基于它们构建新功能，并规划迁移。**

**为什么是这三个？** 官方给出三条共性理由：

1. **采用率低**：feature 支持矩阵显示，真正实现 roots/sampling 的客户端很少；sampling 自 2024 年 11 月就在规范里了，仍未普及。
2. **语义模糊或实现过重**：roots 只是"信息性"指引、服务器可不遵守；sampling 要正确实现需 human-in-the-loop、模型选择、安全考量，还要（自 SEP-1577）支持工具循环，成本极高。
3. **有更成熟的重叠替代品**：每个原语都有更直接、更通用的等价做法。

理解了"为什么弃用"，就理解了"该迁向哪里"。

## 迁移实战：如何把这三个原语换掉

弃用不是世界末日，而是一次"瘦身"。三者都有清晰的替代方案。

### 迁移 1：Sampling → 直接调用 LLM Provider API

采样被弃用的核心理由是：服务器若需要 LLM 能力，**直接对接 LLM 提供商 API** 更可控——你能自己选模型、设参数、拿流式输出，不必依赖宿主是否实现采样。代价是服务器要自带 API Key。

```python
# review_puppy_migrated.py —— 采样改为直接调用 Anthropic API
import sys, logging
from anthropic import Anthropic
from mcp.server.fastmcp import FastMCP

logging.basicConfig(stream=sys.stderr, level=logging.INFO)  # 见"迁移 3"
log = logging.getLogger("review-puppy")
llm = Anthropic()                       # 读 ANTHROPIC_API_KEY
mcp = FastMCP("review-puppy")


@mcp.tool()
async def review_diff(diff_text: str) -> str:
    rsp = llm.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=500,
        system="你是资深工程师，评审简短、具体、可执行。",
        messages=[{"role": "user",
                   "content": "审阅这段 diff，用三句话指出问题：\n\n" + diff_text}],
    )
    log.info("reviewed %d chars", len(diff_text))
    return rsp.content[0].text
```

注意：`ctx: Context` 参数被去掉了——不再需要它来"借脑"。需要密钥是采样的固有优势，也是迁移的固有代价；可通过服务器配置或密钥管理服务注入。

### 迁移 2：Roots → 工具参数 / 配置 / 环境变量

roots 提供的"工作目录上下文"，规范认为用**显式手段**更可靠。三种迁移路径，按推荐度排序：

```python
# 路径 A（最推荐）：把根目录作为显式工具参数
@mcp.tool()
async def review_file(project_root: str, rel_path: str) -> str:
    base = Path(project_root).resolve()
    candidate = (base / rel_path).resolve()
    try:
        candidate.relative_to(base)        # 仍然是软边界，防 ../
    except ValueError:
        return f"❌ 拒绝：{rel_path} 越出 {project_root}"
    text = candidate.read_text(encoding="utf-8", errors="replace")
    return await review_diff(f"（{candidate.name}）\n{text}")

# 路径 B：从环境变量读
#   export MCP_PROJECT_ROOT=/home/u/myproject
root = Path(os.environ["MCP_PROJECT_ROOT"])

# 路径 C：服务器配置文件 / Resource URI
#   让用户在 .mcp.json 或自定义 resource 里声明项目根
```

显式参数最大的好处是**可测试、可审计、可复现**——这正是 roots 因语义模糊而欠缺的。

### 迁移 3：Logging → stderr / Python logging / OpenTelemetry

协议级日志被弃用，因为标准日志基础设施更成熟：stdio 传输下用 `stderr`，HTTP 传输下用 OpenTelemetry。

```python
import sys, logging
logging.basicConfig(
    stream=sys.stderr,                # 关键：stdio 服务器必须走 stderr，stdout 留给 JSON-RPC
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
log = logging.getLogger("review-puppy")

@mcp.tool()
async def review_diff(diff_text: str) -> str:
    log.info("start review, %d chars", len(diff_text))
    ...  # 业务逻辑
    log.warning("diff 过长，已截断到 8000 字符")
    return result
```

为什么不能往 stdout 写？因为 stdio 传输里 **stdout 是 JSON-RPC 报文通道**，混入日志会破坏协议解析——这是排错时最常踩的坑。结构化观测则交给 OpenTelemetry（官方另有 SEP-414 在推进 trace context 标准化）。

### 迁移后的完整服务器

把三处迁移合并，得到一个**不依赖任何反向原语、面向未来**的版本：

```python
# review_puppy_final.py
import os, sys, logging
from pathlib import Path
from anthropic import Anthropic
from mcp.server.fastmcp import FastMCP

logging.basicConfig(stream=sys.stderr, level=logging.INFO)
log = logging.getLogger("review-puppy")
llm = Anthropic()
mcp = FastMCP("review-puppy")


async def _review(text: str) -> str:
    rsp = llm.messages.create(
        model="claude-sonnet-4-6", max_tokens=500,
        system="你是资深工程师，评审简短、具体、可执行。",
        messages=[{"role": "user",
                   "content": "审阅下面内容，三句话指出问题：\n\n" + text}],
    )
    return rsp.content[0].text


@mcp.tool()
async def review_diff(diff_text: str) -> str:
    log.info("review_diff %d chars", len(diff_text))
    return await _review(diff_text)


@mcp.tool()
async def review_file(project_root: str, rel_path: str) -> str:
    base = Path(project_root).resolve()
    candidate = (base / rel_path).resolve()
    try:
        candidate.relative_to(base)
    except ValueError:
        return f"❌ 拒绝：{rel_path} 越出 {project_root}"
    log.info("review_file %s", candidate)
    text = candidate.read_text(encoding="utf-8", errors="replace")
    return await _review(f"（{candidate.name}）\n{text}")


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

对比迁移前后：

```mermaid
flowchart TB
    subgraph OLD["迁移前：依赖反向原语（已弃用）"]
        A1["review_file"] -->|roots/list| A2["ctx.session.list_roots()"]
        A1 -->|sampling/createMessage| A3["ctx.session.create_message()"]
        A1 -.->|notifications/message| A4["ctx.log()（被弃用）"]
    end
    subgraph NEW["迁移后：自包含、面向未来"]
        B1["review_file(project_root, rel_path)"] --> B2["Path 路径检查"]
        B1 --> B3["Anthropic().messages.create()"]
        B1 --> B4["logging → stderr"]
    end
    OLD == 一年后能力移除 ==> NEW
```

## 决策指南：现在到底该用哪一套？

迁移窗口还有约一年，不必恐慌。给你的决策矩阵：

| 你的场景 | 建议 |
|---|---|
| 全新项目，2026 年下半年开始 | **直接用迁移后写法**，别碰采样/roots |
| 现有项目重度依赖采样，且宿主确实支持 | 暂时保留，但**立刻在内部抽象一层**，方便一年后整体替换 |
| 需要零密钥（不能自带 API Key） | 短期仍可用采样，但**尽早评估**由用户侧提供密钥的方案 |
| 需要"知道项目在哪" | 永远优先**工具参数**，roots 仅作旧客户端兼容回退 |
| 需要结构化日志/可观测性 | 上 **OpenTelemetry / stderr**，别用协议级 logging |
| 维护面向公众的客户端 | 至少在 2027 年 6 月前继续支持这三个能力，并给出迁移文档 |

一句话总结策略：**新代码走替代方案，旧代码做适配层，关键路径上提前迁移。**

## 训练营适用性评估

- **MCP 进阶训练营（2–3 天）**：高度适用。本教程覆盖"协议深度 + 2026 最新动向 + 工程迁移"，恰好适合作为进阶模块的核心案例。建议教学时长 **3–4 小时**（1 小时讲反向原语与协议、1 小时动手搭服务器、1 小时迁移实战、0.5 小时讨论）。
- **MCP 入门训练营**：不直接适用。需先掌握 Tools/Resources/Prompts 三原语与 FastMCP 基本用法，否则反向请求、能力协商、双向握手的概念会难以消化。
- **协议/标准化研修营**：适用。SEP-2577 的"为何弃用、弃用流程、替代方案"本身就是一份绝佳的协议治理教学素材，可单独抽出来讲。
- **前置依赖**：参加者需能独立写出带 Context 的 FastMCP 工具，并理解 JSON-RPC request/response。

## 延伸阅读

- MCP 规范《Sampling》——`sampling/createMessage` 的权威定义、能力协商、安全要求
- MCP 规范《Roots》——`roots/list`、`listChanged` 与 `file://` 约束
- SEP-2577《Deprecate Roots, Sampling, and Logging》——弃用动机、时间线、迁移理由（Final 状态，2026-04）
- MCP Python SDK 源码 `src/mcp/server/session.py`——`create_message`、`list_roots` 的真实签名（v1.28.1）
- 社区解读《MCP Is Deprecating Sampling, Roots, and Logging》——弃用对生态的影响与迁移建议
- MCP Python SDK 示例 `examples/stories/sampling/server.py`——官方采样服务器最小范例

## 参考来源

1. [Sampling — Model Context Protocol Specification (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling) — 采样原语的权威规范：`sampling/createMessage` 报文格式、能力协商与 human-in-the-loop 安全要求
2. [SEP-2577: Deprecate Roots, Sampling, and Logging (Final, 2026-04-14)](https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging) — 官方弃用提案全文：弃用范围、一年迁移窗口、三大原语的弃用理由与替代方案
3. [Roots — Model Context Protocol Specification](https://modelcontextprotocol.io/specification/2025-06-18/client/roots) — 根目录原语规范：`roots/list`、`notifications/roots/list_changed` 与 `file://` URI 约束
4. [modelcontextprotocol/python-sdk — session.py (v1.28.1)](https://github.com/modelcontextprotocol/python-sdk/blob/v1.28.1/src/mcp/server/session.py) — `create_message` 与 `list_roots` 的真实方法签名，本教程 API 示例的来源
5. [MCP Is Deprecating Sampling, Roots, and Logging (社区解读)](https://mcpblog.dev/blog/2026-04-17-mcp-deprecating-sampling-roots-logging) — 弃用对生态的影响与一年迁移窗口的工程化解读

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