Model or dataset
samanhappy/mcphub avatar
samanhappy/mcphub

samanhappy/mcphub: a self-hosted MCP gateway you run yourself

Self-hosted MCP gateway and control plane for connecting, controlling, and operating MCP servers.

2,485 stars327 forksTypeScriptApache-2.0

At a glance

What is it?
MCPHub puts one HTTP endpoint in front of every MCP server you use, with auth, groups and logs. It is a control plane, not an MCP server, and the Docker image is the fastest way in.
Who is it for?
Adopt MCPHub if you run more than a couple of MCP servers and want one authenticated endpoint, group-level visibility and a log of tool calls instead of editing every client's config. Skip it if you have one local server and no need for shared credentials, or if you cannot host a process and its data volume yourself.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem MCPHub solves, and who feels it

An MCP client such as Claude Code, Cursor or Cherry Studio talks to MCP servers one connection at a time. Each client keeps its own list of servers, each server has its own transport (stdio, SSE or Streamable HTTP), and any credential a server needs lives wherever that client happens to store it. Five servers across three clients means fifteen places to keep in sync, and no single place to see what was actually called.

MCPHub is the layer between them. The README describes it as "a unified control point between AI clients and MCP servers": you connect servers once, and clients connect to MCPHub instead. That makes it useful to a specific kind of user. Someone running a handful of MCP servers for a team, or for several tools on one machine, gets stable endpoints, per-user credentials and a log. Someone with a single local stdio server and one client gets an extra process to operate for very little in return.

The repository is TypeScript, Apache-2.0 licensed, and the last push was on 2026-09-10, with v1.0.36 released the same day.

How the gateway routes requests and where state lives

The core mechanism is endpoint fan-out. MCPHub connects to each configured server itself, then re-exposes those connections under paths on its own HTTP port. The README lists the shape:

bash
http://localhost:3000/mcp           # All servers
http://localhost:3000/mcp/{group}   # Specific group
http://localhost:3000/mcp/{server}  # Specific server
http://localhost:3000/mcp/$smart    # Smart routing

So a client that only understands one MCP endpoint can still reach everything, a subset, or a single server. Groups are the organising unit: the README says servers are organised into groups with visibility controls and per-server control over Tool, Prompt and Resource exposure. That is a routing decision made once on the hub rather than per client.

Configuration is read from `mcp_settings.json`, and the README calls it hot-swappable: servers can be added, removed or updated without downtime. For anything beyond a single instance, setting `DB_URL` switches configuration into PostgreSQL, which the README calls Database Mode and recommends for production. The `.env.example` confirms the trigger is automatic: setting `DB_URL` enables database mode, and `USE_DB` overrides that detection. Without a database, state such as credentials lives on disk, which is why the Docker examples mount `./data`.

Two features are worth separating from the routing story. Smart routing (`$smart`) uses vector semantic search to pick tools, and the README links to a separate docs page for it. Per-user credentials bind a personal key to one shared server, with encrypted storage and, per the README, isolated stdio runtimes. The second one is the more consequential design choice, because it means stdio servers are spawned per user rather than shared.

Docker deployment and a first CLI call

The README's recommended path is the published image with your own config mounted. Write an `mcp_settings.json` first, with the two servers from the README's own example:

json
{
  "mcpServers": {
    "time": {
      "command": "npx",
      "args": ["-y", "time-mcp"]
    },
    "fetch": {
      "command": "uvx",
      "args": ["mcp-server-fetch"]
    }
  }
}

Then start the container, mounting both the config file and a data directory. The README notes the data mount is what keeps credentials and state across restarts:

bash
docker run -p 3000:3000 -v ./mcp_settings.json:/app/mcp_settings.json -v ./data:/app/data samanhappy/mcphub

Open `http://localhost:3000` and log in as `admin`. On a first launch with no `ADMIN_PASSWORD` set, the README states a random password is generated and printed to the server logs. You can set it up front instead:

bash
docker run -p 3000:3000 -e ADMIN_PASSWORD=your-secure-password samanhappy/mcphub

The same binary doubles as a CLI against the running hub, which the README presents as needing no extra install. A login and a discovery call look like this:

bash
mcphub login --url http://localhost:3000 --username admin
mcphub servers list
mcphub tools list
mcphub tools get fetch_url

The `tools get` call is the one to run before wiring anything up: the README says it shows required parameters and a sample command, so you learn the argument names without reading the server's source. Two image variants exist. `latest` bundles Node.js/pnpm, Python, uv/uvx, Git and build tools; `latest-full` adds a Rust toolchain, Docker Engine and Playwright browsers, with Chrome and Firefox only on amd64. Pick `latest-full` only if you need Rust-based servers or container-in-container work, because it is a larger download.

Authentication defaults are the part to get right

MCP endpoints require authentication by default, and that default is the reason MCPHub is not simply an open proxy on your machine. The README is explicit that the switch to open it sits in the Keys section: disabling Enable Bearer Authentication allows unauthenticated MCP access, and Skip Authentication only affects dashboard login. Those two settings read similarly and do different things, which is a real trap for anyone skimming the UI.

Rate limiting is configured through environment variables in `.env.example`, and the defaults are not obvious. `AUTH_RATE_LIMIT_MAX` defaults to 20 over a 900000 ms window, but the comment states the login limiter counts failed attempts only, so API clients that re-authenticate on token expiry are not locked out. The registration limiter counts every request instead. Setting any `*_MAX` to 0 or "off" removes that limit, and the file warns that this is not passed through to express-rate-limit, where a limit of 0 rejects everything. Disabling a limiter logs a warning at startup.

