当 MCPHub 后面连接了几十甚至上百个 MCP Server 时,客户端只配置一个 MCPHub 地址,它究竟如何知道应该调用哪个服务?本文从 MCP 协议的工具发现机制出发,完整拆解 MCPHub 的统一聚合、单服务、分组和智能路由四种模式。

一、直接连接 /mcp 时,MCPHub 到底返回什么

先给出最重要的结论:

Codex 配置 https://mcphub.example.com/mcp 后,MCPHub 不会在一次响应里返回“100 个 Server 对象,再附带每个 Server 的全部工具”。MCP 协议是分步骤交互的:initialize 只完成协议初始化;Codex 随后调用 tools/list,MCPHub 才返回当前范围内所有可见工具的扁平化定义列表。

假设 MCPHub 配置了下面这些上游 Server:

MCP Server上游工具
githubcreate_issuecreate_pull_request
jiracreate_issueupdate_status
filesystemread_filewrite_file

1.1 第一步:initialize 不返回工具列表

Codex 首先向 /mcp 发送 MCP initialize 请求。MCPHub 返回的主要是协议版本、服务端能力、服务端信息和可选的全局说明:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "...",
    "capabilities": {
      "tools": {},
      "resources": {},
      "prompts": {}
    },
    "serverInfo": {
      "name": "MCPHub",
      "version": "..."
    },
    "instructions": "..."
  }
}

其中 capabilities.tools 的含义是“MCPHub 支持工具能力”,并不表示工具详情已经包含在响应中。

此时不会返回:

  • MCPHub 中配置了哪些 Server。
  • 每个 Server 的详细配置。
  • 所有工具的名称和参数结构。
  • 上游 Server 使用的密钥和环境变量。

1.2 第二步:Codex 主动调用 tools/list

初始化完成后,Codex 会调用:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

这时 MCPHub 才会把当前 /mcp 路由范围内的工具汇总后返回:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "github-create_issue",
        "description": "在 GitHub 仓库中创建 Issue",
        "inputSchema": {
          "type": "object",
          "properties": {
            "repository": { "type": "string" },
            "title": { "type": "string" }
          },
          "required": ["repository", "title"]
        }
      },
      {
        "name": "jira-create_issue",
        "description": "在 Jira 项目中创建工作项",
        "inputSchema": {
          "type": "object",
          "properties": {
            "project": { "type": "string" },
            "summary": { "type": "string" }
          },
          "required": ["project", "summary"]
        }
      },
      {
        "name": "filesystem-read_file",
        "description": "读取指定文件",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": { "type": "string" }
          },
          "required": ["path"]
        }
      }
    ]
  }
}

请注意这个响应结构:它主要是一个 tools 数组,而不是下面这种 Server 树形结构:

{
  "servers": [
    {
      "name": "github",
      "tools": []
    }
  ]
}

也就是说,Codex 通常不会先拿到一份独立的 MCP Server 清单。它看到的是一个已经聚合、过滤并完成命名空间处理的工具列表。

1.3 /mcp 中的“所有工具”具体指哪些工具

这里的“所有工具”不是配置文件中所有 Server 的无条件全集,而是同时满足以下条件的工具:

  1. 上游 Server 已启用。
  2. Server 已连接,或者工具定义已经被 MCPHub 缓存并支持按需启动。
  3. 当前 Bearer Key 或登录用户有权访问该 Server。
  4. 工具没有被 Server 级配置禁用。
  5. 当前使用的是统一 /mcp 路由,而不是某个更小的分组或单服务路由。

如果 MCPHub 中有 100 个可见 Server,每个 Server 平均提供 10 个工具,那么一次 tools/list 就可能返回接近 1000 个工具定义。每个定义还包含描述和 inputSchema,这正是大规模统一聚合端点容易产生工具发现负担和上下文压力的原因。

resourcesprompts 也不是混在 tools/list 中返回的。客户端需要分别调用 resources/listprompts/list

1.4 直接连接 /mcp 的完整时序图

MCPHub 路由时序图 1

二、工具返回以后,MCPHub 如何路由到具体 Server

不同 MCP Server 很可能提供同名工具。例如 GitHub 和 Jira 都可能提供 create_issue

MCPHub 会为工具增加 Server 前缀。默认名称分隔符是 -,也可以通过 systemConfig.nameSeparator 调整:

