server.ts•8.61 kB
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
import { SDKOAuthServerProvider } from '@src/auth/sdkOAuthServerProvider.js';
import { McpConfigManager } from '@src/config/mcpConfigManager.js';
import { RATE_LIMIT_CONFIG } from '@src/constants.js';
import { AsyncLoadingOrchestrator } from '@src/core/capabilities/asyncLoadingOrchestrator.js';
import { McpLoadingManager } from '@src/core/loading/mcpLoadingManager.js';
import { AgentConfigManager } from '@src/core/server/agentConfig.js';
import { ServerManager } from '@src/core/server/serverManager.js';
import logger from '@src/logger/logger.js';
import bodyParser from 'body-parser';
import cors from 'cors';
import express from 'express';
import { Options as RateLimitOptions } from 'express-rate-limit';
import errorHandler from './middlewares/errorHandler.js';
import { httpRequestLogger } from './middlewares/httpRequestLogger.js';
import { createMcpAvailabilityMiddleware } from './middlewares/mcpAvailabilityMiddleware.js';
import { createScopeAuthMiddleware } from './middlewares/scopeAuthMiddleware.js';
import { setupSecurityMiddleware } from './middlewares/securityMiddleware.js';
import createHealthRoutes from './routes/healthRoutes.js';
import createOAuthRoutes from './routes/oauthRoutes.js';
import { setupSseRoutes } from './routes/sseRoutes.js';
import { setupStreamableHttpRoutes } from './routes/streamableHttpRoutes.js';
/**
* ExpressServer orchestrates the HTTP/SSE transport layer for the MCP server.
*
* This class manages the Express application, authentication, and route setup.
* It provides both HTTP and SSE transport options with optional OAuth 2.1 authentication.
*
* @example
* ```typescript
* const serverManager = await setupServer();
* const expressServer = new ExpressServer(serverManager);
* expressServer.start(3050, 'localhost');
* ```
*/
export class ExpressServer {
private app: express.Application;
private serverManager: ServerManager;
private loadingManager?: McpLoadingManager;
private asyncOrchestrator?: AsyncLoadingOrchestrator;
private oauthProvider: SDKOAuthServerProvider;
private configManager: AgentConfigManager;
private customTemplate?: string;
/**
* Creates a new ExpressServer instance.
*
* Initializes the Express application, sets up middleware, authentication,
* and configures all routes for MCP transport and OAuth endpoints.
*
* @param serverManager - The server manager instance for handling MCP operations
* @param loadingManager - Optional loading manager for async MCP server initialization
* @param asyncOrchestrator - Optional async loading orchestrator for listChanged notifications
* @param customTemplate - Optional custom template for instructions
*/
constructor(
serverManager: ServerManager,
loadingManager?: McpLoadingManager,
asyncOrchestrator?: AsyncLoadingOrchestrator,
customTemplate?: string,
) {
this.app = express();
this.serverManager = serverManager;
this.loadingManager = loadingManager;
this.asyncOrchestrator = asyncOrchestrator;
this.customTemplate = customTemplate;
this.configManager = AgentConfigManager.getInstance();
// Configure trust proxy setting before any middleware
this.app.set('trust proxy', this.configManager.getTrustProxy());
// Initialize OAuth provider with custom session storage path if configured
const sessionStoragePath = this.configManager.getSessionStoragePath();
this.oauthProvider = new SDKOAuthServerProvider(sessionStoragePath);
this.setupMiddleware();
this.setupRoutes();
}
/**
* Sets up Express middleware including CORS, body parsing, and error handling.
*
* Configures the basic middleware stack required for the MCP server:
* - Enhanced security middleware (conditional based on feature flag)
* - HTTP request logging for all requests
* - CORS for cross-origin requests
* - JSON body parsing
* - Global error handling
*/
private setupMiddleware(): void {
// Conditionally apply enhanced security middleware (must be first if enabled)
if (this.configManager.isEnhancedSecurityEnabled()) {
this.app.use(...setupSecurityMiddleware());
}
// Add HTTP request logging middleware (early in the stack for complete coverage)
this.app.use(httpRequestLogger);
this.app.use(cors()); // Allow all origins for local dev
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: true }));
// Add error handling middleware
this.app.use(errorHandler);
}
/**
* Sets up all application routes including OAuth and MCP transport endpoints.
*
* Configures the following route groups:
* - OAuth 2.1 endpoints (always available, auth can be disabled)
* - Streamable HTTP transport routes with authentication middleware
* - SSE transport routes with authentication middleware
*
* Logs the authentication status for debugging purposes.
*/
private setupRoutes(): void {
// Setup OAuth routes using SDK's mcpAuthRouter
const issuerUrl = new URL(this.configManager.getUrl());
const rateLimitConfig: Partial<RateLimitOptions> = {
windowMs: this.configManager.getRateLimitWindowMs(),
max: this.configManager.getRateLimitMax(),
message: RATE_LIMIT_CONFIG.OAUTH.MESSAGE,
standardHeaders: true,
legacyHeaders: false,
};
// Get available scopes from MCP config
const mcpConfigManager = McpConfigManager.getInstance();
const availableTags = mcpConfigManager.getAvailableTags();
// Convert tags to supported scopes
const scopesSupported = availableTags.map((tag: string) => `tag:${tag}`);
const authRouter = mcpAuthRouter({
provider: this.oauthProvider,
issuerUrl,
baseUrl: issuerUrl,
scopesSupported,
resourceName: '1MCP Agent - Universal MCP Server Proxy',
authorizationOptions: {
rateLimit: rateLimitConfig,
},
tokenOptions: {
rateLimit: rateLimitConfig,
},
revocationOptions: {
rateLimit: rateLimitConfig,
},
clientRegistrationOptions: {
rateLimit: rateLimitConfig,
},
});
this.app.use(authRouter);
// Setup OAuth management routes (no auth required)
this.app.use('/oauth', createOAuthRoutes(this.oauthProvider, this.loadingManager));
// Setup health check routes (no auth required for monitoring)
this.app.use('/health', createHealthRoutes(this.loadingManager));
// Setup MCP transport routes (auth is handled per-route via scopeAuthMiddleware)
const router = express.Router();
const scopeAuthMiddleware = createScopeAuthMiddleware(this.oauthProvider);
const availabilityMiddleware = createMcpAvailabilityMiddleware(this.loadingManager, {
allowPartialAvailability: true,
includeOAuthServers: false,
});
setupStreamableHttpRoutes(
router,
this.serverManager,
scopeAuthMiddleware,
availabilityMiddleware,
this.asyncOrchestrator,
this.customTemplate,
);
setupSseRoutes(
router,
this.serverManager,
scopeAuthMiddleware,
availabilityMiddleware,
this.asyncOrchestrator,
this.customTemplate,
);
this.app.use(router);
// Log authentication status
if (this.configManager.isAuthEnabled()) {
logger.info('Authentication enabled - OAuth 2.1 endpoints available via SDK');
} else {
logger.info('Authentication disabled - all endpoints accessible without auth');
}
}
/**
* Starts the Express server on the specified port and host.
*
* Binds the Express application to the network interface and logs
* the server status including authentication configuration.
*
* @param port - The port number to listen on
* @param host - The host address to bind to
*/
public start(): void {
const { port, host } = this.configManager.getConfig();
this.app.listen(port, host, () => {
const authStatus = this.configManager.isAuthEnabled() ? 'with authentication' : 'without authentication';
logger.info(`Server is running on port ${port} with HTTP/SSE and Streamable HTTP transport ${authStatus}`);
logger.info(`📋 OAuth Management Dashboard: ${this.configManager.getUrl()}/oauth`);
});
}
/**
* Performs graceful shutdown of the Express server.
*
* Cleans up resources including:
* - Authentication manager shutdown
* - Session cleanup
* - Timer cleanup
*/
public shutdown(): void {
this.oauthProvider.shutdown();
}
}