📑 本页目录(点开跳转)
08c · MCP:从连得上到权限正确
⏱ 28 分钟 | 按需实作:已有 Python 环境,想把工具交给多个宿主使用
🎯 一句话
工具能返回结果,只证明成功路径连通;不同协议、身份和失败输入也有明确结果,才算接入完成。 这一页使用同一个知识助手,实测 HTTP、stdio、新旧协议和授权拒绝。
先准备共同项目的环境与文件。完整代码在Server、Client和协议测试。下面命令均在仓库根目录、已安装项目依赖的 Python 环境运行。
🔬 先看到四条连接都工作
python -m unittest discover -s examples/knowledge-assistant -p test_protocol.py -v
测试会在本机临时端口启动服务,结束后关闭;不会访问外部账户。它分别验证 HTTP 新协议、HTTP 旧协议、stdio 新协议、stdio 旧协议,并验证无凭证、过期、撤权和 scope 不足都不能进入业务工具。
| 观察点 | 预期 | 这一步还不能证明什么 |
|---|---|---|
| 新协议 Client | 2026-07-28,返回有 schema 的证据 |
任意第三方宿主都兼容 |
mode="legacy" |
2025-11-25,同一工具仍可调用 |
已覆盖所有更早版本 |
| HTTP 没有有效 token | 401;scope 不足通常为 403 | 用户在身份平台的登录流程已经接好 |
| 普通用户搜索薪酬 | 不返回 HR 或其他租户资料 | 其他业务资源的权限自然也正确 |
本轮验证的是同一 SDK 的 Client/Server 与真实传输。 上线前还需使用目标 IDE、桌面宿主或另一语言实现进行互测,不能把四个测试叫成“四种宿主验收”。
🧭 一、先锁定协议时代,再看报文
| 项目 | 2025-11-25 | 2026-07-28 |
|---|---|---|
| 初始化 | initialize 和初始化通知 |
移除握手;可先调用 server/discover |
| 能力与版本 | 握手协商 | 每请求 _meta 声明;HTTP 还带对应标准请求头 |
| HTTP 会话 | Server 可选择创建会话 | 不再使用协议级 session ID |
| 流断开 | 按该版服务支持的恢复机制处理 | 丢失在途请求;新 RPC ID 重发,业务操作号保持稳定 |
| 结果 | 旧版结果可能无 resultType |
有 complete / input_required;客户端兼容旧结果缺字段 |
不要用 RPC 的 id 当业务幂等键。一次工单创建遇到断流,重发请求需要新的 RPC ID;它仍是同一笔业务动作,所以工单操作号不变。改掉操作号可能创建第二张工单。
协议依据是2026 变更说明与旧版传输。代码锁定 mcp 2.1.1;升级前分别测试 Client、Server、宿主和依赖,不把包版本与协议日期混为一谈。
结构化输出也要看真实返回
项目使用 Pydantic 输出模型,让 outputSchema 与 structuredContent 对得上。只写一个没有字段类型的 dict 返回注解,在本轮 SDK 实测中只有文本内容,structured_content 是空值。
因此测试不能只断言 is_error=False。还要检查字段、类型、最大条数,以及每条证据的 chunk_id、版本和来源。需要兼容只返回文本的第三方工具时,显式做适配与校验,不把缺字段当空结果悄悄吞掉。
🔐 二、谁给凭证,谁检查权限
本地实验用随机 opaque token 与哈希存储模拟资源服务器验证。token 有用户、租户、scope、audience、过期时间和撤销状态;每次 HTTP 调用重新检查。这是授权负例的测试夹具,不是完整 OAuth 登录系统。 auth.example.invalid 是明确不会访问的占位 issuer。
正式对接身份平台时,补齐如下链路:
- Client 收到 401,读取
WWW-Authenticate给出的资源元数据位置。 - 校验资源与受信任 issuer,发现授权端点;不跟随任意外部 URL 获取凭证。
- 按身份平台支持的机制注册客户端,使用授权码与 PKCE;校验 state、redirect URI,以及返回的 issuer。
- token 请求指定目标 resource;Server 验签或内省,验证 audience、有效期与 scope。
- 将真实用户 subject 与租户绑定,业务查询再查 ACL;不能把 OAuth
client_id直接当终端用户 ID。 - 刷新凭证时按 issuer 隔离保存;撤权后下一请求拒绝;上游 token 不直接透传给下游系统。
采用官方资源服务器说明和授权规范对接已有身份平台,不在业务工具里自造登录流程。
🛑 可以停在这里:已分清协议连接与用户授权。下一段手动连接服务,看错误如何对应到具体层。
🔌 三、手动走一遍 HTTP 与 stdio
一个终端启动本机服务:
python examples/knowledge-assistant/lab.py init
python examples/knowledge-assistant/mcp_server.py --db examples/knowledge-assistant/.runtime/lab.db --port 8932
另一个 PowerShell 终端取得本地短期凭证并调用:
$env:LAB_TOKEN = python examples/knowledge-assistant/lab.py token --port 8932
python examples/knowledge-assistant/mcp_client.py
python examples/knowledge-assistant/mcp_client.py --legacy
凭证只存在本地演练环境;不要放进 prompt、工具参数、截图或仓库。真实产品由认证流程提供它,不能开放本项目的本地 token 命令给远端用户。
不经过 HTTP 时,用显式单用户的 stdio 模式:
python examples/knowledge-assistant/mcp_client.py --stdio-db examples/knowledge-assistant/.runtime/lab.db
该模式从服务进程配置取得 Alice 身份。HTTP token verifier 不会保护 stdio 或内存直连测试。限制子进程的 cwd、环境变量和文件权限;不能让模型通过参数选择它要扮演谁。
🧯 四、把故障分层
| 症状 | 先取什么证据 | 修复与验收 |
|---|---|---|
| 启动就解析失败 | 原始 stdout 与 stderr | 清除 stdout 日志;坏 JSON、UTF-8、路径、退出码分别测 |
| 连上但工具没更新 | 完整枚举结果、分页游标、缓存期限 | 刷新 schema;处理列表变化与名称冲突,不只清 UI |
| 401 / 403 | audience、issuer、scope、到期时间,日志不含原 token | 刷新或重新授权;不重试模型来“修权限” |
is_error=False 却取不到字段 |
实际结果与输出 schema | 明确结构化返回类型,拒绝格式漂移 |
| 本地流正常,代理后一次返回 | 响应 Content-Type、首帧时间、代理 buffering | 关闭相应缓冲;区分 JSON 与 SSE,不强行用一套解析 |
| 重连后重复写入 | RPC ID、业务操作号、下游执行状态 | 先查询/对账;按下游幂等契约重放 |
| 用户取消,服务仍在跑 | 取消信号、子任务状态、超时点 | 传播 deadline,释放资源,展示“取消中/结果未知” |
| 新协议追加输入 | resultType、input requests、支持的能力 |
处理多轮请求;限制轮数,不当成最终成功结果 |
权限、工具结果中的注入、SSRF、Origin/Host 校验和依赖来源还应按安全实践建立负例。根本规则是:远端输入不能扩大当前身份的权限。
🔗 这一章连到哪里
- 08b · 写 Server:回看工具描述、参数和错误消息。
- 12b · 审批与恢复:连接断了以后,业务动作是否已经发生。
- 全栈认证与多租户:把身份落实到数据访问。
✅ 检查点
- 新 RPC ID 为什么可以对应同一个工单操作号?
- 内存直连 Client 测试通过,为什么不能证明 HTTP 授权正确?
- token 的 client_id、subject、audience 分别代表什么?
查看答案与反例
- RPC ID 标识一次协议请求;操作号标识一次业务动作。重发请求可换 RPC ID,不能无故换业务号。
- 内存直连绕过 HTTP 认证层,必须用真实 HTTP 测无凭证、过期、撤权和权限不足。
- client_id 是调用应用,subject 是凭证所代表的主体,audience 是凭证面向的资源。三者不能互换。
🛑 可以停在这里
回来时接住这四点
- 锁定 SDK 与协议版本;四条连接测试不等于任意宿主兼容。
- Host 管用户意图,Server 验凭证与资源权限。
- RPC 重发与业务幂等分开处理,断流不能直接推断未执行。
- 本地 token 是测试夹具,真实 OAuth 仍要对接身份平台验收。
下一节 👉 09 · 计算机与浏览器使用