mcpField
M
mcp-hub

mcp-hub

本地服务
mcp-hub.ni-c.de开发工具32 天前更新

关于 mcp-hub

mcp-hub 将多个 stdio MCP 服务集中在一个容器中,并通过 HTTPS 提供给 MCP 客户端。它支持 OAuth 2.1、工具聚合和按需启动,也可通过 stdio 提供聚合入口。

主要功能

  1. 01通过 HTTPS 汇集多个 MCP 服务
  2. 02使用 OAuth 2.1 和 API 令牌认证
  3. 03以六个元工具聚合服务工具
  4. 04按服务筛选工具
  5. 05按需启动并监控子服务
  6. 06支持远程服务和 stdio 模式

适用场景

  1. 01让无法启动本地进程的客户端连接 MCP 服务
  2. 02通过一个连接访问多个服务
  3. 03限制客户端可调用的服务工具
  4. 04在服务空闲时减少运行中的子进程

MCP 相关

作为 MCP 网关,将 stdio 和远程 MCP 服务呈现给客户端;HTTP 端点支持 OAuth 2.1,也可通过 --stdio 提供本地聚合入口。它兼容两个 MCP 协议版本,并转发工具、提示词、资源变更通知及用户确认请求。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"ni-c-mcp-hub": {
"command": "npx",
"args": [
"-y",
"@ni-c/mcp-hub"
],
"env": {
"CONFIG_PATH": "<CONFIG_PATH>",
"IDLE_TIMEOUT_MINUTES": "<IDLE_TIMEOUT_MINUTES>",
"TOOL_CACHE_PATH": "<TOOL_CACHE_PATH>",
"LOG_FILE": "<LOG_FILE>",
"EXTERNAL_URL": "<EXTERNAL_URL>",
"PASSWORD_HASH": "<PASSWORD_HASH>",
"TRUSTED_PROXIES": "<TRUSTED_PROXIES>",
"CLIENT_REGISTRATION": "<CLIENT_REGISTRATION>",
"CIMD_ALLOWED_ORIGINS": "<CIMD_ALLOWED_ORIGINS>",
"PORT": "<PORT>"
}
}
}
}
启动命令npx -y @ni-c/mcp-hub
需要配置的环境变量
CONFIG_PATHPath to the mcp.json listing the servers to expose, in Claude Code's mcpServers format (default mcp.json in the working directory)
IDLE_TIMEOUT_MINUTESMinutes of inactivity before a server is put to sleep; 0 starts every server at launch and keeps it running (default 60)
TOOL_CACHE_PATHTool snapshots of sleeping servers (default .mcp-hub/tool-cache.json beside the config)
LOG_FILEMirror the hub's log lines into this file; stdout carries the protocol, so logging goes to stderr otherwise
EXTERNAL_URLPublic base URL exactly as clients see it, e.g. https://mcp.example.net (no path)
PASSWORD_HASH密钥bcrypt hash of the OAuth login password (htpasswd -bnBC 10 "" 'pw' | tr -d ':\n')
TRUSTED_PROXIESComma-separated IPs/CIDRs of the reverse proxy allowed to set X-Forwarded-* headers
CLIENT_REGISTRATIONWhich mechanisms a client may use to obtain a client_id: cimd (Client ID Metadata Documents), dcr (RFC 7591 dynamic registration), or both
CIMD_ALLOWED_ORIGINSComma-separated https origins whose client ID metadata documents are accepted, e.g. https://chatgpt.com; unset admits every https origin
PORTListen port inside the container

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@ni-c/mcp-hub0.11.7
ocighcr.io/ni-c/mcp-hub:0.11.7

工具

6

收录的工具列表 · 更新于 1 天前

此工具无需参数。

详细介绍

mcp-hub

CI OpenSSF Scorecard Socket Badge Glama score
npm version container image
docs sponsor

