mcp-cisco-support
Version:
MCP server for Cisco Support APIs including Bug Search and future tools
266 lines • 11.8 kB
JavaScript
"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