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.ts、data-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 具体实现。
核心概念与生命周期
一次完整的权威证明生命周期包含五个相互独立又环环相扣的部分:
- Boot revision(启动修订):由解析器构造时对“不可变启动配置”一次性计算,包括
revision(外部传入的启动版本号)与digest(对不可变配置内容的摘要)。此后任何“当前仍权威”的断言都不会再重新加载 YAML 或扫描 Redis,Boot revision 是全程不变的锚点。 - Source revision(来源修订):被选中服务器“配置来源”的代际标记。数据库来源(database targets)用
createMCPAuthorityDatabaseSourceRevision计算,配置来源(config targets)用createMCPAuthorityConfigSourceRevision结合 boot digest 与解析器产生的完整适用 Config 文档集合(含 inactive 文档)计算。 - Credential revision(凭据修订):由
createMCPAuthorityCredentialRevision依据目标服务器声明的凭据字段与存储的加密凭据文档计算,不落明文。 - OAuth generation(OAuth 代际):预期写入的
credential_set_id代际(无 OAuth 则为null),用于检测令牌轮换。 - 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。其流程为:
- 先调用
calculateArtifactRevision({ parsedConfig, schemas })得到工件修订,非法(非字符串或空白)即抛出malformed_input; - 调用
methods.resolveMCPAuthorityProof从数据库取权威证明; - 取回后再次计算工件修订并比对,任何差异都判定为
malformed_input(“工件在解析权威过程中被改动”),从而封死 parse-before-proof 窗口; - 通过
Object.freeze冻结最终的{ parsedConfig, schemas, authorityProof }信封,并把该信封登记进一个WeakMap(index.ts),记录“签发时工件修订”与“随时可重算修订的闭包”。
注意解析器从不冻结或修改调用方传入的 config/schema 对象本身——测试 index.spec.ts 特意断言Object.isFrozen(resolution.schemas) === false,即数据仍归调用方所有,只是信封被冻结。
三重围栏:publish / bind / execute
useIssuedResolution(index.ts)是所有围栏的公共内核,执行顺序极其严格:
- 从
WeakMap取出签发记录,不存在即拒绝——防止调用方手拼一个结构相同但未经签发的“仿冒信封”绕过校验(测试 index.spec.ts 用{ ...resolution }浅拷贝验证了这一点); - 断言工件修订仍未改变(
assertArtifactsCurrent); - 调用
assertCurrent,让数据库层重取权威快照并与证明比对; - 再次断言工件修订未变——封死“断言在途时被注入修改”的窗口;
- 全部通过后才执行真正的回调(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,以及由十种联邦/本地身份字段(idOnTheSource、openidIssuer、googleId…)摘要出的sourceIdentityDigest与自身revision |
groups | 用户所属群组列表,每个群组含来源与sourceIdentityDigest、revision |
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归一为null、Date转 ISO、MongooseObjectId转十六进制、Map按键排序、拒绝非有限数字/循环引用/非法日期;随后由digestMCPAuthorityValue做sha256后以base64url输出。
这正是 README 中那句关键结论的实现证据:真实解析后的工具工件可能包含函数与类实例,通用 JSON hasher 无法安全规范化它们。因此数据库层只承诺对“可稳定序列化”的数据做摘要,而解析出的 config/schema 工件摘要必须由调用方通过calculateArtifactRevision回调提供——这是设计上刻意留出的扩展点。
解析与证明的硬边界
为保证单次证明可被限界读取、可被安全断言,mcpAuthority.ts 定义了如下上限(均在prepareTargets与assertProofIntegrity中被强制校验):
| 常量 | 值 | 约束对象 |
|---|---|---|
MAX_MCP_AUTHORITY_TARGETS | 32 | 单次解析/证明中的服务器数量 |
MAX_MCP_AUTHORITY_GROUPS | 64 | 群组数 |
MAX_MCP_AUTHORITY_AGENTS | 64 | 单个服务器的关联 Agent 数 |
MAX_MCP_AUTHORITY_CREDENTIALS | 64 | 单次快照的凭据文档数 |
MAX_MCP_AUTHORITY_ACL_ENTRIES | 96 | ACL 条目数 |
MAX_MCP_AUTHORITY_CREDENTIAL_FIELDS | 64 | 单次快照的凭据字段总数 |
MAX_MCP_AUTHORITY_CONFIG_TOMBSTONES | 64 | 单个 Config 的墓碑数 |
| 名称/字段/来源修订长度 | 256 | 字符串字段 |
prepareTargets(mcpAuthority.ts)还做语义校验:服务器名不能重复、归一化后不能产生歧义(两个不同名字归一化相同)、config 来源不得携带databaseId、database 来源必须有合法ObjectId、requiresOAuth由resolvedConfig.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成一个含rows与count的结果文档,再用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 导出),且它们永远不会出现在热路径上:
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。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.reason(MCPAuthorityProofError定义于 mcpAuthority.ts,reason 类型定义于 types/mcpAuthority.ts),共 15 个受控取值:
| 类别 | reason 取值 |
|---|---|
| 输入/结构 | malformed_input |
| 数据不可用 | proof_unavailable |
| 主体 | user_revoked、principal_changed、groups_changed |
| 配置与角色 | config_changed、mcp_use_revoked、role_changed |
| 启动 | boot_revision_changed |
| 服务器与授权 | server_revoked、server_changed、access_revoked、authorization_changed |
| 凭据与 OAuth | credential_changed、oauth_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 调用链中引入同类最新权限校验,可遵循以下已验证的接入顺序:
- 准备:在写入静默期运行
backfillMCPServerNormalizedNames,解决全部归一化命名冲突;随后运行createMCPAuthorityLookupIndexes; - 构造:每个不可变启动配置创建一个
MCPAuthorityProofResolver(注入createMCPAuthorityMethods(mongoose)、bootRevision 与 immutableConfig); - 解析:在完成 MCP 配置解析与 schema 构建后调用
resolve,务必提供能安全规范化真实工件的calculateArtifactRevision,携带返回的冻结信封继续流转; - 围栏:把 catalog/schema/HTTP 发布、binding 与远程
tools/call分别包进publishWithCurrentAuthority/bindWithCurrentAuthority/executeWithCurrentAuthority;OAuth 回调在令牌交换前断言、存储后基于新代际重解析再断言; - 门控:功能门由调用方持有,同一份 proof 必须贯穿所有围栏;
- 观测:只记录 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),仅供参考