一个兼容两个时代的模型上下文协议(MCP)网关:它在一个容器中提供多个 stdio MCP 服务,并通过 HTTPS 发布,同时在每个端点上支持两个 MCP 版本——2026-07-28 和 2025-11-25。客户端自行选择,且无法从响应中判断自己使用的是哪个版本。对于 2026 版本,这还包括引导请求——子服务提出的问题会传递给另一端的用户,而不会在网关处中断 (了解详情)——以及订阅:hub 向客户端提供 subscriptions/listen,并按照子服务所使用的版本订阅它们,因此,即使某个服务从未听说过此功能,也能连接到只支持这一版本的客户端 (了解详情)。

让无法启动本地进程的 MCP 客户端——ChatGPT connectors、Web 和 Code 中的 Claude、Mistral Le Chat、Cursor、LibreChat,以及其他任何 Streamable-HTTP 客户端——都能访问其背后的所有服务。内置 OAuth 2.1 登录由单个密码保护,并为无法使用 OAuth 的客户端(OpenAI Responses API、xAI API、Gemini API)提供长期有效的 API 令牌。各客户端的配置方法: 客户端兼容性。

MCP 客户端通过反向代理连接到 mcp-hub:一个 Node 进程,包含 OAuth 2.1 授权服务器;每个服务各有一个路径,另有聚合路径 /hub;由监督器保持 stdio 子服务和远程上游服务运行

演示:载入配置,启动 hub,通过一个端点访问各服务

想先试用一下? demo/ 是一个临时 hub,包含三个模拟服务——运行 docker compose up -d,然后让 MCP Inspector 或 MCPJam 连接它。无需配置,只需清理一个卷即可。

原因

为每个 stdio MCP 服务分别放入独立的身份验证代理容器,需要为_每个服务_配置完整镜像、OAuth 技术栈、主机名和 compose 堆栈。mcp-hub 用一个进程取代 N 个容器:

  • 配置格式与 Claude Code 的 mcpServers 完全一致——可直接逐项复制。
  • 基于路径的路由:https://host/paperless、https://host/homeassistant,……
  • /hub 聚合端点:只需注册_一个_连接器,即可通过 6 个元工具(list_servers、list_tools、get_tool_schema、call_tool、wake_server、sleep_server)访问所有服务,而不会让模型上下文被 N×工具的架构信息淹没。
  • 按服务筛选工具:任何服务上的 allowTools / denyTools 都可决定 hub 暴露该服务的哪些工具——可以指定确切名称或 list_* 前缀。经过筛选的工具不会出现在 tools/list 中;即使客户端仍调用它,也会在唤醒服务之前被拒绝,因此持有旧架构的客户端无法访问它。
  • 也支持无 HTTP 模式:mcp-hub --stdio 通过 stdin/stdout 提供相同的聚合服务,适用于只能启动本地进程的客户端(Claude Desktop、Codex,……)——使用相同的 mcp.json,无需 TLS、反向代理或登录。网络端点提供身份验证;通过 stdio 连接时,信任边界就是本地用户。
  • 按需管理生命周期:stdio 和 docker 服务在使用时启动,空闲 60 分钟后休眠;与此同时,hub 会根据持久化快照响应 initialize / tools/list——十几个服务只占用正在使用的服务所需的内存。keepAlive: true 可让某个服务豁免休眠,IDLE_TIMEOUT_MINUTES=0 可让 hub 豁免休眠。
  • 优先使用 CIMD 的 OAuth 2.1:客户端通过 客户端 ID 元数据文档标识自身——这是 MCP 规范目前首选的免注册方式——并可使用 private_key_jwt 对其自身文档中的密钥进行验证(仅适用于元数据文档客户端)。RFC 7591 动态注册仍与之并列提供,以兼容较旧的客户端;对于两者都不支持的客户端,可使用 mcp-hub-admin clients add 手动签发凭据;CLIENT_REGISTRATION 则可关闭其中任一机制。
  • 对外连接也支持 OAuth:对于支持 OAuth 的远程服务,可使用 oauth 块代替静态标头。hub 会为自己注册——使用上游签发的凭据、通过 RFC 7591 注册,或使用自身的客户端元数据文档——然后获取并刷新令牌。使用 client_credentials 的上游无需任何操作;需要用户登录时,mcp-hub-admin upstream login 会打印一个 URL。需要重新授权的上游会显示为一个状态为 unauthorized 的服务,而不是让客户端收到令人困惑的 401 错误。
  • 监督管理:子进程会接受 ping 检查,退出时会以指数退避方式重启。服务不可用时会返回 503,而非毫无响应;对于无人使用且不断崩溃重启的服务,hub 会将其暂停,而不是无限重启。
  • 热重载:编辑 mcp.json 后,只会启动、停止或重启受影响的服务。
  • 无状态 Streamable HTTP:没有会话状态,因此 claude.ai 断开连接后不发送 DELETE 的行为不会导致进程或内存泄漏。
  • 双时代兼容:每个端点——/hub、/<name>/mcp 和 --stdio——都支持 MCP 2026-07-28 和 2025-11-25;客户端会自行选择,并且从响应中无法看出它使用的是哪个版本。2026 修订版还包括引导询问:服务向用户提问时,会返回问题而不是推送问题,因此问题能够送达另一端的用户,而不会在网关处丢失。hub 会标明提问的服务,移除可能造成误导的内容,丢弃嵌入式采样和根请求,并将恢复状态绑定到所属的调用。passthrough: "off" 可撤销某个服务提问的权限; 详情。
  • 两个时代均支持变更通知:客户端打开 subscriptions/listen 流,即可获知子服务的工具、提示词或资源何时发生变化。hub 会以各子服务支持的方式订阅:对 2026 服务使用 subscriptions/listen,对 2025 服务使用 resources/subscribe——因此,时代差异由网关处理,而不会成为任一端的问题。状态保存在打开的响应中,而非会话表中,因此不会影响无状态设计。休眠中的服务不会监视任何内容,唤醒时会收到重新读取的通知;subscriptions: "off" 可撤销某个服务推送通知的权限;详情。
  • 轻量化设计:单个 Node 进程,无数据库(状态由 /data 下的一个 JSON 文件和一个签名密钥构成),仅有十个运行时依赖,并提供多架构镜像——项目明确的目标之一是让它能在 Raspberry Pi 之类的单板计算机上流畅运行。

