fix: add /sse + /messages endpoints for Figma compatibility
This commit is contained in:
1 parent
b10d3581aa
commit
24eff3a35a
1 file changed
+49
-8
+49
-8
@@ -29,6 +29,7 @@ import {
|
||||
ListToolsRequestSchema,
|
||||
CallToolRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
||||
import http from 'node:http';
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs/promises';
|
||||
@@ -594,17 +595,25 @@ function errResp(text) {
|
||||
}
|
||||
|
||||
// ---------- HTTP transport w/ Bearer auth ----------
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
|
||||
// Traditional SSE transport sessions (for Figma / older MCP clients)
|
||||
const sseSessions = new Map();
|
||||
|
||||
// Stateful Streamable HTTP transport (for Cursor / modern clients)
|
||||
const mcpTransport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => crypto.randomUUID(),
|
||||
});
|
||||
await server.connect(transport);
|
||||
await server.connect(mcpTransport);
|
||||
|
||||
const httpServer = http.createServer(async (req, res) => {
|
||||
if (!req.url.startsWith('/health')) {
|
||||
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
||||
|
||||
// Auth gate — skip /health only
|
||||
if (!url.pathname.startsWith('/health')) {
|
||||
const auth = req.headers.authorization || '';
|
||||
const expected = `Bearer ${MCP_TOKEN}`;
|
||||
if (auth !== expected) {
|
||||
process.stderr.write(`[auth] denied ${req.method} ${req.url}\n`);
|
||||
process.stderr.write(`[auth] denied ${req.method} ${url.pathname}\n`);
|
||||
res.statusCode = 401;
|
||||
res.setHeader('www-authenticate', 'Bearer');
|
||||
res.setHeader('content-type', 'application/json');
|
||||
@@ -613,17 +622,49 @@ const httpServer = http.createServer(async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (req.url === '/health') {
|
||||
// Lightweight /health for Coolify / Traefik probes
|
||||
if (url.pathname === '/health') {
|
||||
res.statusCode = 200;
|
||||
res.setHeader('content-type', 'application/json');
|
||||
res.end(JSON.stringify({ ok: true, version: '1.2.0', uptime: process.uptime() }));
|
||||
return;
|
||||
}
|
||||
|
||||
process.stderr.write(`[HTTP] ${req.method} ${req.url} -> incoming\n`);
|
||||
process.stderr.write(`[HTTP] ${req.method} ${url.pathname} -> incoming\n`);
|
||||
|
||||
try {
|
||||
await transport.handleRequest(req, res);
|
||||
process.stderr.write(`[HTTP] ${req.method} ${req.url} -> ${res.statusCode}\n`);
|
||||
// ---- Traditional SSE transport (for Figma) ----
|
||||
if (url.pathname === '/sse' && req.method === 'GET') {
|
||||
const sseTransport = new SSEServerTransport('/messages', res);
|
||||
const sessionId = sseTransport.sessionId;
|
||||
sseSessions.set(sessionId, sseTransport);
|
||||
// Clean up on disconnect
|
||||
res.on('close', () => {
|
||||
sseSessions.delete(sessionId);
|
||||
process.stderr.write(`[sse] session ${sessionId} closed\n`);
|
||||
});
|
||||
await server.connect(sseTransport);
|
||||
process.stderr.write(`[sse] session ${sessionId} established\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === '/messages' && req.method === 'POST') {
|
||||
const sessionId = url.searchParams.get('sessionId');
|
||||
if (!sessionId || !sseSessions.has(sessionId)) {
|
||||
res.statusCode = 400;
|
||||
res.setHeader('content-type', 'application/json');
|
||||
res.end(JSON.stringify({ error: `SSE session ${sessionId || '(missing)'} not found` }));
|
||||
return;
|
||||
}
|
||||
const sseTransport = sseSessions.get(sessionId);
|
||||
await sseTransport.handlePostMessage(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Unified /mcp transport (Streamable HTTP) ----
|
||||
// Must go last — catches any path that isn't /health /sse /messages
|
||||
await mcpTransport.handleRequest(req, res);
|
||||
process.stderr.write(`[mcp] ${req.method} ${url.pathname} -> ${res.statusCode}\n`);
|
||||
} catch (e) {
|
||||
process.stderr.write(`[HTTP-ERR] ${e.stack || e}\n`);
|
||||
if (!res.headersSent) {
|
||||
|
||||
Reference in new issue
Block a user