news 2026/9/9 12:38:13

LibreChat MCP Authority Proof 机制解析:为 tools/call 与配置发布加装“最新权限快照”校验的源码级指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LibreChat MCP Authority Proof 机制解析:为 tools/call 与配置发布加装“最新权限快照”校验的源码级指南

LibreChat MCP Authority Proof 机制解析:为 tools/call 与配置发布加装“最新权限快照”校验的源码级指南

【免费下载链接】LibreChatEnhanced ChatGPT Clone: Features Agents, MCP, Skills, DeepSeek, Anthropic, AWS, OpenAI, Responses API, Azure, Groq, o1, GPT-5, Mistral, OpenRouter, Vertex AI, Gemini, Artifacts, AI model switching, message search, Code Interpreter, langchain, DALL-E-3, OpenAPI Actions, Functions, Secure Multi-User Auth, Presets, open-source for self-hosting. Active项目地址: https://gitcode.com/GitHub_Trending/li/LibreChat

在 LibreChat 的 MCP(Model Context Protocol)体系中,服务端服务器的配置解析、OAuth 授权与凭据存储、数据库内目录以及用户/角色/群组权限状态,分散在多个可变的存储位置。如何保证一次远程tools/call或一次配置发布所依据的权限判定,在“校验之后、真正生效之前”没有被并发修改,是一个典型的 TOCTOU(Time-of-check to time-of-use)问题。本文以仓库内 packages/api/src/mcp/authority/README.md 为骨架,结合authority/index.tsdata-schemas中的数据库实现与迁移脚本,完整讲解MCP Authority Proof(权威权限证明)子系统的设计动机、proof 数据结构、解析器与三重“围栏(fence)”API、事务化快照读取策略、上线迁移步骤与可观测性约定。读完你将能理解该模块为什么默认关闭、如何被 AI-1715 这类后续功能栅栏采用,以及如何在发布与执行前获得可验证的“权限仍然有效”断言。

模块定位:默认关闭的增量式“地基”

README 的第一句话就明确了模块属性:

This module is an additive, default-off substrate.

也就是说,MCP Authority Proof 是一个附加式、默认关闭的基础设施层。它并不替代 LibreChat 现有的 MCP 目录(catalog)、OAuth、连接(connection)与工具调用(tool-call)流程,这些既有路径在启用前不会调用它。整个模块的目标是:把一个用户在某一次启动配置(boot configuration)下被选中的 MCP 服务器集合,解析成一份不可篡改、可再次核对的“权限证明(authority proof)”,并让发布、绑定、执行三处关键回调在最终动作发生前再次确认该证明仍然“新鲜”。

从仓库文件布局可以清楚看到它的边界:

  • packages/api/src/mcp/authority/index.ts:MCPAuthorityProofResolver类,纯编排层;
  • packages/api/src/mcp/authority/index.spec.ts:针对解析器的确定性行为测试;
  • packages/data-schemas/src/types/mcpAuthority.ts:proof 的 v1 类型结构与全部拒绝原因(MCPAuthorityRejectionReason);
  • packages/data-schemas/src/methods/mcpAuthority.ts:createMCPAuthorityMethods工厂,封装全部 MongoDB 权威读取与断言;
  • packages/data-schemas/src/migrations/mcpServerNames.ts 与 packages/data-schemas/src/migrations/mcpAuthorityIndexes.ts:上线前必须执行的两个离线迁移。

数据访问层放在data-schemas包,而解析器编排层放在packages/api,类型通过@librechat/data-schemas暴露,这种分层意味着该能力可以被未来任何调用方复用,而不必感知 mongoose 具体实现。

核心概念与生命周期