在其背后运行的服务

该枢纽与服务无关——只要 stdio MCP 服务的入口符合 Claude Code 的 mcpServers 格式(大多数都符合),就可以使用。这十九个服务与枢纽一同构建和维护,因此它们的文档中包含所需的枢纽配置项,其工具筛选规则也与枢纽自身的 allowTools / denyTools 一致:

服务npm可访问的内容
audiobookshelf-mcpaudiobookshelf-mcpAudiobookshelf — 媒体库、收听进度、合集和播放列表
caldav-mcp@ni-c/caldav-mcpCalDAV — 任意支持该协议的服务上的事件、任务和日记条目
calibreweb-mcpcalibreweb-mcpCalibre-Web — 通过 OPDS 源以只读方式访问媒体库
carddav-mcp@ni-c/carddav-mcpCardDAV — 任意支持该协议的服务上的联系人、群组和照片
freshrss-mcp@ni-c/freshrss-mcpFreshRSS — 以纯文本而非流 ID 的形式访问订阅源、分类和文章
google-search-console-mcp@ni-c/google-search-console-mcpGoogle Search Console — 资源、站点地图、搜索分析和 URL 检查
healthchecks-mcphealthchecks-mcpHealthchecks — cron 和运行状态检查,以及失败原因
hetzner-dns-mcphetzner-dns-mcpHetzner Cloud DNS — 区域、记录集以及 BIND 导入/导出
imap-mcp@ni-c/imap-mcpIMAP 邮箱 — 阅读、搜索、整理和起草邮件;无法发送邮件
linkwarden-mcplinkwarden-mcpLinkwarden — 书签、合集以及保存的文章文本
mealie-mcp@ni-c/mealie-mcpMealie — 食谱、膳食计划、购物清单和烹饪书
ntfy-mcp@ni-c/ntfy-mcpntfy — 发布和更新通知、管理用户和主题访问权限
opengist-mcpopengist-mcpOpengist — 代码片段、修订版本、提交历史和原始文件
osm-mcposm-mcpOpenStreetMap — 地理编码、路线规划、等时圈和兴趣点搜索
rustpad-mcprustpad-mcpRustpad — 通过真正的 OT 而非覆盖方式编辑的协作式文档
smtp-mcp@ni-c/smtp-mcpSMTP — 发送邮件,但受收件人允许列表和人工确认机制保护
wg-easy-mcpwg-easy-mcpwg-easy v15+ — 完整的 WireGuard 客户端生命周期管理
wikijs-mcp@ni-c/wikijs-mcpWiki.js — 搜索、阅读和编辑页面,以及管理资源、用户和群组
woodpecker-ci-mcp@ni-c/woodpecker-ci-mcpWoodpecker CI — 仓库、流水线、日志、密钥和 cron
每个服务都能通过 stdio 独立正常运行。如果你希望无法启动本地进程的客户端也能访问它们,或者希望只注册一个连接器而不是十九个,就可以将它们接入 hub。