OAuth is supported in both client and server modes, and the repository ships example configs for it (`examples/oauth-server-config.json`, `examples/oauth-dynamic-registration-config.json`). Social login through GitHub and Google uses Better Auth and, per the README, requires Database Mode. `.env.example` notes that Better Auth bootstrap config is read only at startup, so changing it means a restart, not a config reload.

Where MCPHub is the wrong tool

The clearest limitation is operational weight. MCPHub is a long-running server with a database option, a data directory, a dashboard and its own auth. If you run one stdio server for one client on one laptop, adding it means another process to keep alive, another port, and another credential to rotate, in exchange for routing you did not need.

The Docker image also constrains what servers can run. The default `latest` image installs Node.js, Python, uv/uvx, Git and build tools, so an MCP server distributed as a Rust binary or one that needs a browser will not work there; you need `latest-full`, and on non-amd64 architectures that variant explicitly skips Chrome and Firefox installation, as the Dockerfile's architecture check shows. Container-in-container workflows need additional configuration that the README defers to the Docker setup docs rather than showing inline.

Per-user credentials carry their own cost. The README says stdio runtimes are isolated per user, which is the right call for isolation but means more processes when several users share one server. The README does not document rollback for a bad hot-swap, and it does not state what happens to in-flight tool calls when a server entry is removed from `mcp_settings.json`. Treat hot-swapping as convenient, not as a transactional operation, until you have checked the behaviour yourself.

Finally, the README does not describe multi-instance coordination. Database Mode moves configuration into PostgreSQL, but nothing in the repository's documentation says two MCPHub instances can share one set of stdio servers safely.

How it compares with running mcp-proxy or one client config per server

The obvious alternative is not another gateway but the absence of one. Most MCP clients can be configured with a list of servers directly, and a small proxy such as mcp-proxy can bridge a stdio server to HTTP. That approach has no shared state, no dashboard and no auth layer, which is exactly the point: nothing to operate.

The difference in approach is where control lives. With a per-client config, each client decides which servers exist and holds its own credentials; changing a server means touching every client. With MCPHub, the server list lives on the hub and clients point at paths. That inverts the failure mode: a client config mistake affects one client, while a hub misconfiguration affects everything connected to it. The compensating features are the ones a per-client setup cannot offer at all: group visibility, per-user keys bound to a shared server, tool-call logs with latency, health checks and a single place to revoke access.

There is also a naming collision worth flagging. Several search phrases for "mcphub" refer to an unrelated Neovim plugin, and the repository README is about the self-hosted gateway described here. If you arrived looking for an editor integration, this is a different project with a similar name.

Maintenance, upgrades and the Apache-2.0 terms

The release cadence is visible in the tags: v1.0.34 on 2026-09-02, v1.0.35 on 2026-09-07, v1.0.36 on 2026-09-10. Upgrades follow the image tag, and the practical cost is that the container is where your runtime dependencies live. Moving from `latest` to `latest-full` is a larger image with a Rust toolchain, Docker Engine and Playwright browsers, which is a meaningful change in image size and attack surface for a host that only runs Python and Node servers.

Because configuration can live in `mcp_settings.json` on a mounted volume or in PostgreSQL, the upgrade path differs. File-based installs upgrade by pulling a new image and restarting with the same mounts. Database-backed installs add a schema to keep current, and the README points to a database configuration page rather than describing migrations inline. The README does not document a downgrade path, so keeping the previous image tag available is the only obvious way back.

The licence is Apache-2.0, stated in both the README and `package.json`. That permits commercial use, modification and redistribution, and it includes an explicit patent grant and a requirement to state changes. It also means there is no warranty, and the project offers no commercial support agreement in the README. If you fork and ship it internally, the notice and attribution obligations still apply. None of this is legal advice; check the terms against your own distribution model.

Editorial conclusion

Adopt MCPHub if you run more than a couple of MCP servers and want one authenticated endpoint, group-level visibility and a log of tool calls instead of editing every client's config. Skip it if you have one local server and no need for shared credentials, or if you cannot host a process and its data volume yourself. Before trusting it, verify two things: that your deployment's MCP endpoints really reject unauthenticated calls, since the README warns they are protected by default but can be opened, and that your config and data survive a container restart by mounting ./data as the Docker example shows.

Frequently asked questions

What is an MCP hub?

In this project's terms, it is a self-hosted gateway and control plane that sits between AI clients and MCP servers. MCPHub connects to local and remote servers once and re-exposes them through stable endpoints such as /mcp, /mcp/{group} and /mcp/{server}, adding authentication, visibility controls and logs.

How do I use Docker to run an MCP hub?

The README's recommended command mounts your config and a data directory: docker run -p 3000:3000 -v ./mcp_settings.json:/app/mcp_settings.json -v ./data:/app/data samanhappy/mcphub. The data mount matters because it is what keeps credentials and state across restarts.

Is MCP a JSON file?

Not in general, but MCPHub's server list is. Configuration lives in mcp_settings.json with an mcpServers object, and the README notes it is hot-swappable so entries can be added or removed without downtime. Database Mode can move that configuration into PostgreSQL instead.

What is the MCP system?

The README covers MCP as the protocol MCPHub speaks on both sides: it connects to servers over SSE, Streamable HTTP or stdio, and exposes them to MCP-compatible clients such as Claude Code, Cursor, Cherry Studio and OpenWebUI. The README does not define the protocol itself.

How is MCP different from an API?

The README does not explain the protocol-level difference. What it does show is that MCPHub treats MCP servers as connections it manages and re-exposes over HTTP paths, with per-server control over Tool, Prompt and Resource exposure rather than plain HTTP routes.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. samanhappy/mcphub on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/samanhappy-mcphub.svg)](https://hysenlabs.com/projects/samanhappy-mcphub)