Releases: ni-c/mcp-hub
Release list
v0.5.0
Changed
-
Breaking: access tokens are bound to one resource by default.
RESOURCE_BOUND_TOKENSno longer has to be switched on; RFC 8707 binding is
what you get without asking, and the setting only exists to turn it off.
A token issued for/paperless/mcpreaches neither another server nor/hub,
and an authorization request that names no resource is refused with
invalid_target.Upgrading: tokens issued before this release carry no resource and stop
working, so every connector authorizes once more. To postpone that, set
RESOURCE_BOUND_TOKENS=false— it restores the old behaviour and logs a
warning on every start. The default also applies tocreateHub()for
programmatic use. -
Breaking:
/healthrequires a token for/hub. It reports the same
fleet-wide view as the aggregate — every server's name, state and tool count —
so a token bound to a single server no longer reads it. Unauthenticated
liveness monitoring belongs on/livez, unchanged. -
The
uvlayer is pinned to a version tag (0.12.3) instead oflatest. The
digest is unchanged, so the image content is identical; upgrades now arrive as
readable version bumps rather than opaque digest churn. -
The documentation site builds with VitePress 2. VitePress 1 pins Vite 5,
which is end-of-life and carries unfixable dev-server advisories; Vite 8 clears
them. Documentation tooling is not part of the published package or image.
Added
- Documentation site at mcp-hub.ni-c.de — guides for
configuration, deployment, clients and security, an architecture walkthrough,
a troubleshooting FAQ and a full endpoint/meta-tool reference. Built with
VitePress fromdocs/, which carries its own manifest so the runtime image
and the test matrix are unaffected, and published togh-pagesby
.github/workflows/docs.yml.
v0.4.0
Added
- Published on npm as
@ni-c/mcp-hub
(the unscoped name belongs to an unrelated project).npx @ni-c/mcp-hub
starts the hub,mcp-hub-adminships as a second binary; Docker remains the
recommended deployment. Releases are published via npm Trusted Publishing
(OIDC, with provenance) from the newrelease.yml, which also creates the
GitHub release from this changelog.
Changed
- The version reported by the
/hubserver and the child MCP clients is now
read frompackage.jsoninstead of being hardcoded in two source files. - zod updated to v4, the runtime image moved to
node:26-bookworm-slim, and
all GitHub Actions moved to their current majors (checkout v7, setup-node v7,
CodeQL v4, docker/* v4/v6/v7, trivy-action 0.36).
Fixed
- The
mcp-hubbinary was missing its shebang line, so the npm-installed
command would not execute on Unix.
v0.3.0
Security-hardening release. Every change comes from an internal security audit of the hub; the full deployment guidance now lives in SECURITY.md.
Security
- Resource-bound tokens (RFC 8707): a client that sends a
resourceindicator receives an access token bound to/hubor one/<name>/mcp— usable nowhere else on the hub. The login/consent pages display the requested resource, and the code/refresh exchanges verify it. SetRESOURCE_BOUND_TOKENS=trueto make the indicator mandatory for every new token (recommended; legacy global tokens then stop working and clients re-authorize once). - Access tokens now expire after 15 minutes (down from 24 hours); refresh tokens keep rotating with family-reuse detection.
- Offline revocation:
mcp-hub-admin clients list|revoke(run against the mounted/datawhile the hub is stopped) removes a client's approval and refresh tokens and immediately rejects its outstanding access tokens via arevokedBeforemarker. - Stricter JWT verification: the EdDSA algorithm is pinned and issuer, audience, subject and
client_idare validated explicitly. - Request limits: cheap per-IP rate limits guard
/register,/authorize,/token,/loginand/consentbefore any body parsing; authenticated MCP traffic passes a per-client gate (MCP_REQUESTS_PER_MINUTE, default 120, andMCP_MAX_CONCURRENT_REQUESTS, default 4); the MCP JSON body limit drops to 1 MB (MCP_BODY_LIMIT) and is parsed only after bearer auth; the Node server gets header/request timeouts (HTTP_HEADERS_TIMEOUT_MS,HTTP_REQUEST_TIMEOUT_MS). - Browser hardening on the interactive auth pages: CSP with
frame-ancestors 'none'andform-action 'self',X-Frame-Options: DENY,nosniff,Referrer-Policy: no-referrer;x-powered-byremoved.
Supply chain
- Base images, the uv helper and every GitHub Action are pinned to digests/commit SHAs; npm installs run with
--ignore-scripts. - CI gates publishing behind CodeQL, a Trivy scan of both platform images (HIGH/CRITICAL, zero findings) and
npm audit; published images ship an SBOM andmode=maxprovenance. Dependabot watches npm, Docker and Actions weekly. - The runtime image replaces the base image's bundled npm 10 with npm 12 and patches its two remaining vendored CVEs (brace-expansion, ip-address) in place.
- The compose example now runs with a read-only root filesystem, explicit tmpfs mounts and all capabilities dropped.
Breaking / behavioral changes
/health(per-server status) now requires a bearer token. The new unauthenticated/livezanswers liveness only; the imageHEALTHCHECKuses it, and external monitoring should too.livezjoins the reserved server names.- Access tokens last 15 minutes — clients must use the refresh flow (Claude Web and Claude Code do).
- Enabling
RESOURCE_BOUND_TOKENS=trueinvalidates previously issued global access/refresh tokens; each client re-authorizes once. curlwas removed from the image andtiniis now PID 1.- Install stdio server binaries at exact, reviewed versions in a custom image instead of downloading them at runtime via
npx -y/uvx— required with the read-only root filesystem and strongly recommended regardless (see SECURITY.md).
v0.2.0
Added
LOG_FILE: mirrors every log line into a file (ISO-8601 UTC prefix per line) while leaving the console untouched, sodocker logskeeps working. Gives log followers such as fail2ban a stable path — the Dockerjson-filepath contains the container ID and changes on every recreate, and thejournalddriver maps all stderr to priorityerr, which makes every ordinary line of an MCP server (stdout is reserved for the protocol) look like a system error. Only the hub's own lines are mirrored; stdio children inherit stderr and stay in the container log.
The README documents the matching fail2ban filter and why bans belong in the DOCKER-USER chain.
v0.1.0
First public release of mcp-hub — serve many stdio MCP servers from one container, published over HTTPS for Claude Web custom connectors, Claude Code and any other Streamable-HTTP MCP client.
Highlights
- Config is exactly Claude Code's
mcpServersformat — copy entries 1:1 into/config/mcp.json. - Path-based routing: one endpoint per server (
/<name>,/<name>/mcp). /hubaggregate: register a single connector and reach every server through 4 meta-tools (list_servers,list_tools,get_tool_schema,call_tool) instead of flooding the model context with N×tool schemas.- Built-in OAuth 2.1 authorization server: dynamic client registration, PKCE, single-password login, explicit per-client approval, rotating refresh tokens.
- Supervision: stdio children are spawned at boot, pinged and restarted with exponential backoff; a down server answers 503 instead of hanging. Remote
http/sseupstreams are supported natively with static headers. - Hot reload: edits to
mcp.jsonstart/stop/restart only the affected servers. - Stateless Streamable HTTP and no database — state is one JSON file plus a JWT key under
/data. - Runs non-root (uid 1000),
no-new-privileges, login rate limiting with fail2ban-friendly log lines.
Install
docker pull ghcr.io/ni-c/mcp-hub:0.1.0Multi-arch: linux/amd64 and linux/arm64. Tags: 0.1.0, 0.1, latest (tip of main) and sha-<commit>.
See the README for configuration, the environment variables (EXTERNAL_URL, PASSWORD_HASH, TRUSTED_PROXIES, …) and reverse-proxy requirements.