From 86f87e71e6bec556c1e55a8964b2e185f7e7c0b5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 00:38:12 +0000 Subject: [PATCH] =?UTF-8?q?feat(trinity-core):=20v2.5.0=20=E2=80=94=20resi?= =?UTF-8?q?lient=20session=20handling,=20/health=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add unauthenticated /health endpoint (kills AUTH FAILED spam from health checks) - Stale session + initialize request: ignore stale header, create fresh session - Stale session + tool call: return 400 instead of 404 so client reads JSON-RPC payload - Gemini-consulted architecture (gemini-trinity-core-mcp-sessions-2026-04-14.md) --- services/trinity-core/index.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/services/trinity-core/index.js b/services/trinity-core/index.js index 3c9e3ca..f92be5c 100644 --- a/services/trinity-core/index.js +++ b/services/trinity-core/index.js @@ -52,6 +52,16 @@ app.use(cors({ origin: '*', credentials: true })); app.use(express.json()); app.use(express.urlencoded({ extended: true })); +// ─── Health endpoint (no auth required) ─── +app.get('/health', (req, res) => { + res.status(200).json({ + status: 'ok', + version: '2.5.0', + uptime: Math.floor(process.uptime()), + sessions: activeSessions.size + }); +}); + function auth(req, res, next) { if (req.method === 'OPTIONS') return next(); const token = req.headers.authorization?.replace('Bearer ', ''); @@ -157,7 +167,7 @@ const activeSessions = new Map(); // Create a fresh MCP server instance with tool handlers function createMcpServer() { const mcpServer = new Server( - { name: "trinity-core", version: "2.4.0" }, + { name: "trinity-core", version: "2.5.0" }, { capabilities: { tools: {} } } ); setupToolHandlers(mcpServer); @@ -215,8 +225,13 @@ app.all('/mcp', auth, async (req, res) => { id: null }); } - } else if (!sessionId && req.method === 'POST' && isInitializeRequest(req.body)) { - log(`StreamableHTTP new session (initialize)`); + } else if (req.method === 'POST' && isInitializeRequest(req.body)) { + // Accept initialize requests even if they carry a stale session header + if (sessionId) { + log(`StreamableHTTP re-initialize (stale session ${sessionId} ignored)`); + } else { + log(`StreamableHTTP new session (initialize)`); + } transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (sid) => { @@ -234,12 +249,12 @@ app.all('/mcp', auth, async (req, res) => { const mcpServer = createMcpServer(); await mcpServer.connect(transport); } else if (sessionId && !activeSessions.has(sessionId)) { - // Stale session (e.g. after service restart) — tell client to re-initialize - log(`StreamableHTTP stale session ${sessionId} — returning 404`); - return res.status(404).json({ + // Stale session with non-initialize request — return 400 so client reads the JSON-RPC payload + log(`StreamableHTTP stale session ${sessionId} — returning 400`); + return res.status(400).json({ jsonrpc: '2.0', error: { code: -32001, message: 'Session not found. Please re-initialize.' }, - id: null + id: req.body?.id || null }); } else if (!sessionId && req.method === 'GET') { // Legacy SSE client connecting via GET /mcp @@ -289,7 +304,7 @@ app.post('/mcp/messages', auth, async (req, res) => { const transport = activeSessions.get(sessionId); if (!transport || !(transport instanceof SSEServerTransport)) { log(`Legacy session not found: ${sessionId}`); - return res.status(404).json({ error: "Session not found" }); + return res.status(400).json({ error: "Session not found. Please re-initialize." }); } try { @@ -301,4 +316,4 @@ app.post('/mcp/messages', auth, async (req, res) => { } }); -app.listen(PORT, () => log(`Trinity Core MCP v2.4.0 started on port ${PORT}`)); +app.listen(PORT, () => log(`Trinity Core MCP v2.5.0 started on port ${PORT}`));