feat(trinity-core): v2.5.0 — resilient session handling, /health endpoint
- 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)
This commit is contained in:
@@ -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}`));
|
||||
|
||||
Reference in New Issue
Block a user