为 Remote MCP 建立签名发布与运行时工具漂移的双层防线
一个 Remote MCP 已经通过签名验证,并不代表后续拿到的内容仍然是同一份 Descriptor。准备或连接阶段,Descriptor 可能被替换;运行时保留同名工具时,Schema 和风险元数据也可能发生漂移。更麻烦的是,来自另一个 Extension Center 的身份可能被串进来。
这次推进 HappyNM Extension Center 的一个里程碑,我借助 AI 开发工具梳理需求、拆分验证边界、生成和补齐测试场景,再用类型检查、全量测试和契约测试逐层核对结果。目标不是让工具替我写完一套代码,而是把几个容易混在一起的安全问题拆开,最终落到一套可以被验证的发布与运行时检查上。
先把 Remote MCP 从业务插件模型里分出来
最先需要判断的是:Remote MCP 到底要不要复用 business-plugin ZIP 的发布模型。
结论是不复用。Remote MCP 使用独立的 Release Payload,在契约层保留扩展类型边界。业务插件原有的 Release Payload 不变,共享 Validator 则通过 extensionKind 做显式分支,并拒绝两类输入形状互相混用。
这个判断看似只是数据结构设计,实际影响了后续所有校验。如果把 Remote MCP 伪装成业务插件,Descriptor、工具目录和远程连接相关信息就容易被塞进一个并不适合它们的发布契约里。借助 AI 工具整理需求时,我把“哪些字段属于 Remote MCP”“哪些字段必须继续由 Host 负责”分别列出,再反向检查 Schema 和测试是否真的遵守了这条边界。
Release Gate 只负责发布内容的真实性和运行时目录的一致性,不负责 MCP 传输、凭据、分页、用户确认、逐次调用授权。这些职责继续归属 HappyNM Host。边界明确后,后面的验证才不会变成一个包揽所有问题的“大闸门”。
把 Descriptor 摘要绑定到每一个关键上下文
第二个判断是摘要到底绑定什么。只校验签名文件本身不够,必须确认签名所覆盖的 Descriptor,就是实际准备上下文和后续验证拿到的那一份。
这里固定使用完整 RFC 8785 JCS 字节的 SHA-256 作为 Descriptor 摘要,并要求它同时匹配四个位置:subject、签名 predicate、实际 Descriptor,以及调用方的准备上下文。这样可以覆盖 Descriptor 被替换、签名与实际内容脱节,以及准备阶段串入另一份内容等情况。
共享 Release Validator 复用了 DSSE、Ed25519、JCS、current Key、反回滚和 attestation 链,同时补上了实际 Descriptor 的 authority、扩展 ID、类型和版本绑定。验证不只是“签名是否正确”,还要确认签名者、扩展身份、扩展类型和版本都属于同一条发布关系。
测试夹具曾经暴露出一个细节:Key 与上下文复用了同一个 authority 对象,触发了别名防御。处理方式不是放宽生产校验,而是把夹具改成独立的不可变快照。这个返工很有价值,因为它区分了“生产逻辑发现对象别名风险”和“测试数据为了省事共享引用”这两件事。
不把敏感连接信息带进验证结果
发布验证需要知道 Descriptor 是否匹配,但调用方不需要从验证结果里拿到完整的远程连接配置。为此新增了脱敏 Descriptor 校验投影,只返回摘要,以及 authority 和 extension 身份。
投影明确不返回 endpoint、工具名、Schema、认证配置或可调用对象。这样,验证层可以为后续绑定提供足够的身份信息,又不会把远程连接细节变成一个被广泛传递的结果对象。
我让 AI 工具协助检查这个投影的字段边界,并把“应该返回什么”和“绝不能返回什么”都转成测试关注点。这里的重点不是多写一个类型,而是让校验结果本身保持最小化:它证明哪份内容被验证过,却不顺手暴露这份内容的调用细节。
用实时 tools/list 再做一次漂移检查
签名发布解决的是供应链问题,但 Remote MCP 的工具目录可能在运行时变化。因此,Release 验证结果还需要和既有实时 tools/list 绑定 Gate 组合起来,形成两级防线。
第一层确认当前准备的 Descriptor 来自正确的发布、authority、扩展 ID、类型和版本;第二层把实时工具目录与已验证的发布身份和内容关系进行绑定,重点检查同名工具背后的 Schema 与风险元数据是否漂移。
这套设计没有把实时目录当作新的发布源,也没有让 Release Gate 接管逐次调用授权。它只负责发现“已签名发布”和“运行时工具目录”之间是否出现不应有的变化。对于动态工具目录和其他插件控制面,这种职责拆分也具备复用价值:发布验证负责可信来源,运行时 Gate 负责内容是否仍然一致。
为确保边界没有被测试遗漏,补充了业务插件混入 Descriptor 的拒绝用例、Remote MCP 全套权威 conformance cases、Release Envelope 多 Payload 注释,以及按类型持久化最小安全版本的说明。AI 工具在这里更像一个审查和对抗用例生成器,真正决定是否通过的仍是契约、实现和测试结果。
用分层验证确认它确实落到了可用阶段
最后没有只看定向测试。全仓 TypeScript typecheck 通过;按根 test:unit 顺序完成 24 个 workspace 的 353 项测试,再补跑剩余 6 个 workspace 的 127 项测试,等价全量共 480 项,0 失败。
契约测试严格编译了 34 个 Draft 2020-12 Schema,验证 44 个正例,确认 100 个结构拒绝和 103 个语义或密码学延后。暂存差异检查与空白检查也通过。这样的检查顺序,能把类型层、单元测试层、Schema 层和提交内容层分别照一遍,避免只用某一类结果替代整体判断。
这并不意味着 Remote MCP 已经完整上线。真实 MCP 传输、分页 Collector、用户确认界面、逐次工具授权和跨仓库 contract tests 仍未完成;服务端 Remote MCP 发布存储、租约、撤销投递与生产身份授权也需要后续实现。
目前完成的是更窄但更关键的一段:Remote MCP 有了独立的签名发布契约,Descriptor 摘要、authority、扩展身份和版本被绑定起来,运行时 tools/list 也有了漂移检测入口。对我来说,AI 开发工具最有用的地方不在于替代逐行编码,而在于帮助把复杂要求拆成边界、反例和可重复验证的结果。