feat: add HTTP/SSE transport for remote MCP via Cloudflare Tunnel
- TRANSPORT=http starts HTTP server on PORT (default 3100) - SSE endpoint at /sse, message endpoint at /messages - Health check at /health - TRANSPORT=stdio (default) unchanged for local Claude Code usage - Fix: unused _format variable in matcher.ts
This commit is contained in:
parent
65edff142b
commit
d3250b95bb
4209
package-lock.json
generated
Normal file
4209
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,10 @@
|
|||||||
* @see https://modelcontextprotocol.io
|
* @see https://modelcontextprotocol.io
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as http from "http";
|
||||||
|
|
||||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import {
|
import {
|
||||||
CallToolRequestSchema,
|
CallToolRequestSchema,
|
||||||
@ -234,13 +237,72 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Start server
|
// Start server — stdio (local) or http (remote / Cloudflare Tunnel)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function startHttp(): Promise<void> {
|
||||||
const transport = new StdioServerTransport();
|
const port = parseInt(process.env["PORT"] ?? "3100", 10);
|
||||||
|
// Track active SSE transports by session id
|
||||||
|
const sessions: Record<string, SSEServerTransport> = {};
|
||||||
|
|
||||||
|
const httpServer = http.createServer(async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (req.url === "/health" && req.method === "GET") {
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ status: "ok", service: "PaperCortex", transport: "http" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSE endpoint — client opens a long-lived connection
|
||||||
|
if (req.url === "/sse" && req.method === "GET") {
|
||||||
|
const transport = new SSEServerTransport("/messages", res);
|
||||||
|
sessions[transport.sessionId] = transport;
|
||||||
|
res.on("close", () => {
|
||||||
|
delete sessions[transport.sessionId];
|
||||||
|
});
|
||||||
await server.connect(transport);
|
await server.connect(transport);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message endpoint — client posts tool calls here
|
||||||
|
if (req.url?.startsWith("/messages") && req.method === "POST") {
|
||||||
|
const sessionId = new URL(req.url, `http://localhost`).searchParams.get("sessionId");
|
||||||
|
const transport = sessionId ? sessions[sessionId] : undefined;
|
||||||
|
if (transport) {
|
||||||
|
await transport.handlePostMessage(req, res);
|
||||||
|
} else {
|
||||||
|
res.writeHead(400, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ error: "Session not found" }));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end("Not found");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("HTTP handler error:", err);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.writeHead(500);
|
||||||
|
res.end("Internal error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
httpServer.listen(port, () => {
|
||||||
|
console.error(`PaperCortex MCP Server running on HTTP port ${port} (SSE at /sse)`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const transport = (process.env["TRANSPORT"] ?? "stdio").toLowerCase();
|
||||||
|
|
||||||
|
if (transport === "http") {
|
||||||
|
await startHttp();
|
||||||
|
} else {
|
||||||
|
const stdioTransport = new StdioServerTransport();
|
||||||
|
await server.connect(stdioTransport);
|
||||||
console.error("PaperCortex MCP Server running on stdio");
|
console.error("PaperCortex MCP Server running on stdio");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
main().catch((error) => {
|
||||||
|
|||||||
@ -96,7 +96,7 @@ export function createTransactionMatcher(): TransactionMatcher {
|
|||||||
// TODO: Add support for different CSV delimiters (semicolon for German exports)
|
// TODO: Add support for different CSV delimiters (semicolon for German exports)
|
||||||
// TODO: Handle different date formats (DD.MM.YYYY, YYYY-MM-DD, MM/DD/YYYY)
|
// TODO: Handle different date formats (DD.MM.YYYY, YYYY-MM-DD, MM/DD/YYYY)
|
||||||
|
|
||||||
const _format = format; // Acknowledge format parameter for future use
|
void format; // reserved for future format auto-detection
|
||||||
|
|
||||||
const records = parse(raw, {
|
const records = parse(raw, {
|
||||||
columns: true,
|
columns: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user