一次完整的权威证明生命周期包含五个相互独立又环环相扣的部分:

  1. Boot revision(启动修订):由解析器构造时对“不可变启动配置”一次性计算,包括revision(外部传入的启动版本号)与digest(对不可变配置内容的摘要)。此后任何“当前仍权威”的断言都不会再重新加载 YAML 或扫描 Redis,Boot revision 是全程不变的锚点。
  2. Source revision(来源修订):被选中服务器“配置来源”的代际标记。数据库来源(database targets)用createMCPAuthorityDatabaseSourceRevision计算,配置来源(config targets)用createMCPAuthorityConfigSourceRevision结合 boot digest 与解析器产生的完整适用 Config 文档集合(含 inactive 文档)计算。
  3. Credential revision(凭据修订):由createMCPAuthorityCredentialRevision依据目标服务器声明的凭据字段与存储的加密凭据文档计算,不落明文。
  4. OAuth generation(OAuth 代际):预期写入的credential_set_id代际(无 OAuth 则为null),用于检测令牌轮换。
  5. Artifact revision(工件修订):对“真正被使用的解析后配置与 schema”的可信摘要,见下文专节。

Resolver:每次不可变启动配置对应一个实例

packages/api/src/mcp/authority/index.ts 中的MCPAuthorityProofResolver构造时接收三样东西:

export interface MCPAuthorityProofResolverOptions { methods: Pick< MCPAuthorityMethods, 'resolveMCPAuthorityProof' | 'assertMCPAuthorityProofsCurrent' >; bootRevision: string; immutableConfig: MCPAuthorityImmutableConfig; beforeExecute?: () => void | Promise<void>; }
  • methods:由createMCPAuthorityMethods(mongoose)产出的两个数据库方法;
  • bootRevision+immutableConfig:二者在构造函数中合成为MCPAuthorityBootRevision(通过createMCPAuthorityBootRevision,其内部用deepFreeze递归冻结);
  • beforeExecute:可选的确定性竞态测试钩子,用于模拟“断言前一刻发生变更”。

构造函数完成即固定 boot 摘要,因此设计约束是每个不可变启动配置持有一个 Resolver,而不是全局单例。

resolve:解析并携带证明,绝不冻结调用方对象

resolve(index.ts)接受用户身份、租户、目标服务器列表、解析后的配置、schema 以及调用方自实现的calculateArtifactRevision。其流程为:

  1. 先调用calculateArtifactRevision({ parsedConfig, schemas })得到工件修订,非法(非字符串或空白)即抛出malformed_input
  2. 调用methods.resolveMCPAuthorityProof从数据库取权威证明;
  3. 取回后再次计算工件修订并比对,任何差异都判定为malformed_input(“工件在解析权威过程中被改动”),从而封死 parse-before-proof 窗口;
  4. 通过Object.freeze冻结最终的{ parsedConfig, schemas, authorityProof }信封,并把该信封登记进一个WeakMap(index.ts),记录“签发时工件修订”与“随时可重算修订的闭包”。

注意解析器从不冻结或修改调用方传入的 config/schema 对象本身——测试 index.spec.ts 特意断言Object.isFrozen(resolution.schemas) === false,即数据仍归调用方所有,只是信封被冻结。

三重围栏:publish / bind / execute

useIssuedResolution(index.ts)是所有围栏的公共内核,执行顺序极其严格:

  1. WeakMap取出签发记录,不存在即拒绝——防止调用方手拼一个结构相同但未经签发的“仿冒信封”绕过校验(测试 index.spec.ts 用{ ...resolution }浅拷贝验证了这一点);
  2. 断言工件修订仍未改变(assertArtifactsCurrent);
  3. 调用assertCurrent,让数据库层重取权威快照并与证明比对;
  4. 再次断言工件修订未变——封死“断言在途时被注入修改”的窗口;
  5. 全部通过后才执行真正的回调(publish/bind/execute)。

三种公开围栏语义为:

  • publishWithCurrentAuthority:包裹目录(catalog)、schema、HTTP 响应与 binding 的发布回调;
  • bindWithCurrentAuthority:与发布共用同一内核的绑定回调封装;
  • executeWithCurrentAuthority:在执行前先运行beforeExecute钩子,再走“断言 + 执行”,用于包裹远程tools/call回调。

