#!/usr/bin/env node
import express from "express";
import cors from "cors";
import rateLimit from "express-rate-limit";
import { randomUUID } from "crypto";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { createServer } from "./server.js";
const app = express();
// Configure trust proxy properly for Coolify/reverse proxy
app.set("trust proxy", 1);
app.use(cors());
app.use(express.json());
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 1000,
// Validate that requests come from trusted proxy
validate: { trustProxy: false }
}));
const httpSessions = new Map<string, { transport: StreamableHTTPServerTransport; lastUsed: number }>();
setInterval(() => {
const now = Date.now();
const timeout = 30 * 60 * 1000;
for (const [id, s] of httpSessions) {
if (now - s.lastUsed > timeout) {
s.transport.close().catch(() => {});
httpSessions.delete(id);
}
}
}, 5 * 60 * 1000);
app.get("/", (_, res) => res.send("<h1>Apple Shortcuts MCP Server</h1><p>Endpoints: /mcp (streamable-http), /health</p>"));
app.post("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
if (sessionId && httpSessions.has(sessionId)) {
const session = httpSessions.get(sessionId)!;
session.lastUsed = Date.now();
return await session.transport.handleRequest(req, res, req.body);
}
if (!isInitializeRequest(req.body)) {
return res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Invalid request" }, id: req.body?.id ?? null });
}
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: true,
onsessioninitialized: (id: string) => { httpSessions.set(id, { transport, lastUsed: Date.now() }); },
onsessionclosed: (id: string) => { httpSessions.delete(id); }
});
transport.onclose = () => { if (transport.sessionId) httpSessions.delete(transport.sessionId); };
await createServer().connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.get("/mcp", async (req, res) => {
const sessionId = req.headers["mcp-session-id"] as string;
if (!sessionId) return res.json({ transport: "streamable-http" });
const session = httpSessions.get(sessionId);
if (!session) return res.status(400).json({ error: "No session" });
session.lastUsed = Date.now();
await session.transport.handleRequest(req, res);
});
app.delete("/mcp", async (req, res) => {
const session = httpSessions.get(req.headers["mcp-session-id"] as string);
if (session) { await session.transport.close(); httpSessions.delete(req.headers["mcp-session-id"] as string); }
res.json({ ok: true });
});
app.get("/health", (_, res) => res.json({ status: "ok", sessions: httpSessions.size }));
app.listen(process.env.PORT || 3000, () => console.log(`Server running on port ${process.env.PORT || 3000}`));