关于 mcp-hub
mcp-hub 将多个 stdio MCP 服务集中在一个容器中,并通过 HTTPS 提供给 MCP 客户端。它支持 OAuth 2.1、工具聚合和按需启动,也可通过 stdio 提供聚合入口。
主要功能
- 01通过 HTTPS 汇集多个 MCP 服务
- 02使用 OAuth 2.1 和 API 令牌认证
- 03以六个元工具聚合服务工具
- 04按服务筛选工具
- 05按需启动并监控子服务
- 06支持远程服务和 stdio 模式
适用场景
- 01让无法启动本地进程的客户端连接 MCP 服务
- 02通过一个连接访问多个服务
- 03限制客户端可调用的服务工具
- 04在服务空闲时减少运行中的子进程
MCP 相关
作为 MCP 网关,将 stdio 和远程 MCP 服务呈现给客户端;HTTP 端点支持 OAuth 2.1,也可通过 --stdio 提供本地聚合入口。它兼容两个 MCP 协议版本,并转发工具、提示词、资源变更通知及用户确认请求。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @ni-c/mcp-hubCONFIG_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 otherwiseEXTERNAL_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-* headersCLIENT_REGISTRATIONWhich mechanisms a client may use to obtain a client_id: cimd (Client ID Metadata Documents), dcr (RFC 7591 dynamic registration), or bothCIMD_ALLOWED_ORIGINSComma-separated https origins whose client ID metadata documents are accepted, e.g. https://chatgpt.com; unset admits every https originPORTListen port inside the container本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
6收录的工具列表 · 更新于 1 天前
此工具无需参数。
详细介绍
mcp-hub
一个兼容两个时代的模型上下文协议(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 令牌。各客户端的配置方法: 客户端兼容性。

想先试用一下? 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——都支持 MCP2026-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-mcp | audiobookshelf-mcp | Audiobookshelf — 媒体库、收听进度、合集和播放列表 |
| caldav-mcp | @ni-c/caldav-mcp | CalDAV — 任意支持该协议的服务上的事件、任务和日记条目 |
| calibreweb-mcp | calibreweb-mcp | Calibre-Web — 通过 OPDS 源以只读方式访问媒体库 |
| carddav-mcp | @ni-c/carddav-mcp | CardDAV — 任意支持该协议的服务上的联系人、群组和照片 |
| freshrss-mcp | @ni-c/freshrss-mcp | FreshRSS — 以纯文本而非流 ID 的形式访问订阅源、分类和文章 |
| google-search-console-mcp | @ni-c/google-search-console-mcp | Google Search Console — 资源、站点地图、搜索分析和 URL 检查 |
| healthchecks-mcp | healthchecks-mcp | Healthchecks — cron 和运行状态检查,以及失败原因 |
| hetzner-dns-mcp | hetzner-dns-mcp | Hetzner Cloud DNS — 区域、记录集以及 BIND 导入/导出 |
| imap-mcp | @ni-c/imap-mcp | IMAP 邮箱 — 阅读、搜索、整理和起草邮件;无法发送邮件 |
| linkwarden-mcp | linkwarden-mcp | Linkwarden — 书签、合集以及保存的文章文本 |
| mealie-mcp | @ni-c/mealie-mcp | Mealie — 食谱、膳食计划、购物清单和烹饪书 |
| ntfy-mcp | @ni-c/ntfy-mcp | ntfy — 发布和更新通知、管理用户和主题访问权限 |
| opengist-mcp | opengist-mcp | Opengist — 代码片段、修订版本、提交历史和原始文件 |
| osm-mcp | osm-mcp | OpenStreetMap — 地理编码、路线规划、等时圈和兴趣点搜索 |
| rustpad-mcp | rustpad-mcp | Rustpad — 通过真正的 OT 而非覆盖方式编辑的协作式文档 |
| smtp-mcp | @ni-c/smtp-mcp | SMTP — 发送邮件,但受收件人允许列表和人工确认机制保护 |
| wg-easy-mcp | wg-easy-mcp | wg-easy v15+ — 完整的 WireGuard 客户端生命周期管理 |
| wikijs-mcp | @ni-c/wikijs-mcp | Wiki.js — 搜索、阅读和编辑页面,以及管理资源、用户和群组 |
| woodpecker-ci-mcp | @ni-c/woodpecker-ci-mcp | Woodpecker 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 模式不会使用这两个变量。
| Variable | Required | Description |
|---|---|---|
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>/mcp | Bearer | 单个服务的 Streamable HTTP 端点 |
/hub | Bearer | 包含 6 个元工具的聚合端点 |
/livez | 无 | 最简进程存活检查(200) |
/health | Bearer | 各服务状态(全部正常时为 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
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。