Files
claudia-old/src/components/MCPManager.tsx
2025-06-19 19:24:01 +05:30

215 lines
7.0 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ArrowLeft, Network, Plus, Download, AlertCircle, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { Card } from "@/components/ui/card";
import { Toast, ToastContainer } from "@/components/ui/toast";
import { api, type MCPServer } from "@/lib/api";
import { MCPServerList } from "./MCPServerList";
import { MCPAddServer } from "./MCPAddServer";
import { MCPImportExport } from "./MCPImportExport";
interface MCPManagerProps {
/**
* Callback to go back to the main view
*/
onBack: () => void;
/**
* Optional className for styling
*/
className?: string;
}
/**
* Main component for managing MCP (Model Context Protocol) servers
* Provides a comprehensive UI for adding, configuring, and managing MCP servers
*/
export const MCPManager: React.FC<MCPManagerProps> = ({
onBack,
className,
}) => {
const [activeTab, setActiveTab] = useState("servers");
const [servers, setServers] = useState<MCPServer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null);
// Load servers on mount
useEffect(() => {
loadServers();
}, []);
/**
* Loads all MCP servers
*/
const loadServers = async () => {
try {
setLoading(true);
setError(null);
console.log("MCPManager: Loading servers...");
const serverList = await api.mcpList();
console.log("MCPManager: Received server list:", serverList);
console.log("MCPManager: Server count:", serverList.length);
setServers(serverList);
} catch (err) {
console.error("MCPManager: Failed to load MCP servers:", err);
setError("Failed to load MCP servers. Make sure Claude Code is installed.");
} finally {
setLoading(false);
}
};
/**
* Handles server added event
*/
const handleServerAdded = () => {
loadServers();
setToast({ message: "MCP server added successfully!", type: "success" });
setActiveTab("servers");
};
/**
* Handles server removed event
*/
const handleServerRemoved = (name: string) => {
setServers(prev => prev.filter(s => s.name !== name));
setToast({ message: `Server "${name}" removed successfully!`, type: "success" });
};
/**
* Handles import completed event
*/
const handleImportCompleted = (imported: number, failed: number) => {
loadServers();
if (failed === 0) {
setToast({
message: `Successfully imported ${imported} server${imported > 1 ? 's' : ''}!`,
type: "success"
});
} else {
setToast({
message: `Imported ${imported} server${imported > 1 ? 's' : ''}, ${failed} failed`,
type: "error"
});
}
};
return (
<div className={`flex flex-col h-full bg-background text-foreground ${className || ""}`}>
<div className="max-w-5xl mx-auto w-full flex flex-col h-full">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="flex items-center justify-between p-4 border-b border-border"
>
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="icon"
onClick={onBack}
className="h-8 w-8"
>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<h2 className="text-lg font-semibold flex items-center gap-2">
<Network className="h-5 w-5 text-blue-500" />
MCP Servers
</h2>
<p className="text-xs text-muted-foreground">
Manage Model Context Protocol servers
</p>
</div>
</div>
</motion.div>
{/* Error Display */}
<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="mx-4 mt-4 p-3 rounded-lg bg-destructive/10 border border-destructive/50 flex items-center gap-2 text-sm text-destructive"
>
<AlertCircle className="h-4 w-4" />
{error}
</motion.div>
)}
</AnimatePresence>
{/* Main Content */}
{loading ? (
<div className="flex-1 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : (
<div className="flex-1 overflow-y-auto p-4">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="grid w-full max-w-md grid-cols-3">
<TabsTrigger value="servers" className="gap-2">
<Network className="h-4 w-4 text-blue-500" />
Servers
</TabsTrigger>
<TabsTrigger value="add" className="gap-2">
<Plus className="h-4 w-4 text-green-500" />
Add Server
</TabsTrigger>
<TabsTrigger value="import" className="gap-2">
<Download className="h-4 w-4 text-purple-500" />
Import/Export
</TabsTrigger>
</TabsList>
{/* Servers Tab */}
<TabsContent value="servers" className="mt-6">
<Card>
<MCPServerList
servers={servers}
loading={false}
onServerRemoved={handleServerRemoved}
onRefresh={loadServers}
/>
</Card>
</TabsContent>
{/* Add Server Tab */}
<TabsContent value="add" className="mt-6">
<Card>
<MCPAddServer
onServerAdded={handleServerAdded}
onError={(message: string) => setToast({ message, type: "error" })}
/>
</Card>
</TabsContent>
{/* Import/Export Tab */}
<TabsContent value="import" className="mt-6">
<Card className="overflow-hidden">
<MCPImportExport
onImportCompleted={handleImportCompleted}
onError={(message: string) => setToast({ message, type: "error" })}
/>
</Card>
</TabsContent>
</Tabs>
</div>
)}
</div>
{/* Toast Notifications */}
<ToastContainer>
{toast && (
<Toast
message={toast.message}
type={toast.type}
onDismiss={() => setToast(null)}
/>
)}
</ToastContainer>
</div>
);
};