🏠 总目录📚 本教程 08c · 鉴权与互操作 ← →
📑 本页目录(点开跳转)

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、版本和来源。需要兼容只返回文本的第三方工具时,显式做适配与校验,不把缺字段当空结果悄悄吞掉。

🔐 二、谁给凭证,谁检查权限

身份平台登录、同意、签发凭证Host / Client工具暴露与动作预览MCP Server验 token,再查资源权限取得凭证公钥 / 内省结果带凭证的请求
看右下角的两次检查:凭证有效只回答“你是谁、能调用什么”,订单或文档权限还要单独核对。

本地实验用随机 opaque token 与哈希存储模拟资源服务器验证。token 有用户、租户、scope、audience、过期时间和撤销状态;每次 HTTP 调用重新检查。这是授权负例的测试夹具,不是完整 OAuth 登录系统。 auth.example.invalid 是明确不会访问的占位 issuer。

正式对接身份平台时,补齐如下链路:

  1. Client 收到 401,读取 WWW-Authenticate 给出的资源元数据位置。
  2. 校验资源与受信任 issuer,发现授权端点;不跟随任意外部 URL 获取凭证。
  3. 按身份平台支持的机制注册客户端,使用授权码与 PKCE;校验 state、redirect URI,以及返回的 issuer。
  4. token 请求指定目标 resource;Server 验签或内省,验证 audience、有效期与 scope。
  5. 将真实用户 subject 与租户绑定,业务查询再查 ACL;不能把 OAuth client_id 直接当终端用户 ID。
  6. 刷新凭证时按 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 校验和依赖来源还应按安全实践建立负例。根本规则是:远端输入不能扩大当前身份的权限。

🔗 这一章连到哪里

✅ 检查点

  1. 新 RPC ID 为什么可以对应同一个工单操作号?
  2. 内存直连 Client 测试通过,为什么不能证明 HTTP 授权正确?
  3. token 的 client_id、subject、audience 分别代表什么?
查看答案与反例
  1. RPC ID 标识一次协议请求;操作号标识一次业务动作。重发请求可换 RPC ID,不能无故换业务号。
  2. 内存直连绕过 HTTP 认证层,必须用真实 HTTP 测无凭证、过期、撤权和权限不足。
  3. client_id 是调用应用,subject 是凭证所代表的主体,audience 是凭证面向的资源。三者不能互换。

🛑 可以停在这里

回来时接住这四点

下一节 👉 09 · 计算机与浏览器使用

打卡记录保存在你的浏览器里,首页能看到总进度