({ port = 3001, sendAnalytics } = {})
| 20 | * @returns {Object} - Server instance and express app |
| 21 | */ |
| 22 | export async function startSseServer({ port = 3001, sendAnalytics } = {}) { |
| 23 | // We can't use the SDK's createMcpExpressApp helper because it hard-codes |
| 24 | // express.json's 100kb default and exposes no body-limit option. We |
| 25 | // replicate its wiring here (json body parsing + Host-header validation |
| 26 | // for DNS-rebinding protection) with a 4mb limit instead. |
| 27 | const app = express() |
| 28 | app.use(express.json({ limit: MAX_MESSAGE_SIZE })) |
| 29 | app.use(hostHeaderValidation(['localhost', LOOPBACK_HOST, '[::1]'])) |
| 30 | |
| 31 | // Create the MCP server instance. |
| 32 | const server = new McpServer({ |
| 33 | name: 'serverless', |
| 34 | version: '1.0.0', |
| 35 | }) |
| 36 | |
| 37 | // Add analytics tracking for tool executions if sendAnalytics function is provided |
| 38 | wrapServerWithAnalytics(server, sendAnalytics) |
| 39 | |
| 40 | // Register all tools on the server |
| 41 | registerTools(server) |
| 42 | |
| 43 | // Object to track active SSE transports keyed by sessionId. |
| 44 | const activeTransports = {} |
| 45 | |
| 46 | /** |
| 47 | * GET /sse |
| 48 | * Establishes an SSE connection and stores the transport by its sessionId. |
| 49 | */ |
| 50 | app.get('/sse', async (req, res) => { |
| 51 | const transport = new SSEServerTransport('/messages', res) |
| 52 | await server.connect(transport) |
| 53 | |
| 54 | // Store this connection in the activeTransports map. |
| 55 | activeTransports[transport.sessionId] = transport |
| 56 | console.error(`Client connected: ${transport.sessionId}`) |
| 57 | |
| 58 | // When the connection closes, remove it from the map. |
| 59 | res.on('close', () => { |
| 60 | delete activeTransports[transport.sessionId] |
| 61 | console.error(`Client disconnected: ${transport.sessionId}`) |
| 62 | }) |
| 63 | }) |
| 64 | |
| 65 | /** |
| 66 | * POST /messages |
| 67 | * Routes incoming JSON‑RPC messages to the correct SSE transport based on sessionId. |
| 68 | * Clients must include their sessionId in the query parameters. |
| 69 | */ |
| 70 | app.post('/messages', async (req, res) => { |
| 71 | const sessionId = req.query.sessionId |
| 72 | if (!sessionId) { |
| 73 | res.status(400).send('Missing sessionId in query parameters.') |
| 74 | return |
| 75 | } |
| 76 | const transport = activeTransports[sessionId] |
| 77 | if (!transport) { |
| 78 | res.status(500).send('No active SSE connection for the given sessionId.') |
| 79 | return |
no test coverage detected
searching dependent graphs…