Skip to main content
Glama
http-server.ts5.99 kB
import express, { Request, Response, NextFunction } from 'express'; import cors from 'cors'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { logger } from './logger.js'; export interface HttpServerConfig { port: number; host?: string; apiKey?: string; enableCors?: boolean; } export class HttpMCPServer { private app: express.Application; private mcpServer: Server; private config: HttpServerConfig; private transports: Map<string, SSEServerTransport>; constructor(mcpServer: Server, config: HttpServerConfig) { this.mcpServer = mcpServer; this.config = { host: '0.0.0.0', enableCors: true, ...config }; this.transports = new Map(); this.app = express(); this.setupMiddleware(); this.setupRoutes(); } private setupMiddleware() { // CORS設定 if (this.config.enableCors) { this.app.use(cors({ origin: '*', methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] })); } this.app.use(express.json()); // 認証ミドルウェア(APIキーが設定されている場合) if (this.config.apiKey) { this.app.use((req: Request, res: Response, next: NextFunction) => { // ヘルスチェックは認証不要 if (req.path === '/health') { return next(); } const authHeader = req.headers.authorization; const apiKey = authHeader?.replace('Bearer ', ''); if (!apiKey || apiKey !== this.config.apiKey) { logger.warn('Unauthorized access attempt', { ip: req.ip, path: req.path }); return res.status(401).json({ error: 'Unauthorized', message: 'Invalid or missing API key' }); } next(); }); } // リクエストログ this.app.use((req: Request, res: Response, next: NextFunction) => { logger.info('HTTP request received', { method: req.method, path: req.path, ip: req.ip }); next(); }); } private setupRoutes() { // ヘルスチェック this.app.get('/health', (req: Request, res: Response) => { res.json({ status: 'ok', server: 'universal-mcp-server', version: '1.0.0', transport: 'http-sse', timestamp: new Date().toISOString() }); }); // サーバー情報 this.app.get('/info', (req: Request, res: Response) => { res.json({ name: 'universal-mcp-server', version: '1.0.0', capabilities: { resources: true, tools: true }, transport: 'http-sse', authentication: !!this.config.apiKey }); }); // SSE エンドポイント this.app.get('/sse', async (req: Request, res: Response) => { const clientId = req.query.clientId as string || `client-${Date.now()}`; logger.info('SSE connection established', { clientId }); // SSE ヘッダー設定 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); try { // SSE トランスポート作成 const transport = new SSEServerTransport('/message', res); this.transports.set(clientId, transport); // MCP サーバーと接続 await this.mcpServer.connect(transport); logger.info('SSE transport connected', { clientId }); // クライアント切断時のクリーンアップ req.on('close', () => { logger.info('SSE connection closed', { clientId }); this.transports.delete(clientId); }); } catch (error) { logger.error('SSE connection failed', error as Error, { clientId }); res.end(); } }); // メッセージ受信エンドポイント this.app.post('/message', async (req: Request, res: Response) => { const clientId = req.query.clientId as string; if (!clientId || !this.transports.has(clientId)) { return res.status(400).json({ error: 'Invalid client', message: 'Client ID not found or SSE connection not established' }); } try { const transport = this.transports.get(clientId)!; await transport.handlePostMessage(req, res); logger.debug('Message processed', { clientId }); } catch (error) { logger.error('Message processing failed', error as Error, { clientId }); res.status(500).json({ error: 'Internal error', message: (error as Error).message }); } }); // 404ハンドラー this.app.use((req: Request, res: Response) => { res.status(404).json({ error: 'Not found', message: `Endpoint ${req.path} not found` }); }); // エラーハンドラー this.app.use((err: Error, req: Request, res: Response, next: NextFunction) => { logger.error('Express error', err); res.status(500).json({ error: 'Internal server error', message: err.message }); }); } async start(): Promise<void> { return new Promise((resolve) => { this.app.listen(this.config.port, this.config.host!, () => { logger.info('HTTP MCP Server started', { host: this.config.host, port: this.config.port, authentication: !!this.config.apiKey, endpoints: { health: `http://${this.config.host}:${this.config.port}/health`, info: `http://${this.config.host}:${this.config.port}/info`, sse: `http://${this.config.host}:${this.config.port}/sse`, message: `http://${this.config.host}:${this.config.port}/message` } }); resolve(); }); }); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Amana03/universal-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server