为 oh-my-openagent 添加内置 arXiv MCP:基于 createBuiltinMcps 三层 MCP 体系的完整改造指南
2026/9/18 14:16:33 网站建设 项目流程

为 oh-my-openagent 添加内置 arXiv MCP:基于 createBuiltinMcps 三层 MCP 体系的完整改造指南

【免费下载链接】oh-my-openagentOmO: Just type "mass ulw" keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent

本篇技术指南围绕 oh-my-openagent(OmO)项目中"新增一个内置远程 MCP(arXiv 论文检索)"的完整代码改造展开,覆盖从新建arxiv.ts静态导出、扩展McpNameSchema枚举、在createBuiltinMcps工厂中注册、同步测试用例与模块文档的五个文件级改动。读者读完将掌握内置 MCP 的注册机制、disabled_mcps过滤语义、无鉴权 MCP 的标准实现范式,以及如何在当前仓库中落地一次"最小化、外科手术式"的内置能力扩展。

一、背景:OmO 的三层 MCP 体系与"内置远程 MCP"

在 oh-my-openagent 中,MCP 服务器被组织为三层体系(见 src/mcp 模块说明):

层级来源机制
Tier 1:内置src/mcp/(即packages/omo-opencode/src/mcp/远程 HTTP MCP + 本地 stdio MCP,由createBuiltinMcps()统一创建
Tier 2:Claude Code.mcp.json${VAR}环境变量展开,经claude-code-mcp-loader加载
Tier 3:技能内嵌SKILL.md YAMLSkillMcpManager管理(stdio + HTTP)

本文涉及的改造全部落在 Tier 1。当前createBuiltinMcps工厂(见 index.ts)注册了 4 个内置 MCP:

名称类型端点鉴权用途
websearchremotemcp.exa.ai(默认)或mcp.tavily.comEXA_API_KEY(可选)网页搜索
context7remotemcp.context7.com/mcpCONTEXT7_API_KEY(可选)库文档检索
grep_appremotemcp.grep.appGitHub 代码搜索
lsplocal(stdio)packages/lsp-tools-mcpCLI代码诊断、定义跳转、符号等

其中grep_app是所有"无鉴权远程 MCP"的标准范例——它不依赖环境变量、不需要配置工厂,直接静态导出配置对象(见 grep-app.ts)。新增的 arXiv MCP 正是要复制这一模式,将内置 MCP 数量从 4 提升到 5。

二、新增src/mcp/arxiv.ts:无鉴权静态导出范式

第一步是新建src/mcp/arxiv.ts文件,完整代码如下:

export const arxiv = { type: "remote" as const, url: "https://mcp.arxiv.org", enabled: true, oauth: false as const, }

该对象的四个字段与RemoteMcpConfig类型(定义于 index.ts)逐一对应:

  • type: "remote" as const:声明这是一个远程 HTTP MCP,区别于lsp这类本地 stdio MCP(LocalMcpConfig);
  • url:MCP 服务器端点地址;
  • enabled: true:默认启用,只有在disabled_mcps中显式列出时才被过滤;
  • oauth: false as const:明确无 OAuth 流程。若未来需要鉴权,可参考headers字段(如context7Authorization: Bearer头)。

⚠️ 合并前阻塞点:文档明确指出,https://mcp.arxiv.org是一个占位 URL,真实端点需要在合并前验证。如果不存在官方托管的 arXiv MCP,备选方案包括:社区托管的 MCP 服务器,或基于 arXiv REST API(export.arxiv.org/api/query)自建包装服务。这一不确定性是本次改造唯一的合并阻塞项——在端点被验证之前,该改动不应合入主分支

选择静态导出而非配置工厂的原因:arXiv API 是公开的,不需要 API Key,因此无需像websearch那样依据环境变量动态构造配置(对比 websearch.ts 中 Tavily/Exa 的 provider 分支),也无需像context7那样做CONTEXT7_API_KEY的可选鉴权头归一化(对比 context7.ts)。

三、扩展McpNameSchema:把arxiv纳入内置名称枚举

第二步修改src/mcp/types.ts,将arxiv加入 zod 枚举 Schema:

import { z } from "zod" -export const McpNameSchema = z.enum(["websearch", "context7", "grep_app"]) +export const McpNameSchema = z.enum(["websearch", "context7", "grep_app", "arxiv"]) export type McpName = z.infer<typeof McpNameSchema> export const AnyMcpNameSchema = z.string().min(1) export type AnyMcpName = z.infer<typeof AnyMcpNameSchema>

这段代码的作用(对应现仓库 types.ts 的实现):

  • McpNameSchema是一个 zod 枚举,约束了内置 MCP 的合法名称集合——任何配置引用不在枚举内的内置名称都会在校验阶段失败;
  • McpName是由z.infer推导出的联合类型("websearch" | "context7" | "grep_app" | "arxiv"),在工厂、加载器等代码中提供编译期类型安全;
  • AnyMcpNameSchema(任意非空字符串)与AnyMcpName与之互补,用于放开对自定义/第三方 MCP 名称的限制——这正是测试用例中"忽略未知名称"行为的类型基础。

新增arxiv意味着:名称集合从 3 个扩到 4 个,任何引用McpName类型或依赖McpNameSchema解析的代码路径都会同步感知到新成员。

四、在createBuiltinMcps工厂中注册

第三步修改src/mcp/index.ts,改动包含一个 import 和一个注册块:

import { createWebsearchConfig } from "./websearch" import { context7 } from "./context7" import { grep_app } from "./grep-app" +import { arxiv } from "./arxiv" import type { OhMyOpenCodeConfig } from "../config/schema" -export { McpNameSchema, type McpName } from "./types" +export { McpNameSchema, type McpName } from "./types" type RemoteMcpConfig = { type: "remote" url: string enabled: boolean headers?: Record<string, string> oauth?: false } export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpenCodeConfig) { const mcps: Record<string, RemoteMcpConfig> = {} if (!disabledMcps.includes("websearch")) { mcps.websearch = createWebsearchConfig(config?.websearch) } if (!disabledMcps.includes("context7")) { mcps.context7 = context7 } if (!disabledMcps.includes("grep_app")) { mcps.grep_app = grep_app } + if (!disabledMcps.includes("arxiv")) { + mcps.arxiv = arxiv + } + return mcps }