上游工具:github / create_issue
Codex 看到:github-create_issue

上游工具:jira / create_issue
Codex 看到:jira-create_issue

当用户要求“在代码仓库创建 Issue”时,Codex 根据工具描述选择 github-create_issue。后续过程如下:

MCPHub 路由时序图 2

所以普通 /mcp 路由不是自然语言猜测,而是两个阶段:

  1. Codex 根据 tools/list 返回的工具描述选择工具。
  2. MCPHub 根据完整工具名和内部注册表执行确定性转发。

只有使用 $smart 端点时,MCPHub 才会参与语义检索和工具发现。

三、MCPHub 在调用链中的角色

MCPHub 同时扮演两个角色:

  • 对 Codex 来说,MCPHub 是一个远程 MCP Server。
  • 对真正提供能力的服务来说,MCPHub 又是一个 MCP Client。
用户
  ↓
Codex(MCP Client)
  ↓
MCPHub(下游看它是 Server,上游看它是 Client)
  ├─ GitHub MCP Server
  ├─ Jira MCP Server
  ├─ Filesystem MCP Server
  ├─ Weather MCP Server
  └─ Database MCP Server

因此,MCPHub 本质上是一个 MCP 协议网关,负责连接管理、工具聚合、命名空间、权限过滤和请求转发。

四、单服务路由:/mcp/{server}

如果客户端只需要一个 MCP Server,可以直接使用单服务端点:

https://mcphub.example.com/mcp/github

这相当于通过 MCPHub 代理访问 GitHub MCP Server。客户端不会看到其他 Server 的工具。

MCPHub 路由时序图 3

适用场景包括:

  • 客户端用途非常单一。
  • 希望最小化工具暴露范围。
  • 正在调试某一个 MCP Server。
  • 不需要跨系统编排能力。

五、分组路由:/mcp/{group}

当 MCP Server 数量较多时,更常用的方式是按业务场景创建分组。

例如:

developer-tools
  ├─ github
  ├─ jira
  └─ filesystem

data-tools
  ├─ database
  ├─ object-storage
  └─ spreadsheet

public-services
  ├─ weather
  └─ map

开发类客户端只连接:

https://mcphub.example.com/mcp/developer-tools

MCPHub 只会暴露该分组内、当前凭据有权访问的工具。

MCPHub 路由时序图 4

分组路由的主要价值是:

  • 减少工具发现数量和上下文占用。
  • 降低模型选错工具的概率。
  • 按团队、环境或使用场景划分权限。
  • 避免将敏感工具暴露给不相关的客户端。

需要注意:如果某个 Server 名称与分组名称相同,MCPHub 会优先将其解析为分组。

六、智能路由:/mcp/$smart

当平台拥有上百个 MCP Server、上千个工具时,即使使用 Server 前缀解决了重名问题,也仍然存在工具定义过多的问题。

智能路由端点是:

https://mcphub.example.com/mcp/$smart

也可以将智能搜索限制在一个分组中:

https://mcphub.example.com/mcp/$smart/developer-tools

6.1 智能路由暴露的不是全部业务工具

普通 /mcp 会直接返回所有业务工具;/mcp/$smart 通常只向 Codex 暴露少量元工具:

元工具用途
search_tools使用自然语言语义搜索相关工具
describe_tool按需获取某个工具的完整参数结构,仅渐进式披露模式提供
call_tool调用搜索到的真实工具

因此,Codex 不需要一开始就加载上千个工具定义。

6.2 智能路由时序图

MCPHub 路由时序图 5

智能路由不只是转发器,它还参与了工具发现:

  1. MCPHub 获取所有上游工具的名称、描述和参数。
  2. MCPHub 为工具元数据生成向量。
  3. 向量存储在带有 pgvector 的 PostgreSQL 中。
  4. Codex 调用 search_tools 时,MCPHub 对查询进行语义检索。
  5. Codex 再通过 call_tool 调用选中的真实工具。

智能路由需要额外准备:

  • PostgreSQL。
  • pgvector 扩展。
  • OpenAI 或兼容的 Embedding 服务。
  • 描述清晰、命名规范的 MCP 工具元数据。

七、四种路由模式对比

