From 7591e5c1c9a1daaf91210c49263ba9b37a9f2283 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Sun, 14 Dec 2025 13:40:32 +0000 Subject: [PATCH] Add MCP toggle control --- packages/ui/src/components/instance-info.tsx | 155 +++++++++++++++---- 1 file changed, 122 insertions(+), 33 deletions(-) diff --git a/packages/ui/src/components/instance-info.tsx b/packages/ui/src/components/instance-info.tsx index e3af7c11..4d3a2ce2 100644 --- a/packages/ui/src/components/instance-info.tsx +++ b/packages/ui/src/components/instance-info.tsx @@ -1,4 +1,5 @@ import { Component, Show, For, createSignal, createEffect, onCleanup } from "solid-js" +import Switch from "@suid/material/Switch" import type { Instance, RawMcpStatus } from "../types/instance" import { fetchLspStatus, updateInstance } from "../stores/instances" import { getLogger } from "../lib/logger" @@ -49,6 +50,7 @@ const pendingMetadataRequests = new Set() const InstanceInfo: Component = (props) => { const [isLoadingMetadata, setIsLoadingMetadata] = createSignal(true) + const [pendingMcpActions, setPendingMcpActions] = createSignal>({}) const metadata = () => props.instance.metadata const binaryVersion = () => props.instance.binaryVersion || metadata()?.version @@ -58,6 +60,63 @@ const InstanceInfo: Component = (props) => { } const lspServers = () => metadata()?.lspStatus ?? [] + const setPendingMcpAction = (name: string, action?: "connect" | "disconnect") => { + setPendingMcpActions((prev) => { + const next = { ...prev } + if (action) { + next[name] = action + } else { + delete next[name] + } + return next + }) + } + + const refreshMcpStatus = async () => { + const client = props.instance.client + if (!client?.mcp?.status) { + return + } + + try { + const result = await client.mcp.status() + const status = result.data as RawMcpStatus | undefined + if (!status) return + + updateInstance(props.instance.id, { + metadata: { + ...(props.instance.metadata ?? {}), + mcpStatus: status, + }, + }) + } catch (error) { + log.error("Failed to refresh MCP status", error) + } + } + + const toggleMcpServer = async (serverName: string, shouldEnable: boolean) => { + const client = props.instance.client + if (!client?.mcp) { + return + } + + const action: "connect" | "disconnect" = shouldEnable ? "connect" : "disconnect" + setPendingMcpAction(serverName, action) + + try { + if (shouldEnable) { + await client.mcp.connect({ path: { name: serverName } }) + } else { + await client.mcp.disconnect({ path: { name: serverName } }) + } + await refreshMcpStatus() + } catch (error) { + log.error("Failed to toggle MCP server", { serverName, action, error }) + } finally { + setPendingMcpAction(serverName) + } + } + createEffect(() => { const instance = props.instance const instanceId = instance.id @@ -257,40 +316,70 @@ const InstanceInfo: Component = (props) => {
- {(server) => ( -
-
- {server.name} -
-
- - { - server.status === "running" - ? "Connected" - : server.status === "error" - ? "Error" - : "Disabled" - } - -
-
- - {(error) => ( -
- {error()} + {(server) => { + const pendingAction = pendingMcpActions()[server.name] + const isPending = Boolean(pendingAction) + const isRunning = server.status === "running" + const switchDisabled = isPending || !props.instance.client + + const statusDotClass = () => { + if (isPending) { + return "status-dot animate-pulse" + } + if (server.status === "running") { + return "status-dot ready animate-pulse" + } + if (server.status === "error") { + return "status-dot error" + } + return "status-dot stopped" + } + + const statusDotStyle = () => (isPending ? { background: "var(--status-warning)" } : undefined) + + return ( +
+
+ {server.name} +
+
+
+
+
+ { + if (switchDisabled) return + void toggleMcpServer(server.name, Boolean(checked)) + }} + /> + + + + + + +
- )} - -
- )} +
+ + {(error) => ( +
+ {error()} +
+ )} +
+
+ ) + }}