diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index fe6a372..adf5dfd 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -428,6 +428,45 @@ export function downloadFile(path: string): void { link.remove(); } +export interface McpServer { + id: string; + label: string; + description: string; + url_param: boolean; + env_params: string[]; + enabled: boolean; + params: Record; + state: "inactive" | "connected" | "error"; + error: string | null; + tools: number; +} + +export async function listMcp(): Promise { + const res = await fetch("/api/mcp"); + return (await res.json()).servers; +} + +export async function updateMcp( + id: string, + enabled: boolean, + params: Record +): Promise { + const res = await fetch(`/api/mcp/${id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ enabled, params }), + }); + if (!res.ok) throw await apiError(res, "mise à jour MCP impossible"); + return (await res.json()).servers; +} + +export async function testMcp( + id: string +): Promise<{ ok: boolean; tools: string[]; error: string | null }> { + const res = await fetch(`/api/mcp/${id}/test`, { method: "POST" }); + return res.json(); +} + export async function listSessions(): Promise { const res = await fetch("/api/sessions"); return (await res.json()).sessions; diff --git a/frontend/src/panels/SettingsView.tsx b/frontend/src/panels/SettingsView.tsx index 0d80c40..12a6acd 100644 --- a/frontend/src/panels/SettingsView.tsx +++ b/frontend/src/panels/SettingsView.tsx @@ -4,10 +4,18 @@ import { deleteModel, getBenchScores, getHardware, + listMcp, pullModel, runBench, + testMcp, + updateMcp, +} from "../api/client"; +import type { + AgentConfig, + BenchResult, + HardwareInfo, + McpServer, } from "../api/client"; -import type { AgentConfig, BenchResult, HardwareInfo } from "../api/client"; import { DownloadIcon, RefreshIcon } from "../components/Icon"; const TOOL_DESC: Record = { @@ -466,6 +474,8 @@ export function SettingsView() { + + @@ -498,6 +508,117 @@ export function SettingsView() { /** Benchmark : évalue le modèle sélectionné sur 5 mini-épreuves. */ /** Panneau Matériel : GPU vu par Loki vs GPU réellement utilisé par Ollama. */ +function McpCard() { + const [servers, setServers] = useState([]); + const [testing, setTesting] = useState(null); + const [testResult, setTestResult] = useState>({}); + + useEffect(() => { + listMcp().then(setServers).catch(() => {}); + }, []); + + const toggle = async (s: McpServer) => { + setServers(await updateMcp(s.id, !s.enabled, s.params)); + }; + + const setParam = async (s: McpServer, key: string, value: string) => { + setServers(await updateMcp(s.id, s.enabled, { ...s.params, [key]: value })); + }; + + const doTest = async (s: McpServer) => { + setTesting(s.id); + try { + const r = await testMcp(s.id); + setTestResult((m) => ({ + ...m, + [s.id]: r.ok + ? `✓ ${r.tools.length} outil(s) : ${r.tools.join(", ").slice(0, 120)}` + : `✗ ${r.error ?? "échec"}`, + })); + } finally { + setTesting(null); + } + }; + + return ( + +
SERVEURS MCP
+
+ Outils professionnels pour l'agent (navigateur, docs, web). Désactivé = + zéro coût. +
+
+ {servers.map((s) => ( +
+
+ +
+
+ {s.label} + {s.tools > 0 && ( + + {s.tools} outil(s) + + )} +
+
+ {s.description} +
+
+ + toggle(s)} /> +
+ {(s.url_param || s.env_params.length > 0) && ( +
+ {s.url_param && ( + setParam(s, "command", e.target.value)} + placeholder="commande stdio (ex. npx -y mon-mcp) ou URL" + className="border-2 border-line bg-card px-2 py-1 text-[12px] text-ink" + /> + )} + {s.env_params.map((k) => ( + setParam(s, k, e.target.value)} + placeholder={k} + className="border-2 border-line bg-card px-2 py-1 text-[12px] text-ink" + /> + ))} +
+ )} + {s.error && ( +
{s.error}
+ )} + {testResult[s.id] && ( +
+ {testResult[s.id]} +
+ )} +
+ ))} +
+
+ ); +} + function HardwareCard() { const [hw, setHw] = useState(null);