---
title: 'Architecture'
description: 'How MCPHub is laid out at runtime'
---

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](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

| Path | Purpose |
| ---- | ------- |
| `src/server.ts` | `AppServer` class — process lifecycle, MCP route registration. |
| `src/index.ts` | CLI entrypoint that constructs and starts `AppServer`. |
| `src/routes/index.ts` | All HTTP route definitions (single file). |
| `src/middlewares/` | Auth (`auth.ts`), user context (`userContext.ts`), i18n, common middleware. |
| `src/controllers/` | One controller per resource (servers, groups, users, tools, prompts, resources, logs, activities, OAuth, bearer keys, templates, market, cloud, registry, MCPB upload, health, config). |
| `src/services/` | Long-lived domain services — see the table below. |
| `src/dao/` | Data access layer with a file backend (default) and a TypeORM-based DB backend (`*DaoDbImpl.ts`). The factory in `src/dao/DaoFactory.ts` selects the implementation. |
| `src/db/` | TypeORM `DataSource`, entities (`src/db/entities/`), repositories, subscribers. Loaded only in database mode. |
| `src/models/` | Lightweight in-memory models (e.g. `User`, `OAuth`) that wrap the DAO layer for convenience. |
| `src/clients/` | Upstream MCP client wrappers (stdio / SSE / streamable-http / OpenAPI). |
| `src/types/` | Shared TypeScript types — start here when looking for config schemas. |
| `src/utils/` | Helpers: rate limiting, JWT, vector helpers, file migration, bearer-token parsing, etc. |
| `src/betterAuth.ts` | Optional [Better Auth](https://better-auth.com/) integration (GitHub/Google OAuth). |
| `frontend/` | Vite + React dashboard. Talks to the API over `/api`. In dev, `frontend/vite.config.ts` proxies to `http://localhost:3000`. |

## Service layer

The services in `src/services/` are the core domain layer. Notable members:

| Service | Responsibility |
| ------- | -------------- |
| `mcpService.ts` | Lifecycle of all upstream MCP clients (connect, reconnect, capability cache, tool/prompt/resource registries). |
| `sseService.ts` | Handles HTTP requests on `/sse/*` and `/mcp/*` and proxies them to the right upstream client / group / smart route. |
| `smartRoutingService.ts` | Vector-search-based tool discovery. Requires Postgres + pgvector and an OpenAI-compatible embedding endpoint. |
| `vectorSearchService.ts` | Embedding store and similarity search backing smart routing. |
| `oauthService.ts` | Acts as an **OAuth client** to upstream MCP servers that require OAuth. |
| `oauthServerService.ts` | Acts as an **OAuth authorization server** for downstream MCP clients (uses `@node-oauth/oauth2-server`). |
| `betterAuthConfig.ts` + `src/betterAuth.ts` | Optional social-login layer; mounted at `${basePath}${betterAuthConfig.basePath}` (default `/api/auth/better`). |
| `activityLoggingService.ts` | Records request/response activities. Persists to the `mcphub_activity` table; only active in database mode. |
| `logService.ts` | In-process log buffer that powers `/api/logs` and the SSE stream at `/api/logs/stream`. |
| `templateService.ts` | Export / import of `mcp_settings.json` snapshots and group templates. |
| `marketService.ts` / `cloudService.ts` / `registry.ts` | Read-mostly catalogs surfaced by the dashboard. |
| `keepAliveService.ts` | Optional periodic ping for `sse` upstream servers. |

## Data layer

MCPHub supports two storage backends. The choice is decided once at startup:

```
useDatabase = (USE_DB === 'true') OR (USE_DB unset AND DB_URL set)
```

### 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](/configuration/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.
