From 2333af6ed3085721cdc244b153097bd0d1b353a8 Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Fri, 26 Dec 2025 13:49:05 -0300 Subject: Desktop: MCP UI (#6162) Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> --- packages/app/src/components/dialog-select-mcp.tsx | 91 ++++++++++++++++++++++ .../app/src/components/session-lsp-indicator.tsx | 40 ++++++++++ .../app/src/components/session-mcp-indicator.tsx | 36 +++++++++ packages/app/src/components/status-bar.tsx | 14 ++++ 4 files changed, 181 insertions(+) create mode 100644 packages/app/src/components/dialog-select-mcp.tsx create mode 100644 packages/app/src/components/session-lsp-indicator.tsx create mode 100644 packages/app/src/components/session-mcp-indicator.tsx create mode 100644 packages/app/src/components/status-bar.tsx (limited to 'packages/app/src/components') diff --git a/packages/app/src/components/dialog-select-mcp.tsx b/packages/app/src/components/dialog-select-mcp.tsx new file mode 100644 index 000000000..c29cd827e --- /dev/null +++ b/packages/app/src/components/dialog-select-mcp.tsx @@ -0,0 +1,91 @@ +import { Component, createMemo, createSignal, Show } from "solid-js" +import { useSync } from "@/context/sync" +import { useSDK } from "@/context/sdk" +import { Dialog } from "@opencode-ai/ui/dialog" +import { List } from "@opencode-ai/ui/list" +import { Switch } from "@opencode-ai/ui/switch" + +export const DialogSelectMcp: Component = () => { + const sync = useSync() + const sdk = useSDK() + const [loading, setLoading] = createSignal(null) + + const items = createMemo(() => + Object.entries(sync.data.mcp ?? {}) + .map(([name, status]) => ({ name, status: status.status })) + .sort((a, b) => a.name.localeCompare(b.name)), + ) + + const toggle = async (name: string) => { + if (loading()) return + setLoading(name) + const status = sync.data.mcp[name] + if (status?.status === "connected") { + await sdk.client.mcp.disconnect({ name }) + } else { + await sdk.client.mcp.connect({ name }) + } + const result = await sdk.client.mcp.status() + if (result.data) sync.set("mcp", result.data) + setLoading(null) + } + + const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length) + const totalCount = createMemo(() => items().length) + + return ( + + x?.name ?? ""} + items={items} + filterKeys={["name", "status"]} + sortBy={(a, b) => a.name.localeCompare(b.name)} + onSelect={(x) => { + if (x) toggle(x.name) + }} + > + {(i) => { + const mcpStatus = () => sync.data.mcp[i.name] + const status = () => mcpStatus()?.status + const error = () => { + const s = mcpStatus() + return s?.status === "failed" ? s.error : undefined + } + const enabled = () => status() === "connected" + return ( +
+
+
+ {i.name} + + connected + + + failed + + + needs auth + + + disabled + + + ... + +
+ + {error()} + +
+
e.stopPropagation()}> + toggle(i.name)} /> +
+
+ ) + }} +
+
+ ) +} diff --git a/packages/app/src/components/session-lsp-indicator.tsx b/packages/app/src/components/session-lsp-indicator.tsx new file mode 100644 index 000000000..98d6d6dfd --- /dev/null +++ b/packages/app/src/components/session-lsp-indicator.tsx @@ -0,0 +1,40 @@ +import { createMemo, Show } from "solid-js" +import { Icon } from "@opencode-ai/ui/icon" +import { useSync } from "@/context/sync" +import { Tooltip } from "@opencode-ai/ui/tooltip" + +export function SessionLspIndicator() { + const sync = useSync() + + const lspStats = createMemo(() => { + const lsp = sync.data.lsp ?? [] + const connected = lsp.filter((s) => s.status === "connected").length + const hasError = lsp.some((s) => s.status === "error") + const total = lsp.length + return { connected, hasError, total } + }) + + const tooltipContent = createMemo(() => { + const lsp = sync.data.lsp ?? [] + if (lsp.length === 0) return "No LSP servers" + return lsp.map((s) => s.name).join(", ") + }) + + return ( + 0}> + +
+ 0, + }} + /> + {lspStats().connected} LSP +
+
+
+ ) +} diff --git a/packages/app/src/components/session-mcp-indicator.tsx b/packages/app/src/components/session-mcp-indicator.tsx new file mode 100644 index 000000000..17a6f2e1a --- /dev/null +++ b/packages/app/src/components/session-mcp-indicator.tsx @@ -0,0 +1,36 @@ +import { createMemo, Show } from "solid-js" +import { Button } from "@opencode-ai/ui/button" +import { Icon } from "@opencode-ai/ui/icon" +import { useDialog } from "@opencode-ai/ui/context/dialog" +import { useSync } from "@/context/sync" +import { DialogSelectMcp } from "@/components/dialog-select-mcp" + +export function SessionMcpIndicator() { + const sync = useSync() + const dialog = useDialog() + + const mcpStats = createMemo(() => { + const mcp = sync.data.mcp ?? {} + const entries = Object.entries(mcp) + const enabled = entries.filter(([, status]) => status.status === "connected").length + const failed = entries.some(([, status]) => status.status === "failed") + const total = entries.length + return { enabled, failed, total } + }) + + return ( + 0}> + + + ) +} diff --git a/packages/app/src/components/status-bar.tsx b/packages/app/src/components/status-bar.tsx new file mode 100644 index 000000000..e0e25c60b --- /dev/null +++ b/packages/app/src/components/status-bar.tsx @@ -0,0 +1,14 @@ +import { Show, type ParentProps } from "solid-js" +import { usePlatform } from "@/context/platform" + +export function StatusBar(props: ParentProps) { + const platform = usePlatform() + return ( +
+ + v{platform.version} + +
{props.children}
+
+ ) +} -- cgit v1.2.3