From a727671a30202a2daeb1e47668f8bf6ca9b2ed30 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Sun, 26 Oct 2025 15:16:41 +0530 Subject: [PATCH] fix: build is failing because of bad export --- apps/webapp/app/routes/api.v1.deep-search.tsx | 2 +- apps/webapp/app/trigger/utils/mcp.ts | 56 +++++++++ apps/webapp/server.js | 111 ++++++++++++++++++ 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 apps/webapp/server.js diff --git a/apps/webapp/app/routes/api.v1.deep-search.tsx b/apps/webapp/app/routes/api.v1.deep-search.tsx index b0cba95..6fa3b1a 100644 --- a/apps/webapp/app/routes/api.v1.deep-search.tsx +++ b/apps/webapp/app/routes/api.v1.deep-search.tsx @@ -36,7 +36,7 @@ const DeepSearchBodySchema = z.object({ .optional(), }); -export function createSearchMemoryTool(token: string) { +function createSearchMemoryTool(token: string) { return tool({ description: "Search the user's memory for relevant facts and episodes. Use this tool multiple times with different queries to gather comprehensive context.", diff --git a/apps/webapp/app/trigger/utils/mcp.ts b/apps/webapp/app/trigger/utils/mcp.ts index 1aaed85..4e64565 100644 --- a/apps/webapp/app/trigger/utils/mcp.ts +++ b/apps/webapp/app/trigger/utils/mcp.ts @@ -6,6 +6,62 @@ import * as path from "path"; import { prisma } from "./prisma"; +export const configureStdioMCPEnvironment = ( + spec: any, + account: any, +): { env: Record; args: any[] } => { + if (!spec.mcp) { + return { env: {}, args: [] }; + } + + const mcpSpec = spec.mcp; + const configuredMCP = { ...mcpSpec }; + + // Replace config placeholders in environment variables + if (configuredMCP.env) { + for (const [key, value] of Object.entries(configuredMCP.env)) { + if (typeof value === "string" && value.includes("${config:")) { + // Extract the config key from the placeholder + const configKey = value.match(/\$\{config:(.*?)\}/)?.[1]; + if ( + configKey && + account.integrationConfiguration && + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (account.integrationConfiguration as any)[configKey] + ) { + configuredMCP.env[key] = value.replace( + `\${config:${configKey}}`, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (account.integrationConfiguration as any)[configKey], + ); + } + } + + if (typeof value === "string" && value.includes("${integrationConfig:")) { + // Extract the config key from the placeholder + const configKey = value.match(/\$\{integrationConfig:(.*?)\}/)?.[1]; + if ( + configKey && + account.integrationDefinition.config && + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (account.integrationDefinition.config as any)[configKey] + ) { + configuredMCP.env[key] = value.replace( + `\${integrationConfig:${configKey}}`, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (account.integrationDefinition.config as any)[configKey], + ); + } + } + } + } + + return { + env: configuredMCP.env || {}, + args: Array.isArray(configuredMCP.args) ? configuredMCP.args : [], + }; +}; + export const fetchAndSaveStdioIntegrations = async () => { try { logger.info("Starting stdio integrations fetch and save process"); diff --git a/apps/webapp/server.js b/apps/webapp/server.js new file mode 100644 index 0000000..ae58f2c --- /dev/null +++ b/apps/webapp/server.js @@ -0,0 +1,111 @@ +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; +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 = viteDevServer + ? () => viteDevServer.ssrLoadModule("virtual:remix/server-build") + : await import("./build/server/index.js"); + const module = viteDevServer + ? (await build()).entry.module + : 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 module.authenticateHybridRequest(req, { + allowJWT: true, + }); + if (!authenticationResult) { + res.status(401).json({ error: "Authentication required" }); + return; + } + await module.handleSessionRequest(req, res, authenticationResult.userId); + }); + app.post("/api/v1/mcp", async (req, res) => { + const authenticationResult = await module.authenticateHybridRequest(req, { + 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 module.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 module.authenticateHybridRequest(req, { + allowJWT: true, + }); + if (!authenticationResult) { + res.status(401).json({ error: "Authentication required" }); + return; + } + await module.handleSessionRequest(req, res, authenticationResult.userId); + }); + 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);