From 3207959af06d8644ca7ef23c6287b59c19995b06 Mon Sep 17 00:00:00 2001 From: Jerome Date: Fri, 13 Jun 2025 16:04:25 +0100 Subject: [PATCH] Linting --- client/src/App.tsx | 4 +- client/src/lib/hooks/useConnection.ts | 27 +- server/src/index.ts | 529 ++++++++++++++------------ 3 files changed, 310 insertions(+), 250 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 8aca8697..054a4f69 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -349,9 +349,9 @@ const App = () => { const headers: HeadersInit = {}; const proxyAuthToken = getMCPProxyAuthToken(config); if (proxyAuthToken) { - headers['Authorization'] = `Bearer ${proxyAuthToken}`; + headers["Authorization"] = `Bearer ${proxyAuthToken}`; } - + fetch(`${getMCPProxyAddress(config)}/config`, { headers }) .then((response) => response.json()) .then((data) => { diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index c2eccb72..472b391f 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -246,7 +246,7 @@ export function useConnection({ const proxyAuthToken = getMCPProxyAuthToken(config); const headers: HeadersInit = {}; if (proxyAuthToken) { - headers['Authorization'] = `Bearer ${proxyAuthToken}`; + headers["Authorization"] = `Bearer ${proxyAuthToken}`; } const proxyHealthResponse = await fetch(proxyHealthUrl, { headers }); const proxyHealth = await proxyHealthResponse.json(); @@ -269,7 +269,7 @@ export function useConnection({ const isProxyAuthError = (error: unknown): boolean => { return ( - error instanceof Error && + error instanceof Error && error.message.includes("Authentication required. Use the session token") ); }; @@ -335,7 +335,7 @@ export function useConnection({ const proxyAuthToken = getMCPProxyAuthToken(config); const proxyHeaders: HeadersInit = {}; if (proxyAuthToken) { - proxyHeaders['Authorization'] = `Bearer ${proxyAuthToken}`; + proxyHeaders["Authorization"] = `Bearer ${proxyAuthToken}`; } // Create appropriate transport @@ -356,7 +356,11 @@ export function useConnection({ fetch: ( url: string | URL | globalThis.Request, init: RequestInit | undefined, - ) => fetch(url, { ...init, headers: { ...headers, ...proxyHeaders } }), + ) => + fetch(url, { + ...init, + headers: { ...headers, ...proxyHeaders }, + }), }, requestInit: { headers: { ...headers, ...proxyHeaders }, @@ -372,7 +376,11 @@ export function useConnection({ fetch: ( url: string | URL | globalThis.Request, init: RequestInit | undefined, - ) => fetch(url, { ...init, headers: { ...headers, ...proxyHeaders } }), + ) => + fetch(url, { + ...init, + headers: { ...headers, ...proxyHeaders }, + }), }, requestInit: { headers: { ...headers, ...proxyHeaders }, @@ -388,7 +396,11 @@ export function useConnection({ fetch: ( url: string | URL | globalThis.Request, init: RequestInit | undefined, - ) => fetch(url, { ...init, headers: { ...headers, ...proxyHeaders } }), + ) => + fetch(url, { + ...init, + headers: { ...headers, ...proxyHeaders }, + }), }, requestInit: { headers: { ...headers, ...proxyHeaders }, @@ -471,7 +483,8 @@ export function useConnection({ if (isProxyAuthError(error)) { toast({ title: "Proxy Authentication Required", - description: "Please enter the session token from the proxy server console in the Configuration settings.", + description: + "Please enter the session token from the proxy server console in the Configuration settings.", variant: "destructive", }); setConnectionStatus("error"); diff --git a/server/src/index.ts b/server/src/index.ts index a6ca2f42..9afdb1c6 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -89,46 +89,57 @@ app.use((req, res, next) => { const webAppTransports: Map = new Map(); // Web app transports by web app sessionId const serverTransports: Map = new Map(); // Server Transports by web app sessionId -const sessionToken = randomBytes(32).toString('hex'); +const sessionToken = randomBytes(32).toString("hex"); const authDisabled = !!process.env.DANGEROUSLY_OMIT_AUTH; // Origin validation middleware to prevent DNS rebinding attacks -const originValidationMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { +const originValidationMiddleware = ( + req: express.Request, + res: express.Response, + next: express.NextFunction, +) => { const origin = req.headers.origin; - + // Default origins based on CLIENT_PORT or use environment variable - const clientPort = process.env.CLIENT_PORT || '6274'; + const clientPort = process.env.CLIENT_PORT || "6274"; const defaultOrigins = [ `http://localhost:${clientPort}`, - `http://127.0.0.1:${clientPort}` + `http://127.0.0.1:${clientPort}`, ]; - const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || defaultOrigins; - + const allowedOrigins = + process.env.ALLOWED_ORIGINS?.split(",") || defaultOrigins; + if (origin && !allowedOrigins.includes(origin)) { console.error(`Invalid origin: ${origin}`); - res.status(403).json({ - error: 'Forbidden - invalid origin', - message: 'Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.' + res.status(403).json({ + error: "Forbidden - invalid origin", + message: + "Request blocked to prevent DNS rebinding attacks. Configure allowed origins via environment variable.", }); return; } next(); }; -const authMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { +const authMiddleware = ( + req: express.Request, + res: express.Response, + next: express.NextFunction, +) => { if (authDisabled) { return next(); } const sendUnauthorized = () => { - res.status(401).json({ - error: "Unauthorized", - message: "Authentication required. Use the session token shown in the console when starting the server." + res.status(401).json({ + error: "Unauthorized", + message: + "Authentication required. Use the session token shown in the console when starting the server.", }); }; const authHeader = req.headers.authorization; - if (!authHeader || !authHeader.startsWith('Bearer ')) { + if (!authHeader || !authHeader.startsWith("Bearer ")) { sendUnauthorized(); return; } @@ -151,7 +162,7 @@ const authMiddleware = (req: express.Request, res: express.Response, next: expre sendUnauthorized(); return; } - + next(); }; @@ -216,38 +227,152 @@ const createTransport = async (req: express.Request): Promise => { } }; -app.get("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { - const sessionId = req.headers["mcp-session-id"] as string; - console.log(`Received GET message for sessionId ${sessionId}`); - try { - const transport = webAppTransports.get( - sessionId, - ) as StreamableHTTPServerTransport; - if (!transport) { - res.status(404).end("Session not found"); - return; - } else { - await transport.handleRequest(req, res); - } - } catch (error) { - console.error("Error in /mcp route:", error); - res.status(500).json(error); - } -}); - -app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { - const sessionId = req.headers["mcp-session-id"] as string | undefined; - let serverTransport: Transport | undefined; - if (!sessionId) { +app.get( + "/mcp", + originValidationMiddleware, + authMiddleware, + async (req, res) => { + const sessionId = req.headers["mcp-session-id"] as string; + console.log(`Received GET message for sessionId ${sessionId}`); try { - console.log("New StreamableHttp connection request"); + const transport = webAppTransports.get( + sessionId, + ) as StreamableHTTPServerTransport; + if (!transport) { + res.status(404).end("Session not found"); + return; + } else { + await transport.handleRequest(req, res); + } + } catch (error) { + console.error("Error in /mcp route:", error); + res.status(500).json(error); + } + }, +); + +app.post( + "/mcp", + originValidationMiddleware, + authMiddleware, + async (req, res) => { + const sessionId = req.headers["mcp-session-id"] as string | undefined; + let serverTransport: Transport | undefined; + if (!sessionId) { + try { + console.log("New StreamableHttp connection request"); + try { + serverTransport = await createTransport(req); + } catch (error) { + if (error instanceof SseError && error.code === 401) { + console.error( + "Received 401 Unauthorized from MCP server:", + error.message, + ); + res.status(401).json(error); + return; + } + + throw error; + } + + console.log("Created StreamableHttp server transport"); + + const webAppTransport = new StreamableHTTPServerTransport({ + sessionIdGenerator: randomUUID, + onsessioninitialized: (sessionId) => { + webAppTransports.set(sessionId, webAppTransport); + serverTransports.set(sessionId, serverTransport!); + console.log("Client <-> Proxy sessionId: " + sessionId); + }, + }); + console.log("Created StreamableHttp client transport"); + + await webAppTransport.start(); + + mcpProxy({ + transportToClient: webAppTransport, + transportToServer: serverTransport, + }); + + await (webAppTransport as StreamableHTTPServerTransport).handleRequest( + req, + res, + req.body, + ); + } catch (error) { + console.error("Error in /mcp POST route:", error); + res.status(500).json(error); + } + } else { + console.log(`Received POST message for sessionId ${sessionId}`); + try { + const transport = webAppTransports.get( + sessionId, + ) as StreamableHTTPServerTransport; + if (!transport) { + res.status(404).end("Transport not found for sessionId " + sessionId); + } else { + await (transport as StreamableHTTPServerTransport).handleRequest( + req, + res, + ); + } + } catch (error) { + console.error("Error in /mcp route:", error); + res.status(500).json(error); + } + } + }, +); + +app.delete( + "/mcp", + originValidationMiddleware, + authMiddleware, + async (req, res) => { + const sessionId = req.headers["mcp-session-id"] as string | undefined; + console.log(`Received DELETE message for sessionId ${sessionId}`); + let serverTransport: Transport | undefined; + if (sessionId) { + try { + serverTransport = serverTransports.get( + sessionId, + ) as StreamableHTTPClientTransport; + if (!serverTransport) { + res.status(404).end("Transport not found for sessionId " + sessionId); + } else { + await ( + serverTransport as StreamableHTTPClientTransport + ).terminateSession(); + webAppTransports.delete(sessionId); + serverTransports.delete(sessionId); + console.log(`Transports removed for sessionId ${sessionId}`); + } + res.status(200).end(); + } catch (error) { + console.error("Error in /mcp route:", error); + res.status(500).json(error); + } + } + }, +); + +app.get( + "/stdio", + originValidationMiddleware, + authMiddleware, + async (req, res) => { + try { + console.log("New STDIO connection request"); + let serverTransport: Transport | undefined; try { serverTransport = await createTransport(req); + console.log("Created server transport"); } catch (error) { if (error instanceof SseError && error.code === 401) { console.error( - "Received 401 Unauthorized from MCP server:", - error.message, + "Received 401 Unauthorized from MCP server. Authentication failure.", ); res.status(401).json(error); return; @@ -256,211 +381,127 @@ app.post("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => throw error; } - console.log("Created StreamableHttp server transport"); - - const webAppTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: randomUUID, - onsessioninitialized: (sessionId) => { - webAppTransports.set(sessionId, webAppTransport); - serverTransports.set(sessionId, serverTransport!); - console.log("Client <-> Proxy sessionId: " + sessionId); - }, - }); - console.log("Created StreamableHttp client transport"); - - await webAppTransport.start(); - - mcpProxy({ - transportToClient: webAppTransport, - transportToServer: serverTransport, - }); - - await (webAppTransport as StreamableHTTPServerTransport).handleRequest( - req, - res, - req.body, - ); - } catch (error) { - console.error("Error in /mcp POST route:", error); - res.status(500).json(error); - } - } else { - console.log(`Received POST message for sessionId ${sessionId}`); - try { - const transport = webAppTransports.get( - sessionId, - ) as StreamableHTTPServerTransport; - if (!transport) { - res.status(404).end("Transport not found for sessionId " + sessionId); - } else { - await (transport as StreamableHTTPServerTransport).handleRequest( - req, - res, - ); - } - } catch (error) { - console.error("Error in /mcp route:", error); - res.status(500).json(error); - } - } -}); - -app.delete("/mcp", originValidationMiddleware, authMiddleware, async (req, res) => { - const sessionId = req.headers["mcp-session-id"] as string | undefined; - console.log(`Received DELETE message for sessionId ${sessionId}`); - let serverTransport: Transport | undefined; - if (sessionId) { - try { - serverTransport = serverTransports.get( - sessionId, - ) as StreamableHTTPClientTransport; - if (!serverTransport) { - res.status(404).end("Transport not found for sessionId " + sessionId); - } else { - await ( - serverTransport as StreamableHTTPClientTransport - ).terminateSession(); - webAppTransports.delete(sessionId); - serverTransports.delete(sessionId); - console.log(`Transports removed for sessionId ${sessionId}`); - } - res.status(200).end(); - } catch (error) { - console.error("Error in /mcp route:", error); - res.status(500).json(error); - } - } -}); - -app.get("/stdio", originValidationMiddleware, authMiddleware, async (req, res) => { - try { - console.log("New STDIO connection request"); - let serverTransport: Transport | undefined; - try { - serverTransport = await createTransport(req); - console.log("Created server transport"); - } catch (error) { - if (error instanceof SseError && error.code === 401) { - console.error( - "Received 401 Unauthorized from MCP server. Authentication failure.", - ); - res.status(401).json(error); - return; - } - - throw error; - } - - const webAppTransport = new SSEServerTransport("/message", res); - console.log("Created client transport"); - - webAppTransports.set(webAppTransport.sessionId, webAppTransport); - serverTransports.set(webAppTransport.sessionId, serverTransport); - - await webAppTransport.start(); - - (serverTransport as StdioClientTransport).stderr!.on("data", (chunk) => { - if (chunk.toString().includes("MODULE_NOT_FOUND")) { - webAppTransport.send({ - jsonrpc: "2.0", - method: "notifications/stderr", - params: { - content: "Command not found, transports removed", - }, - }); - webAppTransport.close(); - serverTransport.close(); - webAppTransports.delete(webAppTransport.sessionId); - serverTransports.delete(webAppTransport.sessionId); - console.error("Command not found, transports removed"); - } else { - webAppTransport.send({ - jsonrpc: "2.0", - method: "notifications/stderr", - params: { - content: chunk.toString(), - }, - }); - } - }); - - mcpProxy({ - transportToClient: webAppTransport, - transportToServer: serverTransport, - }); - } catch (error) { - console.error("Error in /stdio route:", error); - res.status(500).json(error); - } -}); - -app.get("/sse", originValidationMiddleware, authMiddleware, async (req, res) => { - try { - console.log( - "New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp", - ); - let serverTransport: Transport | undefined; - try { - serverTransport = await createTransport(req); - } catch (error) { - if (error instanceof SseError && error.code === 401) { - console.error( - "Received 401 Unauthorized from MCP server. Authentication failure.", - ); - res.status(401).json(error); - return; - } else if (error instanceof SseError && error.code === 404) { - console.error( - "Received 404 not found from MCP server. Does the MCP server support SSE?", - ); - res.status(404).json(error); - return; - } else if (JSON.stringify(error).includes("ECONNREFUSED")) { - console.error("Connection refused. Is the MCP server running?"); - res.status(500).json(error); - } else { - throw error; - } - } - - if (serverTransport) { const webAppTransport = new SSEServerTransport("/message", res); - webAppTransports.set(webAppTransport.sessionId, webAppTransport); console.log("Created client transport"); - serverTransports.set(webAppTransport.sessionId, serverTransport!); - console.log("Created server transport"); + + webAppTransports.set(webAppTransport.sessionId, webAppTransport); + serverTransports.set(webAppTransport.sessionId, serverTransport); await webAppTransport.start(); + (serverTransport as StdioClientTransport).stderr!.on("data", (chunk) => { + if (chunk.toString().includes("MODULE_NOT_FOUND")) { + webAppTransport.send({ + jsonrpc: "2.0", + method: "notifications/stderr", + params: { + content: "Command not found, transports removed", + }, + }); + webAppTransport.close(); + serverTransport.close(); + webAppTransports.delete(webAppTransport.sessionId); + serverTransports.delete(webAppTransport.sessionId); + console.error("Command not found, transports removed"); + } else { + webAppTransport.send({ + jsonrpc: "2.0", + method: "notifications/stderr", + params: { + content: chunk.toString(), + }, + }); + } + }); + mcpProxy({ transportToClient: webAppTransport, transportToServer: serverTransport, }); + } catch (error) { + console.error("Error in /stdio route:", error); + res.status(500).json(error); } - } catch (error) { - console.error("Error in /sse route:", error); - res.status(500).json(error); - } -}); + }, +); -app.post("/message", originValidationMiddleware, authMiddleware, async (req, res) => { - try { - const sessionId = req.query.sessionId; - console.log(`Received POST message for sessionId ${sessionId}`); +app.get( + "/sse", + originValidationMiddleware, + authMiddleware, + async (req, res) => { + try { + console.log( + "New SSE connection request. NOTE: The sse transport is deprecated and has been replaced by StreamableHttp", + ); + let serverTransport: Transport | undefined; + try { + serverTransport = await createTransport(req); + } catch (error) { + if (error instanceof SseError && error.code === 401) { + console.error( + "Received 401 Unauthorized from MCP server. Authentication failure.", + ); + res.status(401).json(error); + return; + } else if (error instanceof SseError && error.code === 404) { + console.error( + "Received 404 not found from MCP server. Does the MCP server support SSE?", + ); + res.status(404).json(error); + return; + } else if (JSON.stringify(error).includes("ECONNREFUSED")) { + console.error("Connection refused. Is the MCP server running?"); + res.status(500).json(error); + } else { + throw error; + } + } - const transport = webAppTransports.get( - sessionId as string, - ) as SSEServerTransport; - if (!transport) { - res.status(404).end("Session not found"); - return; + if (serverTransport) { + const webAppTransport = new SSEServerTransport("/message", res); + webAppTransports.set(webAppTransport.sessionId, webAppTransport); + console.log("Created client transport"); + serverTransports.set(webAppTransport.sessionId, serverTransport!); + console.log("Created server transport"); + + await webAppTransport.start(); + + mcpProxy({ + transportToClient: webAppTransport, + transportToServer: serverTransport, + }); + } + } catch (error) { + console.error("Error in /sse route:", error); + res.status(500).json(error); } - await transport.handlePostMessage(req, res); - } catch (error) { - console.error("Error in /message route:", error); - res.status(500).json(error); - } -}); + }, +); + +app.post( + "/message", + originValidationMiddleware, + authMiddleware, + async (req, res) => { + try { + const sessionId = req.query.sessionId; + console.log(`Received POST message for sessionId ${sessionId}`); + + const transport = webAppTransports.get( + sessionId as string, + ) as SSEServerTransport; + if (!transport) { + res.status(404).end("Session not found"); + return; + } + await transport.handlePostMessage(req, res); + } catch (error) { + console.error("Error in /message route:", error); + res.status(500).json(error); + } + }, +); app.get("/health", (req, res) => { res.json({ @@ -481,22 +522,28 @@ app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => { } }); -const PORT = parseInt(process.env.PORT || '6277', 10); -const HOST = process.env.HOST || '127.0.0.1'; +const PORT = parseInt(process.env.PORT || "6277", 10); +const HOST = process.env.HOST || "127.0.0.1"; const server = app.listen(PORT, HOST); server.on("listening", () => { console.log(`āš™ļø Proxy server listening on ${HOST}:${PORT}`); if (!authDisabled) { console.log(`šŸ”‘ Session token: ${sessionToken}`); - console.log(`Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`); - + console.log( + `Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth`, + ); + // Display clickable URL with pre-filled token - const clientPort = process.env.CLIENT_PORT || '6274'; + const clientPort = process.env.CLIENT_PORT || "6274"; const clientUrl = `http://localhost:${clientPort}/?MCP_PROXY_AUTH_TOKEN=${sessionToken}`; - console.log(`\nšŸ”— Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`); + console.log( + `\nšŸ”— Open inspector with token pre-filled:\n ${clientUrl}\n (Auto-open is disabled when authentication is enabled)\n`, + ); } else { - console.log(`āš ļø WARNING: Authentication is disabled. This is not recommended.`); + console.log( + `āš ļø WARNING: Authentication is disabled. This is not recommended.`, + ); } }); server.on("error", (err) => {