测试 index.spec.ts 验证了execute的事件顺序必须是mutate → assert → execute,而publish/bind则是assert → callback;一旦最终断言 reject,动作回调绝不会被调用(见 index.spec.ts)。另外测试还覆盖了断言进行中被注入的异步修改(index.spec.ts)以及在最后一道后置检查与回调之间竞争微任务的极端场景(index.spec.ts),确保回调看到的始终是断言通过时刻的工件内容。

权威证明的数据结构与每次解析的边界

Proof V1 的三段式结构

packages/data-schemas/src/types/mcpAuthority.ts 定义了MCPAuthorityProofV1

export interface MCPAuthorityProofV1 { readonly version: typeof MCP_AUTHORITY_PROOF_VERSION; // 恒为 1 readonly shared: MCPAuthoritySharedProofV1; readonly servers: readonly MCPAuthorityServerProofV1[]; readonly revision: string; }

shared(共享部分)覆盖所有与该用户相关的身份与策略输入:

字段含义
user用户 ID、租户 ID、角色名、provider,以及由十种联邦/本地身份字段(idOnTheSourceopenidIssuergoogleId…)摘要出的sourceIdentityDigest与自身revision
groups用户所属群组列表,每个群组含来源与sourceIdentityDigestrevision
configs按 role-base / role / group / user 槽位排列的适用 Config 文档证明(present/active/priority/configVersion/mcpOverrideDigest/tombstones
role角色的 id、name、use(是否仍持有MCP_SERVERS.USE权限)与revision
boot启动修订(revision + digest)
groupsRevision/configsRevision群组、Config 证明数组的摘要
revision上述整体的摘要

servers数组则为每个被选中的服务器保存一份MCPAuthorityServerProofV1:除serverName外还包括归一化服务器名normalizedServerName、来源(config/database)、databaseId、来源修订、解析配置摘要resolvedConfigDigest、服务器修订、关联 Agent ID 列表、直接访问/Agent 访问标志(directAccess/agentAccess)、授权修订、凭据字段与凭据修订、OAuth 需求与oauthGrantGeneration/oauthRevision,以及把以上全部绑定在一起的effectivePolicyDigest和最终revision

由于 proof 内每个层级都带有自身内容的摘要,assertProofIntegrity可以在不查询数据库的情况下先完成结构自校验(例如groupsRevision必须等于对groups数组的摘要,effectivePolicyDigest必须等于 shared/server/source/credential/oauth 各修订的组合摘要),任何被手工拼凑的证明都会在这里以malformed_input失败。

规范摘要:为何不能直接 JSON.stringify

packages/data-schemas/src/methods/mcpAuthority.ts 中stableStringify实现了一套“稳定性序列化”:对象键排序、undefined归一为nullDate转 ISO、MongooseObjectId转十六进制、Map按键排序、拒绝非有限数字/循环引用/非法日期;随后由digestMCPAuthorityValuesha256后以base64url输出。

这正是 README 中那句关键结论的实现证据:真实解析后的工具工件可能包含函数与类实例,通用 JSON hasher 无法安全规范化它们。因此数据库层只承诺对“可稳定序列化”的数据做摘要,而解析出的 config/schema 工件摘要必须由调用方通过calculateArtifactRevision回调提供——这是设计上刻意留出的扩展点。

解析与证明的硬边界

为保证单次证明可被限界读取、可被安全断言,mcpAuthority.ts 定义了如下上限(均在prepareTargetsassertProofIntegrity中被强制校验):

常量约束对象
MAX_MCP_AUTHORITY_TARGETS32单次解析/证明中的服务器数量
MAX_MCP_AUTHORITY_GROUPS64群组数
MAX_MCP_AUTHORITY_AGENTS64单个服务器的关联 Agent 数
MAX_MCP_AUTHORITY_CREDENTIALS64单次快照的凭据文档数
MAX_MCP_AUTHORITY_ACL_ENTRIES96ACL 条目数
MAX_MCP_AUTHORITY_CREDENTIAL_FIELDS64单次快照的凭据字段总数
MAX_MCP_AUTHORITY_CONFIG_TOMBSTONES64单个 Config 的墓碑数
名称/字段/来源修订长度256字符串字段

prepareTargets(mcpAuthority.ts)还做语义校验:服务器名不能重复、归一化后不能产生歧义(两个不同名字归一化相同)、config 来源不得携带databaseId、database 来源必须有合法ObjectIdrequiresOAuthresolvedConfig.oauth != null或显式标志推断等。

数据库实现:限界、单事务、面向 DocumentDB 的稳妥读取

一次事务完成整个快照

loadAuthoritativeSnapshot(mcpAuthority.ts)启动一个全新的 Mongo 事务,其选项精确对应 README 的描述:

session.startTransaction({ readPreference: 'primary', readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, });
  • readPreference: 'primary'保证读主库,避免从库延迟造成“读到旧权限”;
  • readConcern: 'snapshot'让同一事务内跨集合的读取看到一致的快照;
  • writeConcern: majority保证事务提交被多数副本确认。

若调用方已传入一个进行中的session,方法会直接拒绝(proof_unavailable),避免把权威读取塞进调用方的旧快照事务中。租户上下文也必须在请求期与用户实际tenantId一致,否则同样拒绝。

读取分布在 9 个模型上(见AUTHORITY_SNAPSHOT_MODELS,mcpAuthority.ts:User / Role / Group / Config / MCPServer / Agent / AclEntry / PluginAuth / Token),而“限界操作”体现在两种读取模式上:

  • find 类查询统一.limit(上限 + 1)并使用.setOptions({ singleBatch: true }),另发一条带同样 limit 的countDocuments,二者用assertCompleteBatch做同快照计数相等校验;
  • 聚合查询(Config、Agent 关联)把$limit内的行$group成一个含rowscount的结果文档,再用unwrapAggregateBatch展开——这正是 README 所述“aggregations collapse their bounded rows into one result document”。

任一集合的行数超过上限、与计数不符、或批结构异常,都会以proof_unavailable立即失败关闭(fail closed),绝不会在一个 DocumentDB 事务内发起getMore去取超限的更多批次。

命名空间预热与 30 秒冷却

DocumentDB 有一个特别约束:事务内引用不存在的集合会直接报错,而asMCPError会把它归一到proof_unavailable。若某部署从未写过PluginAuth/Token,那么一切权威证明都会莫名失败。为此ensureSnapshotNamespaces(mcpAuthority.ts)在事务打开前按进程对 9 个集合各执行一次createCollection()(对 MongoDB 无害,仅 DocumentDB 需要“物化命名空间”),NamespaceExists(code 48)视为成功,其他错误全部上抛。

同时,为避免数据库故障时每个权威请求都重复触发串行 DDL 形成“重试风暴”,模块缓存失败并在SNAPSHOT_NAMESPACE_RETRY_COOLDOWN_MS = 30_000(mcpAuthority.ts)内快速重放上次失败;冷却结束后才允许再次尝试。snapshotNamespaceRetryCooldownMs钩子让测试无需真实等待即可覆盖冷却两侧的行为。

应用方式:Integration Fences

README 给出 AI-1715 在既有默认关闭滚动门(rollout gate)之后如何采用这套地基:

  • 发布路径:紧贴目录、schema、HTTP 响应与 binding 的发布回调使用publishWithCurrentAuthority
  • 远程执行路径:在完成 connection、OAuth、Graph、OBO 工作后,用executeWithCurrentAuthority包裹远程tools/call回调。beforeExecute钩子只服务于确定性竞态测试;权威断言在钩子之后、回调之前立即执行;
  • OAuth 回调路径:断言必须发生在令牌交换(token exchange)之前。精确代际存储完成后,重新解析一份绑定到已存储代际的新 proof,并在唤醒等待者之前立即断言它。存储前的那份 proof 预期会在 grant 存在后拒绝且绝不重用;若重解析或存储后断言失败,只删除该回调自己写入的凭据代际;
  • 名称一致性校验:在解析或断言 proof 之前,必须确认路由层服务器名、解析后的 flow-id 服务器名与已存 flow-state 服务器名三者完全相同。

README 同时强调:仅构造 Resolver 并不会启用任何 scoped catalog 行为。功能门控的归属权在调用方(caller owns the feature gate),且必须把同一份 proof 依次带到每一道最终围栏。若中途替换了不同的 proof,围栏会因信封未经本 Resolver 签发而直接拒绝。

上线前置步骤:两次离线迁移

在打开门控之前,必须按顺序完成两个迁移(均由 packages/data-schemas/src/migrations/index.ts 导出),且它们永远不会出现在热路径上

  1. backfillMCPServerNormalizedNames(packages/data-schemas/src/migrations/mcpServerNames.ts):在 MCP 服务器写入静默(quiesced)时执行。它以主库 + majority 读逐批扫描mcpservers,按[tenantId, normalizedServerName]校验同租户内不存在归一化冲突,随后以每批 500 条bulkWrite回填normalizedServerName,最后创建唯一索引normalizedServerName_1_tenantId_1(带$existspartial filter)。迁移过程中发现的任何冲突都会抛出MCPServerNameMigrationError,必须先解决才能重试,不能带着冲突开启 proof
  2. createMCPAuthorityLookupIndexes(packages/data-schemas/src/migrations/mcpAuthorityIndexes.ts):创建权威快照读取所需的四个组合索引,分别覆盖群组成员查询、Agent 的 MCP 服务器关联查询、凭据查询与 OAuth 令牌查询:
集合索引键名称
groups{ memberIds: 1, tenantId: 1 }memberIds_1_tenantId_1
agents{ mcpServerNames: 1, tenantId: 1 }mcpServerNames_1_tenantId_1
pluginauths{ userId: 1, pluginKey: 1, authField: 1, tenantId: 1 }userId_1_pluginKey_1_authField_1_tenantId_1
tokens{ userId: 1, type: 1, identifier: 1, tenantId: 1 }userId_1_type_1_identifier_1_tenantId_1

两个迁移全部完成后,才允许开启 proof 功能。

拒绝原因与可观测性

有限的拒绝原因集合

proof 的每次失败都映射为MCPAuthorityProofError.reasonMCPAuthorityProofError定义于 mcpAuthority.ts,reason 类型定义于 types/mcpAuthority.ts),共 15 个受控取值:

类别reason 取值
输入/结构malformed_input
数据不可用proof_unavailable
主体user_revokedprincipal_changedgroups_changed
配置与角色config_changedmcp_use_revokedrole_changed
启动boot_revision_changed
服务器与授权server_revokedserver_changedaccess_revokedauthorization_changed
凭据与 OAuthcredential_changedoauth_grant_changed

这些 reason 是有界枚举,可直接作为计数器标签或结构化日志字段。README 的可观测性约定非常明确:应记录 reason、围栏名(fence name)与可选的服务器名;严禁记录proof 本体、解析后的配置、凭据摘要、OAuth 代际、用户来源标识符与底层查询错误。所有意外的数据库错误与畸形记录都会被asMCPError(mcpAuthority.ts)归一为proof_unavailable,从而在不暴露任何已存数据的前提下失败关闭。这也与上文 DocumentDB 集合缺失时的行为一致:对外只有一个“权威数据不可得”,具体原因只进日志。

竞态断言的语义小结

综合 mcpAuthority.ts 与 compareProofs 可以看到,assertMCPAuthorityProofsCurrent先做完整自校验,再比较所有 proof 必须共享同一份主体快照、boot 必须与当前解析器一致,然后重新加载当前权威快照并逐项比对:

