ββββ ββββ ββββββββββββββ βββ ββββββββββββββββββ βββββββ
βββββ βββββββββββββββββββββ βββ ββββββββββββββββββββββββββββ
ββββββββββββββ ββββββββ ββββββββββββββ βββββββββββ βββ
ββββββββββββββ βββββββ ββββββββββββββ βββββββββββ βββ
βββ βββ ββββββββββββββ βββ ββββββββββββββ ββββββββββββ
βββ βββ ββββββββββ βββ ββββββββββββββ βββ βββββββ
One endpoint for all your MCP servers, with the access control, auth, and auditing a team needs.
Hosted version: mcphero.io Β· or self-host with this repo.
MCP Hero is a self-hostable gateway for Model Context Protocol servers. It mounts many upstream MCP servers behind a single URL and adds the things you need to run them for real:
- One endpoint β point your MCP client (Claude and friends) at one gateway URL instead of wiring up each server separately.
- Role-based access control β decide which roles can reach which servers, down to individual tools.
- Per-user OAuth to upstreams β each user authenticates to upstream servers as themselves; tokens are stored encrypted.
- Hosted stdio MCPs β run
command:-style stdio MCP servers in isolated sandboxes, not just remote HTTP ones. - Audit logging β every tool call is recorded.
- A web dashboard β manage servers, roles, and users without touching config files.
You can run it two ways, controlled by a couple of environment variables (see below): a zero-config standalone mode for a single person or team, or a multi-tenant cloud mode (what powers mcphero.io).
The fastest way to try it. No Python or Node toolchain required β just Docker.
git clone https://github.com/nseniak/mcphero.git
cd mcphero
docker compose --profile standalone up --buildThen open http://localhost:8080. On first launch you'll see an email picker (standalone uses a no-login dev auth by default); pick any email and you become the admin of the default org. Config and data persist in Docker volumes (mcpolis-config, mcpolis-data), so your setup survives restarts.
That's the whole install. To add your first MCP server, use Upstreams β Add in the dashboard.
Standalone defaults to a no-real-auth email picker, which is perfect on your own machine. If you expose the dashboard to a network, turn on real Google sign-in β see below.
The default email picker (dev_stub) has no real login. To require Google sign-in instead:
-
Create a Google OAuth client. In the Google Cloud Console β Credentials, create an OAuth client ID (Web application). Add your dashboard URL as an Authorized JavaScript origin, and set the Authorized redirect URI to
<your-url>/mcp/oauth/google/callback(for local use that'shttp://localhost:8080/mcp/oauth/google/callback). -
Set these env vars (and point
MCPOLIS_SERVER_URLat the same URL whose callback you registered):MCPOLIS_OAUTH_PROVIDER=google MCPOLIS_GOOGLE_CLIENT_ID=<client id> MCPOLIS_GOOGLE_CLIENT_SECRET=<client secret> MCPOLIS_SERVER_URL=http://localhost:8080 # the URL whose redirect URI you registered
Where they go depends on how you run:
- From source:
backend/.env. - Cloud:
.env.cloud.docker.prod(cloud already requiresgoogle). - Docker standalone: add them under an
environment:block on thestandaloneservice indocker-compose.yml(that image takes no runtime env file).
- From source:
-
Restart. Logins now go through Google, and the returned email is matched to roles.
The detailed walkthrough (and per-upstream OAuth) is in the backend README.
Almost all behavior comes down to three environment variables:
| Variable | Values | What it controls |
|---|---|---|
MCPOLIS_MODE |
standalone (default) Β· cloud |
Storage + tenancy. standalone = a single org, file-backed storage, no external services. cloud = multi-org SaaS, MongoDB + Redis, encrypted tokens, horizontally scalable. |
MCPOLIS_OAUTH_PROVIDER |
dev_stub (default) Β· google |
Dashboard auth. dev_stub = pick-an-email with no real login (ideal for local/standalone). google = real Google OAuth. Cloud mode forces google and rejects dev_stub at startup. |
MCPOLIS_SANDBOX_PROVIDER |
empty = auto (default) Β· e2b Β· local-subprocess |
How stdio MCP servers execute. Auto-selects e2b (isolated remote sandbox) when MCPOLIS_E2B_API_KEY is set, otherwise local-subprocess (spawned on the host, no isolation β dev only). Cloud mode requires e2b. |
The first two combine into the setups you'd actually want:
standalone+dev_stubβ zero-config local run. This is the Quick start above.standalone+googleβ a hardened single-tenant deployment you can expose safely.cloud+googleβ the multi-tenant SaaS (what mcphero.io runs).
MCPOLIS_SANDBOX_PROVIDER is mostly automatic: drop in an MCPOLIS_E2B_API_KEY and stdio servers run isolated in a remote sandbox; leave it unset and they run as local subprocesses (fine on your own machine, flagged as unsafe at startup). Whether stdio MCPs are allowed at all is a separate gate β MCPOLIS_ALLOW_STDIO_MCP (on by default in standalone, off in cloud).
Set these in backend/.env (from-source) or your Docker env file. The defaults already give you the standalone experience, so you only touch them when you want real auth, cloud mode, or remote sandboxing.
For working on MCP Hero itself (hot-reloading dashboard, tests, etc.).
- Python 3.12 in an environment of your choice (venv, conda, uv, ...). The dependency manager is Poetry.
- Node.js 20+
- Docker (only for cloud mode, or to run tests against Mongo)
# Activate your Python 3.12 environment first, then:
cd backend && poetry install && cd ..
cd frontend && npm install && cd ..The shell scripts assume your project Python environment is already active. (They source an optional, gitignored run-in-env.local.sh if you want them to activate it for you β handy if you use conda or pyenv. See run-in-env.sh.)
bash start.sh standalone # standalone, file-backed, no containers
bash start.sh cloud # cloud: starts Docker (Mongo + Redis), multi-org
bash start.sh # defaults to cloudFrom source, the dashboard runs on the Vite dev server:
- Dashboard: http://localhost:5173
- Gateway/API backend: http://localhost:8080 (logs at
/tmp/mcpolis-backend.log)
bash stop.sh tears it down (--all also stops the Mongo + Redis containers).
bash backend/run-unit-tests.sh # backend unit tests (pytest)
bash frontend/run-unit-tests.sh # frontend unit tests (vitest)
bash tests/run-e2e-tests.sh # full-stack E2E (Playwright)
bash backend/run-pyright.sh src/ tests/ # type check
cd backend && poetry run ruff check . # lintIntegration tests that hit the live E2B API are gated by an API key β see backend/tests/integration/.env.test.example.
Cloud mode is the multi-tenant SaaS shape: MongoDB-backed, Redis-coordinated, horizontally scalable behind nginx, with encrypted OAuth tokens and real Google auth.
-
Create the secrets file from the template and fill it in:
cp .env.cloud.docker.example .env.cloud.docker.prod
MCPOLIS_SESSION_SECRET=<random-string> MCPOLIS_ENCRYPTION_KEY=<random-string> MCPOLIS_SERVER_URL=https://your-domain.com MCPOLIS_OAUTH_PROVIDER=google # cloud requires real OAuth MCPOLIS_GOOGLE_CLIENT_ID=<your-client-id> MCPOLIS_GOOGLE_CLIENT_SECRET=<your-client-secret>
-
Build and start:
docker compose --profile cloud up --build # single backend docker compose --profile cloud up --build --scale backend=2 # horizontal scaling
This starts nginx (port 80) + backend(s) + MongoDB + Redis.
When co-tenanting MCP Hero behind another proxy (Caddy, Traefik, an ALB, K8s ingress), use the docker-compose.proxied.yml overlay, which drops nginx's host port binding and joins a shared external Docker network:
docker network create web
docker compose -f docker-compose.yml -f docker-compose.proxied.yml --profile cloud up -d --build βββββββββββ
:80 ββββββββββ>β nginx β
ββββββ¬βββββ
β sticky sessions (mcp-session-id header)
ββββββββββΌβββββββββ
v v v
ββββββββββββββββββββββββββββββ
βbackend1ββbackend2ββbackend3β
βββββ¬ββββββββββ¬ββββββββββ¬βββββ
β β β
ββββββ΄ββββββββββ΄ββββββββββ΄βββββ
β MongoDB Redis β
βββββββββββββββββββββββββββββββ
- nginx serves the dashboard SPA and reverse-proxies API/MCP traffic with sticky routing on the
mcp-session-idheader. - Backends are stateless except for in-memory upstream connections; a Mongo-backed distributed lock coordinates token refresh, and SIGTERM drains gracefully.
- MongoDB stores org config, upstream definitions, OAuth tokens (AES-256-GCM encrypted), and audit logs.
- Redis provides cross-backend pub/sub and rate limiting.
/healthβ always{"status": "ok"}./healthzβ{"status": "ok"}normally,{"status": "draining"}(503) during graceful shutdown. Use this for load-balancer checks.
Most domain logic is shared; divergence is concentrated in the storage factory, the startup-secret validator, and a handful of org-context branches, all selected at boot via MCPOLIS_MODE.
| Concern | Standalone | Cloud |
|---|---|---|
| Persistence | JSON files in backend/config/ + backend/data/ |
MongoDB |
| Org model | Single default org auto-created at boot |
Multi-org; users create orgs at signup |
| First-user bootstrap | First login becomes admin of default |
Orgs created via signup flow |
| Dashboard auth default | dev_stub (in-app email picker) |
google (required) |
| Field encryption | None (plaintext on disk) | AES-256-GCM on OAuth tokens via MCPOLIS_ENCRYPTION_KEY |
| Rate limiter | In-process counter | Redis sliding window |
| Event stream / dashboard SSE | In-process pub/sub | Redis channels, per-org isolation |
| Distributed lock | No-op | Mongo TTL collection |
| Sandbox refs (E2B reattach) | In-memory (lost on restart) | Mongo-backed (survives restart) |
stdio MCP default |
Allowed | Disabled (override via MCPOLIS_ALLOW_STDIO_MCP) |
| Sandbox provider | e2b or local-subprocess |
e2b only |
| Startup secret validation | Skipped | Enforces session/encryption/Mongo/Redis (+ E2B when used) |
Multi-org UI (OrgSwitcher, Team page) |
Hidden | Shown |
backend/β Python (Poetry) MCP gateway servicefrontend/β React + Vite + Tailwind admin dashboarddocker/β Dockerfiles, nginx config, entrypoint scriptdocs/β user-facing documentation
In standalone mode, backend/config/ and backend/data/ hold sensitive material (API keys, OAuth tokens, client secrets). For production, consider encrypting at rest:
- macOS: encrypted disk image (
hdiutil create -encryption AES-256) - Linux: gocryptfs or LUKS
- Cross-platform: VeraCrypt
In cloud mode, secrets live in .env.cloud.docker.prod (gitignored) and tokens are encrypted in MongoDB via MCPOLIS_ENCRYPTION_KEY.
Warning: rotating
MCPOLIS_ENCRYPTION_KEYinvalidates all stored OAuth tokens β users will need to re-authenticate their upstream connections.
Found a vulnerability? Please report it privately β see SECURITY.md. Don't open a public issue for security problems.
MCP Hero is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE for the full text.
The AGPL's network-use clause (section 13) means that if you run a modified version of MCP Hero as a network service, you must make the corresponding source available to its users. If the AGPL doesn't fit your use case, a commercial license is available β reach out via the contact page at mcphero.io.
"MCP Hero", the MCP Hero name, and the MCP Hero logos are trademarks of Nitsan Seniak. The AGPL covers the source code only; it does not grant any right to use the MCP Hero name or logos.
You may run, modify, and redistribute the code under the AGPL. The brand assets (hero-* logos, favicon, OG images) are bundled so the official build renders correctly β but if you operate your own or a modified instance, please replace them with your own branding and don't present it as the official MCP Hero service.