diff --git a/frontend/src/components/mcp-config-modal.tsx b/frontend/src/components/mcp-config-modal.tsx index f0476b4..0b7f0f1 100644 --- a/frontend/src/components/mcp-config-modal.tsx +++ b/frontend/src/components/mcp-config-modal.tsx @@ -4,7 +4,7 @@ import { useState, useEffect, useRef } from "react"; import { useCoAgent } from "@copilotkit/react-core"; import { useLocalStorage } from "@/hooks/use-local-storage"; import { ConnectionType, ServerConfig, MCP_STORAGE_KEY } from "@/lib/mcp-config-types"; -import { X, Plus, Server, Globe, Trash2 } from "lucide-react"; +import { X, Plus, Server, Globe, Trash2, Loader2 } from "lucide-react"; import { AvailableAgents } from "@/lib/available-agents"; // External link icon component @@ -80,6 +80,14 @@ export function MCPConfigModal({ isOpen, onClose }: MCPConfigModalProps) { const [url, setUrl] = useState(""); const [isLoading, setIsLoading] = useState(true); const [showAddServerForm, setShowAddServerForm] = useState(false); + + const [testResults, setTestResults] = useState>({}); + + const [testingServer, setTestingServer] = useState(null); // Calculate server statistics const totalServers = Object.keys(configs).length; @@ -129,6 +137,97 @@ export function MCPConfigModal({ isOpen, onClose }: MCPConfigModalProps) { const newConfigs = { ...configs }; delete newConfigs[name]; setConfigs(newConfigs); + + // Also remove test result for this server + const newTestResults = { ...testResults }; + delete newTestResults[name]; + setTestResults(newTestResults); + }; + + const testServer = async (name: string, config: ServerConfig) => { + setTestingServer(name); + + try { + if (config.transport === "stdio") { + // For stdio servers, we can only validate the configuration format + // since we can't execute commands from the browser + if (config.command) { + setTestResults(prev => ({ + ...prev, + [name]: { + success: true, + message: "Server configuration is valid! (Note: Command execution can only be tested in runtime)", + timestamp: Date.now(), + }, + })); + } else { + setTestResults(prev => ({ + ...prev, + [name]: { + success: false, + message: "Command is required for stdio server", + timestamp: Date.now(), + }, + })); + } + } else if (config.transport === "sse") { + // Test sse server by sending a simple request + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + const response = await fetch(config.url, { + method: "GET", + headers: { + "Accept": "text/event-stream", + }, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (response.ok) { + setTestResults(prev => ({ + ...prev, + [name]: { + success: true, + message: "Server test successful!", + timestamp: Date.now(), + }, + })); + } else { + setTestResults(prev => ({ + ...prev, + [name]: { + success: false, + message: `Server returned error: ${response.status} ${response.statusText}`, + timestamp: Date.now(), + }, + })); + } + } catch (error) { + setTestResults(prev => ({ + ...prev, + [name]: { + success: false, + message: `Connection failed: ${error instanceof Error ? error.message : "Unknown error"}`, + timestamp: Date.now(), + }, + })); + } + } + } catch (error) { + setTestResults(prev => ({ + ...prev, + [name]: { + success: false, + message: `Test failed: ${error instanceof Error ? error.message : "Unknown error"}`, + timestamp: Date.now(), + }, + })); + } finally { + setTestingServer(null); + } }; if (!isOpen) return null; @@ -201,46 +300,84 @@ export function MCPConfigModal({ isOpen, onClose }: MCPConfigModalProps) { ) : (
- {Object.entries(configs).map(([name, config]) => ( -
-
-
-
-

{name}

-
- {config.transport === "stdio" ? ( - - ) : ( - - )} - {config.transport} + {Object.entries(configs).map(([name, config]) => { + const testResult = testResults[name]; + const isTesting = testingServer === name; + + return ( +
+
+
+
+

{name}

+
+ {config.transport === "stdio" ? ( + + ) : ( + + )} + {config.transport} +
+
+
+ +
- -
-
- {config.transport === "stdio" ? ( - <> -

Command: {config.command}

-

- Args: {config.args.join(" ")} -

- - ) : ( -

URL: {config.url}

+
+ {config.transport === "stdio" ? ( + <> +

Command: {config.command}

+

+ Args: {config.args.join(" ")} +

+ + ) : ( +

URL: {config.url}

+ )} +
+ + {/* Test Result */} + {testResult && ( +
+
+ {testResult.message} + + {new Date(testResult.timestamp).toLocaleTimeString()} + +
+
)}
-
- ))} + ); + })}
)}