From 3a1e5d0e6e2e7d13a010718fc6508d6d551015ff Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 21:56:02 +0000 Subject: [PATCH 01/27] Apply patch [skip ci] --- apps/web/src/types/mcp.ts | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 apps/web/src/types/mcp.ts diff --git a/apps/web/src/types/mcp.ts b/apps/web/src/types/mcp.ts new file mode 100644 index 000000000..ce4d51549 --- /dev/null +++ b/apps/web/src/types/mcp.ts @@ -0,0 +1,70 @@ +import { Tool } from "./tool"; + +// Common fields for all MCP server configurations +export interface MCPServerConfig { + defaultToolTimeout?: number; + outputHandling?: + | "content" + | "artifact" + | { + audio?: "content" | "artifact"; + image?: "content" | "artifact"; + resource?: "content" | "artifact"; + text?: "content" | "artifact"; + }; +} + +// STDIO transport configuration +export interface MCPServerStdioConfig extends MCPServerConfig { + type: "stdio"; + transport: "stdio"; + command: string; + args: string[]; + cwd?: string; + encoding?: string; + env?: Record; + restart?: { + delayMs?: number; + enabled?: boolean; + maxAttempts?: number; + }; + stderr?: "overlapped" | "pipe" | "ignore" | "inherit"; +} + +// OAuth client provider interface (placeholder for now) +export interface OAuthClientProvider { + type: "oauth" | "bearer" | "api-key"; + clientId?: string; + authorizationUrl?: string; + tokenUrl?: string; + apiKey?: string; +} + +// HTTP/SSE transport configuration +export interface MCPServerHTTPConfig extends MCPServerConfig { + type: "http" | "sse"; + transport: "http" | "sse"; + url: string; + authProvider?: OAuthClientProvider; + automaticSSEFallback?: boolean; + headers?: Record; + reconnect?: { + delayMs?: number; + enabled?: boolean; + maxAttempts?: number; + }; +} + +export type MCPServerConfiguration = + | MCPServerStdioConfig + | MCPServerHTTPConfig; + +export interface MCPServersConfig { + [serverName: string]: MCPServerConfiguration; +} + +// Update tool type to include server association +export interface ToolWithServer extends Tool { + serverName: string; + serverConfig: MCPServerConfiguration; +} From b37e9f354887f2bad7abc75f0a968825506db71c Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 21:56:40 +0000 Subject: [PATCH 02/27] Apply patch [skip ci] --- apps/web/src/types/tool.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/web/src/types/tool.ts b/apps/web/src/types/tool.ts index 7be3f09e9..b2d365ce2 100644 --- a/apps/web/src/types/tool.ts +++ b/apps/web/src/types/tool.ts @@ -18,3 +18,7 @@ export interface Tool { */ inputSchema: InputSchema; } + +// Re-export ToolWithServer from mcp.ts for convenience +export type { ToolWithServer } from "./mcp"; + From 2258fd04372309b0e4da3ff6d0d4f3f8303e8e1a Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 21:57:14 +0000 Subject: [PATCH 03/27] Apply patch [skip ci] --- apps/web/src/types/configurable.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/web/src/types/configurable.ts b/apps/web/src/types/configurable.ts index 669eaeed8..de60caef3 100644 --- a/apps/web/src/types/configurable.ts +++ b/apps/web/src/types/configurable.ts @@ -72,6 +72,14 @@ export type ConfigurableFieldMCPMetadata = { label: string; type: "mcp"; default?: { + // New multi-server configuration + servers?: { + [serverName: string]: { + tools?: string[]; + enabled?: boolean; + }; + }; + // Deprecated fields for backward compatibility tools?: string[]; url?: string; auth_required?: boolean; @@ -99,3 +107,4 @@ export type ConfigurableFieldAgentsMetadata = { name?: string; }[]; }; + From 7443bf00eff87e73362a83d0af2c121cb7b89a37 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 21:57:42 +0000 Subject: [PATCH 04/27] Apply patch [skip ci] --- apps/web/src/lib/environment/mcp-servers.ts | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/web/src/lib/environment/mcp-servers.ts diff --git a/apps/web/src/lib/environment/mcp-servers.ts b/apps/web/src/lib/environment/mcp-servers.ts new file mode 100644 index 000000000..fbd95b643 --- /dev/null +++ b/apps/web/src/lib/environment/mcp-servers.ts @@ -0,0 +1,37 @@ +import { MCPServersConfig } from "@/types/mcp"; + +/** + * Get MCP servers configuration from environment variables. + * Supports both new multi-server format and legacy single-server format. + * + * @returns MCPServersConfig object with server configurations + */ +export function getMCPServers(): MCPServersConfig { + const serversJson = process.env.NEXT_PUBLIC_MCP_SERVERS; + + if (!serversJson) { + // Backward compatibility: check for single server config + const singleServerUrl = process.env.NEXT_PUBLIC_MCP_SERVER_URL; + if (singleServerUrl) { + return { + default: { + type: "http", + transport: "http", + url: singleServerUrl, + authProvider: + process.env.NEXT_PUBLIC_MCP_AUTH_REQUIRED === "true" + ? { type: "bearer" } + : undefined, + }, + }; + } + return {}; + } + + try { + return JSON.parse(serversJson); + } catch (e) { + console.error("Failed to parse NEXT_PUBLIC_MCP_SERVERS", e); + return {}; + } +} From 54576bd446a5f20a7d96e062d4bc68e4fc272926 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 21:58:59 +0000 Subject: [PATCH 05/27] Apply patch [skip ci] --- apps/web/src/hooks/use-mcp.tsx | 208 ++++++++++++++++++++++----------- 1 file changed, 142 insertions(+), 66 deletions(-) diff --git a/apps/web/src/hooks/use-mcp.tsx b/apps/web/src/hooks/use-mcp.tsx index a2cca83b1..efa5286e5 100644 --- a/apps/web/src/hooks/use-mcp.tsx +++ b/apps/web/src/hooks/use-mcp.tsx @@ -1,102 +1,178 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { Tool } from "@/types/tool"; import { useState } from "react"; +import { getMCPServers } from "@/lib/environment/mcp-servers"; +import { MCPServerConfiguration, ToolWithServer } from "@/types/mcp"; -function getMCPUrlOrThrow() { - if (!process.env.NEXT_PUBLIC_BASE_API_URL) { - throw new Error("NEXT_PUBLIC_BASE_API_URL is not defined"); - } +export interface UseMCPOptions { + name: string; + version: string; + serverName?: string; // Optional: connect to specific server +} - const url = new URL(process.env.NEXT_PUBLIC_BASE_API_URL); - url.pathname = `${url.pathname}${url.pathname.endsWith("/") ? "" : "/"}oap_mcp`; - return url; +export interface MCPConnection { + serverName: string; + client: Client; + config: MCPServerConfiguration; } /** - * Custom hook for interacting with the Model Context Protocol (MCP). - * Provides functions to connect to an MCP server and list available tools. + * Custom hook for interacting with multiple Model Context Protocol (MCP) servers. + * Provides functions to connect to MCP servers and manage tools from multiple sources. */ -export default function useMCP({ - name, - version, -}: { - name: string; - version: string; -}) { - const [tools, setTools] = useState([]); - const [cursor, setCursor] = useState(""); - - /** - * Creates an MCP client and connects it to the specified server URL. - * @param url - The URL of the MCP server. - * @param options - Client identification options. - * @param options.name - The name of the client. - * @param options.version - The version of the client. - * @returns A promise that resolves to the connected MCP client instance. - */ - const createAndConnectMCPClient = async () => { - const url = getMCPUrlOrThrow(); - const connectionClient = new StreamableHTTPClientTransport(new URL(url)); - const mcp = new Client({ - name, - version, - }); +export default function useMCP({ name, version, serverName }: UseMCPOptions) { + const [connections, setConnections] = useState>( + new Map() + ); + const [toolsByServer, setToolsByServer] = useState< + Map + >(new Map()); + const [cursorsByServer, setCursorsByServer] = useState>( + new Map() + ); + + const createAndConnectMCPClient = async ( + serverName: string, + serverConfig: MCPServerConfiguration + ): Promise => { + if (serverConfig.type === "stdio") { + // Handle stdio transport (not supported in browser) + throw new Error("STDIO transport not supported in browser environment"); + } - await mcp.connect(connectionClient); - return mcp; + // Handle HTTP/SSE transport - use proxy route for same-origin + const proxyUrl = new URL(window.location.origin); + proxyUrl.pathname = `/api/oap_mcp/${serverName}`; + + const transport = new StreamableHTTPClientTransport(proxyUrl); + const client = new Client({ name, version }); + + await client.connect(transport); + return client; }; - /** - * Connects to an MCP server and retrieves the list of available tools. - * @param url - The URL of the MCP server. - * @param options - Client identification options. - * @param options.name - The name of the client. - * @param options.version - The version of the client. - * @returns A promise that resolves to an array of available tools. - */ - const getTools = async (nextCursor?: string): Promise => { - const mcp = await createAndConnectMCPClient(); - const tools = await mcp.listTools({ cursor: nextCursor }); + const getToolsFromServer = async ( + serverName: string, + nextCursor?: string + ): Promise => { + const servers = getMCPServers(); + const serverConfig = servers[serverName]; + + if (!serverConfig) { + throw new Error(`Server ${serverName} not found in configuration`); + } + + let connection = connections.get(serverName); + if (!connection) { + const client = await createAndConnectMCPClient(serverName, serverConfig); + connection = { serverName, client, config: serverConfig }; + setConnections((prev) => new Map(prev).set(serverName, connection!)); + } + + const tools = await connection.client.listTools({ cursor: nextCursor }); + if (tools.nextCursor) { - setCursor(tools.nextCursor); + setCursorsByServer((prev) => + new Map(prev).set(serverName, tools.nextCursor!) + ); } else { - setCursor(""); + setCursorsByServer((prev) => { + const next = new Map(prev); + next.delete(serverName); + return next; + }); } - return tools.tools; + + return tools.tools.map((tool) => ({ + ...tool, + serverName, + serverConfig, + })); + }; + + const getAllTools = async (): Promise => { + const servers = getMCPServers(); + const allTools: ToolWithServer[] = []; + + await Promise.all( + Object.keys(servers).map(async (serverName) => { + try { + const tools = await getToolsFromServer(serverName); + allTools.push(...tools); + } catch (e) { + console.error(`Failed to get tools from ${serverName}:`, e); + } + }) + ); + + return allTools; }; - /** - * Calls a tool on the MCP server. - * @param name - The name of the tool. - * @param version - The version of the tool. Optional. - * @param args - The arguments to pass to the tool. - * @returns A promise that resolves to the response from the tool. - */ const callTool = async ({ name, args, version, + serverName: specificServer, }: { name: string; args: Record; version?: string; + serverName?: string; }) => { - const mcp = await createAndConnectMCPClient(); - const response = await mcp.callTool({ - name, - version, - arguments: args, + // Find which server has this tool + let targetServer = specificServer; + + if (!targetServer) { + for (const [server, tools] of toolsByServer.entries()) { + if (tools.some((t) => t.name === name)) { + targetServer = server; + break; + } + } + } + + if (!targetServer) { + throw new Error(`Tool ${name} not found in any server`); + } + + const connection = connections.get(targetServer); + if (!connection) { + throw new Error(`Not connected to server ${targetServer}`); + } + + return connection.client.callTool({ name, version, arguments: args }); + }; + + // Legacy compatibility - maintain old interface + const tools = Array.from(toolsByServer.values()).flat(); + const setTools = (newTools: ToolWithServer[]) => { + const newMap = new Map(); + newTools.forEach((tool) => { + const serverTools = newMap.get(tool.serverName) || []; + serverTools.push(tool); + newMap.set(tool.serverName, serverTools); }); - return response; + setToolsByServer(newMap); }; + // Legacy single cursor - returns first server's cursor + const cursor = Array.from(cursorsByServer.values())[0] || ""; + return { - getTools, + getToolsFromServer, + getAllTools, callTool, - createAndConnectMCPClient, + toolsByServer, + setToolsByServer, + cursorsByServer, + connections, + // Legacy compatibility + getTools: getAllTools, + createAndConnectMCPClient: () => + createAndConnectMCPClient("default", getMCPServers().default), tools, setTools, cursor, }; } + From d9c4f58b140c5f0f274423a5c086ece3f90b3148 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:00:01 +0000 Subject: [PATCH 06/27] Apply patch [skip ci] --- apps/web/src/providers/MCP.tsx | 67 +++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/apps/web/src/providers/MCP.tsx b/apps/web/src/providers/MCP.tsx index fca7a885a..c5724b853 100644 --- a/apps/web/src/providers/MCP.tsx +++ b/apps/web/src/providers/MCP.tsx @@ -3,36 +3,82 @@ import React, { useContext, PropsWithChildren, useEffect, - useRef, useState, } from "react"; import useMCP from "../hooks/use-mcp"; +import { getMCPServers } from "@/lib/environment/mcp-servers"; +import { MCPServersConfig, ToolWithServer } from "@/types/mcp"; -type MCPContextType = ReturnType & { loading: boolean }; +interface MCPContextType { + servers: MCPServersConfig; + toolsByServer: Map; + loading: boolean; + loadingByServer: Map; + getToolsFromServer: ( + serverName: string, + cursor?: string + ) => Promise; + getAllTools: () => Promise; + callTool: (params: any) => Promise; + cursorsByServer: Map; + // Legacy compatibility + tools: ToolWithServer[]; + setTools: (tools: ToolWithServer[]) => void; + cursor: string; + getTools: () => Promise; + createAndConnectMCPClient: () => Promise; +} const MCPContext = createContext(null); export const MCPProvider: React.FC = ({ children }) => { const mcpState = useMCP({ - name: "Tools Interface", + name: "Open Agent Platform", version: "1.0.0", }); - const firstRequestMade = useRef(false); + const [loading, setLoading] = useState(false); + const [loadingByServer, setLoadingByServer] = useState>( + new Map() + ); + const servers = getMCPServers(); useEffect(() => { - if (mcpState.tools.length || firstRequestMade.current) return; - - firstRequestMade.current = true; + // Initial load of tools from all servers setLoading(true); mcpState - .getTools() - .then((tools) => mcpState.setTools(tools)) + .getAllTools() + .then((tools) => { + const toolsMap = new Map(); + tools.forEach((tool) => { + const serverTools = toolsMap.get(tool.serverName) || []; + serverTools.push(tool); + toolsMap.set(tool.serverName, serverTools); + }); + mcpState.setToolsByServer(toolsMap); + }) .finally(() => setLoading(false)); }, []); return ( - + {children} ); @@ -45,3 +91,4 @@ export const useMCPContext = () => { } return context; }; + From 16ea8285cf9613bcfc00ef85ae80b78e4febb1ea Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:00:50 +0000 Subject: [PATCH 07/27] Apply patch [skip ci] --- .../api/oap_mcp/[server]/[...path]/route.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts diff --git a/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts b/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts new file mode 100644 index 000000000..a7fe1c435 --- /dev/null +++ b/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts @@ -0,0 +1,78 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getMCPServers } from "@/lib/environment/mcp-servers"; +import { handleServerAuth } from "@/lib/mcp-auth"; + +export const runtime = "edge"; + +export async function proxyRequest( + req: NextRequest, + { params }: { params: { server: string; path: string[] } } +): Promise { + const servers = getMCPServers(); + const serverConfig = servers[params.server]; + + if (!serverConfig) { + return NextResponse.json( + { message: `Server ${params.server} not found` }, + { status: 404 } + ); + } + + if (serverConfig.type === "stdio") { + return NextResponse.json( + { message: "STDIO transport not supported via proxy" }, + { status: 400 } + ); + } + + // Construct target URL + const path = params.path.join("/"); + const targetUrl = new URL(serverConfig.url); + targetUrl.pathname = `${targetUrl.pathname}/mcp/${path}`; + + // Handle authentication based on server config + const headers = new Headers(); + req.headers.forEach((value, key) => { + if (key.toLowerCase() !== "host") { + headers.append(key, value); + } + }); + + // Apply server-specific auth + if (serverConfig.authProvider) { + const accessToken = await handleServerAuth(serverConfig, req); + if (accessToken) { + headers.set("Authorization", `Bearer ${accessToken}`); + } + } + + // Apply custom headers + if (serverConfig.headers) { + Object.entries(serverConfig.headers).forEach(([key, value]) => { + headers.set(key, value); + }); + } + + // Make the proxied request + const response = await fetch(targetUrl.toString(), { + method: req.method, + headers, + body: req.body, + }); + + // Return the response + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }); +} + +// Export handlers for all HTTP methods +export const GET = proxyRequest; +export const POST = proxyRequest; +export const PUT = proxyRequest; +export const PATCH = proxyRequest; +export const DELETE = proxyRequest; +export const HEAD = proxyRequest; +export const OPTIONS = proxyRequest; From b4c96a7fd1e381dc95102c72d11639023b5d6eb2 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:01:37 +0000 Subject: [PATCH 08/27] Apply patch [skip ci] --- apps/web/src/lib/mcp-auth.ts | 124 +++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 apps/web/src/lib/mcp-auth.ts diff --git a/apps/web/src/lib/mcp-auth.ts b/apps/web/src/lib/mcp-auth.ts new file mode 100644 index 000000000..67e7c25ec --- /dev/null +++ b/apps/web/src/lib/mcp-auth.ts @@ -0,0 +1,124 @@ +import { MCPServerHTTPConfig } from "@/types/mcp"; +import { NextRequest } from "next/server"; + +interface ServerAuthState { + serverName: string; + accessToken?: string; + refreshToken?: string; + expiresAt?: number; +} + +/** + * Handle authentication for a specific MCP server based on its configuration. + * Supports bearer tokens, API keys, and OAuth flows. + */ +export async function handleServerAuth( + serverConfig: MCPServerHTTPConfig, + req: NextRequest +): Promise { + if (!serverConfig.authProvider) { + return null; + } + + const { authProvider } = serverConfig; + + switch (authProvider.type) { + case "oauth": + return handleOAuthFlow(serverConfig, authProvider, req); + case "bearer": + return handleBearerToken(serverConfig, req); + case "api-key": + return authProvider.apiKey || null; + default: + console.warn( + `Unknown auth provider type: ${(authProvider as any).type}` + ); + return null; + } +} + +/** + * Handle OAuth 2.0 authentication flow. + * This is a stub implementation that should be expanded based on specific OAuth requirements. + */ +async function handleOAuthFlow( + serverConfig: MCPServerHTTPConfig, + authProvider: any, + req: NextRequest +): Promise { + // Check for existing valid token + const existingToken = await getStoredToken(serverConfig.url); + if (existingToken && !isTokenExpired(existingToken)) { + return existingToken.accessToken || null; + } + + // TODO: Implement OAuth 2.0 flow as per MCP spec + // This would involve: + // 1. Discovery of authorization server + // 2. Dynamic client registration if needed + // 3. Authorization code flow with PKCE + // 4. Token exchange + // 5. Token storage + + // For now, return null as placeholder + console.warn( + "OAuth flow not fully implemented. Returning null for OAuth authentication." + ); + return null; +} + +/** + * Handle bearer token authentication. + * Checks multiple sources for bearer tokens in order of precedence. + */ +async function handleBearerToken( + serverConfig: MCPServerHTTPConfig, + req: NextRequest +): Promise { + // Check for bearer token in various sources + const authHeader = req.headers.get("Authorization"); + if (authHeader?.startsWith("Bearer ")) { + return authHeader.substring(7); + } + + // Check cookies + const tokenCookie = req.cookies.get("X-MCP-Access-Token"); + if (tokenCookie) { + return tokenCookie.value; + } + + // Check environment variables + const envTokens = process.env.MCP_TOKENS; + if (envTokens) { + try { + const tokens = JSON.parse(envTokens); + return tokens[serverConfig.url] || null; + } catch (e) { + console.error("Failed to parse MCP_TOKENS", e); + } + } + + return null; +} + +/** + * Get stored token for a server URL. + * This is a placeholder implementation that should be replaced with actual token storage. + */ +async function getStoredToken( + serverUrl: string +): Promise { + // TODO: Implement actual token storage retrieval + // This could use cookies, local storage, or a server-side session store + return null; +} + +/** + * Check if a token has expired. + */ +function isTokenExpired(authState: ServerAuthState): boolean { + if (!authState.expiresAt) { + return false; + } + return Date.now() > authState.expiresAt; +} From 6db3b52bee8df71277aec44dff5f90ac194d7530 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:02:40 +0000 Subject: [PATCH 09/27] Apply patch [skip ci] --- apps/web/src/app/api/oap_mcp/proxy-request.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/app/api/oap_mcp/proxy-request.ts b/apps/web/src/app/api/oap_mcp/proxy-request.ts index 0376d13da..39235e885 100644 --- a/apps/web/src/app/api/oap_mcp/proxy-request.ts +++ b/apps/web/src/app/api/oap_mcp/proxy-request.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { createServerClient } from "@supabase/ssr"; +import { getMCPServers } from "@/lib/environment/mcp-servers"; // This will contain the object which contains the access token const MCP_TOKENS = process.env.MCP_TOKENS; @@ -239,3 +240,4 @@ export async function proxyRequest(req: NextRequest): Promise { ); } } + From 8dbdaf4c77e9e3f39b504639d04d24b2e720de0b Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:03:27 +0000 Subject: [PATCH 10/27] Apply patch [skip ci] --- apps/web/src/app/api/oap_mcp/proxy-request.ts | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/api/oap_mcp/proxy-request.ts b/apps/web/src/app/api/oap_mcp/proxy-request.ts index 39235e885..fa535937a 100644 --- a/apps/web/src/app/api/oap_mcp/proxy-request.ts +++ b/apps/web/src/app/api/oap_mcp/proxy-request.ts @@ -90,6 +90,31 @@ async function getMcpAccessToken(supabaseToken: string, mcpServerUrl: URL) { * @returns The response from the MCP server. */ export async function proxyRequest(req: NextRequest): Promise { + // Extract the path after '/api/oap_mcp/' + // Example: /api/oap_mcp/foo/bar -> /foo/bar + const url = new URL(req.url); + const path = url.pathname.replace(/^\/api\/oap_mcp/, ""); + + // Check if the first path segment might be a server name + const pathSegments = path.split('/').filter(Boolean); + if (pathSegments.length > 0) { + const servers = getMCPServers(); + const potentialServerName = pathSegments[0]; + + // If the first segment matches a configured server, delegate to new proxy + if (servers[potentialServerName]) { + // Import and use the new per-server proxy handler + const { proxyRequest: newProxyRequest } = await import('./[server]/[...path]/route'); + return newProxyRequest(req, { + params: { + server: potentialServerName, + path: pathSegments.slice(1) + } + }); + } + } + + // Legacy behavior - continue with single server logic if (!MCP_SERVER_URL) { return new Response( JSON.stringify({ @@ -100,11 +125,6 @@ export async function proxyRequest(req: NextRequest): Promise { ); } - // Extract the path after '/api/oap_mcp/' - // Example: /api/oap_mcp/foo/bar -> /foo/bar - const url = new URL(req.url); - const path = url.pathname.replace(/^\/api\/oap_mcp/, ""); - // Construct the target URL const targetUrlObj = new URL(MCP_SERVER_URL); targetUrlObj.pathname = `${targetUrlObj.pathname}${targetUrlObj.pathname.endsWith("/") ? "" : "/"}mcp${path}${url.search}`; @@ -241,3 +261,4 @@ export async function proxyRequest(req: NextRequest): Promise { } } + From 1a7a11192446f3c37129a1f354d95dfdfb2ac7d5 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:04:08 +0000 Subject: [PATCH 11/27] Apply patch [skip ci] --- apps/web/src/features/tools/playground/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/features/tools/playground/index.tsx b/apps/web/src/features/tools/playground/index.tsx index 7a214d688..cb7f18b60 100644 --- a/apps/web/src/features/tools/playground/index.tsx +++ b/apps/web/src/features/tools/playground/index.tsx @@ -19,10 +19,11 @@ import { toast } from "sonner"; import { useRouter } from "next/navigation"; export default function ToolsPlaygroundInterface() { - const { tools, loading, callTool } = useMCPContext(); + const { tools, toolsByServer, servers, loading, callTool } = useMCPContext(); const router = useRouter(); const [selectedToolName, setSelectedToolName] = useQueryState("tool"); + const [selectedServerName, setSelectedServerName] = useQueryState("server"); const [selectedTool, setSelectedTool] = useState(); const [inputValues, setInputValues] = useState({}); const [response, setResponse] = useState(null); @@ -192,3 +193,4 @@ export default function ToolsPlaygroundInterface() { ); } + From fee57b6265ff32d18da3aaf7c43280747a8fdabc Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:04:26 +0000 Subject: [PATCH 12/27] Apply patch [skip ci] --- .../src/features/tools/playground/index.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/web/src/features/tools/playground/index.tsx b/apps/web/src/features/tools/playground/index.tsx index cb7f18b60..7d47d7016 100644 --- a/apps/web/src/features/tools/playground/index.tsx +++ b/apps/web/src/features/tools/playground/index.tsx @@ -47,17 +47,32 @@ export default function ToolsPlaygroundInterface() { return; } - const tool = tools.find((tool) => tool.name === selectedToolName); + // Find tool across all servers + let foundTool = null; + let foundServer = null; + + for (const [serverName, serverTools] of toolsByServer.entries()) { + const tool = serverTools.find((t) => t.name === selectedToolName); + if (tool) { + foundTool = tool; + foundServer = serverName; + break; + } + } - if (!tool) { + if (!foundTool) { toast.error("Tool not found", { richColors: true }); setSelectedToolName(null); router.replace("/tools"); return; } + resetState(); - setSelectedTool(tool); - }, [tools, loading, selectedToolName]); + setSelectedTool(foundTool); + if (foundServer && foundServer !== selectedServerName) { + setSelectedServerName(foundServer); + } + }, [tools, toolsByServer, loading, selectedToolName, selectedServerName]); const handleInputChange = (newValues: any) => { setInputValues(newValues); @@ -194,3 +209,4 @@ export default function ToolsPlaygroundInterface() { ); } + From c63e1ec933481f529a7163eb867994c783ca4efa Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:04:41 +0000 Subject: [PATCH 13/27] Apply patch [skip ci] --- apps/web/src/features/tools/playground/index.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/web/src/features/tools/playground/index.tsx b/apps/web/src/features/tools/playground/index.tsx index 7d47d7016..719b79fa6 100644 --- a/apps/web/src/features/tools/playground/index.tsx +++ b/apps/web/src/features/tools/playground/index.tsx @@ -17,6 +17,13 @@ import _ from "lodash"; import { useQueryState } from "nuqs"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; export default function ToolsPlaygroundInterface() { const { tools, toolsByServer, servers, loading, callTool } = useMCPContext(); @@ -210,3 +217,4 @@ export default function ToolsPlaygroundInterface() { } + From 564b895819ab9e14bd47f06d2ac5f712ac9a7b8a Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:05:30 +0000 Subject: [PATCH 14/27] Apply patch [skip ci] --- .../src/features/tools/playground/index.tsx | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/web/src/features/tools/playground/index.tsx b/apps/web/src/features/tools/playground/index.tsx index 719b79fa6..c3d25f6a3 100644 --- a/apps/web/src/features/tools/playground/index.tsx +++ b/apps/web/src/features/tools/playground/index.tsx @@ -143,14 +143,38 @@ export default function ToolsPlaygroundInterface() {

