fix(app): refresh directory MCP status (#31194)

This commit is contained in:
Luke Parker 2026-06-07 15:53:23 +10:00 committed by GitHub
parent f24049736e
commit a29deb1ee9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 101 additions and 58 deletions

View file

@ -1,13 +1,10 @@
import { useMutation, useQueryClient } from "@tanstack/solid-query"
import { Component, createMemo, 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"
import { useLanguage } from "@/context/language"
import { useQueryOptions } from "@/context/server-sync"
import { pathKey } from "@/utils/path-key"
import { useMcpToggle } from "@/context/mcp"
const statusLabels = {
connected: "mcp.status.connected",
@ -19,10 +16,7 @@ const statusLabels = {
export const DialogSelectMcp: Component = () => {
const sync = useSync()
const sdk = useSDK()
const language = useLanguage()
const queryClient = useQueryClient()
const queryOptions = useQueryOptions()
const items = createMemo(() =>
Object.entries(sync.data.mcp ?? {})
@ -30,21 +24,7 @@ export const DialogSelectMcp: Component = () => {
.sort((a, b) => a.name.localeCompare(b.name)),
)
const toggle = useMutation(() => ({
mutationFn: async (name: string) => {
const status = sync.data.mcp[name]
if (status?.status === "connected") {
await sdk.client.mcp.disconnect({ name })
return
}
if (status?.status === "needs_auth") {
await sdk.client.mcp.auth.authenticate({ name })
return
}
await sdk.client.mcp.connect({ name })
},
onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))),
}))
const toggle = useMcpToggle()
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
const totalCount = createMemo(() => items().length)

View file

@ -3,7 +3,6 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Icon } from "@opencode-ai/ui/icon"
import { Switch } from "@opencode-ai/ui/switch"
import { Tabs } from "@opencode-ai/ui/tabs"
import { useMutation, useQueryClient } from "@tanstack/solid-query"
import { showToast } from "@/utils/toast"
import { useNavigate } from "@solidjs/router"
import { type Accessor, createEffect, createMemo, For, type JSXElement, onCleanup, Show } from "solid-js"
@ -11,14 +10,12 @@ import { createStore } from "solid-js/store"
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { useSDK } from "@/context/sdk"
import { ServerConnection, useServer } from "@/context/server"
import { useSync } from "@/context/sync"
import { type ServerHealth } from "@/utils/server-health"
import { useQueryOptions } from "@/context/server-sync"
import { pathKey } from "@/utils/path-key"
import { useGlobal } from "@/context/global"
import { useSettings } from "@/context/settings"
import { useMcpToggle } from "@/context/mcp"
const pluginEmptyMessage = (value: string, file: string): JSXElement => {
const parts = value.split(file)
@ -99,37 +96,6 @@ const useDefaultServerKey = (
}
}
const useMcpToggleMutation = () => {
const sync = useSync()
const sdk = useSDK()
const language = useLanguage()
const queryClient = useQueryClient()
const queryOptions = useQueryOptions()
return useMutation(() => ({
mutationFn: async (name: string) => {
const status = sync.data.mcp[name]
if (status?.status === "connected") {
await sdk.client.mcp.disconnect({ name })
return
}
if (status?.status === "needs_auth") {
await sdk.client.mcp.auth.authenticate({ name })
return
}
await sdk.client.mcp.connect({ name })
},
onSuccess: () => queryClient.refetchQueries(queryOptions.mcp(pathKey(sync.directory))),
onError: (err) => {
showToast({
variant: "error",
title: language.t("common.requestFailed"),
description: err instanceof Error ? err.message : String(err),
})
},
}))
}
type ServerStatusState = {
servers: () => ServerStatusItem[]
defaultKey: () => ServerConnection.Key | undefined
@ -311,7 +277,7 @@ export function StatusPopoverBody(props: { shown: Accessor<boolean> }) {
dialogRun += 1
})
const sortedServers = createMemo(() => listServersByHealth(global.servers.list(), server.key, global.servers.health))
const toggleMcp = useMcpToggleMutation()
const toggleMcp = useMcpToggle()
const defaultServer = useDefaultServerKey(platform.getDefaultServer)
const mcpNames = createMemo(() => Object.keys(sync.data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
const mcpStatus = (name: string) => sync.data.mcp?.[name]?.status

View file

@ -609,6 +609,9 @@ export const createDirSyncContext = (
)
},
},
mcp: {
toggle: (name: string) => serverSync.mcp.toggle(directory, name),
},
absolute,
get directory() {
return current()[0].path.directory

View file

@ -0,0 +1,34 @@
import { describe, expect, test } from "bun:test"
import { toggleMcp } from "./mcp"
describe("toggleMcp", () => {
test("runs the status action before refreshing the owning query", async () => {
const calls: string[] = []
const input = (status: "connected" | "needs_auth" | "disabled") => ({
status,
connect: async () => {
calls.push("connect")
},
disconnect: async () => {
calls.push("disconnect")
},
authenticate: async () => {
calls.push("authenticate")
},
refresh: async () => {
calls.push("refresh")
},
})
await toggleMcp(input("connected"))
expect(calls).toEqual(["disconnect", "refresh"])
calls.length = 0
await toggleMcp(input("needs_auth"))
expect(calls).toEqual(["authenticate", "refresh"])
calls.length = 0
await toggleMcp(input("disabled"))
expect(calls).toEqual(["connect", "refresh"])
})
})

View file

@ -0,0 +1,18 @@
import type { McpStatus } from "@opencode-ai/sdk/v2/client"
export async function toggleMcp(input: {
status: McpStatus["status"]
connect: () => Promise<void>
disconnect: () => Promise<void>
authenticate: () => Promise<void>
refresh: () => Promise<void>
}) {
await {
connected: input.disconnect,
needs_auth: input.authenticate,
disabled: input.connect,
failed: input.connect,
needs_client_registration: input.connect,
}[input.status]()
await input.refresh()
}

View file

@ -0,0 +1,19 @@
import { useMutation } from "@tanstack/solid-query"
import { useLanguage } from "@/context/language"
import { useSync } from "@/context/sync"
import { showToast } from "@/utils/toast"
export function useMcpToggle() {
const sync = useSync()
const language = useLanguage()
return useMutation(() => ({
mutationFn: sync.mcp.toggle,
onError: (error) =>
showToast({
variant: "error",
title: language.t("common.requestFailed"),
description: error instanceof Error ? error.message : String(error),
}),
}))
}

View file

@ -36,6 +36,7 @@ import { ServerConnection, useServer } from "./server"
import { retry } from "@opencode-ai/core/util/retry"
import type { ServerScope } from "@/utils/server-scope"
import { persisted } from "@/utils/persist"
import { toggleMcp } from "./global-sync/mcp"
type GlobalStore = {
ready: boolean
@ -481,6 +482,28 @@ export function createServerSyncContextInner(_serverSDK?: ServerSDK) {
todo: {
set: setSessionTodo,
},
mcp: {
toggle: async (directory: string, name: string) => {
const key = directoryKey(directory)
const sdk = sdkFor(key)
const status = children.child(key, { bootstrap: false })[0].mcp[name].status
await toggleMcp({
status,
connect: async () => {
await sdk.mcp.connect({ name })
},
disconnect: async () => {
await sdk.mcp.disconnect({ name })
},
authenticate: async () => {
await sdk.mcp.auth.authenticate({ name })
},
refresh: async () => {
await queryClient.refetchQueries(queryOptionsApi.mcp(key))
},
})
},
},
}
}