  • 用户修订不一致 →principal_changed
  • 群组修订不一致 →groups_changed;Config 修订不一致 →config_changed
  • 角色use被置否 →mcp_use_revoked;角色修订变化 →role_changed
  • 服务器来源/数据库 ID/服务器修订/解析配置摘要变化 →server_changed;数据库源且无任何访问 →access_revoked
  • 授权修订/凭据修订/OAuth 修订/策略摘要任一变化 →authorization_changed/credential_changed/oauth_grant_changed/server_changed

任一差异都意味着“解析时看到的权限已经过期”,执行必须中止——这正是关闭解析-证明窗口与凭据轮换窗口的核心保证。

建议的接入顺序与总结

若你需要在 LibreChat 的 MCP 调用链中引入同类最新权限校验,可遵循以下已验证的接入顺序:

  1. 准备:在写入静默期运行backfillMCPServerNormalizedNames,解决全部归一化命名冲突;随后运行createMCPAuthorityLookupIndexes
  2. 构造:每个不可变启动配置创建一个MCPAuthorityProofResolver(注入createMCPAuthorityMethods(mongoose)、bootRevision 与 immutableConfig);
  3. 解析:在完成 MCP 配置解析与 schema 构建后调用resolve,务必提供能安全规范化真实工件的calculateArtifactRevision,携带返回的冻结信封继续流转;
  4. 围栏:把 catalog/schema/HTTP 发布、binding 与远程tools/call分别包进publishWithCurrentAuthority/bindWithCurrentAuthority/executeWithCurrentAuthority;OAuth 回调在令牌交换前断言、存储后基于新代际重解析再断言;
  5. 门控:功能门由调用方持有,同一份 proof 必须贯穿所有围栏;
  6. 观测:只记录 reason、fence 名与服务器名,依托有界 reason 集合构建指标与告警。

MCP Authority Proof 通过“启动摘要 + 各来源代际 + 工件修订 + 单事务权威快照 + 最终围栏双检”的组合,把多集合、多租户、可轮换的权限状态压缩成一份可复核、有界、失败关闭的证明,为 LibreChat 在 MCP 服务器发布与工具执行上提供了一层可审计的“最后时刻权限确认”。理解这套设计与实现,有助于你在自己的 Agent/MCP 平台中复现同样的并发安全模式。

【免费下载链接】LibreChatEnhanced ChatGPT Clone: Features Agents, MCP, Skills, DeepSeek, Anthropic, AWS, OpenAI, Responses API, Azure, Groq, o1, GPT-5, Mistral, OpenRouter, Vertex AI, Gemini, Artifacts, AI model switching, message search, Code Interpreter, langchain, DALL-E-3, OpenAPI Actions, Functions, Secure Multi-User Auth, Presets, open-source for self-hosting. Active项目地址: https://gitcode.com/GitHub_Trending/li/LibreChat

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 12:37:53

从ECC纠错码到MBIST:内存与存储错误排查全指南

“ECC”这三个字母&#xff0c;在存储、服务器和嵌入式芯片圈子里几乎天天都能看到。有人内存报错时在日志里撞见uncorr. ecc开头的记录&#xff0c;有人打开存储管理界面发现Uncorrectable ECC Error: 2&#xff0c;还有人调试单片机时遇到MBIST ECC failure直接愣住。这几个词…

作者头像 李华
网站建设 2026/9/9 12:36:11

Base64不是加密!一文彻底搞懂编码与加密的本质区别

1. 编码和加密是两个世界&#xff1a;一次Base64解析引发的概念清理1.1 为什么很多人把Base64当成加密先讲一件真实的事情。几年前我带一个刚入行的新人做接口联调&#xff0c;他对着前端传过来的一串eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiJ9告诉我&#xff1a;“这串数据被加…

作者头像 李华
网站建设 2026/9/9 12:34:08

阿里滑块验证动态UA(X82YX5SEC)生成算法逆向全记录

简介&#xff1a;面向Python开发者与安全研究人员&#xff0c;这份资源围绕阿里X82YX5SEC滑块UA算法&#xff0c;提供了一套基于Python的自动化识别与模拟通过示例。代码涉及滑块缺口定位、图像特征提取、轨迹生成、请求参数构造等关键环节&#xff0c;适合想将图像处理、模式识…

作者头像 李华