Tools Playground

- { - resetState(); - setSelectedTool(t); - setSelectedToolName(t.name); - }} - /> +
+ + { + resetState(); + setSelectedTool(t); + setSelectedToolName(t.name); + // Update server selection if tool is from different server + if ('serverName' in t) { + setSelectedServerName(t.serverName); + } + }} + /> +
@@ -218,3 +242,4 @@ export default function ToolsPlaygroundInterface() { + From 1a8bb5e9fc4728b6eb6baca978718a7004c0be56 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:06:03 +0000 Subject: [PATCH 15/27] Apply patch [skip ci] --- .../tool-selection-by-server.tsx | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx new file mode 100644 index 000000000..ba541e04a --- /dev/null +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx @@ -0,0 +1,64 @@ +import { ToolWithServer } from "@/types/mcp"; +import { ConfigFieldTool } from "@/features/chat/components/configuration-sidebar/config-field"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import { useState } from "react"; + +interface ToolSelectionByServerProps { + toolsByServer: Map; + selectedTools: string[]; + onToolToggle: (toolName: string) => void; +} + +export function ToolSelectionByServer({ + toolsByServer, + selectedTools, + onToolToggle, +}: ToolSelectionByServerProps) { + const [expandedServers, setExpandedServers] = useState>( + new Set(Array.from(toolsByServer.keys())) + ); + + const toggleServer = (serverName: string) => { + const newExpanded = new Set(expandedServers); + if (newExpanded.has(serverName)) { + newExpanded.delete(serverName); + } else { + newExpanded.add(serverName); + } + setExpandedServers(newExpanded); + }; + + return ( +
+ {Array.from(toolsByServer.entries()).map(([serverName, tools]) => ( + toggleServer(serverName)} + > + + {serverName} ({tools.length} tools) + {expandedServers.has(serverName) ? ( + + ) : ( + + )} + + + {tools.map((tool) => ( + onToolToggle(tool.name)} + /> + ))} + + + ))} +
+ ); +} From 8a39de6b8e882294473dff4eddf1aed2067d9768 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:06:31 +0000 Subject: [PATCH 16/27] Apply patch [skip ci] --- .../agents/components/create-edit-agent-dialogs/agent-form.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx index effa3cdcd..da9064f02 100644 --- a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx @@ -22,6 +22,7 @@ import { import _ from "lodash"; import { useFetchPreselectedTools } from "@/hooks/use-fetch-preselected-tools"; import { Controller, useFormContext } from "react-hook-form"; +import { ToolSelectionByServer } from "./tool-selection-by-server"; export function AgentFieldsFormLoading() { return ( @@ -262,3 +263,4 @@ export function AgentFieldsForm({
); } + From 4638eb0690c106027ed97772a76a1674ffd56a64 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:06:47 +0000 Subject: [PATCH 17/27] Apply patch [skip ci] --- .../agents/components/create-edit-agent-dialogs/agent-form.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx index da9064f02..f8c0bffd9 100644 --- a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx @@ -61,7 +61,7 @@ export function AgentFieldsForm({ config: Record; }>(); - const { tools, setTools, getTools, cursor, loading } = useMCPContext(); + const { tools, toolsByServer, setTools, getTools, cursor, loading } = useMCPContext(); const { toolSearchTerm, debouncedSetSearchTerm, displayTools } = useSearchTools(tools, { preSelectedTools: toolConfigurations[0]?.default?.tools, @@ -264,3 +264,4 @@ export function AgentFieldsForm({ ); } + From 37079312a424d5f9885178c8b88fe153c3fc7ca5 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:07:29 +0000 Subject: [PATCH 18/27] Apply patch [skip ci] --- .../create-edit-agent-dialogs/agent-form.tsx | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx index f8c0bffd9..b32ae2fdb 100644 --- a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx @@ -153,28 +153,25 @@ export function AgentFieldsForm({ />
- {toolConfigurations[0]?.label - ? displayTools.map((c) => ( - ( - - )} + {toolConfigurations[0]?.label && ( + ( + { + const currentTools = value?.tools || []; + const newTools = currentTools.includes(toolName) + ? currentTools.filter((t: string) => t !== toolName) + : [...currentTools, toolName]; + onChange({ ...value, tools: newTools }); + }} /> - )) - : null} + )} + /> + )} {displayTools.length === 0 && toolSearchTerm && (

No tools found matching "{toolSearchTerm}". @@ -265,3 +262,4 @@ export function AgentFieldsForm({ } + From 7dce3fb9c45ed8cbdc33c602950140ca4ceee03a Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:07:53 +0000 Subject: [PATCH 19/27] Apply patch [skip ci] --- .../features/chat/components/configuration-sidebar/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx index ddc13302d..5a0afa370 100644 --- a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx +++ b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx @@ -26,6 +26,7 @@ import { import { toast } from "sonner"; import _ from "lodash"; import { useMCPContext } from "@/providers/MCP"; +import { ToolSelectionByServer } from "@/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server"; import { Search } from "@/components/ui/tool-search"; import { useSearchTools } from "@/hooks/use-search-tools"; import { useFetchPreselectedTools } from "@/hooks/use-fetch-preselected-tools"; @@ -478,3 +479,4 @@ export const ConfigurationSidebar = forwardRef< }); ConfigurationSidebar.displayName = "ConfigurationSidebar"; + From 3d8ec7bf8df27a818e0400bb73fa55feb2bf0290 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:09:25 +0000 Subject: [PATCH 20/27] Apply patch [skip ci] --- .../features/chat/components/configuration-sidebar/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx index 5a0afa370..ef4a37fc0 100644 --- a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx +++ b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx @@ -123,7 +123,7 @@ export const ConfigurationSidebar = forwardRef< AIConfigPanelProps >(({ className, open }, ref: ForwardedRef) => { const { configsByAgentId, resetConfig } = useConfigStore(); - const { tools, setTools, getTools, cursor } = useMCPContext(); + const { tools, toolsByServer, setTools, getTools, cursor } = useMCPContext(); const [agentId] = useQueryState("agentId"); const [deploymentId] = useQueryState("deploymentId"); const [threadId] = useQueryState("threadId"); @@ -480,3 +480,4 @@ export const ConfigurationSidebar = forwardRef< ConfigurationSidebar.displayName = "ConfigurationSidebar"; + From 18f39865a87e5d068152d1741563a7e9aaa942dc Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:09:58 +0000 Subject: [PATCH 21/27] Apply patch [skip ci] --- .../configuration-sidebar/index.tsx | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx index ef4a37fc0..acc00fe90 100644 --- a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx +++ b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx @@ -366,20 +366,34 @@ export const ConfigurationSidebar = forwardRef< placeholder="Search tools..." />

+ {agentId && toolsByServer.size > 0 && ( + { + const currentConfig = configsByAgentId[agentId]?.[toolConfigurations[0]?.label] || {}; + const currentTools = currentConfig.tools || []; + const newTools = currentTools.includes(toolName) + ? currentTools.filter((t: string) => t !== toolName) + : [...currentTools, toolName]; + + // Update the config store + const newConfig = { + ...currentConfig, + tools: newTools + }; + useConfigStore.getState().setConfig( + agentId, + toolConfigurations[0]?.label, + newConfig + ); + }} + /> + )} {agentId && - displayTools.length > 0 && - displayTools.map((c, index) => ( - - ))} - {agentId && - displayTools.length === 0 && + tools.length === 0 && toolSearchTerm && (

No tools found matching "{toolSearchTerm}". @@ -481,3 +495,4 @@ export const ConfigurationSidebar = forwardRef< ConfigurationSidebar.displayName = "ConfigurationSidebar"; + From e39ea741998604e8f0f01dc637bfea76345ded03 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:10:42 +0000 Subject: [PATCH 22/27] Apply patch [skip ci] --- .../scripts/migrate-single-to-multi-mcp.ts | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 apps/web/scripts/migrate-single-to-multi-mcp.ts diff --git a/apps/web/scripts/migrate-single-to-multi-mcp.ts b/apps/web/scripts/migrate-single-to-multi-mcp.ts new file mode 100644 index 000000000..753645a4e --- /dev/null +++ b/apps/web/scripts/migrate-single-to-multi-mcp.ts @@ -0,0 +1,94 @@ +#!/usr/bin/env node + +/** + * Migration script to convert legacy single MCP server configuration to multi-server format + * + * Usage: + * node scripts/migrate-single-to-multi-mcp.ts + * + * This script reads the legacy environment variables: + * - NEXT_PUBLIC_MCP_SERVER_URL + * - NEXT_PUBLIC_MCP_AUTH_REQUIRED + * + * And generates the new NEXT_PUBLIC_MCP_SERVERS JSON configuration. + * + * The output can be copied to your .env file or environment configuration. + * + * Example output: + * NEXT_PUBLIC_MCP_SERVERS='{"default":{"type":"http","url":"http://localhost:3001","authProvider":{"type":"bearer"}}}' + */ + +import { MCPServersConfig, MCPServerHTTPConfig } from "../src/types/mcp"; + +function migrateSingleToMultiMCP(): void { + const legacyUrl = process.env.NEXT_PUBLIC_MCP_SERVER_URL; + const authRequired = process.env.NEXT_PUBLIC_MCP_AUTH_REQUIRED === "true"; + + if (!legacyUrl) { + console.error("❌ No legacy MCP server configuration found."); + console.error(" NEXT_PUBLIC_MCP_SERVER_URL is not set."); + process.exit(1); + } + + console.log("🔍 Found legacy MCP configuration:"); + console.log(` URL: ${legacyUrl}`); + console.log(` Auth Required: ${authRequired}`); + console.log(""); + + // Create the new multi-server configuration + const serverConfig: MCPServerHTTPConfig = { + type: "http", + url: legacyUrl, + }; + + // Add auth provider if authentication was required + if (authRequired) { + serverConfig.authProvider = { + type: "bearer", + }; + } + + const multiServerConfig: MCPServersConfig = { + default: serverConfig, + }; + + // Generate the JSON string + const jsonConfig = JSON.stringify(multiServerConfig); + + console.log("✅ Generated multi-server configuration:"); + console.log(""); + console.log("Add the following to your .env file:"); + console.log("====================================="); + console.log(`NEXT_PUBLIC_MCP_SERVERS='${jsonConfig}'`); + console.log("====================================="); + console.log(""); + console.log("📝 Notes:"); + console.log(" - The legacy server is now named 'default'"); + console.log(" - You can add more servers by editing the JSON"); + console.log(" - The legacy variables can be removed after migration"); + console.log(""); + console.log("Example with multiple servers:"); + console.log("=============================="); + const exampleConfig: MCPServersConfig = { + default: serverConfig, + "github-tools": { + type: "http", + url: "https://api.github.com/mcp", + authProvider: { + type: "api-key", + apiKey: "your-api-key-here", + }, + }, + "local-stdio": { + type: "stdio", + command: "node", + args: ["./local-mcp-server.js"], + }, + }; + console.log(`NEXT_PUBLIC_MCP_SERVERS='${JSON.stringify(exampleConfig, null, 2)}'`); +} + +// Run the migration +if (require.main === module) { + migrateSingleToMultiMCP(); +} From 8b6a463ff34d9d0941d47de246f3a5262db15a07 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:12:42 +0000 Subject: [PATCH 23/27] Apply patch [skip ci] --- .../scripts/migrate-single-to-multi-mcp.ts | 14 +++--- .../api/oap_mcp/[server]/[...path]/route.ts | 6 +-- apps/web/src/app/api/oap_mcp/proxy-request.ts | 16 +++---- .../create-edit-agent-dialogs/agent-form.tsx | 10 ++--- .../tool-selection-by-server.tsx | 14 ++++-- .../configuration-sidebar/index.tsx | 44 ++++++++++--------- .../src/features/tools/playground/index.tsx | 15 +++---- apps/web/src/hooks/use-mcp.tsx | 13 +++--- apps/web/src/lib/environment/mcp-servers.ts | 2 +- apps/web/src/lib/mcp-auth.ts | 14 +++--- apps/web/src/providers/MCP.tsx | 5 +-- apps/web/src/types/configurable.ts | 1 - apps/web/src/types/mcp.ts | 4 +- apps/web/src/types/tool.ts | 1 - 14 files changed, 81 insertions(+), 78 deletions(-) diff --git a/apps/web/scripts/migrate-single-to-multi-mcp.ts b/apps/web/scripts/migrate-single-to-multi-mcp.ts index 753645a4e..078bbb62c 100644 --- a/apps/web/scripts/migrate-single-to-multi-mcp.ts +++ b/apps/web/scripts/migrate-single-to-multi-mcp.ts @@ -2,18 +2,18 @@ /** * Migration script to convert legacy single MCP server configuration to multi-server format - * + * * Usage: * node scripts/migrate-single-to-multi-mcp.ts - * + * * This script reads the legacy environment variables: * - NEXT_PUBLIC_MCP_SERVER_URL * - NEXT_PUBLIC_MCP_AUTH_REQUIRED - * + * * And generates the new NEXT_PUBLIC_MCP_SERVERS JSON configuration. - * + * * The output can be copied to your .env file or environment configuration. - * + * * Example output: * NEXT_PUBLIC_MCP_SERVERS='{"default":{"type":"http","url":"http://localhost:3001","authProvider":{"type":"bearer"}}}' */ @@ -85,7 +85,9 @@ function migrateSingleToMultiMCP(): void { args: ["./local-mcp-server.js"], }, }; - console.log(`NEXT_PUBLIC_MCP_SERVERS='${JSON.stringify(exampleConfig, null, 2)}'`); + console.log( + `NEXT_PUBLIC_MCP_SERVERS='${JSON.stringify(exampleConfig, null, 2)}'`, + ); } // Run the migration diff --git a/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts b/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts index a7fe1c435..738567505 100644 --- a/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts +++ b/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts @@ -6,7 +6,7 @@ export const runtime = "edge"; export async function proxyRequest( req: NextRequest, - { params }: { params: { server: string; path: string[] } } + { params }: { params: { server: string; path: string[] } }, ): Promise { const servers = getMCPServers(); const serverConfig = servers[params.server]; @@ -14,14 +14,14 @@ export async function proxyRequest( if (!serverConfig) { return NextResponse.json( { message: `Server ${params.server} not found` }, - { status: 404 } + { status: 404 }, ); } if (serverConfig.type === "stdio") { return NextResponse.json( { message: "STDIO transport not supported via proxy" }, - { status: 400 } + { status: 400 }, ); } diff --git a/apps/web/src/app/api/oap_mcp/proxy-request.ts b/apps/web/src/app/api/oap_mcp/proxy-request.ts index fa535937a..161a7ea6c 100644 --- a/apps/web/src/app/api/oap_mcp/proxy-request.ts +++ b/apps/web/src/app/api/oap_mcp/proxy-request.ts @@ -94,22 +94,24 @@ export async function proxyRequest(req: NextRequest): Promise { // Example: /api/oap_mcp/foo/bar -> /foo/bar const url = new URL(req.url); const path = url.pathname.replace(/^\/api\/oap_mcp/, ""); - + // Check if the first path segment might be a server name - const pathSegments = path.split('/').filter(Boolean); + const pathSegments = path.split("/").filter(Boolean); if (pathSegments.length > 0) { const servers = getMCPServers(); const potentialServerName = pathSegments[0]; - + // If the first segment matches a configured server, delegate to new proxy if (servers[potentialServerName]) { // Import and use the new per-server proxy handler - const { proxyRequest: newProxyRequest } = await import('./[server]/[...path]/route'); + const { proxyRequest: newProxyRequest } = await import( + "./[server]/[...path]/route" + ); return newProxyRequest(req, { params: { server: potentialServerName, - path: pathSegments.slice(1) - } + path: pathSegments.slice(1), + }, }); } } @@ -260,5 +262,3 @@ export async function proxyRequest(req: NextRequest): Promise { ); } } - - diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx index b32ae2fdb..b99b58c45 100644 --- a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx @@ -61,7 +61,8 @@ export function AgentFieldsForm({ config: Record; }>(); - const { tools, toolsByServer, setTools, getTools, cursor, loading } = useMCPContext(); + const { tools, toolsByServer, setTools, getTools, cursor, loading } = + useMCPContext(); const { toolSearchTerm, debouncedSetSearchTerm, displayTools } = useSearchTools(tools, { preSelectedTools: toolConfigurations[0]?.default?.tools, @@ -164,7 +165,9 @@ export function AgentFieldsForm({ onToolToggle={(toolName) => { const currentTools = value?.tools || []; const newTools = currentTools.includes(toolName) - ? currentTools.filter((t: string) => t !== toolName) + ? currentTools.filter( + (t: string) => t !== toolName, + ) : [...currentTools, toolName]; onChange({ ...value, tools: newTools }); }} @@ -260,6 +263,3 @@ export function AgentFieldsForm({

); } - - - diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx index ba541e04a..fcae27a19 100644 --- a/apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/tool-selection-by-server.tsx @@ -1,6 +1,10 @@ import { ToolWithServer } from "@/types/mcp"; import { ConfigFieldTool } from "@/features/chat/components/configuration-sidebar/config-field"; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { ChevronDown, ChevronRight } from "lucide-react"; import { useState } from "react"; @@ -16,7 +20,7 @@ export function ToolSelectionByServer({ onToolToggle, }: ToolSelectionByServerProps) { const [expandedServers, setExpandedServers] = useState>( - new Set(Array.from(toolsByServer.keys())) + new Set(Array.from(toolsByServer.keys())), ); const toggleServer = (serverName: string) => { @@ -37,8 +41,10 @@ export function ToolSelectionByServer({ open={expandedServers.has(serverName)} onOpenChange={() => toggleServer(serverName)} > - - {serverName} ({tools.length} tools) + + + {serverName} ({tools.length} tools) + {expandedServers.has(serverName) ? ( ) : ( diff --git a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx index acc00fe90..e3b112d62 100644 --- a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx +++ b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx @@ -370,35 +370,42 @@ export const ConfigurationSidebar = forwardRef< { - const currentConfig = configsByAgentId[agentId]?.[toolConfigurations[0]?.label] || {}; + const currentConfig = + configsByAgentId[agentId]?.[ + toolConfigurations[0]?.label + ] || {}; const currentTools = currentConfig.tools || []; const newTools = currentTools.includes(toolName) - ? currentTools.filter((t: string) => t !== toolName) + ? currentTools.filter( + (t: string) => t !== toolName, + ) : [...currentTools, toolName]; - + // Update the config store const newConfig = { ...currentConfig, - tools: newTools + tools: newTools, }; - useConfigStore.getState().setConfig( - agentId, - toolConfigurations[0]?.label, - newConfig - ); + useConfigStore + .getState() + .setConfig( + agentId, + toolConfigurations[0]?.label, + newConfig, + ); }} /> )} - {agentId && - tools.length === 0 && - toolSearchTerm && ( -

- No tools found matching "{toolSearchTerm}". -

- )} + {agentId && tools.length === 0 && toolSearchTerm && ( +

+ No tools found matching "{toolSearchTerm}". +

+ )} {!agentId && (

Select an agent to see tools. @@ -493,6 +500,3 @@ export const ConfigurationSidebar = forwardRef< }); ConfigurationSidebar.displayName = "ConfigurationSidebar"; - - - diff --git a/apps/web/src/features/tools/playground/index.tsx b/apps/web/src/features/tools/playground/index.tsx index c3d25f6a3..6f1debdd4 100644 --- a/apps/web/src/features/tools/playground/index.tsx +++ b/apps/web/src/features/tools/playground/index.tsx @@ -57,7 +57,7 @@ export default function ToolsPlaygroundInterface() { // Find tool across all servers let foundTool = null; let foundServer = null; - + for (const [serverName, serverTools] of toolsByServer.entries()) { const tool = serverTools.find((t) => t.name === selectedToolName); if (tool) { @@ -73,7 +73,7 @@ export default function ToolsPlaygroundInterface() { router.replace("/tools"); return; } - + resetState(); setSelectedTool(foundTool); if (foundServer && foundServer !== selectedServerName) { @@ -156,7 +156,10 @@ export default function ToolsPlaygroundInterface() { All servers {Object.keys(servers).map((serverName) => ( - + {serverName} ))} @@ -169,7 +172,7 @@ export default function ToolsPlaygroundInterface() { setSelectedTool(t); setSelectedToolName(t.name); // Update server selection if tool is from different server - if ('serverName' in t) { + if ("serverName" in t) { setSelectedServerName(t.serverName); } }} @@ -239,7 +242,3 @@ export default function ToolsPlaygroundInterface() {

); } - - - - diff --git a/apps/web/src/hooks/use-mcp.tsx b/apps/web/src/hooks/use-mcp.tsx index efa5286e5..dc9e51a32 100644 --- a/apps/web/src/hooks/use-mcp.tsx +++ b/apps/web/src/hooks/use-mcp.tsx @@ -22,18 +22,18 @@ export interface MCPConnection { */ export default function useMCP({ name, version, serverName }: UseMCPOptions) { const [connections, setConnections] = useState>( - new Map() + new Map(), ); const [toolsByServer, setToolsByServer] = useState< Map >(new Map()); const [cursorsByServer, setCursorsByServer] = useState>( - new Map() + new Map(), ); const createAndConnectMCPClient = async ( serverName: string, - serverConfig: MCPServerConfiguration + serverConfig: MCPServerConfiguration, ): Promise => { if (serverConfig.type === "stdio") { // Handle stdio transport (not supported in browser) @@ -53,7 +53,7 @@ export default function useMCP({ name, version, serverName }: UseMCPOptions) { const getToolsFromServer = async ( serverName: string, - nextCursor?: string + nextCursor?: string, ): Promise => { const servers = getMCPServers(); const serverConfig = servers[serverName]; @@ -73,7 +73,7 @@ export default function useMCP({ name, version, serverName }: UseMCPOptions) { if (tools.nextCursor) { setCursorsByServer((prev) => - new Map(prev).set(serverName, tools.nextCursor!) + new Map(prev).set(serverName, tools.nextCursor!), ); } else { setCursorsByServer((prev) => { @@ -102,7 +102,7 @@ export default function useMCP({ name, version, serverName }: UseMCPOptions) { } catch (e) { console.error(`Failed to get tools from ${serverName}:`, e); } - }) + }), ); return allTools; @@ -175,4 +175,3 @@ export default function useMCP({ name, version, serverName }: UseMCPOptions) { cursor, }; } - diff --git a/apps/web/src/lib/environment/mcp-servers.ts b/apps/web/src/lib/environment/mcp-servers.ts index fbd95b643..cad39d0c6 100644 --- a/apps/web/src/lib/environment/mcp-servers.ts +++ b/apps/web/src/lib/environment/mcp-servers.ts @@ -3,7 +3,7 @@ import { MCPServersConfig } from "@/types/mcp"; /** * Get MCP servers configuration from environment variables. * Supports both new multi-server format and legacy single-server format. - * + * * @returns MCPServersConfig object with server configurations */ export function getMCPServers(): MCPServersConfig { diff --git a/apps/web/src/lib/mcp-auth.ts b/apps/web/src/lib/mcp-auth.ts index 67e7c25ec..f87724d8b 100644 --- a/apps/web/src/lib/mcp-auth.ts +++ b/apps/web/src/lib/mcp-auth.ts @@ -14,7 +14,7 @@ interface ServerAuthState { */ export async function handleServerAuth( serverConfig: MCPServerHTTPConfig, - req: NextRequest + req: NextRequest, ): Promise { if (!serverConfig.authProvider) { return null; @@ -30,9 +30,7 @@ export async function handleServerAuth( case "api-key": return authProvider.apiKey || null; default: - console.warn( - `Unknown auth provider type: ${(authProvider as any).type}` - ); + console.warn(`Unknown auth provider type: ${(authProvider as any).type}`); return null; } } @@ -44,7 +42,7 @@ export async function handleServerAuth( async function handleOAuthFlow( serverConfig: MCPServerHTTPConfig, authProvider: any, - req: NextRequest + req: NextRequest, ): Promise { // Check for existing valid token const existingToken = await getStoredToken(serverConfig.url); @@ -62,7 +60,7 @@ async function handleOAuthFlow( // For now, return null as placeholder console.warn( - "OAuth flow not fully implemented. Returning null for OAuth authentication." + "OAuth flow not fully implemented. Returning null for OAuth authentication.", ); return null; } @@ -73,7 +71,7 @@ async function handleOAuthFlow( */ async function handleBearerToken( serverConfig: MCPServerHTTPConfig, - req: NextRequest + req: NextRequest, ): Promise { // Check for bearer token in various sources const authHeader = req.headers.get("Authorization"); @@ -106,7 +104,7 @@ async function handleBearerToken( * This is a placeholder implementation that should be replaced with actual token storage. */ async function getStoredToken( - serverUrl: string + serverUrl: string, ): Promise { // TODO: Implement actual token storage retrieval // This could use cookies, local storage, or a server-side session store diff --git a/apps/web/src/providers/MCP.tsx b/apps/web/src/providers/MCP.tsx index c5724b853..e77768dc9 100644 --- a/apps/web/src/providers/MCP.tsx +++ b/apps/web/src/providers/MCP.tsx @@ -16,7 +16,7 @@ interface MCPContextType { loadingByServer: Map; getToolsFromServer: ( serverName: string, - cursor?: string + cursor?: string, ) => Promise; getAllTools: () => Promise; callTool: (params: any) => Promise; @@ -39,7 +39,7 @@ export const MCPProvider: React.FC = ({ children }) => { const [loading, setLoading] = useState(false); const [loadingByServer, setLoadingByServer] = useState>( - new Map() + new Map(), ); const servers = getMCPServers(); @@ -91,4 +91,3 @@ export const useMCPContext = () => { } return context; }; - diff --git a/apps/web/src/types/configurable.ts b/apps/web/src/types/configurable.ts index de60caef3..926d7efcf 100644 --- a/apps/web/src/types/configurable.ts +++ b/apps/web/src/types/configurable.ts @@ -107,4 +107,3 @@ export type ConfigurableFieldAgentsMetadata = { name?: string; }[]; }; - diff --git a/apps/web/src/types/mcp.ts b/apps/web/src/types/mcp.ts index ce4d51549..40459a740 100644 --- a/apps/web/src/types/mcp.ts +++ b/apps/web/src/types/mcp.ts @@ -55,9 +55,7 @@ export interface MCPServerHTTPConfig extends MCPServerConfig { }; } -export type MCPServerConfiguration = - | MCPServerStdioConfig - | MCPServerHTTPConfig; +export type MCPServerConfiguration = MCPServerStdioConfig | MCPServerHTTPConfig; export interface MCPServersConfig { [serverName: string]: MCPServerConfiguration; diff --git a/apps/web/src/types/tool.ts b/apps/web/src/types/tool.ts index b2d365ce2..3d3a990ae 100644 --- a/apps/web/src/types/tool.ts +++ b/apps/web/src/types/tool.ts @@ -21,4 +21,3 @@ export interface Tool { // Re-export ToolWithServer from mcp.ts for convenience export type { ToolWithServer } from "./mcp"; - From 99aa4db00dfcef71a1af79c191ce4c035ece46cf Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:13:30 +0000 Subject: [PATCH 24/27] Apply patch [skip ci] --- apps/web/src/providers/MCP.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/providers/MCP.tsx b/apps/web/src/providers/MCP.tsx index e77768dc9..197d108f0 100644 --- a/apps/web/src/providers/MCP.tsx +++ b/apps/web/src/providers/MCP.tsx @@ -38,7 +38,7 @@ export const MCPProvider: React.FC = ({ children }) => { }); const [loading, setLoading] = useState(false); - const [loadingByServer, setLoadingByServer] = useState>( + const [loadingByServer] = useState>( new Map(), ); const servers = getMCPServers(); @@ -91,3 +91,4 @@ export const useMCPContext = () => { } return context; }; + From c01e7bf5d0deb486d1e355169c325d0cb747e7aa Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Sun, 3 Aug 2025 22:18:26 +0000 Subject: [PATCH 25/27] Empty commit to trigger CI From 4a8fa78ef5beec8fd5064756cd12b5d6d52f1ef4 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 7 Aug 2025 10:25:49 -0700 Subject: [PATCH 26/27] fix linting --- .../components/create-edit-agent-dialogs/agent-form.tsx | 1 - .../chat/components/configuration-sidebar/index.tsx | 8 +++----- apps/web/src/providers/MCP.tsx | 5 +---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx index b99b58c45..ef914db3a 100644 --- a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx @@ -9,7 +9,6 @@ import { ConfigField, ConfigFieldAgents, ConfigFieldRAG, - ConfigFieldTool, } from "@/features/chat/components/configuration-sidebar/config-field"; import { useSearchTools } from "@/hooks/use-search-tools"; import { useMCPContext } from "@/providers/MCP"; diff --git a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx index e3b112d62..731d66007 100644 --- a/apps/web/src/features/chat/components/configuration-sidebar/index.tsx +++ b/apps/web/src/features/chat/components/configuration-sidebar/index.tsx @@ -8,7 +8,6 @@ import { ConfigField, ConfigFieldAgents, ConfigFieldRAG, - ConfigFieldTool, } from "@/features/chat/components/configuration-sidebar/config-field"; import { ConfigSection } from "@/features/chat/components/configuration-sidebar/config-section"; import { useConfigStore } from "@/features/chat/hooks/use-config-store"; @@ -146,10 +145,9 @@ export const ConfigurationSidebar = forwardRef< setOpenNameAndDescriptionAlertDialog, ] = useState(false); - const { toolSearchTerm, debouncedSetSearchTerm, displayTools } = - useSearchTools(tools, { - preSelectedTools: toolConfigurations[0]?.default?.tools, - }); + const { toolSearchTerm, debouncedSetSearchTerm } = useSearchTools(tools, { + preSelectedTools: toolConfigurations[0]?.default?.tools, + }); const { loadingMore, setLoadingMore } = useFetchPreselectedTools({ tools, setTools, diff --git a/apps/web/src/providers/MCP.tsx b/apps/web/src/providers/MCP.tsx index 197d108f0..728f7b260 100644 --- a/apps/web/src/providers/MCP.tsx +++ b/apps/web/src/providers/MCP.tsx @@ -38,9 +38,7 @@ export const MCPProvider: React.FC = ({ children }) => { }); const [loading, setLoading] = useState(false); - const [loadingByServer] = useState>( - new Map(), - ); + const [loadingByServer] = useState>(new Map()); const servers = getMCPServers(); useEffect(() => { @@ -91,4 +89,3 @@ export const useMCPContext = () => { } return context; }; - From c1fd4226583f096e9058f148eb455d576e76067e Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 19 Aug 2025 12:18:30 -0700 Subject: [PATCH 27/27] cr --- .../scripts/migrate-single-to-multi-mcp.ts | 3 + .../api/oap_mcp/[server]/[...path]/route.ts | 103 ++++++------------ apps/web/src/app/api/oap_mcp/proxy-request.ts | 73 ++++++++++++- .../create-edit-agent-dialogs/agent-form.tsx | 4 +- apps/web/src/hooks/use-mcp.tsx | 32 ++++-- apps/web/src/providers/MCP.tsx | 6 +- 6 files changed, 137 insertions(+), 84 deletions(-) diff --git a/apps/web/scripts/migrate-single-to-multi-mcp.ts b/apps/web/scripts/migrate-single-to-multi-mcp.ts index 078bbb62c..38e2475d6 100644 --- a/apps/web/scripts/migrate-single-to-multi-mcp.ts +++ b/apps/web/scripts/migrate-single-to-multi-mcp.ts @@ -38,6 +38,7 @@ function migrateSingleToMultiMCP(): void { // Create the new multi-server configuration const serverConfig: MCPServerHTTPConfig = { type: "http", + transport: "http", url: legacyUrl, }; @@ -73,6 +74,7 @@ function migrateSingleToMultiMCP(): void { default: serverConfig, "github-tools": { type: "http", + transport: "http", url: "https://api.github.com/mcp", authProvider: { type: "api-key", @@ -81,6 +83,7 @@ function migrateSingleToMultiMCP(): void { }, "local-stdio": { type: "stdio", + transport: "stdio", command: "node", args: ["./local-mcp-server.js"], }, diff --git a/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts b/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts index 738567505..54aa2b424 100644 --- a/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts +++ b/apps/web/src/app/api/oap_mcp/[server]/[...path]/route.ts @@ -1,78 +1,47 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getMCPServers } from "@/lib/environment/mcp-servers"; -import { handleServerAuth } from "@/lib/mcp-auth"; +import { NextRequest } from "next/server"; +import { proxyMultiServerRequest } from "../../proxy-request"; export const runtime = "edge"; -export async function proxyRequest( - req: NextRequest, - { params }: { params: { server: string; path: string[] } }, -): Promise { - const servers = getMCPServers(); - const serverConfig = servers[params.server]; - - if (!serverConfig) { - return NextResponse.json( - { message: `Server ${params.server} not found` }, - { status: 404 }, - ); - } - - if (serverConfig.type === "stdio") { - return NextResponse.json( - { message: "STDIO transport not supported via proxy" }, - { status: 400 }, - ); - } +function extractParamsFromUrl(req: NextRequest) { + const pathname = req.nextUrl?.pathname ?? new URL(req.url).pathname; + const afterBase = pathname.replace(/^\/api\/oap_mcp\//, ""); + const segments = afterBase.split("/").filter(Boolean); + const [server, ...path] = segments; + return { server, path } as const; +} - // Construct target URL - const path = params.path.join("/"); - const targetUrl = new URL(serverConfig.url); - targetUrl.pathname = `${targetUrl.pathname}/mcp/${path}`; +export async function GET(req: NextRequest) { + const { server, path } = extractParamsFromUrl(req); + return proxyMultiServerRequest(req, { params: { server, path } }); +} - // Handle authentication based on server config - const headers = new Headers(); - req.headers.forEach((value, key) => { - if (key.toLowerCase() !== "host") { - headers.append(key, value); - } - }); +export async function POST(req: NextRequest) { + const { server, path } = extractParamsFromUrl(req); + return proxyMultiServerRequest(req, { params: { server, path } }); +} - // Apply server-specific auth - if (serverConfig.authProvider) { - const accessToken = await handleServerAuth(serverConfig, req); - if (accessToken) { - headers.set("Authorization", `Bearer ${accessToken}`); - } - } +export async function PUT(req: NextRequest) { + const { server, path } = extractParamsFromUrl(req); + return proxyMultiServerRequest(req, { params: { server, path } }); +} - // Apply custom headers - if (serverConfig.headers) { - Object.entries(serverConfig.headers).forEach(([key, value]) => { - headers.set(key, value); - }); - } +export async function PATCH(req: NextRequest) { + const { server, path } = extractParamsFromUrl(req); + return proxyMultiServerRequest(req, { params: { server, path } }); +} - // Make the proxied request - const response = await fetch(targetUrl.toString(), { - method: req.method, - headers, - body: req.body, - }); +export async function DELETE(req: NextRequest) { + const { server, path } = extractParamsFromUrl(req); + return proxyMultiServerRequest(req, { params: { server, path } }); +} - // Return the response - return new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers: response.headers, - }); +export async function HEAD(req: NextRequest) { + const { server, path } = extractParamsFromUrl(req); + return proxyMultiServerRequest(req, { params: { server, path } }); } -// Export handlers for all HTTP methods -export const GET = proxyRequest; -export const POST = proxyRequest; -export const PUT = proxyRequest; -export const PATCH = proxyRequest; -export const DELETE = proxyRequest; -export const HEAD = proxyRequest; -export const OPTIONS = proxyRequest; +export async function OPTIONS(req: NextRequest) { + const { server, path } = extractParamsFromUrl(req); + return proxyMultiServerRequest(req, { params: { server, path } }); +} diff --git a/apps/web/src/app/api/oap_mcp/proxy-request.ts b/apps/web/src/app/api/oap_mcp/proxy-request.ts index 161a7ea6c..fe509d18f 100644 --- a/apps/web/src/app/api/oap_mcp/proxy-request.ts +++ b/apps/web/src/app/api/oap_mcp/proxy-request.ts @@ -1,6 +1,71 @@ +import { getMCPServers } from "@/lib/environment/mcp-servers"; +import { handleServerAuth } from "@/lib/mcp-auth"; import { NextRequest, NextResponse } from "next/server"; import { createServerClient } from "@supabase/ssr"; -import { getMCPServers } from "@/lib/environment/mcp-servers"; + +export async function proxyMultiServerRequest( + req: NextRequest, + { params }: { params: { server: string; path: string[] } }, +): Promise { + const servers = getMCPServers(); + const serverConfig = servers[params.server]; + + if (!serverConfig) { + return NextResponse.json( + { message: `Server ${params.server} not found` }, + { status: 404 }, + ); + } + + if (serverConfig.type === "stdio") { + return NextResponse.json( + { message: "STDIO transport not supported via proxy" }, + { status: 400 }, + ); + } + + // Construct target URL + const path = params.path.join("/"); + const targetUrl = new URL(serverConfig.url); + targetUrl.pathname = `${targetUrl.pathname}/mcp/${path}`; + + // Handle authentication based on server config + const headers = new Headers(); + req.headers.forEach((value, key) => { + if (key.toLowerCase() !== "host") { + headers.append(key, value); + } + }); + + // Apply server-specific auth + if (serverConfig.authProvider) { + const accessToken = await handleServerAuth(serverConfig, req); + if (accessToken) { + headers.set("Authorization", `Bearer ${accessToken}`); + } + } + + // Apply custom headers + if (serverConfig.headers) { + Object.entries(serverConfig.headers).forEach(([key, value]) => { + headers.set(key, value); + }); + } + + // Make the proxied request + const response = await fetch(targetUrl.toString(), { + method: req.method, + headers, + body: req.body, + }); + + // Return the response + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }); +} // This will contain the object which contains the access token const MCP_TOKENS = process.env.MCP_TOKENS; @@ -103,11 +168,7 @@ export async function proxyRequest(req: NextRequest): Promise { // If the first segment matches a configured server, delegate to new proxy if (servers[potentialServerName]) { - // Import and use the new per-server proxy handler - const { proxyRequest: newProxyRequest } = await import( - "./[server]/[...path]/route" - ); - return newProxyRequest(req, { + return proxyMultiServerRequest(req, { params: { server: potentialServerName, path: pathSegments.slice(1), diff --git a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx index ef914db3a..cd44fb871 100644 --- a/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx +++ b/apps/web/src/features/agents/components/create-edit-agent-dialogs/agent-form.tsx @@ -60,7 +60,7 @@ export function AgentFieldsForm({ config: Record; }>(); - const { tools, toolsByServer, setTools, getTools, cursor, loading } = + const { tools, toolsByServer, setTools, getTools, getToolsByServer, cursor, loading } = useMCPContext(); const { toolSearchTerm, debouncedSetSearchTerm, displayTools } = useSearchTools(tools, { @@ -192,7 +192,7 @@ export function AgentFieldsForm({ onClick={async () => { try { setLoadingMore(true); - const moreTool = await getTools(cursor); + const moreTool = await getToolsByServer(cursor, cursor); setTools((prevTools) => [ ...prevTools, ...moreTool, diff --git a/apps/web/src/hooks/use-mcp.tsx b/apps/web/src/hooks/use-mcp.tsx index dc9e51a32..18d838277 100644 --- a/apps/web/src/hooks/use-mcp.tsx +++ b/apps/web/src/hooks/use-mcp.tsx @@ -1,6 +1,6 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { useState } from "react"; +import { Dispatch, SetStateAction, useState } from "react"; import { getMCPServers } from "@/lib/environment/mcp-servers"; import { MCPServerConfiguration, ToolWithServer } from "@/types/mcp"; @@ -108,6 +108,11 @@ export default function useMCP({ name, version, serverName }: UseMCPOptions) { return allTools; }; + const getToolsByServer = async (serverName: string, cursor?: string): Promise => { + const tools = await getToolsFromServer(serverName, cursor); + return tools; + }; + const callTool = async ({ name, args, @@ -145,14 +150,24 @@ export default function useMCP({ name, version, serverName }: UseMCPOptions) { // Legacy compatibility - maintain old interface const tools = Array.from(toolsByServer.values()).flat(); - const setTools = (newTools: ToolWithServer[]) => { - const newMap = new Map(); - newTools.forEach((tool) => { - const serverTools = newMap.get(tool.serverName) || []; - serverTools.push(tool); - newMap.set(tool.serverName, serverTools); + const setTools: Dispatch> = (value) => { + setToolsByServer((prevMap) => { + const prevTools = Array.from(prevMap.values()).flat(); + const nextTools = + typeof value === "function" + ? (value as (prevState: ToolWithServer[]) => ToolWithServer[])( + prevTools, + ) + : value; + + const newMap = new Map(); + nextTools.forEach((tool) => { + const serverTools = newMap.get(tool.serverName) || []; + serverTools.push(tool); + newMap.set(tool.serverName, serverTools); + }); + return newMap; }); - setToolsByServer(newMap); }; // Legacy single cursor - returns first server's cursor @@ -166,6 +181,7 @@ export default function useMCP({ name, version, serverName }: UseMCPOptions) { setToolsByServer, cursorsByServer, connections, + getToolsByServer, // Legacy compatibility getTools: getAllTools, createAndConnectMCPClient: () => diff --git a/apps/web/src/providers/MCP.tsx b/apps/web/src/providers/MCP.tsx index 728f7b260..effd48703 100644 --- a/apps/web/src/providers/MCP.tsx +++ b/apps/web/src/providers/MCP.tsx @@ -4,6 +4,8 @@ import React, { PropsWithChildren, useEffect, useState, + Dispatch, + SetStateAction, } from "react"; import useMCP from "../hooks/use-mcp"; import { getMCPServers } from "@/lib/environment/mcp-servers"; @@ -23,9 +25,10 @@ interface MCPContextType { cursorsByServer: Map; // Legacy compatibility tools: ToolWithServer[]; - setTools: (tools: ToolWithServer[]) => void; + setTools: Dispatch>; cursor: string; getTools: () => Promise; + getToolsByServer: (serverName: string, cursor?: string) => Promise; createAndConnectMCPClient: () => Promise; } @@ -74,6 +77,7 @@ export const MCPProvider: React.FC = ({ children }) => { setTools: mcpState.setTools, cursor: mcpState.cursor, getTools: mcpState.getTools, + getToolsByServer: mcpState.getToolsByServer, createAndConnectMCPClient: mcpState.createAndConnectMCPClient, }} >