mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-27 22:10:11 -04:00
fix(app): show MCP connection failures as toasts (#45522)
This commit is contained in:
@@ -85,3 +85,94 @@ for (const shared of [true, false]) {
|
||||
expect(requests.every((request) => request.directory === workspace)).toBe(true)
|
||||
})
|
||||
}
|
||||
|
||||
for (const surface of ["popover", "dialog"] as const) {
|
||||
test(`shows connection failures from the MCP ${surface} and allows reconnecting`, async ({ page }, testInfo) => {
|
||||
const error = "Streamable HTTP error: Error POSTing to endpoint: 404 Not Found"
|
||||
const state = { fail: true, status: surface === "popover" ? "failed" : "disabled" }
|
||||
const requests: { path: string; directory: string }[] = []
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { showStatus: true } }))
|
||||
})
|
||||
await mockOpenCodeServer(page, {
|
||||
directory,
|
||||
project: {
|
||||
id: projectID,
|
||||
worktree: directory,
|
||||
vcs: "git",
|
||||
name: "mcp-workspace",
|
||||
time: { created: 1700000000000, updated: 1700000000000 },
|
||||
sandboxes: [workspace],
|
||||
},
|
||||
provider: { all: [], connected: [], default: {} },
|
||||
sessions: [{ id: sessionID, projectID, directory: workspace, title }],
|
||||
pageMessages: () => ({ items: [] }),
|
||||
})
|
||||
await page.route("**/api/mcp**", async (route) => {
|
||||
if (route.request().method() === "OPTIONS") return route.fallback()
|
||||
const url = new URL(route.request().url())
|
||||
const target = url.searchParams.get("location[directory]") ?? directory
|
||||
requests.push({ path: url.pathname, directory: target })
|
||||
if (url.pathname === "/api/mcp/figma-desktop/connect") {
|
||||
state.status = state.fail ? "failed" : "connected"
|
||||
// Connection failures are reported by the refreshed status, not the HTTP response.
|
||||
return route.fulfill({ status: 204 })
|
||||
}
|
||||
return route.fulfill({
|
||||
json: {
|
||||
location: { directory: target },
|
||||
data:
|
||||
url.pathname === "/api/mcp/resource"
|
||||
? { resources: [], templates: [] }
|
||||
: [
|
||||
{
|
||||
name: "figma-desktop",
|
||||
status: { status: target === workspace ? state.status : "connected", error },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
|
||||
await expectSessionTitle(page, title)
|
||||
await expect(page.getByRole("textbox", { name: "Prompt", exact: true })).toBeEditable()
|
||||
if (surface === "popover") await page.getByRole("button", { name: "Status", exact: true }).click()
|
||||
if (surface === "dialog") await page.keyboard.press("ControlOrMeta+;")
|
||||
const panel =
|
||||
surface === "popover" ? page.getByRole("tabpanel") : page.getByRole("dialog", { name: "MCPs", exact: true })
|
||||
const toggle = panel.getByRole("switch")
|
||||
await expect(panel.getByText("figma-desktop", { exact: true })).toBeVisible()
|
||||
await expect(toggle).not.toBeChecked()
|
||||
await expect(toggle).toBeEnabled()
|
||||
requests.length = 0
|
||||
|
||||
await panel.locator('[data-slot="switch-control"]').click()
|
||||
const toast = page
|
||||
.getByRole("listitem", { includeHidden: true })
|
||||
.filter({ has: page.getByText("Request failed", { exact: true }) })
|
||||
await expect(toast.getByText(`figma-desktop: ${error}`, { exact: true })).toBeVisible()
|
||||
await expect(toggle).not.toBeChecked()
|
||||
await expect(toggle).toBeEnabled()
|
||||
expect(requests.filter((request) => request.path.endsWith("/connect"))).toEqual([
|
||||
{ path: "/api/mcp/figma-desktop/connect", directory: workspace },
|
||||
])
|
||||
expect(requests.every((request) => request.directory === workspace)).toBe(true)
|
||||
await expect(toast).toHaveCSS("opacity", "1")
|
||||
await testInfo.attach("mcp-connection-error", { body: await page.screenshot(), contentType: "image/png" })
|
||||
|
||||
if (surface === "popover") await page.keyboard.press("Escape")
|
||||
if (surface === "dialog") await panel.getByRole("button", { name: "Close", exact: true }).click()
|
||||
await expect(panel).toBeHidden()
|
||||
await toast.getByRole("button", { name: "Dismiss", exact: true }).click()
|
||||
await expect(toast).toBeHidden()
|
||||
state.fail = false
|
||||
if (surface === "popover") await page.getByRole("button", { name: "Status", exact: true }).click()
|
||||
if (surface === "dialog") await page.keyboard.press("ControlOrMeta+;")
|
||||
await expect(toggle).toBeEnabled()
|
||||
await panel.locator('[data-slot="switch-control"]').click()
|
||||
await expect(toggle).toBeChecked()
|
||||
await expect(toggle).toBeEnabled()
|
||||
await expect(toast).toBeHidden()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ export function useMcpToggle(directory?: Accessor<string | undefined>, onSuccess
|
||||
data.location.mcp.server.invalidate(ref)
|
||||
data.location.mcp.resource.invalidate(ref)
|
||||
await Promise.all([data.location.mcp.server.sync(ref), data.location.mcp.resource.sync(ref), onSuccess?.()])
|
||||
// A successful HTTP response can still leave the MCP connection in a failed state.
|
||||
const status = data.location.mcp.server.list(ref)?.find((item) => item.name === name)?.status
|
||||
if (status?.status === "failed") throw new Error(`${name}: ${status.error}`)
|
||||
},
|
||||
onError: (error) =>
|
||||
showToast({
|
||||
|
||||
Reference in New Issue
Block a user