'use client'; import { useState, useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardAction } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Loader2, Play, Square, RefreshCw, Save } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import { McpJsonEditor } from '@/components/chat/mcp-json-editor'; interface McpServerConfig { command: string; args: string[]; env?: Record; } interface McpConfig { mcpServers: Record; } interface McpServerStatus { name: string; status: 'running' | 'stopped' | 'error'; pid?: number; } export function McpConfigPanel() { const [config, setConfig] = useState({ mcpServers: {} }); const [statuses, setStatuses] = useState([]); const [configJson, setConfigJson] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [configPath, setConfigPath] = useState(''); const { toast } = useToast(); useEffect(() => { loadConfig(); loadStatus(); loadConfigPath(); // Refresh status every 5 seconds const interval = setInterval(() => { loadStatus(); }, 5000); return () => clearInterval(interval); }, []); const loadConfig = async () => { try { setIsLoading(true); const loadedConfig = await invoke('load_mcp_config'); // Convert from Rust HashMap to TypeScript Record // Always ensure minimum structure: { mcpServers: {} } const config: McpConfig = { mcpServers: loadedConfig.mcp_servers || loadedConfig.mcpServers || {} }; setConfig(config); setConfigJson(JSON.stringify(config, null, 2)); } catch (error) { console.error('Failed to load MCP config:', error); // On error, set minimum structure const minConfig: McpConfig = { mcpServers: {} }; setConfig(minConfig); setConfigJson(JSON.stringify(minConfig, null, 2)); toast({ title: 'Erro', description: 'Falha ao carregar configuração MCP', variant: 'destructive', }); } finally { setIsLoading(false); } }; const loadStatus = async () => { try { const serverStatuses = await invoke('list_mcp_server_status'); setStatuses(serverStatuses); } catch (error) { console.error('Failed to load MCP status:', error); } }; const loadConfigPath = async () => { try { const path = await invoke('get_mcp_config_path_command'); setConfigPath(path); } catch (error) { console.error('Failed to load config path:', error); } }; const saveConfig = async () => { try { setIsSaving(true); // Trim whitespace from JSON string const trimmedJson = configJson.trim(); // Validate JSON structure if (!trimmedJson) { toast({ title: 'Erro', description: 'JSON vazio. Por favor, insira uma configuração válida.', variant: 'destructive', }); setIsSaving(false); return; } let parsedConfig: McpConfig; try { parsedConfig = JSON.parse(trimmedJson); } catch (e) { const errorMessage = e instanceof Error ? e.message : 'Erro desconhecido ao fazer parse do JSON'; console.error('JSON parse error:', e); toast({ title: 'Erro de JSON', description: `JSON inválido: ${errorMessage}`, variant: 'destructive', }); setIsSaving(false); return; } // Validate required structure if (!parsedConfig || typeof parsedConfig !== 'object') { toast({ title: 'Erro', description: 'Configuração inválida: deve ser um objeto JSON.', variant: 'destructive', }); setIsSaving(false); return; } // Ensure mcpServers exists and is an object if (!parsedConfig.mcpServers || typeof parsedConfig.mcpServers !== 'object') { parsedConfig.mcpServers = {}; } // Always ensure minimum structure: { mcpServers: {} } const normalizedConfig: McpConfig = { mcpServers: parsedConfig.mcpServers || {} }; // The Rust struct uses serde(rename = "mcpServers") so we can pass it directly // No need to convert mcpServers to mcp_servers await invoke('save_mcp_config', { config: normalizedConfig }); setConfig(normalizedConfig); // Update JSON to reflect normalized structure setConfigJson(JSON.stringify(normalizedConfig, null, 2)); toast({ title: 'Sucesso', description: 'Configuração MCP salva com sucesso', }); // Reload status after saving loadStatus(); } catch (error) { console.error('Failed to save MCP config:', error); const errorMessage = error instanceof Error ? error.message : 'Erro desconhecido'; toast({ title: 'Erro', description: `Falha ao salvar configuração MCP: ${errorMessage}`, variant: 'destructive', }); } finally { setIsSaving(false); } }; const startServer = async (name: string) => { try { const serverConfig = config.mcpServers[name]; if (!serverConfig) { toast({ title: 'Erro', description: `Servidor '${name}' não encontrado na configuração`, variant: 'destructive', }); return; } await invoke('start_mcp_server', { name, config: serverConfig }); toast({ title: 'Sucesso', description: `Servidor '${name}' iniciado`, }); loadStatus(); } catch (error) { console.error('Failed to start server:', error); const errorMessage = error instanceof Error ? error.message : String(error); toast({ title: 'Erro ao Iniciar Servidor', description: errorMessage || `Falha ao iniciar servidor '${name}'`, variant: 'destructive', }); } }; const stopServer = async (name: string) => { try { await invoke('stop_mcp_server', { name }); toast({ title: 'Sucesso', description: `Servidor '${name}' parado`, }); loadStatus(); } catch (error) { console.error('Failed to stop server:', error); toast({ title: 'Erro', description: `Falha ao parar servidor '${name}'`, variant: 'destructive', }); } }; const restartServer = async (name: string) => { try { await invoke('restart_mcp_server', { name }); toast({ title: 'Sucesso', description: `Servidor '${name}' reiniciado`, }); loadStatus(); } catch (error) { console.error('Failed to restart server:', error); toast({ title: 'Erro', description: `Falha ao reiniciar servidor '${name}'`, variant: 'destructive', }); } }; const restartAll = async () => { try { const started = await invoke('restart_all_mcp_servers'); toast({ title: 'Sucesso', description: `${started.length} servidor(es) iniciado(s)`, }); loadStatus(); } catch (error) { console.error('Failed to restart all servers:', error); toast({ title: 'Erro', description: 'Falha ao reiniciar servidores', variant: 'destructive', }); } }; const getStatusBadgeVariant = (status: string) => { switch (status) { case 'running': return 'default'; case 'stopped': return 'secondary'; case 'error': return 'destructive'; default: return 'secondary'; } }; if (isLoading) { return (
); } return (

MCP Servers

Gerencie servidores Model Context Protocol

{configPath && (

Config: {configPath}

)}
{/* Server Status List */}
{Object.keys(config.mcpServers).length === 0 ? (

Nenhum servidor MCP configurado. Adicione servidores no JSON abaixo.

) : ( Object.keys(config.mcpServers).map((name) => { const status = statuses.find(s => s.name === name); const isRunning = status?.status === 'running'; return (
{name} {status && ( {status.status} )} {status?.pid && ( PID: {status.pid} )}
{isRunning ? ( <> ) : ( )}
Comando:{' '} {config.mcpServers[name].command} {config.mcpServers[name].args.join(' ')}
{config.mcpServers[name].env && (
Env:{' '} {Object.keys(config.mcpServers[name].env || {}).join(', ')}
)}
); }) )}
{/* JSON Editor */} Configuração JSON Edite a configuração MCP no formato JSON padrão
); }