fix(tui): contain MCP sidebar errors (#44003)

This commit is contained in:
Kit Langton
2026-08-21 19:00:06 -04:00
committed by GitHub
parent 2c8e2a2b28
commit 667c274c7f
2 changed files with 39 additions and 24 deletions
+13 -11
View File
@@ -33,14 +33,23 @@ function Status(props: { status: McpServer["status"]; loading: boolean }) {
return <>Disabled </>
}
export function DialogMcp() {
export function DialogMcp(props: { initialServer?: string; details?: boolean } = {}) {
const data = useData()
const dialog = useDialog()
const client = useClient()
const toast = useToast()
const theme = useTheme("elevated")
const [focused, setFocused] = createSignal<string>()
const [detail, setDetail] = createSignal<McpServer>()
const servers = createMemo(() =>
pipe(
data.location.mcp.server.list() ?? [],
sortBy((server) => server.name),
),
)
const initial = props.initialServer ? servers().find((server) => server.name === props.initialServer) : undefined
const [focused, setFocused] = createSignal<string | undefined>(props.initialServer)
const [detail, setDetail] = createSignal<McpServer | undefined>(
props.details && initial?.status.status === "failed" ? initial : undefined,
)
const [loading, setLoading] = createSignal<string | null>(null)
const statusColor = (status: McpServer["status"]) => {
@@ -50,13 +59,6 @@ export function DialogMcp() {
return theme.text.subdued
}
const servers = createMemo(() =>
pipe(
data.location.mcp.server.list() ?? [],
sortBy((server) => server.name),
),
)
createEffect(() => {
if (focused()) return
const first = servers()[0]
@@ -153,7 +155,7 @@ export function DialogMcp() {
title={`MCP server: ${server().name}`}
error={statusError(server().status) ?? "Unknown MCP connection error"}
onBack={() => {
setDetail()
setDetail(undefined)
dialog.setSize("medium")
}}
/>
@@ -1,5 +1,6 @@
import { Plugin } from "@opencode-ai/plugin/tui"
import { createMemo, For, Match, Show, Switch, createSignal } from "solid-js"
import { DialogMcp } from "../../component/dialog-mcp"
function View(props: { context: Plugin.Context; sessionID: string }) {
const [open, setOpen] = createSignal(true)
@@ -39,7 +40,16 @@ function View(props: { context: Plugin.Context; sessionID: string }) {
<Show when={list().length <= 2 || open()}>
<For each={list()}>
{(item) => (
<box flexDirection="row" gap={1}>
<box
flexDirection="row"
gap={1}
minWidth={0}
onMouseUp={() =>
props.context.ui.dialog.show(() => (
<DialogMcp initialServer={item.name} details={item.status.status === "failed"} />
))
}
>
<text
flexShrink={0}
style={{
@@ -48,18 +58,21 @@ function View(props: { context: Plugin.Context; sessionID: string }) {
>
</text>
<text fg={theme.text.default} wrapMode="word">
{item.name}{" "}
<span style={{ fg: theme.text.subdued }}>
<Switch fallback={item.status.status}>
<Match when={item.status.status === "connected"}>Connected</Match>
<Match when={item.status.status === "failed"}>
<i>{item.status.status === "failed" ? item.status.error : undefined}</i>
</Match>
<Match when={item.status.status === "disabled"}>Disabled</Match>
<Match when={item.status.status === "needs_auth"}>Needs auth</Match>
</Switch>
</span>
<text fg={theme.text.default} wrapMode="none" truncate flexGrow={1} flexShrink={1} minWidth={0}>
<b>{item.name}</b>
</text>
<text
fg={item.status.status === "failed" ? theme.text.feedback.error.default : theme.text.subdued}
wrapMode="none"
flexShrink={0}
>
<Switch fallback={item.status.status}>
<Match when={item.status.status === "connected"}>Connected</Match>
<Match when={item.status.status === "pending"}>Connecting</Match>
<Match when={item.status.status === "failed"}>Error</Match>
<Match when={item.status.status === "disabled"}>Disabled</Match>
<Match when={item.status.status === "needs_auth"}>Sign in</Match>
</Switch>
</text>
</box>
)}