配置

/config/mcp.json — 与 Claude Code 完全相同(${VAR} 会从容器环境变量中展开;Claude Code 会忽略未知字段,因此该文件可互换使用)。在镜像中安装经过审核且版本固定的 stdio 服务二进制文件;不要在运行时下载可变的软件包:

{
  "mcpServers": {
    "paperless": {
      "command": "paperless-mcp",
      "args": [],
      "env": { "PAPERLESS_API_TOKEN": "${PAPERLESS_API_TOKEN}" }
    },
    "homeassistant": {
      "type": "http",
      "url": "http://homeassistant:8123/api/mcp",
      "headers": { "Authorization": "Bearer ${HA_TOKEN}" }
    },
    "private-thing": { "command": "some-mcp", "args": [], "hub": false },
    "paperless-readonly": {
      "command": "paperless-mcp",
      "allowTools": ["search_*", "get_document"],
      "denyTools": ["delete_document"]
    },
    "untrusted": {
      "type": "docker",
      "image": "ghcr.io/example/untrusted-mcp@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
      "network": "none",
      "memory": "256m"
    }
  }
}

stdio 服务(command/args/env)会作为受监督的子进程启动。远程服务(type: "http" 或 "sse",带有 url 和可选的 headers)会作为 MCP 客户端连接,并在每次请求中注入配置的请求头——适用相同的监督机制(ping、退避重连、热重载)。支持 OAuth 的上游服务使用 oauth 块而非请求头:hub 会自行注册(静态注册、通过 RFC 7591 注册,或通过客户端元数据文档注册),获取并刷新令牌;如果授权需要用户参与,则通过管理 CLI 启动一次浏览器访问。 "hub": false 会将服务从 /hub 聚合端点中隐藏;服务自身的路径仍可使用。allowTools / denyTools 可进行更精细的筛选,并适用于所有类型的服务:被筛选掉的工具不会出现在 tools/list 和 /hub 中,即使被调用也会遭到拒绝——且服务不会因此启动。 保留名称:mcp、hub、authorize、token、register、login、consent、health、livez、revoke、jwks、interaction、session、userinfo、upstream、.well-known。

