From aa78585a45f7e7c241ef5a83be2b31f0f9362cf3 Mon Sep 17 00:00:00 2001 From: Agent Date: Fri, 13 Mar 2026 11:00:43 +0000 Subject: [PATCH] =?UTF-8?q?mcp-hub-009:=20Graceful=20backend=20failure=20?= =?UTF-8?q?=E2=80=94=20JSON-RPC=20error=20responses=20and=20session=20clea?= =?UTF-8?q?nup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/relay.js | 51 ++++++++++++++++++++++++++++++++++++++--- src/routes/mcp-proxy.js | 2 +- src/ws-server.js | 3 +++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/relay.js b/src/relay.js index 6155902..1cc4378 100644 --- a/src/relay.js +++ b/src/relay.js @@ -3,9 +3,21 @@ const registry = require('./backend-registry'); const pendingRequests = new Map(); -function sendToBackend(serviceId, message, clientSessionId) { +function sendToBackend(serviceId, message, clientSessionId, sessions) { const ws = registry.get(serviceId); - if (!ws) return null; + if (!ws) { + if (sessions && message.id !== undefined && message.id !== null) { + const session = sessions.get(clientSessionId); + if (session) { + session.res.write('event: message\ndata: ' + JSON.stringify({ + jsonrpc: '2.0', + id: message.id, + error: { code: -32603, message: 'Backend unavailable' } + }) + '\n\n'); + } + } + return null; + } const requestId = uuidv4(); pendingRequests.set(requestId, { serviceId, clientSessionId }); @@ -53,4 +65,37 @@ function handleBackendMessage(serviceId, data, sessions) { } } -module.exports = { sendToBackend, handleBackendMessage }; +function cleanupBackend(serviceId, sessions) { + // Clean up pending requests for this backend + for (const [requestId, pending] of pendingRequests) { + if (pending.serviceId === serviceId) { + const session = sessions.get(pending.clientSessionId); + if (session) { + // Write error notification event + session.res.write('event: message\ndata: ' + JSON.stringify({ + jsonrpc: '2.0', + id: null, + error: { code: -32603, message: 'Backend disconnected' } + }) + '\n\n'); + } + pendingRequests.delete(requestId); + } + } + + // Clean up active SSE sessions for this backend + for (const [clientSessionId, session] of sessions) { + if (session.serviceId === serviceId) { + // Write final error event + session.res.write('event: message\ndata: ' + JSON.stringify({ + jsonrpc: '2.0', + id: null, + error: { code: -32603, message: 'Backend disconnected' } + }) + '\n\n'); + // End the response + session.res.end(); + sessions.delete(clientSessionId); + } + } +} + +module.exports = { sendToBackend, handleBackendMessage, cleanupBackend }; diff --git a/src/routes/mcp-proxy.js b/src/routes/mcp-proxy.js index 8e92b89..973f04a 100644 --- a/src/routes/mcp-proxy.js +++ b/src/routes/mcp-proxy.js @@ -42,7 +42,7 @@ router.post('/:serviceId/message', (req, res) => { return res.status(502).json({ error: 'backend not connected' }); } - relay.sendToBackend(serviceId, req.body, sessionId); + relay.sendToBackend(serviceId, req.body, sessionId, sessions); return res.status(202).json({ status: 'accepted' }); }); diff --git a/src/ws-server.js b/src/ws-server.js index ac610d1..8e08c82 100644 --- a/src/ws-server.js +++ b/src/ws-server.js @@ -78,6 +78,9 @@ function setupWsServer(httpServer) { if (pingTimer) clearInterval(pingTimer); if (authenticated && serviceId) { registry.unregister(serviceId); + const { cleanupBackend } = require('./relay'); + const { sessions } = require('./routes/mcp-proxy'); + cleanupBackend(serviceId, sessions); } });