# Anthropic 的 MCP 课程，止步于规范的硬性规则

> 采样和传输讲得清楚，但干净的 stdout、Origin 校验和采样人工审批，仍要自己实现并测试。

原文: https://fdetimes.net/zh/books-courses/anthropic-mcp-advanced-topics-course-review/

2026 年 4 月 19 日，MCP Python SDK 仓库里出现了一个 issue。内容很短：配置了 `stateless_http=True` 的 Streamable HTTP 服务器仍然接受 `GET /mcp`，与规范不符。这个 bug 不大，但它说明即便是官方库，也并不总是遵循规范。

学习“Model Context Protocol: Advanced Topics”也应该抱着这种态度。这是 Anthropic 在 Anthropic Academy（托管于 Skilljar）上开设的课程，重点是服务器与客户端通信、传输机制，以及生产部署中需要注意的问题。课程页面只写了 © 2026，没有给出发布日期或时长。

对前线部署工程师（FDE）来说，这些都是客户现场的实际问题。MCP 服务器跑在哪里？模型密钥由谁持有？放到负载均衡器后面会怎样？课程回答了其中大部分，剩下的要靠自己从规范里弄清楚。

## 谁适合学，按什么顺序学？

课程默认学员已掌握 Python、异步编程、JSON/HTTP 以及 SSE 基础。如果还不能熟练编写异步 Python，先把这一关过了。否则传输部分的课程会很吃力，因为要同时学协议和语法。

比较合理的顺序是：先学完课程，再读 2025-06-18 版规范中的 Sampling 和 Transports 两页，最后再去看上面那个 SDK issue。这样就能从概念走到强制规则，再看到真实代码在哪里仍未达标。有三点值得记住。

## 服务器借用客户端的模型

按照课程的说法，采样（sampling）是 MCP 服务器通过所连接的客户端请求调用语言模型的机制。MCP 规范讲明了好处：服务器无需自己的 API 密钥就能获得 AI 能力，而访问、模型选择和权限仍由客户端控制。

服务器也不会写死模型名称。它告诉客户端自己对成本、速度和模型质量各有多看重，并附上几个模型名称提示。规范强调，这些提示只是建议，最终决定权在客户端。

设想要为客户构建一个汇总工单的服务器。有了采样，就不必再向客户为服务器单独申请密钥，客户的安全团队仍能决定使用哪个模型。

但规范附带一条建议：始终应有人能够拒绝采样请求。如果客户端是你写的，这个审批环节就是你的责任。

## stdout 只留给 MCP 消息

规范定义了两种标准传输：stdio 和 Streamable HTTP，并建议客户端尽可能支持 stdio。stdio 有一条强制规则：服务器不得向 stdout 写入任何非有效 MCP 消息的内容，日志写入 stderr。

自己写服务器时很容易犯这个错。一个调试用的 `print` 就会污染消息流，让客户端收到无法解析的数据。所以搭建服务器的第一件事，在写任何工具之前，就是配置 logger：

```python
import logging, sys

logging.basicConfig(stream=sys.stderr, level=logging.INFO)
log = logging.getLogger("mcp-server")
log.info("服务器启动中")  # 输出到 stderr，绝不触碰 stdout
```

预期结果：每行日志都出现在 stderr 上，stdout 只承载 MCP 消息。为保险起见，可以故意加一句 `print("debug")`，观察客户端的反应，下次就能认出这种症状。

## 状态决定扩展方式

课程最有价值的部分，是教你根据部署需求、所需功能和扩展限制来选择传输方式，包括何时使用无状态 HTTP，在负载均衡器后做水平扩展。

按照规范，Streamable HTTP 中的会话是可选的。服务器可以在初始化时通过 `Mcp-Session-Id` 请求头分配一个 ID，此后客户端必须在每个请求中回传这个 ID。

设想客户在负载均衡器后运行三个实例。实例 A 发放了会话并持有状态，但下一个请求落到了实例 B，而 B 对这个会话一无所知。无状态运行可以避免这个问题，但并非没有代价。

课程指出，这些配置标志会影响服务器发起的请求和流式传输。采样恰恰是服务器发起的请求，所以需要确认它在所选配置下依然可用。

**要点:** 课程教你协议；只有对照规范测试，才能知道系统实际如何表现。

还有两条规则应直接写进测试套件，每条单独一个命令，这样请求被拒时就能确切知道是哪条规则拦下的。把下面的地址换成你的服务器地址。

第一个命令检查 SDK issue 中提出的不一致：向运行 `stateless_http=True` 的服务器发送一个不带其他请求头的 `GET`。

```bash
curl -i -X GET http://localhost:8000/mcp
```

该 issue 建议无状态服务器对此请求返回 405。如果你的服务器仍然接受它，记录下来，并在自己的层面拦截，而不是指望库来处理。

第二个命令检查 `Origin` 请求头的强制规则：Streamable HTTP 服务器必须在每个连接上校验它，以防范 DNS 重绑定攻击。这里用 `POST`，避免和上面的 `GET` 测试混在一起：

```bash
curl -i -X POST http://localhost:8000/mcp \
  -H "Origin: http://evil.example" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
```

需要看到的结果是请求被拒绝。然后去掉 `Origin` 这一行重跑同一命令：如果只有带外部 Origin 的版本被拦截，就说明 Origin 校验在起作用，请求不是因为其他原因失败的。

## 把课程变成简历上的证据

不要只在简历上列出结业证书，而要附上一个能证明你真正动手做过的小仓库。

构建一个服务器：同时支持 stdio 和 Streamable HTTP，日志写入 stderr，有一个拒绝外部 Origin 的测试，有一套在两个实例后运行的无状态配置，并配一个包含采样审批环节的客户端。

在 README 里写一小段话，说明为什么选择有状态或无状态。

当 FDE 岗位描述提到 MCP、Agent 集成或生产部署时，这段话就是面试时要带去的东西。要准备好讲清其中的取舍，比如为什么会话 ID 会让负载均衡变复杂，而不只是列出调用过哪些 API。

学完这门课的人会很多，但亲手向自己的服务器发过 `GET /mcp`、看看会返回什么的人要少得多。

**本周动手试试:**

- 写一个小型 stdio MCP 服务器，把所有日志发到 stderr，然后向 stdout 加一句 print，看看客户端如何反应。
- 以 stateless_http=True 运行一个 Streamable HTTP 服务器，分别发送一个 GET /mcp 和一个带外部 Origin 的 POST，记录服务器对两者的返回。
- 在客户端中加一个步骤，在调用模型之前批准或拒绝每个采样请求。

## 信源

- [Model Context Protocol: Advanced Topics (Anthropic Academy)](https://anthropic.skilljar.com/model-context-protocol-advanced-topics)

- [Sampling - Model Context Protocol Specification (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling)

- [Transports - Model Context Protocol Specification (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)

- [Return 405 on GET when stateless_http=True (Issue #2474, modelcontextprotocol/python-sdk)](https://github.com/modelcontextprotocol/python-sdk/issues/2474)
