import { createRequestHandler } from "@remix-run/express"; import compression from "compression"; import express from "express"; import morgan from "morgan"; // import { // handleMCPRequest, // handleSessionRequest, // } from "~/services/mcp.server"; // import { authenticateHybridRequest } from "~/services/routeBuilders/apiBuilder.server"; let viteDevServer: any; let remixHandler; async function init() { if (process.env.NODE_ENV !== "production") { const vite = await import("vite"); viteDevServer = await vite.createServer({ server: { middlewareMode: true }, }); } const build: any = viteDevServer ? () => viteDevServer.ssrLoadModule("virtual:remix/server-build") : await import("./build/server/index.js"); const { authenticateHybridRequest, handleMCPRequest, handleSessionRequest } = build.entry.module; remixHandler = createRequestHandler({ build }); const app = express(); app.use(compression()); // http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header app.disable("x-powered-by"); // handle asset requests if (viteDevServer) { app.use(viteDevServer.middlewares); } else { // Vite fingerprints its assets so we can cache forever. app.use( "/assets", express.static("build/client/assets", { immutable: true, maxAge: "1y" }), ); } // Everything else (like favicon.ico) is cached for an hour. You may want to be // more aggressive with this caching. app.use(express.static("build/client", { maxAge: "1h" })); app.use(morgan("tiny")); app.get("/api/v1/mcp", async (req, res) => { const authenticationResult = await authenticateHybridRequest(req as any, { allowJWT: true, }); if (!authenticationResult) { res.status(401).json({ error: "Authentication required" }); return; } await handleSessionRequest(req, res); }); app.post("/api/v1/mcp", async (req, res) => { const authenticationResult = await authenticateHybridRequest(req as any, { allowJWT: true, }); if (!authenticationResult) { res.status(401).json({ error: "Authentication required" }); return; } let body = ""; req.on("data", (chunk) => { body += chunk; }); req.on("end", async () => { try { const parsedBody = JSON.parse(body); const queryParams = req.query; // Get query parameters from the request await handleMCPRequest( req, res, parsedBody, authenticationResult, queryParams, ); } catch (error) { res.status(400).json({ error: "Invalid JSON" }); } }); }); app.delete("/api/v1/mcp", async (req, res) => { const authenticationResult = await authenticateHybridRequest(req as any, { allowJWT: true, }); if (!authenticationResult) { res.status(401).json({ error: "Authentication required" }); return; } await handleSessionRequest(req, res); }); app.options("/api/v1/mcp", (_, res) => { res.json({}); }); app.get("/.well-known/oauth-authorization-server", (req, res) => { res.json({ issuer: process.env.APP_ORIGIN, authorization_endpoint: `${process.env.APP_ORIGIN}/oauth/authorize`, token_endpoint: `${process.env.APP_ORIGIN}/oauth/token`, registration_endpoint: `${process.env.APP_ORIGIN}/oauth/register`, scopes_supported: ["mcp"], response_types_supported: ["code"], grant_types_supported: [ "authorization_code", "refresh_token", "client_credentials", ], code_challenge_methods_supported: ["S256", "plain"], token_endpoint_auth_methods_supported: [ "client_secret_basic", "none", "client_secret_post", ], }); }); // handle SSR requests app.all("*", remixHandler); const port = process.env.REMIX_APP_PORT || 3000; app.listen(port, () => console.log(`Express server listening at http://localhost:${port}`), ); } init().catch(console.error);