import { useState, useRef, useEffect } from 'react' import { useMcp, type Tool } from 'use-mcp/react' import { Info, X } from 'lucide-react' // MCP Connection wrapper that only renders when active function McpConnection({ serverUrl, onConnectionUpdate, }: { serverUrl: string onConnectionUpdate: (data: any) => void }) { // Use the MCP hook with the server URL const connection = useMcp({ url: serverUrl, debug: true, autoRetry: false, popupFeatures: 'width=500,height=600,resizable=yes,scrollbars=yes', }) // Update parent component with connection data useEffect(() => { onConnectionUpdate(connection) }, [ connection.state, connection.tools, connection.error, connection.log.length, connection.authUrl, ]) // Return null as this is just a hook wrapper return null } export function McpServers({ onToolsUpdate, }: { onToolsUpdate?: (tools: Tool[]) => void }) { const [serverUrl, setServerUrl] = useState(() => { return sessionStorage.getItem('mcpServerUrl') || '' }) const [isActive, setIsActive] = useState(false) const [connectionData, setConnectionData] = useState({ state: 'not-connected', tools: [], error: undefined, log: [], authUrl: undefined, retry: () => {}, disconnect: () => {}, authenticate: () => Promise.resolve(undefined), callTool: (_name: string, _args?: Record) => Promise.resolve(undefined), clearStorage: () => {}, }) const [toolForms, setToolForms] = useState>>({}) const [toolExecutionLogs, setToolExecutionLogs] = useState>({}) const logRef = useRef(null) const executionLogRef = useRef(null) // Extract connection properties const { state, tools, error, log, authUrl, disconnect, authenticate } = connectionData // Notify parent component when tools change useEffect(() => { if (onToolsUpdate && tools.length > 0) { onToolsUpdate( tools.map((t: Tool) => ({ ...t, callTool: (args: Record) => connectionData.callTool(t.name, args), })), ) } }, [tools, onToolsUpdate]) // Handle connection const handleConnect = () => { if (!serverUrl.trim()) return setIsActive(true) } // Handle disconnection const handleDisconnect = () => { disconnect() setIsActive(false) setConnectionData({ state: 'not-connected', tools: [], error: undefined, log: [], authUrl: undefined, retry: () => {}, disconnect: () => {}, authenticate: () => Promise.resolve(undefined), callTool: (_name: string, _args?: Record) => Promise.resolve(undefined), clearStorage: () => {}, }) } // Handle authentication if popup was blocked const handleManualAuth = async () => { try { await authenticate() } catch (err) { console.error('Authentication error:', err) } } // Auto-scroll log to bottom useEffect(() => { if (logRef.current) { logRef.current.scrollTop = logRef.current.scrollHeight } }, [log]) // Generate status badge based on connection state const getStatusBadge = () => { const baseClasses = 'px-2 py-1 rounded-full text-xs font-medium' switch (state) { case 'discovering': return ( Discovering ) case 'authenticating': return ( Authenticating ) case 'connecting': return ( Connecting ) case 'loading': return ( Loading ) case 'ready': return ( Connected ) case 'failed': return ( Failed ) case 'not-connected': default: return ( Not Connected ) } } // Log the tools to console when they change (for debugging) useEffect(() => { if (tools.length > 0) { console.log('MCP Tools available:', tools) } }, [tools]) // Initialize form data when tools change useEffect(() => { const newForms: Record> = {} tools.forEach((tool: Tool) => { if (tool.inputSchema && tool.inputSchema.properties) { const formData: Record = {} Object.entries(tool.inputSchema.properties).forEach(([key, schema]: [string, any]) => { // Set default values based on type if (schema.type === 'number' || schema.type === 'integer') { formData[key] = schema.default || 0 } else if (schema.type === 'boolean') { formData[key] = schema.default || false } else { formData[key] = schema.default || '' } }) newForms[tool.name] = formData } }) setToolForms(newForms) }, [tools]) // Handle form input changes const handleFormChange = (toolName: string, fieldName: string, value: any) => { setToolForms(prev => ({ ...prev, [toolName]: { ...prev[toolName], [fieldName]: value } })) } // Handle tool execution const handleRunTool = async (tool: Tool) => { const args = toolForms[tool.name] || {} const argsStr = JSON.stringify(args) // Add execution start message const startMessage = `Calling ${tool.name}(${argsStr})\n` setToolExecutionLogs(prev => ({ ...prev, [tool.name]: (prev[tool.name] || '') + startMessage })) try { const result = await connectionData.callTool(tool.name, args) const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2) setToolExecutionLogs(prev => ({ ...prev, [tool.name]: (prev[tool.name] || '') + `${resultStr}\n\n` })) } catch (error) { setToolExecutionLogs(prev => ({ ...prev, [tool.name]: (prev[tool.name] || '') + `Error: ${error}\n\n` })) } } // Clear execution log for specific tool const clearExecutionLog = (toolName: string) => { setToolExecutionLogs(prev => ({ ...prev, [toolName]: '' })) } // Render form field based on schema const renderFormField = (toolName: string, fieldName: string, schema: any, isRequired: boolean) => { const value = toolForms[toolName]?.[fieldName] || '' if (schema.type === 'number' || schema.type === 'integer') { return ( { const newValue = e.target.value === '' ? '' : (schema.type === 'integer' ? parseInt(e.target.value) || 0 : parseFloat(e.target.value) || 0) handleFormChange(toolName, fieldName, newValue) }} /> ) } else if (schema.type === 'boolean') { return ( handleFormChange(toolName, fieldName, e.target.checked)} /> ) } else { // String or other text input return ( handleFormChange(toolName, fieldName, e.target.value)} /> ) } } return (
MCP Servers {getStatusBadge()}

Connect to Model Context Protocol (MCP) servers to access additional AI capabilities.

{ const newValue = e.target.value setServerUrl(newValue) sessionStorage.setItem('mcpServerUrl', newValue) }} disabled={isActive && state !== 'failed'} /> {state === 'ready' || (isActive && state !== 'not-connected' && state !== 'failed') ? ( ) : ( )}
{/* Authentication Link if needed */} {authUrl && (

Authentication required. Please click the link below:

Authenticate in new window
)} {/* Available Tools section - always present */}

Available Tools ({tools.length})

{tools.length === 0 ? (
No tools available. Connect to an MCP server to see available tools.
) : (
{tools.map((tool: Tool, index: number) => (
{/* Tool title outside the card */}

{tool.name}

{tool.description && (

{tool.description}

)} {/* Form for tool parameters */} {tool.inputSchema && tool.inputSchema.properties && (
{Object.entries(tool.inputSchema.properties).map(([fieldName, schema]: [string, any]) => { const isRequired = tool.inputSchema.required?.includes(fieldName) || false const isTextInput = schema.type !== 'number' && schema.type !== 'integer' && schema.type !== 'boolean' return (
{schema.description && (
{schema.description}
)}
{renderFormField(tool.name, fieldName, schema, isRequired)}
) })}
)} {/* Run button */} {/* Per-tool execution log - only show if there's content */} {toolExecutionLogs[tool.name] && (
Execution Log