UNPKG

@mcp-shark/mcp-shark

Version:

Aggregate multiple Model Context Protocol (MCP) servers into a single unified interface with a powerful monitoring UI. Prov deep visibility into every request and response.

286 lines (254 loc) 7.79 kB
import { useState, useEffect } from 'react'; import { colors } from './theme'; import SetupHeader from './components/SetupHeader'; import ConfigFileSection from './components/ConfigFileSection'; import WhatThisDoesSection from './components/WhatThisDoesSection'; import ServerControl from './components/ServerControl'; import MessageDisplay from './components/MessageDisplay'; import BackupList from './components/BackupList'; import ConfigViewerModal from './components/ConfigViewerModal'; import { useServiceExtraction } from './hooks/useServiceExtraction'; import { useConfigManagement } from './hooks/useConfigManagement'; function CompositeSetup() { const [fileContent, setFileContent] = useState(''); const [filePath, setFilePath] = useState(''); const [updatePath, setUpdatePath] = useState(''); const [status, setStatus] = useState({ running: false, pid: null }); const [loading, setLoading] = useState(false); const [message, setMessage] = useState(null); const [error, setError] = useState(null); const { services, selectedServices, setSelectedServices } = useServiceExtraction( fileContent, filePath ); const { detectedPaths, detecting, detectConfigPaths, backups, loadingBackups, loadBackups, viewingConfig, configContent, loadingConfig, handleViewConfig, setViewingConfig, setConfigContent, viewingBackup, backupContent, loadingBackup, handleViewBackup, handleDeleteBackup, setViewingBackup, setBackupContent, } = useConfigManagement(); useEffect(() => { fetchStatus(); const interval = setInterval(fetchStatus, 2000); return () => clearInterval(interval); }, []); const handleRestore = async (backupPath, originalPath) => { if ( !confirm( 'Are you sure you want to restore this backup? This will overwrite the current config file.' ) ) { return; } try { const res = await fetch('/api/config/restore', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ backupPath, originalPath }), }); const data = await res.json(); if (res.ok) { setMessage(data.message || 'Config restored successfully'); setError(null); loadBackups(); detectConfigPaths(); } else { setError(data.error || 'Failed to restore backup'); setMessage(null); } } catch (err) { setError(err.message || 'Failed to restore backup'); setMessage(null); } }; const handleDelete = async (backupPath) => { const success = await handleDeleteBackup(backupPath); if (success) { setMessage('Backup deleted successfully'); setError(null); } else { setError('Failed to delete backup'); setMessage(null); } }; const fetchStatus = async () => { try { const res = await fetch('/api/composite/status'); const data = await res.json(); setStatus(data); } catch (err) { console.error('Failed to fetch status:', err); } }; const handleFileSelect = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { setFileContent(event.target.result); }; reader.readAsText(file); } }; const handlePathInput = (e) => { const value = e.target.value; setFilePath(value); if (value) { setFileContent(''); } }; const handleUpdatePathInput = (e) => { setUpdatePath(e.target.value); }; const handleSetup = async () => { setLoading(true); setError(null); setMessage(null); try { const payload = fileContent ? { fileContent, filePath: updatePath || null } : { filePath }; if (selectedServices.size > 0) { payload.selectedServices = Array.from(selectedServices); } const res = await fetch('/api/composite/setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json(); if (res.ok) { const msg = data.message || 'MCP Shark server started successfully'; setMessage(data.backupPath ? `${msg} (Backup saved to ${data.backupPath})` : msg); setFileContent(''); setFilePath(''); setUpdatePath(''); setSelectedServices(new Set()); setTimeout(fetchStatus, 1000); } else { setError(data.error || 'Failed to setup MCP Shark server'); } } catch (err) { setError(err.message || 'Failed to setup MCP Shark server'); } finally { setLoading(false); } }; const handleStop = async () => { setLoading(true); setError(null); setMessage(null); try { const res = await fetch('/api/composite/stop', { method: 'POST', }); const data = await res.json(); if (res.ok) { const msg = data.message || 'MCP Shark server stopped'; setMessage( data.message && data.message.includes('restored') ? 'MCP Shark server stopped and original config file restored' : msg ); setTimeout(fetchStatus, 1000); } else { setError(data.error || 'Failed to stop MCP Shark server'); } } catch (err) { setError(err.message || 'Failed to stop MCP Shark server'); } finally { setLoading(false); } }; const canStart = (fileContent || filePath) && (services.length === 0 || selectedServices.size > 0); return ( <div style={{ position: 'relative', width: '100%', height: '100%', background: colors.bgPrimary, overflow: 'auto', }} > <div style={{ width: '100%', maxWidth: '1200px', margin: '0 auto', padding: '32px', minHeight: '100%', boxSizing: 'border-box', }} > <SetupHeader /> <ConfigFileSection detectedPaths={detectedPaths} detecting={detecting} onDetect={detectConfigPaths} onPathSelect={(path) => { setFilePath(path); setFileContent(''); setUpdatePath(path); }} onViewConfig={handleViewConfig} filePath={filePath} fileContent={fileContent} updatePath={updatePath} onFileSelect={handleFileSelect} onPathChange={handlePathInput} onUpdatePathChange={handleUpdatePathInput} services={services} selectedServices={selectedServices} onSelectionChange={setSelectedServices} /> <ServerControl status={status} loading={loading} onStart={handleSetup} onStop={handleStop} canStart={canStart} /> <MessageDisplay message={message} error={error} /> <BackupList backups={backups} loadingBackups={loadingBackups} onRefresh={loadBackups} onRestore={handleRestore} onView={handleViewBackup} onDelete={handleDelete} /> <WhatThisDoesSection filePath={filePath} updatePath={updatePath} /> </div> <ConfigViewerModal viewingConfig={viewingConfig} configContent={configContent} loadingConfig={loadingConfig} viewingBackup={viewingBackup} backupContent={backupContent} loadingBackup={loadingBackup} onClose={() => { setViewingConfig(null); setConfigContent(null); setViewingBackup(null); setBackupContent(null); }} /> </div> ); } export default CompositeSetup;