Skip to main content
This page describes the actual moving parts of MCPHub today. Anything not listed here is not implemented — there is no built-in Redis, no Prometheus exporter, no WebSocket server, no plugin loader.

Process model

MCPHub runs as a single Node.js (Express) process. The entrypoint is src/index.ts, which delegates to AppServer in src/server.ts. AppServer is responsible for:
  1. Initializing i18n (src/utils/i18n.ts).
  2. Bootstrapping the default admin user (src/models/User.ts:initializeDefaultUser).
  3. Initializing the optional upstream-MCP OAuth client provider (src/services/oauthService.ts) and the optional MCPHub OAuth authorization server (src/services/oauthServerService.ts).
  4. Wiring middlewares (src/middlewares/index.ts) and HTTP routes (src/routes/index.ts).
  5. Spawning / connecting all enabled upstream MCP servers via initUpstreamServers() in src/services/mcpService.ts.
  6. Mounting the SSE / streamable-HTTP MCP routes (/mcp/:group, /sse/:group, and their user-scoped variants /:user/mcp/..., /:user/sse/...).
  7. Serving the built frontend from frontend/dist/ (unless DISABLE_WEB=true).
Shutdown is handled in 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 in src/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 in mcp_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 in src/db/entities/:
  • User, BearerKey, OAuthClient, OAuthToken
  • Server, Group, SystemConfig, UserConfig
  • BuiltinPrompt, BuiltinResource
  • Activity — request/response logging (only populated in DB mode).
  • VectorEmbedding — pgvector-backed table used by smart routing.
The DAO factory in 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 in src/routes/index.ts and mounted by AppServer:
  1. Public endpoints (no auth):
    • GET /health
    • GET ${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.
  2. Authenticated dashboard / management API under ${basePath}/api/*. Authentication is enforced by src/middlewares/auth.ts, which accepts (in order):
    • A configured system-level bearer key (/api/auth/keys) whose accessType is all.
    • An OAuth access token issued by MCPHub’s authorization server (when enabled).
    • A Better Auth session cookie (when enabled).
    • An x-auth-token JWT issued by /api/auth/login, or the same value as a ?token= query.
    • If systemConfig.routing.skipAuth is true, unauthenticated dashboard API calls receive a synthetic guest user with admin privileges.
  3. 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 via mcpConnectionRateLimiter and pass through sseUserContextMiddleware before being dispatched by sseService.ts. System-level key scopes are enforced inside sseService.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 the mcphub_activity table. Database mode only.
There is no Prometheus exporter, no OpenTelemetry SDK initialization and no built-in distributed tracing. If you need those, scrape /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 under frontend/. 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.