【免费下载链接】OpenMausBot
Open-source Grok Bot alternative with a virtual machine that bots can use
项目地址:
https://gitcode.com/GitHub_Trending/op/OpenMausBot
点击查看 免费下载
本文基于仓库中的实现计划 docs/superpowers/plans/2026-08-31-03-mcp-registry.md,完整解读 OpenMausBot 的 MCP Registry(Connections Center 第一阶段)设计:目标、全局约束、文件地图与七个任务的接口、验证要求和提交策略,并结合 server/mcp-registry.ts、server/mcp-probe.ts、server/mcp-bridge.ts、server/drivers/claude.ts 等已落地源码,说明这套“用户添加 MCP 服务器 → 先探测后启用 → 按能力挂载到驱动”的机制最终长成了什么样子。读完你能掌握该模块的完整设计脉络、各任务的验收测试点,以及计划与当前实现的演进差异。
一、目标与背景:MCP 无处不在,却没有任何用户可达的入口
计划开篇的 Goal 一句话概括了要解决的问题:让用户添加自己的 MCP 服务器(本地 stdio 或远程 HTTP),启用前先测试它、查看它暴露的工具,并可以为单个 Bot 授予单个工具的权限;Composio 从“唯一方式”降级为“连接方式之一”。
计划文档在 Background 一节指出了当时的代码现状:
- server/drivers/claude.ts 已经会把 harness 自有的多个集成(composio、computer、agents、phone、dweb 与 ogb 权限代理)组装成 mcpServers,写入临时配置文件后传给 CLI;
- server/mcp-bridge.ts 是一套完整且有测试覆盖的 stdio 桥接:带活性看门狗(liveness watchdog)和策略门(policy gate);
- server/container-mcp.ts 与 server/vps-container-mcp.ts 是它仅有的两个入口;
- 但 server/config.ts 里没有 mcpServers 键——用户没有任何途径添加一台自己的 MCP 服务器,唯一能到达 Bot 的用户 MCP 只有 Antigravity 驱动从用户自己的配置文件里合并进来、但不管理的那部分。
架构上有一条贯穿始终的决策:每一台用户添加的服务器(无论 stdio 还是远程)都通过 harness 拥有的 stdio 桥进程访问。计划引用了 server/contracts.ts 中 Composio 桥的存在理由——harness 控制的桥能把连接行为变成跨 provider CLI 一致的东西;而按工具粒度的白名单,也是靠扩展 createGateInterceptor(它本来就在拦截 tools/call 帧)来执行。技术栈:TypeScript、Node 24、Vitest、JSON-RPC over stdio、React、zod。
二、全局约束(Global Constraints)
计划声明自己就是规格("none — this plan is its own spec"),与 控制面路线图共享全局约束。对 MCP Registry 最关键的四条:
依赖方面该计划不依赖任何前置工作,可与路线图中的 P1/P2 并行执行。
三、文件地图:计划要动哪些文件
计划给出的 File map 是七个任务的执行清单:
| 新建 | server/mcp-registry.ts + 测试 | 验证、存储、列出并脱敏(redact)用户 MCP 服务器定义 |
| 新建 | server/mcp-probe.ts + 测试 | 连接服务器,执行 initialize + tools/list,返回工具列表或类型化失败;有界且可取消 |
| 新建 | server/mcp-user-entry.ts + 测试 | 驱动 spawn 的桥入口:stdio 子进程或 HTTP relay,前面挂工具白名单门 |
| 修改 | server/mcp-bridge.ts(createGateInterceptor) | 泛化为接受“每次调用的裁决函数” |
| 修改 | server/config.ts | AppConfig 增加 mcpServers,读路径脱敏 |
| 修改 | server/store.ts | BotRecord.mcpGrants |
| 修改 | server/contracts.ts | integrations.custom、capabilities.customMcp |
| 修改 | server/drivers/claude.ts、server/drivers/antigravity.ts、server/drivers/pi.ts | 挂载 integrations.custom,声明能力 |
| 修改 | server/index.ts | CRUD、probe、grant 端点 |
| 新建 | src/components/McpServersPanel.tsx + 测试 | Connections Center 面板 |
| 修改 | src/components/PluginsPanel.tsx | 第二个标签页,让 Composio 与直连 MCP 并排 |
下面按任务逐一展开计划定义的接口与验收点,再对照当前仓库的实现。
四、Task 1:注册表的形状——解析、命名与脱敏
计划定义的接口:
export interface McpStdioServer { transport: "stdio"; command: string; args: string[]; env?: Record<string, string> }
export interface McpHttpServer { transport: "http"; url: string; headers?: Record<string, string> }
export type McpServerConfig = (McpStdioServer | McpHttpServer) & { label?: string };
export interface McpServerListing {
name: string; transport: "stdio" | "http"; label?: string;
command?: string; args?: string[]; url?: string;
secretKeys: string[]; // 只有名字,绝没有值
tools: McpToolListing[]; // 上次成功探测的结果;探测前为空
probedAt?: number; probeError?: string;
}
export function parseMcpServer(name: string, value: unknown): McpServerConfig | { error: string };
export function isMcpServerName(name: string): boolean;
export function redactServer(name: string, cfg: McpServerConfig, probe?: McpProbeRecord): McpServerListing;
验收测试要求包括:合法 stdio/http 条目可解析;非 loopback 的 http:// URL 被拒绝(无 TLS 的远程 MCP 会把 bearer header 明文传输);file:// 等其它 scheme 被拒绝;空 command 被拒绝;含点、斜杠、大写的名字被拒绝;redactServer 对 env: { GITHUB_TOKEN: "ghp_real" } 返回 secretKeys: ["GITHUB_TOKEN"] 且序列化结果中不含 ghp_real;携带未知额外键的条目必须能往返而不抛异常。
配置落点上的一个微妙不对称,计划特意点出:appConfigPatchSchema 只 omit 了 instances,所以 mcpServers 自动成为可写字段,写路径无需额外接线;而 GET /api/config 返回的是手工构建的 configStatus(),读路径必须刻意接线——mcpServers 只能以 redactServer 的脱敏形式出现,绝不能原样暴露 cfg.mcpServers。提交信息:feat(mcp): user MCP server registry with redacted reads。
当前实现对照。 server/mcp-registry.ts 已成为这套机制的核心,且比计划更丰富:
- 名称规则从计划中的“小写字母数字 + 连字符、1–64 字符”演变为 /^[a-z][a-z0-9_-]{0,31}$/(server/mcp-registry.ts#L90),并配合 shared/mcp-name.ts 中的 RESERVED_MCP_NAMES(ogb、computer、agents、composio、data、browser、phone、dweb 等)——自定义服务器不得占用 harness 自己的名字,importedMcpServerName 会给撞名的条目自动加 mcp- 前缀;
- “只写不读”通过 listMcpServers 落地:stdio 条目只回传 envKeys(排序后的键名数组),remote 条目只回传 headerKeys,从不回传值;
- 编辑时的“保留原密钥”用 z.literal(true) 表达:mutation schema 中 env 的值可以是字符串或 true,resolveSecrets 把 true 解析回已存储的值(server/mcp-registry.ts#L229-L244),这正是“读路径永不回传值、写路径允许保留”的具体实现;
- 限额成为显式常量:MAX_MCP_SERVERS = 20、MAX_ARGS = 64、MAX_ENV = 64、MAX_HEADERS = 32、MAX_RESULT_BUDGET = 2_000_000(server/mcp-registry.ts#L79-L84);
- 还新增了计划中没有的安全细节:isReservedMcpEnvEntry 禁止自定义服务器设置 OMB_/OGB_ 前缀的环境变量(这些名字携带 harness 的 bearer),urlError 禁止把凭据写进 URL 的 userinfo 部分,强制“凭据放 header”;
- “导入即禁用”在 parseMcpServersImport 中有明确注释:粘贴进来的 enabled 字段被直接丢弃,“nothing runs before it was tested”(没测试过就什么都不能跑);addDisabledMcpServer 则保证 Bot 通过 API 添加服务器时连开关都发不出来(BOT_CANNOT_SWITCH)。
在 server/config.ts 中,mcpServers 作为宽松字段存在(z.record(z.string(), z.unknown()),见 server/config.ts#L451),并且 appConfigPatchSchema 将 mcpServers 列为 omit 之一——用户必须走专门的 MCP 路由而不是通用 config patch,这是与计划描述略有不同的演化。customMcpServers(cfg, only?, botId?)(server/config.ts#L1270-L1285)负责把存储的条目转换成驱动可挂载的 spec:解析失败的条目跳过并打日志(skipMcpEntry),而不是让整个配置崩掉——“未知配置降级而不抛异常”的约束在这里兑现。
五、Task 2:先探测,再信任——probe
计划定义的接口与行为:
export interface McpProbeRecord { tools: McpToolListing[]; probedAt: number }
export type McpProbeResult = { ok: true; probe: McpProbeRecord } | { ok: false; error: string };
export function probeMcpServer(cfg: McpServerConfig, timeoutMs?: number): Promise<McpProbeResult>;
默认超时 10 000 ms。错误字符串面向人,且绝不能包含 header 值、env 值或 provider 原始错误体。验收测试覆盖了四组危险场景:
实现要点:spawn(stdio)或 fetch(http),发送 initialize 然后 tools/list,收到第二个响应即 resolve,任何情况下都要拆掉现场;错误串再经过 redactSecrets(server/redact.ts)作为第二道防线。
当前实现对照。 server/mcp-probe.ts 完整落实了这四组要求,并进一步细化:
- probeStdioMcpServer 中 finish() 的每一步都调用 killCliTree(child) 收尸(server/mcp-probe.ts#L178-L185)——“被 kill 而非被遗弃”在实现层面是有据可查的;子进程 stderr 用 resume() 排空而不保留(注释直言:子进程 stderr 常含密钥);
- 错误消息走 publicProbeError 白名单:spawn / timeout / protocol / closed / cancelled 五种,全部是固定文案,不拼接任何来自子进程或响应体的内容;
- 工具名与描述经过 redactConfiguredValues:把已配置的每个 secret 值在输出里替换为 [redacted],名称截断 200 字符、描述截断 500 字符,工具列表上限 MAX_TOOLS = 100,超出时回传 total;
- 超时参数具体化为 STDIO_PROBE_TIMEOUT_MS = 8_000 与远程的 REMOTE_PROBE_TIMEOUT_MS(server/mcp-probe.ts#L40-L41);
- 计划没预料到的能力也被补进来了:401 时走 discoverMcpAuth 探测 OAuth 元数据,返回 auth: "required",让 UI 从“密钥被拒”升级为“引导用户登录”(对应后续计划 docs/superpowers/plans/2026-09-30-mcp-sign-in.md);失败原因被归类为 keys / unreachable / timeout,其中后两者提示“可以重试”;
- stdio 探测环境由 probeEnvironment 构造:先剥掉工作区凭据(stripWorkspaceCredentialEnv)与 provider 凭据(PROVIDER_CREDENTIAL_ENV),再叠加服务器自己的 env——探测进程永不继承 OpenMaus 的凭据。
六、Task 3:泛化策略门——让 gate 接受“每次调用的裁决”
计划把 server/mcp-bridge.ts 的 createGateInterceptor 从固定的 isHeld/refusalText 语义改造成通用的裁决函数:
export type CallVerdict = { allow: true } | { allow: false; text: string };
export function createGateInterceptor(options: {
verdict: (toolName: string) => Promise<CallVerdict>;
forward: (line: string) => void;
refuse: (line: string) => void;
}): (line: string) => void;
现有调用方(server/container-mcp.ts、server/vps-container-mcp.ts)在同一提交内迁移为 verdict: async () => (await isHeld()) ? { allow: false, text: CONTROL_REFUSAL_PLAIN } : { allow: true }——计划特别警告:迁移必须一个提交做完,半途而废会让树里同时存在两种 gate 形状。
验收测试中最值得记住的一条是顺序性测试:非 tools/call 帧与不可解析的行原样转发,且跨异步裁决的顺序必须保持——两次快速调用中若第一个裁决比第二个解析得慢,到达子进程的顺序仍必须正确。计划称这是“最可能坏的性质”,因为它依赖 createGateInterceptor 内部那条序列化的 queue 链在重构后原样存活。
当前实现对照。 server/mcp-bridge.ts#L146-L183 的 createGateInterceptor 保留了计划的精髓:let queue: Promise<void> = Promise.resolve(); queue = queue.then(async () => {…}) 的串行化链一字未动,注释明确解释为什么——“检查是异步的,在帧 N 之前回答帧 N+1 会乱掉 agent 的协议流”。当前签名收敛为 check: (frame) => Promise<ControlState> 的形态(服务 who-is-driving 控制流),而非计划中的 verdict(toolName);按工具裁决的语义被移到了驱动层与权限 broker(见第八节)。createLineSplitter(server/mcp-bridge.ts#L121-L144)按计划被复用——“MCP stdio 是一行一个 JSON-RPC 帧,行边界是唯一切入检查或注入的安全位置”,http relay 路径也复用同一个 splitter 而不是写第二份。
七、Task 4 与 Task 5:桥入口、每 Bot 授权与 API 端点
Task 4(桥入口)要求一个以 node mcp-user-entry.ts <serverName> 启动的进程,定义与白名单走环境变量而非 argv:OMB_MCP_SERVER(JSON 定义)和 OMB_MCP_ALLOW(逗号分隔工具名或 *)。计划给出的理由很硬:server/container-mcp.ts 已确立“argv 通过 ps 对全世界可读”这条规则。行为要求包括 default-deny——没有 OMB_MCP_ALLOW 的桥必须拒绝一切,“一个没有策略启动的桥不能变成一个没有策略的桥”;畸形定义必须拒绝执行而非带病运行。
**Task 5(每 Bot 授权)**定义 BotRecord.mcpGrants?: Record<string, string[]>(服务器名 → 授权工具名,["*"] 表示全部;缺省或空 = 无访问,与 Task 4 的 default-deny 一致),以及四个端点:GET/POST/DELETE /api/mcp/servers、POST /api/mcp/servers/:name/probe、PUT /api/bots/:id/mcp-grants。五组失败先行的 API 测试各有针对性:
- POST /api/mcp/servers 存服务器后,GET 返回 secretKeys 但无值;
- 新服务器不挂到任何 Bot——添加服务器不能让它连上任何东西;
- POST …/probe 返回工具列表,之后的 GET 携带缓存的 tools 与 probedAt;
- PUT /api/bots/:id/mcp-grants 拒绝授予不存在的服务器,也拒绝授予最近一次成功探测没有列出的工具名——“授予一个从没人见过的工具,是拼写错误变成静默 no-op 的方式”;
- 删除服务器时清掉所有 Bot 上的对应 grant——“悬空 grant 在名字被复用时复活”是真实风险。
当前实现对照(含与计划的偏离)。 从源码结构看,Task 4/5 的最终形态与计划有明显演化,这是理解现状的关键:
- 当前树中没有 server/mcp-user-entry.ts,也搜不到 BotRecord.mcpGrants 或 /api/bots/:id/mcp-grants。每 Bot 的“授权”没有做成独立 grant 表,而是落在两处:存储层用 enabled、bot(该服务器只属于某个 Bot)与 runsOn(属于另一台电脑)字段过滤——mcpServerServes 判定某 Bot 是否有权启动某服务器(server/mcp-registry.ts#L74-L77);运行层用驱动对每个用户服务器挂 gateServer 与 toolScope,用户服务器不做预授权,每次调用都路由到权限 broker(见 server/drivers/claude.ts#L1137-L1146 注释:User servers are not pre-allowed, so acceptEdits routes each call to the broker)。计划中的 default-deny 精神以“默认走人工批准”的形式保留了。
- API 层落在 server/routes/mcp-servers.ts:GET /api/mcp/servers(响应经 mcpServerResponse() 把密钥表示为布尔并附登录态)、POST /api/mcp/servers、POST /api/mcp/servers/import(批量导入)、PUT/PATCH/DELETE /api/mcp/servers/:name,以及 POST /api/mcp/servers/:name/test——注意端点叫 test 而非计划中的 probe,且带并发上限 MAX_CONCURRENT_MCP_PROBES = 2(超出返回 429)、客户端断连即 abort 的取消语义(server/routes/mcp-servers.ts#L59-L110)。
- “删除即清空授权”等价地体现在:服务器条目删除后,没有引用它的挂载自然消失;forgetPeople 则保证 Team Cloud 场景下成员个人的 key 与登录态随服务器一起删除。
- 计划中 stdio 服务器一律经桥 spawn 的设计,在最终实现里区分了传输:remote(http/sse)条目“已经是 CLI 的形状,CLI 自己连接”(Claude Code 只发送 spec 定义的 initialize 字段,且保留大目录下的 MCP 工具搜索);stdio 条目才由 harness 侧控制启动环境。server/mcp-bot-servers.ts 进一步处理“某服务器的程序或文件在某个 Bot 自己的目录里”的情形:判定归属、把会话临时目录里的文件拷入 workspaces/<bot>/tools/ 并改写路径(keepInBotFolder),保证隔离环境里 Bot 只能够到自己的文件。
八、Task 6:挂载到驱动——契约字段与命名冲突防线
计划要求新增两个契约字段:
integrations.custom?: Array<{ name: string; command: string; args: string[]; env: Record<string, string> }>;
capabilities.customMcp?: boolean;
customMcp: true 只声明在 claude、antigravity、pi 三个驱动上,其余驱动一律缺省——能力规则(第二节的第 3 条)在类型层面的直接落地:“绝不要告诉 Bot 它驱动无法挂载的工具”。
三组失败先行的驱动测试各有深意:
当前实现对照。 server/contracts.ts 中 integrations.custom 最终定为 Record<string, McpServerSpec>(server/contracts.ts#L182-L188,附带 mcpApps 与 customResultBudgets 两个姊妹字段),capabilities.customMcp 注释为 “The driver mounts integrations.custom”(server/contracts.ts#L258-L259)。claude 驱动的挂载逻辑与计划第 1 组测试逐字对应:先逐个检查 Object.hasOwn(mcpServers, name),已存在的保留名直接跳过用户条目(server/drivers/claude.ts#L1142-L1155)——即“harness 挂载不可被用户服务器影子化”;RESERVED_MCP_NAMES(shared/mcp-name.ts#L2-L13)在注册入口就提前拦掉大部分撞名。计划中“argsKey 包含 custom 条目使缓存失效”的要求,在现状里对应 harness 把每个用户服务器经 gateServer 包装、并按 toolScope 过滤(canUseMcpServer)后才写入 0600 的临时 MCP 配置文件(server/drivers/claude.ts#L1156-L1179,注释:The MCP config carries credentials and argv is readable via ps: a 0600 file, removed at settle)——凭据不落 argv 的原则(Task 4 的核心断言)在最终代码里依然是硬约束。
九、Task 7:Connections Center UI
计划定义的界面行为与验收:
- 纯函数表单校验器 validateServerForm:接受带 command 的 stdio 表单与 https URL;拒绝裸 http://example.com、拒绝空名字,并指出出错字段;
- 面板包含 stdio/远程添加表单、调用 probe 路由并渲染返回工具的 Test connection 按钮、按 Bot 的授权编辑器(逐工具打勾);没有成功探测过的服务器不能被授权——授权 UI 保持禁用并显示探测错误;
- 每行显示服务器在哪里运行:stdio 条目标注 “on this computer”,http 条目显示 origin。计划把它称作“社区要求的‘显示连接器在哪运行、数据去哪’诉求,一行诚实文字而非一个功能”;
- PluginsPanel.tsx 重构为两个标签页——"Apps"(现有 Composio 目录,代码不动,只包起来)与 "MCP servers"(新面板);
- 收尾命令:focused test、pnpm typecheck、pnpm check:contrast;提交信息 feat(mcp): connections center with direct MCP servers。
当前实现对照。 src/components/McpServersPanel.tsx 及其测试(McpServersPanel.add.test.ts、McpServersPanel.whop.test.ts 等)已落地,src/components/PluginsPanel.tsx 保留了 Composio/连接器目录并新增按 mcp 维度的过滤(AppsFilter = "all" | "connected" | "mcp"),与计划“包起来而不是搬走”的约束一致。
十、计划的自评审与落地状态小结
计划末尾的 Self-review 对社区 Connections Center 清单做了逐项覆盖检查:策展目录已存在(/api/connectors/catalog);本地 stdio 与远程 HTTP(Task 1/4/7);先测后启用(Task 2);发现并启用单个工具(Task 2/5/7);按 Bot 授权(Task 5);显示运行位置(Task 7)。引导式 OAuth 被明确排除:每个 MCP 服务器一套 OAuth 需要回调监听与 token 存储,是独立的计划;当下用 bearer header 覆盖常见远程场景。类型一致性上,McpServerConfig 是 Task 1 生产、Task 2/4/5 消费的同一类型;McpToolListing 由 Task 2 生产、5 与 7 消费;CallVerdict 只由 Task 4 的白名单函数产生。排序风险上,Task 3 重构了两个现有入口依赖的模块,必须单提交落地且 pnpm vitest run server/ 全绿后 Task 4 才能开工,否则 Task 4 的测试会以“与 Task 4 无关的原因”失败。
对照当前仓库可以得出三条结论性的落地状态:
对想在本地验证这套机制的读者,可依次查看:注册表与测试 server/mcp-registry.test.ts、探测与测试 server/mcp-probe.test.ts、路由与测试 server/routes/mcp-servers.test.ts、桥接 server/mcp-bridge.ts、面板 src/components/McpServersPanel.tsx,以及 e2e 用例 server/mcp-own-servers.e2e.test.ts 与 server/mcp-selection.e2e.test.ts。适用前提:TypeScript + Node 24 + pnpm/Vitest 工作区;本文所述行号对应当前仓库快照,代码演进后行号可能漂移,但上述文件与函数名(parseStoredMcpServer、probeMcpServer、createGateInterceptor、customMcpServers、RESERVED_MCP_NAMES)可作为稳定的检索锚点。
赞
【免费下载链接】OpenMausBot
Open-source Grok Bot alternative with a virtual machine that bots can use
项目地址:
https://gitcode.com/GitHub_Trending/op/OpenMausBot
点击查看 免费下载
相关推荐
Unity Mod Manager终极指南:轻松管理游戏模组的一站式解决方案
TimeCat跨浏览器兼容性:Edge、Firefox、Chrome、Safari全面支持
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心







评论前必须登录!
注册