结合当前仓库 index.ts 的实现,可以提炼出createBuiltinMcps的注册语义:

  1. 过滤机制:工厂接收disabledMcps: string[](对应配置中的disabled_mcps),通过includes逐一判断;只过滤内置名称,对playwrightcustom等未知名称静默忽略;
  2. 注册顺序websearchcontext7grep_apparxiv,返回的mcps对象键序即此顺序,测试中Object.keys(result).toHaveLength()断言依赖这一约定;
  3. 默认启用:不传disabledMcps时(= []默认值),全部内置 MCP 注册;
  4. 配置注入websearch是唯一消费config的条目(provider 选择),arxivcontext7grep_app一样直接静态导出、不依赖 config。

五、同步测试:数量断言修正与新用例

第四步修改src/mcp/index.test.ts。由于内置 MCP 总数变化,所有断言Object.keys(result)长度的既有用例必须同步 +1(3 → 4、2 → 3),同时新增一个针对arxiv的过滤用例。完整改动如下:

describe("createBuiltinMcps", () => { test("should return all MCPs when disabled_mcps is empty", () => { // given const disabledMcps: string[] = [] // when const result = createBuiltinMcps(disabledMcps) // then expect(result).toHaveProperty("websearch") expect(result).toHaveProperty("context7") expect(result).toHaveProperty("grep_app") - expect(Object.keys(result)).toHaveLength(3) + expect(result).toHaveProperty("arxiv") + expect(Object.keys(result)).toHaveLength(4) }) test("should filter out disabled built-in MCPs", () => { // given const disabledMcps = ["context7"] // when const result = createBuiltinMcps(disabledMcps) // then expect(result).toHaveProperty("websearch") expect(result).not.toHaveProperty("context7") expect(result).toHaveProperty("grep_app") - expect(Object.keys(result)).toHaveLength(2) + expect(result).toHaveProperty("arxiv") + expect(Object.keys(result)).toHaveLength(3) }) test("should filter out all built-in MCPs when all disabled", () => { // given - const disabledMcps = ["websearch", "context7", "grep_app"] + const disabledMcps = ["websearch", "context7", "grep_app", "arxiv"] // when const result = createBuiltinMcps(disabledMcps) // then expect(result).not.toHaveProperty("websearch") expect(result).not.toHaveProperty("context7") expect(result).not.toHaveProperty("grep_app") + expect(result).not.toHaveProperty("arxiv") expect(Object.keys(result)).toHaveLength(0) }) test("should ignore custom MCP names in disabled_mcps", () => { // given const disabledMcps = ["context7", "playwright", "custom"] // when const result = createBuiltinMcps(disabledMcps) // then expect(result).toHaveProperty("websearch") expect(result).not.toHaveProperty("context7") expect(result).toHaveProperty("grep_app") - expect(Object.keys(result)).toHaveLength(2) + expect(result).toHaveProperty("arxiv") + expect(Object.keys(result)).toHaveLength(3) }) test("should handle empty disabled_mcps by default", () => { // given // when const result = createBuiltinMcps() // then expect(result).toHaveProperty("websearch") expect(result).toHaveProperty("context7") expect(result).toHaveProperty("grep_app") - expect(Object.keys(result)).toHaveLength(3) + expect(result).toHaveProperty("arxiv") + expect(Object.keys(result)).toHaveLength(4) }) test("should only filter built-in MCPs, ignoring unknown names", () => { // given const disabledMcps = ["playwright", "sqlite", "unknown-mcp"] // when const result = createBuiltinMcps(disabledMcps) // then expect(result).toHaveProperty("websearch") expect(result).toHaveProperty("context7") expect(result).toHaveProperty("grep_app") - expect(Object.keys(result)).toHaveLength(3) + expect(result).toHaveProperty("arxiv") + expect(Object.keys(result)).toHaveLength(4) }) + test("should filter out arxiv when disabled", () => { + // given + const disabledMcps = ["arxiv"] + + // when + const result = createBuiltinMcps(disabledMcps) + + // then + expect(result).toHaveProperty("websearch") + expect(result).toHaveProperty("context7") + expect(result).toHaveProperty("grep_app") + expect(result).not.toHaveProperty("arxiv") + expect(Object.keys(result)).toHaveLength(3) + }) + // ... existing tavily test unchanged })

这组用例覆盖了工厂的全部行为面:空禁用列表返回全部、单名禁用、全量禁用、忽略自定义名称、默认参数、未知名称过滤,以及新成员arxiv的专属禁用路径。数量断言是这套测试的"注册完整性哨兵"——任何未来新增内置 MCP 而忘记更新断言时,测试会立即失败,从而强制开发者同步维护注册表。

六、模块文档同步:src/mcp/AGENTS.md

第五步更新src/mcp/AGENTS.md,让模块文档与代码保持"生成即同步"的一致性:

-# src/mcp/ — 3 Built-in Remote MCPs +# src/mcp/ — 4 Built-in Remote MCPs **Generated:** 2026-03-06 ## OVERVIEW -Tier 1 of the three-tier MCP system. 3 remote HTTP MCPs created via `createBuiltinMcps(disabledMcps, config)`. +Tier 1 of the three-tier MCP system. 4 remote HTTP MCPs created via `createBuiltinMcps(disabledMcps, config)`. ## BUILT-IN MCPs | Name | URL | Env Vars | Tools | |------|-----|----------|-------| | **websearch** | `mcp.exa.ai` (default) or `mcp.tavily.com` | `EXA_API_KEY` (optional), `TAVILY_API_KEY` (if tavily) | Web search | | **context7** | `mcp.context7.com/mcp` | `CONTEXT7_API_KEY` (optional) | Library documentation | | **grep_app** | `mcp.grep.app` | None | GitHub code search | +| **arxiv** | `mcp.arxiv.org` | None | arXiv paper search | ... ## FILES | File | Purpose | |------|---------| | `index.ts` | `createBuiltinMcps()` factory | -| `types.ts` | `McpNameSchema`: "websearch" \| "context7" \| "grep_app" | +| `types.ts` | `McpNameSchema`: "websearch" \| "context7" \| "grep_app" \| "arxiv" | | `websearch.ts` | Exa/Tavily provider with config | | `context7.ts` | Context7 with optional auth header | | `grep-app.ts` | Grep.app (no auth) | +| `arxiv.ts` | arXiv paper search (no auth) |

注意文档中"Env Vars"一列为None,再次确认arxiv属于无鉴权、零环境变量的 MCP。当前仓库的 src/mcp 模块说明 采用的正是这张表格的结构(Name / Type / Endpoint / Env Vars / Tools),新增条目只需照表补一行即可。

七、改动总览与影响面评估

本次改造共涉及 5 个文件,总计约 37 行新增/修改,属于刻意保持的"最小化、外科手术式"改动:

文件改动行数类型
src/mcp/arxiv.ts+6(新建)创建
src/mcp/types.ts修改 1 行修改
src/mcp/index.ts+5(import + 注册块)修改
src/mcp/index.test.ts约 20 行(数量修正 + 新用例)修改
src/mcp/AGENTS.md约 6 行修改

影响面可以分三层评估:

  1. 编译期McpNameSchemaMcpName类型变更会传播到所有依赖该枚举的模块;types.ts从 index.ts 中被 re-export(export { McpNameSchema, type McpName } from "./types"),因此任何import { McpName } from "./mcp"的调用方都会感知新成员;
  2. 运行时createBuiltinMcps默认返回 5 个内置 MCP(若沿用文档假定的 4 远程 + lsp 的基线则为 5),disabled_mcps新增"arxiv"关键字;用户可在配置中通过disabled_mcps: ["arxiv"]关闭该功能;
  3. 测试与文档:长度断言强制同步,模块文档表格同步更新,防止注册表与文档漂移。

八、合并前的验收清单

基于文档标注的阻塞点与仓库既有实践,合并前应完成:

  1. 验证端点真实性:确认https://mcp.arxiv.org是否存在可用的托管 MCP 服务;若不存在,评估社区托管服务器或基于export.arxiv.org/api/query的自建包装方案,并将最终 URL 写入arxiv.ts
  2. 跑通测试套件:执行src/mcp/index.test.ts全部用例,确认 6 个既有用例(数量修正后)与 1 个新用例全部通过;
  3. 类型检查:确认McpNameSchema枚举扩展后无类型错误、无遗漏的 exhaustiveness 检查;
  4. 文档核验AGENTS.md的 Built-in 表格、FILES 表格与代码实际状态一致。

同类实现还可参考 senpi 侧的 builtin-mcps 组件——它在 senpi 扩展 API 中以registerMcpServer注册context7grep_app,并引入lifecycle: "lazy"exposure: "search"的延迟暴露策略(避免低频工具常驻占用约 1.7K prompt tokens 的 schema 开销)。若未来 arXiv 检索被判定为低频工具,可借鉴该模式将其同样调整为按名延迟暴露。

【免费下载链接】oh-my-openagentOmO: Just type "mass ulw" keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询