UNPKG

mcp-cisco-support

Version:

MCP server for Cisco Support APIs including Bug Search and future tools

266 lines 11.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSSEServer = createSSEServer; const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js"); const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const node_crypto_1 = require("node:crypto"); const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); const helmet_1 = __importDefault(require("helmet")); const morgan_1 = __importDefault(require("morgan")); const mcp_server_js_1 = require("./mcp-server.js"); function createSSEServer(mcpServer) { const app = (0, express_1.default)(); // Security middleware app.use((0, helmet_1.default)()); app.use((0, cors_1.default)()); app.use((0, morgan_1.default)('combined')); app.use(express_1.default.json({ limit: '10mb' })); app.use(express_1.default.urlencoded({ extended: true })); const transportMap = new Map(); const streamableTransports = {}; // SSE endpoint - establishes SSE connection and connects to MCP server app.get("/sse", async (req, res) => { try { mcp_server_js_1.logger.info('SSE connection request received'); // Create SSE transport with proper endpoint - it will set the headers const transport = new sse_js_1.SSEServerTransport("/messages", res); // Store transport for message handling transportMap.set(transport.sessionId, transport); mcp_server_js_1.logger.info('SSE transport created', { sessionId: transport.sessionId }); // Connect MCP server to transport await mcpServer.connect(transport); mcp_server_js_1.logger.info('MCP server connected to SSE transport', { sessionId: transport.sessionId, totalTransports: transportMap.size }); // Set up cleanup handlers before connecting transport.onclose = () => { mcp_server_js_1.logger.info('SSE connection closed', { sessionId: transport.sessionId }); transportMap.delete(transport.sessionId); }; transport.onerror = (error) => { mcp_server_js_1.logger.error('SSE transport error', { sessionId: transport.sessionId, error: error.message }); }; // Handle cleanup when connection closes req.on('close', () => { transportMap.delete(transport.sessionId); mcp_server_js_1.logger.info('SSE request closed', { sessionId: transport.sessionId, totalTransports: transportMap.size }); }); // Handle errors on response stream res.on('error', (error) => { mcp_server_js_1.logger.error('SSE response stream error', { sessionId: transport.sessionId, error: error.message }); transportMap.delete(transport.sessionId); }); } catch (error) { mcp_server_js_1.logger.error('Failed to establish SSE connection', { error }); if (!res.headersSent) { res.status(500).json({ error: 'Failed to establish SSE connection', message: error instanceof Error ? error.message : 'Unknown error' }); } } }); // MCP JSON-RPC endpoint - handles direct MCP calls using StreamableHTTP app.post("/mcp", async (req, res) => { try { mcp_server_js_1.logger.info('Direct MCP call received', { method: req.body?.method }); // Check for existing session ID const sessionId = req.headers['mcp-session-id']; let transport; if (sessionId && streamableTransports[sessionId]) { // Reuse existing transport transport = streamableTransports[sessionId]; mcp_server_js_1.logger.info('Reusing existing transport', { sessionId }); } else if (!sessionId && (0, types_js_1.isInitializeRequest)(req.body)) { // New initialization request transport = new streamableHttp_js_1.StreamableHTTPServerTransport({ sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(), onsessioninitialized: (sessionId) => { mcp_server_js_1.logger.info('Session initialized', { sessionId }); streamableTransports[sessionId] = transport; } }); // Set up cleanup handler transport.onclose = () => { const sid = transport.sessionId; if (sid && streamableTransports[sid]) { mcp_server_js_1.logger.info('Transport closed, cleaning up', { sessionId: sid }); delete streamableTransports[sid]; } }; // Connect the transport to the MCP server await mcpServer.connect(transport); await transport.handleRequest(req, res, req.body); return; // Already handled } else { // Invalid request - no session ID or not initialization request mcp_server_js_1.logger.error('Invalid MCP request', { sessionId, isInit: (0, types_js_1.isInitializeRequest)(req.body) }); res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: null, }); return; } // Handle the request with existing transport await transport.handleRequest(req, res, req.body); } catch (error) { mcp_server_js_1.logger.error('Failed to handle MCP call', { error }); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }); // MCP GET endpoint - handles SSE streams for StreamableHTTP app.get("/mcp", async (req, res) => { try { const sessionId = req.headers['mcp-session-id']; if (!sessionId || !streamableTransports[sessionId]) { mcp_server_js_1.logger.error('Invalid session ID for GET request', { sessionId }); res.status(400).send('Invalid or missing session ID'); return; } mcp_server_js_1.logger.info('SSE stream request', { sessionId }); const transport = streamableTransports[sessionId]; await transport.handleRequest(req, res); } catch (error) { mcp_server_js_1.logger.error('Failed to handle SSE stream', { error }); if (!res.headersSent) { res.status(500).send('Error establishing SSE stream'); } } }); // MCP DELETE endpoint - handles session termination app.delete("/mcp", async (req, res) => { try { const sessionId = req.headers['mcp-session-id']; if (!sessionId || !streamableTransports[sessionId]) { mcp_server_js_1.logger.error('Invalid session ID for DELETE request', { sessionId }); res.status(400).send('Invalid or missing session ID'); return; } mcp_server_js_1.logger.info('Session termination request', { sessionId }); const transport = streamableTransports[sessionId]; await transport.handleRequest(req, res); } catch (error) { mcp_server_js_1.logger.error('Failed to handle session termination', { error }); if (!res.headersSent) { res.status(500).send('Error processing session termination'); } } }); // Messages endpoint - handles MCP JSON-RPC messages app.post("/messages", async (req, res) => { const sessionId = req.query.sessionId; if (!sessionId) { mcp_server_js_1.logger.error('Message received without sessionId'); res.status(400).json({ error: 'sessionId is required' }); return; } mcp_server_js_1.logger.info('Message received for session', { sessionId, method: req.body?.method }); const transport = transportMap.get(sessionId); if (transport) { try { // Let the transport handle the message - must await and pass req.body await transport.handlePostMessage(req, res, req.body); } catch (error) { mcp_server_js_1.logger.error('Failed to handle message', { sessionId, error: error instanceof Error ? error.message : error }); if (!res.headersSent) { res.status(500).json({ error: 'Failed to handle message', message: error instanceof Error ? error.message : 'Unknown error' }); } } } else { mcp_server_js_1.logger.error('Transport not found for session', { sessionId }); res.status(404).json({ error: 'Session not found', sessionId }); } }); // Health check endpoint app.get('/health', (_req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), activeTransports: transportMap.size, server: 'mcp-cisco-support-sse' }); }); // Server info endpoint app.get('/', (_req, res) => { res.json({ name: 'Cisco Support MCP SSE Server', description: 'MCP Server-Sent Events transport for Cisco Support APIs', endpoints: { mcp: '/mcp (POST/GET/DELETE) - MCP StreamableHTTP endpoint', sse: '/sse (GET) - Legacy SSE connection', messages: '/messages (POST) - Legacy SSE messages', health: '/health (GET) - Health check' }, activeTransports: transportMap.size + Object.keys(streamableTransports).length, timestamp: new Date().toISOString() }); }); // Error handling middleware app.use((error, req, res, _next) => { mcp_server_js_1.logger.error('Unhandled SSE server error', { error: error.message, path: req.path, method: req.method }); res.status(500).json({ error: 'Internal server error', timestamp: new Date().toISOString() }); }); // 404 handler app.use((req, res) => { res.status(404).json({ error: 'Endpoint not found', path: req.path, availableEndpoints: ['/mcp (POST/GET/DELETE)', '/sse', '/messages', '/health', '/'], timestamp: new Date().toISOString() }); }); return app; } //# sourceMappingURL=sse-server.js.map