所有 stdio 子进程都与 hub 共用 Unix 用户,并且可以读取挂载到 hub 的文件。只安装完全可信的 stdio 服务。信任级别不同的服务应放在自己的容器中——而且它们无需通过 HTTP 通信:

  • type: "docker" — hub 会创建容器,并通过 Docker API 跨容器边界使用 stdio 通信。无需 HTTP 监听器、Bearer 令牌或镜像中的桥接进程。hub 自身不会获得 Docker 套接字:由独立的 mcp-hub-docker-proxy 容器持有该套接字,并且只允许执行 mcp.json 描述的容器操作——不允许任何特权操作、主机挂载或其他镜像。凭据可以保存在代理端(secretsFrom),这样 hub 进程就无需持有凭据;轮换凭据只需编辑文件:代理会监视该文件,并使用新值重新创建沙箱。
  • type: "unix" / "tcp" — 由你启动容器,hub 连接到套接字。hub 无需任何权限;Unix 套接字可以连接到以 network_mode: none 运行的沙箱。

这两种方式都使用规范要求自定义传输复用的、以换行符分隔的 JSON-RPC。请参阅沙箱和 SECURITY.md。

使用自定义镜像时,请将每个软件包都固定到确切版本:

FROM ghcr.io/ni-c/mcp-hub:0.10.0   # pin @sha256:<digest> in production
USER root
RUN npm install -g [email protected]
USER node
环境变量

如果没有非空的 PASSWORD 或 bcrypt 格式的 PASSWORD_HASH,HTTP hub 仍会启动,但登录功能会被禁用:启动日志会说明这一点,登录页面会返回带有原因的 503,且无法批准任何客户端——因此也不会签发令牌。配置了哈希时会优先使用哈希;如果哈希不是 bcrypt 格式,登录功能也会以同样方式禁用,而不会回退到 PASSWORD。 本地 --stdio 模式不会使用这两个变量。

VariableRequiredDescription
EXTERNAL_URL是公共基础 URL,例如 https://mcp.example.net(不含路径)
PASSWORD_HASH二选一登录密码的 bcrypt 哈希(htpasswd -bnBC 10 "" 'pw' | tr -d ':\n')
PASSWORD二选一PASSWORD_HASH 的明文替代项
TRUSTED_PROXIES否允许设置 X-Forwarded-* 的逗号分隔 IP/CIDR(见下文)
RESOURCE_BOUND_TOKENS否绑定到 /hub 或某个 /<name>/mcp 的 RFC 8707 令牌,默认值为 true;仅在需要继续使用 0.5 之前未绑定的令牌时设为 false
DEFAULT_RESOURCE否当客户端未发送 resource 参数时,令牌绑定到的服务器名称(或 hub);未设置时 → 此类请求将被拒绝
MCP_BODY_LIMIT否经过身份验证的 MCP JSON 请求正文大小限制,默认值为 1mb
MCP_REQUESTS_PER_MINUTE否每个 OAuth 客户端的请求数限制,默认值为 120
MCP_MAX_CONCURRENT_REQUESTS否每个 OAuth 客户端的进行中请求数限制,默认值为 4
MCP_MAX_CONCURRENT_STREAMS否每个 OAuth 客户端打开的 SSE 侦听流数量限制——每个已连接会话一个,默认值为 32
HTTP_HEADERS_TIMEOUT_MS否Node HTTP 标头超时,默认值为 10000
HTTP_REQUEST_TIMEOUT_MS否完整请求超时,默认值为 310000(略长于工具调用超时)
PORT否侦听端口(镜像中默认值为 80,镜像外为 3000)
CONFIG_PATH否默认值为 /config/mcp.json
DATA_PATH否默认值为 /data
LOG_FILE否同时将所有日志输出写入此文件,例如 /data/mcp-hub.log(见下文)
CLIENT_REGISTRATION否客户端可用于 client_id 的注册机制:cimd、dcr 或两者(默认值)
CIMD_ALLOWED_ORIGINS否接受其元数据文档的纯 https 来源;未设置时 → 允许任意来源
CIMD_ALLOW_PRIVATE_ADDRESSES否仅用于本地开发;放宽 SSRF 防护,每次启动时都会发出警告
DCR_MAX_CLIENTS否存储的动态注册数量上限,默认值为 500
DCR_PENDING_TTL_HOURS否从未获批的注册信息保留时长,默认值为 24 小时
DCR_INACTIVE_DAYS否未使用的已批准注册信息保留时长,默认值为 90 天
IDLE_TIMEOUT_MINUTES否按需服务器休眠前的空闲分钟数,默认值为 60;设为 0 可禁用
TOOL_CACHE_PATH否休眠服务器的快照,默认值为 <DATA_PATH>/tool-cache.json
MCP_CALL_TIMEOUT_MS否单次转发工具调用的截止时间,默认值为 300000
MCP_RESET_TIMEOUT_ON_PROGRESS否允许进度通知延长此截止时间,默认值为 false
DOCKER_HOST对于 docker 服务器策略代理的套接字;直接使用守护进程套接字会默认拒绝
完整表格(包括 stdio 模式下适用的内容)请参阅
环境变量参考。

