Process model
MCPHub runs as a single Node.js (Express) process. The entrypoint issrc/index.ts, which delegates to AppServer in src/server.ts. AppServer is responsible for:
- Initializing i18n (
src/utils/i18n.ts). - Bootstrapping the default admin user (
src/models/User.ts:initializeDefaultUser). - Initializing the optional upstream-MCP OAuth client provider (
src/services/oauthService.ts) and the optional MCPHub OAuth authorization server (src/services/oauthServerService.ts). - Wiring middlewares (
src/middlewares/index.ts) and HTTP routes (src/routes/index.ts). - Spawning / connecting all enabled upstream MCP servers via
initUpstreamServers()insrc/services/mcpService.ts. - Mounting the SSE / streamable-HTTP MCP routes (
/mcp/:group,/sse/:group, and their user-scoped variants/:user/mcp/...,/:user/sse/...). - Serving the built frontend from
frontend/dist/(unlessDISABLE_WEB=true).
AppServer.shutdown(): stop the HTTP server, close all upstream MCP clients, then close the database pool if database mode is on.
Top-level directories
Service layer
The services insrc/services/ are the core domain layer. Notable members:
Data layer
MCPHub supports two storage backends. The choice is decided once at startup:File mode (default)
All persistent state lives inmcp_settings.json (path configurable via MCPHUB_SETTING_PATH). Users, servers, groups, system config and OAuth state are read/written through src/dao/*Dao.ts file implementations. This is the simplest deployment mode and works for single-instance use.
Database mode
When enabled, MCPHub uses TypeORM against PostgreSQL. The schema is defined by entities insrc/db/entities/:
User,BearerKey,OAuthClient,OAuthTokenServer,Group,SystemConfig,UserConfigBuiltinPrompt,BuiltinResourceActivity— request/response logging (only populated in DB mode).VectorEmbedding— pgvector-backed table used by smart routing.
src/dao/DaoFactory.ts swaps in the *DaoDbImpl.ts variants when database mode is on. A one-time migration script src/scripts/migrate-to-database.ts copies an existing mcp_settings.json into the database; the helper in src/utils/migration.ts runs the same path on first start.
See Database Configuration for setup details.
Request paths
There are three classes of HTTP route, all defined insrc/routes/index.ts and mounted by AppServer:
- Public endpoints (no auth):
GET /healthGET ${basePath}/config,GET ${basePath}/public-config- All
/oauth/*and/.well-known/oauth-*endpoints (the OAuth flow itself). - All
${basePath}/api/openapi*and${basePath}/api/tools/...OpenAPI endpoints. POST /api/auth/login,POST /api/auth/register.
- Authenticated dashboard / management API under
${basePath}/api/*. Authentication is enforced bysrc/middlewares/auth.ts, which accepts (in order):- A configured system-level bearer key (
/api/auth/keys) whoseaccessTypeisall. - An OAuth access token issued by MCPHub’s authorization server (when enabled).
- A Better Auth session cookie (when enabled).
- An
x-auth-tokenJWT issued by/api/auth/login, or the same value as a?token=query. - If
systemConfig.routing.skipAuthistrue, unauthenticated dashboard API calls receive a syntheticguestuser with admin privileges.
- A configured system-level bearer key (
- MCP transport routes:
${basePath}/mcp/:group?,${basePath}/sse/:group?, plus their user-scoped variants${basePath}/:user/mcp/...and${basePath}/:user/sse/.... These are rate-limited viamcpConnectionRateLimiterand pass throughsseUserContextMiddlewarebefore being dispatched bysseService.ts. System-level key scopes are enforced insidesseService.ts; user-level keys restore their owner’s live user context so server visibility filtering applies.
Observability
What actually ships today:GET /health— process & database & MCP-server connection status.GET /api/logs,DELETE /api/logs,GET /api/logs/stream(Server-Sent Events of the in-memory log buffer).GET /api/activities*— query themcphub_activitytable. Database mode only.
/health//api/logs/stream or wrap the process at the infrastructure layer (sidecar, log shipper, etc.).
Frontend
The dashboard is a Vite + React + Tailwind app underfrontend/. After pnpm build it is emitted to frontend/dist/ and served by the backend as static files at ${basePath}/. In development (pnpm dev), Vite runs on :5173 and proxies /api, /mcp, /sse, /oauth, /health to the backend on :3000 — see frontend/vite.config.ts.
The frontend uses the same JWT / Bearer / Better Auth session that the management API expects; it does not have its own auth.