路由方式地址示例Codex 能看到什么路由依据适用场景
统一聚合/mcpinitialize 后,通过 tools/list 获得所有有权限工具的扁平化定义;不返回独立 Server 清单完整工具名与内部映射Server 较少、工具数量可控
单服务/mcp/github单个 Server 的工具URL 中的 Server 名单一用途、调试、强隔离
分组路由/mcp/developer-tools指定分组中的工具URL 中的 Group + 完整工具名团队、领域、环境隔离
智能路由/mcp/$smartsearch_toolscall_tool 等元工具向量语义检索 + 工具映射上百个 Server、上千个工具
分组智能路由/mcp/$smart/developer-tools分组范围内的智能路由元工具分组过滤 + 向量语义检索大规模平台的推荐模式

八、100 个以上 MCP Server 的推荐设计

如果平台已经接入 100 个以上的 MCP Server,不建议让所有 Codex 客户端直接使用统一 /mcp 端点。

推荐使用两级收敛:

第一级:使用 Group 按领域、团队或环境缩小范围
第二级:在 Group 内使用 Smart Routing 动态发现工具

示例:

/mcp/$smart/developer-tools
/mcp/$smart/data-tools
/mcp/$smart/operations-tools

这样可以同时获得:

  • 更小的语义搜索空间。
  • 更清晰的权限边界。
  • 更高的工具匹配准确率。
  • 更少的上下文占用。
  • 更容易排查的调用链路。

九、Codex 配置示例

9.1 使用分组路由

[mcp_servers.mcphub_developer]
url = "https://mcphub.example.com/mcp/developer-tools"
bearer_token_env_var = "MCPHUB_TOKEN"
startup_timeout_sec = 20
tool_timeout_sec = 120
enabled = true

9.2 使用分组智能路由

[mcp_servers.mcphub_developer_smart]
url = "https://mcphub.example.com/mcp/$smart/developer-tools"
bearer_token_env_var = "MCPHUB_TOKEN"
startup_timeout_sec = 20
tool_timeout_sec = 120
enabled = true

也可以使用 Codex CLI 添加:

codex mcp add mcphub-developer \
  --url "https://mcphub.example.com/mcp/developer-tools" \
  --bearer-token-env-var MCPHUB_TOKEN

生产环境建议使用 Bearer Key,并将 Key 的访问范围限制到指定 Group 或 Server。不要为了方便而关闭公网实例的 MCP 认证。

十、容易混淆的几个概念

10.1 只配置域名还不够

下面通常是 MCPHub 管理页面,而不是 MCP 传输端点:

https://mcphub.example.com

Codex 应连接明确的 Streamable HTTP MCP 地址,例如:

https://mcphub.example.com/mcp
https://mcphub.example.com/mcp/developer-tools
https://mcphub.example.com/mcp/$smart/developer-tools

10.2 普通聚合不等于智能路由

普通 /mcp 的工作方式是“暴露工具,由 Codex 选择,再按工具名精确转发”。

智能 /mcp/$smart 的工作方式是“先通过语义搜索发现工具,再调用工具”。

10.3 认证和路由是两个不同层面

Bearer Key 决定客户端是否可以访问某些 Server 或 Group;路由机制决定某次工具调用最终被转发到哪里。

10.4 SSE 已不再是首选

MCPHub 已将 /sse 标记为废弃。新的 Codex 配置应优先使用 /mcp Streamable HTTP 端点。

十一、总结

MCPHub 能够通过一个入口管理大量 MCP Server,并不是因为域名本身具备某种自动识别能力。Codex 先通过 initialize 建立 MCP 会话,再通过 tools/list 获取扁平化工具定义,最后通过 tools/call 调用某个明确工具。整个过程依赖三层机制:

  1. 工具发现:汇总所有允许访问的 MCP 工具。
  2. 工具命名和注册表:使用带 Server 前缀的工具名建立确定性映射。
  3. 路由策略:根据统一、单服务、分组或智能端点控制工具暴露与调用范围。

对于较小规模的平台,/mcp/{group} 通常已经足够;对于上百个 Server、上千个工具的平台,更合适的方案是:

/mcp/$smart/{group}

也就是先用 Group 建立清晰的权限和领域边界,再使用 Smart Routing 完成组内工具的渐进式发现。

参考资料

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