Initial commit
This commit is contained in:
234
mcp-server/httpServer.sse.backup
Normal file
234
mcp-server/httpServer.sse.backup
Normal file
@@ -0,0 +1,234 @@
|
||||
import http from "node:http";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import { MCP_PORT } from "./config/index.js";
|
||||
import {
|
||||
sessionCredentials,
|
||||
sessionApiClients,
|
||||
clearSessionCredentials
|
||||
} from "./auth/index.js";
|
||||
|
||||
// Active SSE sessions
|
||||
const activeSessions = new Map();
|
||||
|
||||
/**
|
||||
* Create and start the MCP HTTP server for SSE transport
|
||||
*/
|
||||
export function startHttpServer(server) {
|
||||
const mcpHttpServer = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
||||
|
||||
// Handle SSE connection (GET /sse)
|
||||
if (req.method === "GET" && url.pathname === "/sse") {
|
||||
console.error(`[MCP HTTP] GET /sse - New SSE connection from ${req.headers.origin || req.socket.remoteAddress}`);
|
||||
|
||||
// Extract credentials from headers
|
||||
const token = req.headers['x-acai-token'];
|
||||
const tokenHash = req.headers['x-acai-token-hash'];
|
||||
const website = req.headers['x-acai-website'];
|
||||
|
||||
// Create SSE transport
|
||||
const transport = new SSEServerTransport("/message", res);
|
||||
const sessionId = transport.sessionId;
|
||||
|
||||
// Wrap the send method to add logging and fix SSE format
|
||||
const originalSend = transport.send.bind(transport);
|
||||
transport.send = async (message) => {
|
||||
try {
|
||||
if (!transport._sseResponse) {
|
||||
throw new Error('Not connected');
|
||||
}
|
||||
|
||||
// Serialize message and ensure it's valid for SSE format
|
||||
const messageJson = JSON.stringify(message);
|
||||
console.error(`[MCP HTTP] SSE.send - Session ${sessionId} - Sending message (${messageJson.substring(0, 100)}...)`);
|
||||
|
||||
// SSE format: properly handle multiline data
|
||||
// If JSON contains newlines, each line must be prefixed with "data: "
|
||||
const lines = messageJson.split('\n');
|
||||
let sseData = 'event: message\n';
|
||||
if (lines.length === 1) {
|
||||
// Single line JSON
|
||||
sseData += `data: ${messageJson}\n\n`;
|
||||
} else {
|
||||
// Multiline JSON - prefix each line
|
||||
for (const line of lines) {
|
||||
sseData += `data: ${line}\n`;
|
||||
}
|
||||
sseData += '\n';
|
||||
}
|
||||
|
||||
transport._sseResponse.write(sseData);
|
||||
console.error(`[MCP HTTP] SSE.send - Session ${sessionId} - Message sent successfully (${sseData.length} bytes)`);
|
||||
} catch (error) {
|
||||
console.error(`[MCP HTTP] SSE.send - Session ${sessionId} - ERROR sending message:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
console.error(`[MCP HTTP] GET /sse - Session ${sessionId} created`);
|
||||
|
||||
// Store credentials for this session
|
||||
if (token && website) {
|
||||
sessionCredentials.set(sessionId, {
|
||||
token,
|
||||
tokenHash: tokenHash || null,
|
||||
website,
|
||||
profileName: 'http-session'
|
||||
});
|
||||
console.error(`[MCP HTTP] GET /sse - Session ${sessionId} authenticated for ${website}`);
|
||||
} else {
|
||||
console.warn(`[MCP HTTP] GET /sse - Session ${sessionId} started without credentials`);
|
||||
}
|
||||
|
||||
// Store session
|
||||
activeSessions.set(sessionId, transport);
|
||||
|
||||
// Handle transport close
|
||||
transport.onclose = () => {
|
||||
console.error(`[MCP HTTP] GET /sse - Session ${sessionId} closed`);
|
||||
activeSessions.delete(sessionId);
|
||||
clearSessionCredentials(sessionId);
|
||||
};
|
||||
|
||||
// Connect server to transport
|
||||
try {
|
||||
console.error(`[MCP HTTP] GET /sse - Session ${sessionId} connecting server to transport...`);
|
||||
await server.connect(transport);
|
||||
console.error(`[MCP HTTP] GET /sse - Session ${sessionId} server connected successfully`);
|
||||
} catch (error) {
|
||||
console.error(`[MCP HTTP] GET /sse - Session ${sessionId} ERROR:`, error);
|
||||
activeSessions.delete(sessionId);
|
||||
clearSessionCredentials(sessionId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle POST messages (POST /message?sessionId=xxx)
|
||||
if (req.method === "POST" && url.pathname === "/message") {
|
||||
const sessionId = url.searchParams.get("sessionId");
|
||||
|
||||
if (!sessionId) {
|
||||
res.writeHead(400, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: "Missing session ID" }));
|
||||
return;
|
||||
}
|
||||
|
||||
const transport = activeSessions.get(sessionId);
|
||||
if (!transport) {
|
||||
res.writeHead(404, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: "Session not found" }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read request body
|
||||
let body = "";
|
||||
req.on("data", (chunk) => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
|
||||
req.on("end", async () => {
|
||||
try {
|
||||
console.error(`[MCP HTTP] POST /message - Session ${sessionId} - Received request, parsing...`);
|
||||
const parsedBody = JSON.parse(body);
|
||||
console.error(`[MCP HTTP] POST /message - Session ${sessionId} - Parsed body: ${JSON.stringify(parsedBody).substring(0, 100)}...`);
|
||||
|
||||
// Track when handlePostMessage starts and ends
|
||||
const beforeTime = Date.now();
|
||||
await transport.handlePostMessage(req, res, parsedBody);
|
||||
const afterTime = Date.now();
|
||||
|
||||
console.error(`[MCP HTTP] POST /message - Session ${sessionId} - handlePostMessage completed (took ${afterTime - beforeTime}ms)`);
|
||||
console.error(`[MCP HTTP] POST /message - Session ${sessionId} - Response headersSent: ${res.headersSent}, writableEnded: ${res.writableEnded}`);
|
||||
} catch (error) {
|
||||
console.error(`[MCP HTTP] POST /message - Session ${sessionId} - ERROR:`, error.message);
|
||||
console.error(`[MCP HTTP] POST /message - Session ${sessionId} - Stack:`, error.stack);
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Health check
|
||||
if (req.method === "GET" && url.pathname === "/health") {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({
|
||||
status: "ok",
|
||||
activeSessions: activeSessions.size,
|
||||
mode: "sse"
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve generated images from temp folder
|
||||
if (req.method === "GET" && url.pathname.startsWith("/generated-images/")) {
|
||||
const fileId = url.pathname.split("/generated-images/")[1];
|
||||
if (!fileId) {
|
||||
res.writeHead(400, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: "File ID required" }));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const tempDir = process.env.GENERATED_IMAGES_TEMP_DIR || path.join(os.tmpdir(), 'generated-images');
|
||||
const filename = `generated-${fileId}.jpg`;
|
||||
const filePath = path.join(tempDir, filename);
|
||||
|
||||
// Security: ensure file is within temp directory (prevent path traversal)
|
||||
const resolvedPath = path.resolve(filePath);
|
||||
const resolvedTempDir = path.resolve(tempDir);
|
||||
if (!resolvedPath.startsWith(resolvedTempDir)) {
|
||||
res.writeHead(403, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: "Access denied" }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
res.writeHead(404, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: "File not found" }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read and serve file
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "image/jpeg",
|
||||
"Content-Length": fileBuffer.length,
|
||||
"Cache-Control": "public, max-age=3600"
|
||||
});
|
||||
res.end(fileBuffer);
|
||||
return;
|
||||
} catch (error) {
|
||||
res.writeHead(500, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 404 for other routes
|
||||
res.writeHead(404, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ error: "Not found" }));
|
||||
});
|
||||
|
||||
mcpHttpServer.on("error", (error) => {
|
||||
console.error(`[MCP] HTTP server error:`, error);
|
||||
});
|
||||
|
||||
mcpHttpServer.listen(MCP_PORT, () => {
|
||||
console.error(`[MCP] SSE server listening on http://localhost:${MCP_PORT}/sse`);
|
||||
console.error(`[MCP] Clients should connect to: http://localhost:${MCP_PORT}/sse`);
|
||||
console.error(`[MCP] Provide credentials via headers: X-Acai-Token, X-Acai-Website, X-Acai-Token-Hash`);
|
||||
});
|
||||
|
||||
return mcpHttpServer;
|
||||
}
|
||||
|
||||
export { activeSessions };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user