/data 存放 Ed25519 JWT 密钥、已注册的 OAuth 客户端、授权记录和刷新令牌。请将其挂载为卷——重新创建该目录会使所有连接器授权失效。

每个访问令牌都绑定到一个资源。OAuth 客户端会包含端点的 RFC 9728 文档中公布的资源——无需在客户端进行配置——生成的令牌也仅在该资源上有效:用于 /paperless/mcp 的令牌无法调用 /hub、/health 或其他服务。较短的 /<name> 路由会规范化为 /<name>/mcp。

RESOURCE_BOUND_TOKENS=false 会关闭此功能,仅用于从 0.4 及更早版本迁移的部署;这些版本颁发的令牌不绑定资源,可以访问所有路径。设置该变量时,Hub 会记录警告。移除该变量会使这些未绑定资源的令牌失效,因此每个连接器都需要重新授权一次。

TRUSTED_PROXIES 决定 req.ip 的值,因此也决定登录速率限制器的计数依据。只列出你自己的反向代理,并确保它会_覆盖_ X-Forwarded-For,而不是追加内容——否则客户端可以提供自己的地址并不断轮换,以绕过单 IP 限制。如果未设置此变量,所有请求看起来都来自代理,按 IP 限制就会退化为单一的全局计数器(Hub 会在启动时记录警告)。无论如何,每 15 分钟最多允许失败 100 次。

运行

选项 A — 使用 GHCR 上的预构建映像(推荐)

每次推送到 main 以及每次发布 vX.Y.Z 版本标签时,都会为 linux/amd64 和 linux/arm64 发布映像。请在软件包页面浏览各个版本。

docker pull ghcr.io/ni-c/mcp-hub:0.10.0

标签包括:latest(main 的最新提交)、X.Y.Z 和 X.Y(发布版本),以及用于指定构建的 sha-<commit>。

如需可控更新,请使用版本标签,而不是 latest。如需不可变部署,请记录 docker image inspect 解析出的摘要,并在 Compose 中使用 ghcr.io/ni-c/mcp-hub:<version>@sha256:<digest>。

使用 compose 时,复制示例并将其指向映像,而不是构建:

services:
  mcp-hub:
    image: ghcr.io/ni-c/mcp-hub:0.10.0 # replaces `build: .`; pin a digest in production
    # ...rest of docker-compose.example.yml unchanged
cp docker-compose.example.yml docker-compose.yml   # adjust, swap build → image
mkdir -p config && cp mcp.json.example config/mcp.json  # adjust
mkdir -p data && sudo chown -R 1000:1000 data       # container runs as uid 1000
docker compose up -d

或者不使用 compose:

mkdir -p data && sudo chown -R 1000:1000 data       # container runs as uid 1000
docker run -d --name mcp-hub \
  -p 127.0.0.1:7690:80 \
  -e EXTERNAL_URL="https://mcp.example.net" \
  -e PASSWORD_HASH="$(htpasswd -bnBC 10 '' 'yourpassword' | tr -d ':\n')" \
  -e TRUSTED_PROXIES="192.168.1.0/24" \
  -v "$PWD/config:/config:ro" \
  -v "$PWD/data:/data" \
  ghcr.io/ni-c/mcp-hub:0.10.0

