Skip to content
34 changes: 31 additions & 3 deletions packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSync } from "@tui/context/sync"
import { createMemo, For, Show, Switch, Match } from "solid-js"
import { createMemo, createSignal, For, Show, Switch, Match } from "solid-js"
import { createStore } from "solid-js/store"
import { useTheme } from "../../context/theme"
import { Locale } from "@/util/locale"
Expand All @@ -10,7 +10,10 @@ import { Installation } from "@/installation"
import { useKeybind } from "../../context/keybind"
import { useDirectory } from "../../context/directory"
import { useKV } from "../../context/kv"
import { useLocal } from "@tui/context/local"
import { useSDK } from "@tui/context/sdk"
import { TodoItem } from "../../component/todo-item"
import { Log } from "@/util/log"

export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
const sync = useSync()
Expand All @@ -27,6 +30,28 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
lsp: true,
})

const local = useLocal()
const sdk = useSDK()
const [loading, setLoading] = createSignal<string | null>(null)

async function handleToggle(name: string) {
if (loading() !== null) return
setLoading(name)
try {
await local.mcp.toggle(name)
const status = await sdk.client.mcp.status()
if (status.data) sync.set("mcp", status.data)
} catch (error) {
Log.Default.error("Failed to toggle MCP", {
error: error instanceof Error ? error.message : String(error),
name: error instanceof Error ? error.name : undefined,
stack: error instanceof Error ? error.stack : undefined,
})
} finally {
setLoading(null)
}
}

// Sort MCP servers alphabetically for consistent display order
const mcpEntries = createMemo(() => Object.entries(sync.data.mcp).sort(([a], [b]) => a.localeCompare(b)))

Expand Down Expand Up @@ -130,7 +155,7 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
<Show when={mcpEntries().length <= 2 || expanded.mcp}>
<For each={mcpEntries()}>
{([key, item]) => (
<box flexDirection="row" gap={1}>
<box flexDirection="row" gap={1} onMouseDown={() => handleToggle(key)}>
<text
flexShrink={0}
style={{
Expand All @@ -147,10 +172,13 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
>
</text>
<text fg={theme.text} wrapMode="word">
<text fg={loading() === key ? theme.textMuted : theme.text} wrapMode="word">
{key}{" "}
<span style={{ fg: theme.textMuted }}>
<Switch fallback={item.status}>
<Match when={loading() === key}>
<i>Loading…</i>
</Match>
<Match when={item.status === "connected"}>Connected</Match>
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
<Match when={item.status === "disabled"}>Disabled</Match>
Expand Down