使用 docker compose pull && docker compose up -d 更新到较新映像(或者先运行 docker pull …,再重新创建容器)。

选项 B — 从源代码构建
cp docker-compose.example.yml docker-compose.yml   # adjust
mkdir -p config && cp mcp.json.example config/mcp.json  # adjust
docker compose up -d --build
选项 C — npm(不使用容器)
CONFIG_PATH=./mcp.json DATA_PATH=./data PASSWORD_HASH='...' \
  npx @ni-c/mcp-hub

安装为 @ni-c/mcp-hub (不带作用域的 npm 名称属于另一个无关项目),并提供 mcp-hub 和 mcp-hub-admin 二进制文件。容器仍是推荐的部署方式——它提供了 SECURITY.md 所假定的隔离、只读根文件系统和资源限制。

反向代理要求:终止 TLS,允许 WebSockets/SSE(关闭代理缓冲,将请求超时设为 310 秒以上,将请求正文大小限制设为不高于 MCP_BODY_LIMIT,并传递 X-Forwarded-Proto/Host。

连接客户端:将 https://<host>/hub(或针对单个服务的 https://<host>/<name>/mcp)添加为自定义连接器——可用于 ChatGPT(开发者模式)、Claude Web、Mistral Le Chat、Cursor、LibreChat 或任何其他支持 OAuth 的 MCP 客户端——然后使用密码登录一次。Claude Code: claude mcp add -t http name https://<host>/<name>/mcp。仅使用 API 的客户端 (OpenAI Responses API、xAI、Gemini API)则使用管理员签发的令牌——参见客户端兼容性。

每个客户端在请求访问的每个服务上只需确认一次。输入密码会批准发出请求的客户端访问页面上显示的服务;只要登录会话仍然有效,尚未见过的客户端——或请求访问另一个服务的客户端——就会看到明确的 批准 / 拒绝 页面,而不是代码。获批客户端此后会静默重新连接。

列出客户端或撤销某个客户端的访问权限。CLI 与运行中的 hub 共用 /data,双方在操作状态文件前都会重新读取它,因此可以针对正在运行的容器执行此操作——撤销会在下一个请求时生效:

docker exec mcp-hub node /app/dist/admin.js clients list
docker exec mcp-hub node /app/dist/admin.js clients revoke CLIENT_ID
docker exec mcp-hub node /app/dist/admin.js clients delete CLIENT_ID
docker exec mcp-hub node /app/dist/admin.js clients prune --dry-run

撤销会删除批准记录和所有刷新令牌,并立即拒绝已签发的访问令牌。下次连接时需要明确批准。 delete 会进一步删除注册信息,而 prune 会按需应用注册生命周期规则——从未获批的注册会在一天后过期,未使用的注册会在 90 天后过期,动态注册的客户端也可以通过 RFC 7592 删除自己的注册信息。

对于完全不支持 OAuth 的客户端——OpenAI Responses API、xAI API、Gemini 的 mcp_server 工具、纯请求头客户端——同一 CLI 可以签发长期有效、绑定资源的 API 令牌:

docker exec mcp-hub node /app/dist/admin.js tokens create --resource hub --days 90 --label "openai"
docker exec mcp-hub node /app/dist/admin.js tokens list
docker exec mcp-hub node /app/dist/admin.js tokens revoke TOKEN_ID

令牌只显示一次,且不会存储;tokens revoke 会立即生效。各客户端的配置说明: 客户端兼容性。

端点

路径身份验证用途
/<name>, /<name>/mcpBearer单个服务的 Streamable HTTP 端点
/hubBearer包含 6 个元工具的聚合端点
/livez无最简进程存活检查(200)
/healthBearer各服务状态(全部正常时为 200/降级时为 503)
/authorize, /token, /register, /revoke, /interaction/<uid>/…—OAuth 2.1 · CIMD + DCR;登录和授权页面位于 /interaction/ 下
/register/<client_id>注册令牌RFC 7592:客户端读取、更改或移除自身注册信息
/upstream/callback已签名的状态 + hub 会话上游在 upstream login 后返回的位置
/.well-known/mcp-hub-client/<id>.json无hub 自身的客户端元数据文档,每个 cimd 上游各有一个
/.well-known/oauth-authorization-server[/…]无RFC 8414 元数据
/.well-known/oauth-protected-resource[/…]无RFC 9728 元数据(限定路径)

注意事项与限制

  • 更改通知(listChanged、资源更新)通过 2026-07-28 上的 subscriptions/listen 传递,其状态由开放的响应承载,而非会话表。不会向 2025-11-25 客户端提供这些通知,因为该版本需要无状态传输无法保留的通道,因此 不会公布此能力,也不会在公布后丢弃。按需启动的服务在休眠期间不会监视任何内容; 下次唤醒时会重新建立订阅,并通知客户端重新读取。
  • 2026-07-28 上的 Elicitation 端到端传递:它是结果,而非推送。Sampling 和日志消息不会转发。
  • 访问令牌是不透明的,有效期为 15 分钟。撤销客户端会在其下次请求时生效,而非等到令牌过期——令牌是对已存储记录的引用,因此撤销即删除。刷新令牌会轮换;重放已轮换失效的令牌会被视为泄漏,并撤销整个授权,包括访问令牌。
  • 上游身份验证与 hub 自身的 OAuth 完全解耦:上游令牌过期只会将对应服务标记为 unauthorized(其路径返回 503,可在 /health 中查看)——客户端不会看到上游的 401。
  • 一次登录可以授权多个连接器,但每个令牌仅对其所请求的服务或 /hub 有效。注册仍保持开放,符合 MCP 规范的意图;客户端只有在确认后才会收到代码,且代码只会发送到已确认的重定向目标。
  • 登录失败会受到速率限制(每个 IP 每 15 分钟 10 次),并记录为 mcp-hub: authentication failure from <ip>,供 fail2ban 使用。
  • 身份验证页面禁止被嵌入框架,并带有限制性 CSP。只有在 Bearer 验证通过后才会解析 MCP 请求正文,并且正文大小、每客户端请求速率和每客户端并发数均有限制。
将 fail2ban 日志写入文件

LOG_FILE=/data/mcp-hub.log 会将每条日志行镜像写入该文件,每条记录占一行,并带有 ISO-8601 UTC 前缀,同时不影响控制台输出——因此 docker logs 仍可正常使用。然后,监狱会直接读取该文件:

# /etc/fail2ban/filter.d/mcp-hub-auth.conf
[Definition]
failregex = mcp-hub: authentication failure from <HOST>\s*$
            mcp-hub: login rate limit exceeded from <HOST>\s*$
            mcp-hub: consent with an invalid CSRF token from <HOST>\s*$
ignoreregex =

只有 Hub 自身的日志行会被镜像——stdio 子进程直接继承 stderr,因此它们的输出仍保留在容器日志中,文件也会保持较小。使用 logrotate 轮转日志(由于 Hub 会保持文件打开,请使用 copytruncate)。

为什么不直接读取容器自身的日志:Docker 的 json-file 路径包含容器 ID,每次重新创建容器时都会变化;而 journald 驱动会将所有 stderr 映射为 err 优先级——MCP 服务必须保持 stdout 空闲以供协议使用,因此会将日志写入 stderr,这样每条普通日志都会显示为系统错误,并淹没主机监控信息。

当 Hub 通过基于容器的反向代理发布时,封禁规则应放在 DOCKER-USER 链中(banaction = iptables-allports):此类流量经 DNAT 和 FORWARD 到达,不会经过 INPUT。

开发

npm install
npm test           # vitest: config, OAuth flow, proxy E2E, hub, hot reload
npm run dev        # tsx, needs EXTERNAL_URL/PASSWORD/CONFIG_PATH/DATA_PATH

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。