You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Desktop MCP toggle can fail or block on resource catalog refresh #48386
After a successful MCP toggle connect, the Desktop UI synchronously refetches the global MCP resource catalog across all connected servers. A slow or unsupported resources/list call therefore blocks the toggle or surfaces a false failure. The impact rises with more connected MCP servers, but one problematic server is sufficient.
Expected behavior
The MCP toggle should complete after the MCP connection status is refreshed. Resource catalog discovery should run independently and best-effort; a slow, unsupported, or rejected resources/list must not block or fail the toggle.
Steps to reproduce
Configure at least one MCP server whose resources/list response is slow or unsupported.
Open the Desktop app and enable that MCP server.
Observe that the toggle waits for resource discovery or reports a failure despite the connection succeeding.
Validation
The cause was reproduced and validated locally with a focused unit-test scenario in which resource discovery remains pending after connect: the toggle resolves without waiting for it. A rejection case also verifies that the background resource refresh is safely handled without an unhandled rejection or toggle failure.
Additional context
This is a Desktop MCP toggle flow issue; no credentials, private endpoints, or local paths are required to reproduce the behavior.
Thanks for the clear report. I could confirm this on the latest dev (1e7ba46f8).
I couldn't run the Desktop app here, so I ran the toggle logic from useMcpToggle (packages/app/src/providers/connect/mcp.ts) in a unit test, with the MCP connect and the status refresh succeeding:
When the resource catalog refresh never finishes, the toggle stays pending even though the server is already connected.
When the resource catalog refresh fails, the toggle fails with that error. In the app that shows up as a "request failed" toast, even though the connection worked.
This happens because the toggle waits for the resource catalog refresh before it checks the connection status. One caveat: servers that don't support resources are skipped, and slow listings are cut off by a timeout. So in practice the problem shows up with slow servers that do support resources, or when the refresh fails.
The script below reproduces both cases. Copy it to packages/app/src/providers/connect/ in a dev checkout and run it with bun test from packages/app.
Reproduction script (mcp-repro.test.ts)
// Reproduction for anomalyco/opencode#48386: the MCP toggle awaits the resource catalog refresh.// Place at packages/app/src/providers/connect/mcp-repro.test.ts in a dev checkout, then: cd packages/app && bun test src/providers/connect/mcp-repro.test.tsimport{describe,expect,mock,test}from"bun:test"letresourceSync: ()=>Promise<unknown>=async()=>{}consttoasts: unknown[]=[]constcalls: string[]=[]mock.module("@tanstack/solid-query",()=>({useMutation: (fn: ()=>unknown)=>fn()}))mock.module("@/runtime/i18n/language",()=>({useLanguage: ()=>({t: (k: string)=>k})}))mock.module("@/runtime/platform/platform",()=>({usePlatform: ()=>({openExternal(){}})}))mock.module("@/shell/notifications/toast",()=>({showToast: (t: unknown)=>toasts.push(t)}))mock.module("@/runtime/server/client",()=>({useServerSDK: ()=>({api: {mcp: {list: async()=>({data: [{name: "slow",status: {status: "disabled"}}]}),connect: async()=>calls.push("connect"),disconnect: async()=>calls.push("disconnect"),},},}),}))mock.module("@/runtime/server/current",()=>({useData: ()=>({location: {default: ()=>({directory: "/proj"}),mcp: {server: {invalidate(){},sync: async()=>calls.push("server.sync"),list: ()=>[{name: "slow",status: {status: "connected"}}],},resource: {invalidate(){},sync: ()=>resourceSync()},},},}),}))const{ useMcpToggle }=awaitimport("./mcp")describe("#48386 MCP toggle vs resource catalog refresh",()=>{test("toggle should not wait for a pending resource refresh",async()=>{resourceSync=()=>newPromise(()=>{})// resources/list never answersconstopts=useMcpToggle()asanyconstresult=awaitPromise.race([opts.mutationFn("slow").then(()=>"resolved"),newPromise((r)=>setTimeout(()=>r("still pending after 500ms"),500)),])console.log("pending resource refresh ->",result,calls)expect(result).toBe("resolved")})test("toggle should not fail when the resource refresh rejects",async()=>{resourceSync=async()=>{thrownewError("resources/list failed")}constopts=useMcpToggle()asanyconstresult=awaitopts.mutationFn("slow").then(()=>"resolved",(e: Error)=>`rejected: ${e.message}`,)console.log("rejected resource refresh ->",result)expect(result).toBe("resolved")})})
Thanks for reproducing this and clarifying the scope.
I checked my local fix against both scenarios. With resource refresh
separated from the status refresh, the toggle resolves when resource
discovery remains pending, and it also resolves when resource refresh
rejects. The existing targeted tests pass as well.
My local checkout uses a different implementation layout from the dev
revision in your reproduction, so I adapted the two scenarios to its
toggleMcp function rather than running your test file unchanged. I have not
yet verified the behavior in the running Desktop app with a real MCP server.
The related PR #48391 was closed without being merged. Would a rebased
version of that fix, including regression tests for both pending and
rejected resource refreshes, be useful?
MP
st 28. 10. 2026 v 15:03 odesílatel opencodejr[bot] ***@***.***>
napsal:
*opencodejr[bot]* left a comment (anomalyco/opencode#48386)
<#48386 (comment)>
Thanks for the clear report. I could confirm this on the latest dev (
1e7ba46).
I couldn't run the Desktop app here, so I ran the toggle logic from
useMcpToggle (packages/app/src/providers/connect/mcp.ts) in a unit test,
with the MCP connect and the status refresh succeeding:
- When the resource catalog refresh never finishes, the toggle stays
pending even though the server is already connected.
- When the resource catalog refresh fails, the toggle fails with that
error. In the app that shows up as a "request failed" toast, even though
the connection worked.
This happens because the toggle waits for the resource catalog refresh
before it checks the connection status. One caveat: servers that don't
support resources are skipped, and slow listings are cut off by a timeout.
So in practice the problem shows up with slow servers that do support
resources, or when the refresh fails.
The script below reproduces both cases. Copy it to
packages/app/src/providers/connect/ in a dev checkout and run it with bun
test from packages/app.
Reproduction script (mcp-repro.test.ts)
// Reproduction for anomalyco/opencode#48386: the MCP toggle awaits the resource catalog refresh.// Place at packages/app/src/providers/connect/mcp-repro.test.ts in a dev checkout, then: cd packages/app && bun test src/providers/connect/mcp-repro.test.tsimport { describe, expect, mock, test } from "bun:test"
let resourceSync: () => Promise<unknown> = async () => {}const toasts: unknown[] = []const calls: string[] = []
***@***.***/solid-query", () => ({ useMutation: (fn: () => unknown) => fn() }))mock.module("@/runtime/i18n/language", () => ({ useLanguage: () => ({ t: (k: string) => k }) }))mock.module("@/runtime/platform/platform", () => ({ usePlatform: () => ({ openExternal() {} }) }))mock.module("@/shell/notifications/toast", () => ({ showToast: (t: unknown) => toasts.push(t) }))mock.module("@/runtime/server/client", () => ({
useServerSDK: () => ({
api: {
mcp: {
list: async () => ({ data: [{ name: "slow", status: { status: "disabled" } }] }),
connect: async () => calls.push("connect"),
disconnect: async () => calls.push("disconnect"),
},
},
}),}))mock.module("@/runtime/server/current", () => ({
useData: () => ({
location: {
default: () => ({ directory: "/proj" }),
mcp: {
server: {
invalidate() {},
sync: async () => calls.push("server.sync"),
list: () => [{ name: "slow", status: { status: "connected" } }],
},
resource: { invalidate() {}, sync: () => resourceSync() },
},
},
}),}))
const { useMcpToggle } = await import("./mcp")
describe("#48386 MCP toggle vs resource catalog refresh", () => {
test("toggle should not wait for a pending resource refresh", async () => {
resourceSync = () => new Promise(() => {}) // resources/list never answers
const opts = useMcpToggle() as any
const result = await Promise.race([
opts.mutationFn("slow").then(() => "resolved"),
new Promise((r) => setTimeout(() => r("still pending after 500ms"), 500)),
])
console.log("pending resource refresh ->", result, calls)
expect(result).toBe("resolved")
})
test("toggle should not fail when the resource refresh rejects", async () => {
resourceSync = async () => {
throw new Error("resources/list failed")
}
const opts = useMcpToggle() as any
const result = await opts.mutationFn("slow").then(
() => "resolved",
(e: Error) => `rejected: ${e.message}`,
)
console.log("rejected resource refresh ->", result)
expect(result).toBe("resolved")
})})
Run it: bun mcp-repro.test.ts
—
Reply to this email directly, view it on GitHub
<#48386?email_source=notifications&email_token=BYQVA5EARE4O4IT6VOOGMCL5WH4CRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMNBXGEYDOMZWHA22M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-6471073685>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BYQVA5GHFMWBRF2QRYTUBGT5WH4CRAVCNFSNUABFKJSXA33TNF2G64TZHM4TONJXGM2DGMJZHNEXG43VMU5TKNBRG4YDSMRUGIZ2C5QC>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/BYQVA5HI2V5EY47ICKJKENT5WH4CRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMNBXGEYDOMZWHA22M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG>
and Android
<https://github.com/notifications/mobile/android/BYQVA5HFUGAUP4GEMWEPZS35WH4CRA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMNBXGEYDOMZWHA22M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>.
Download it today!
You are receiving this because you authored the thread.Message ID:
***@***.***>
Activity
opencodejr commentedon Oct 28, 2026
Thanks for the clear report. I could confirm this on the latest
dev(1e7ba46f8).I couldn't run the Desktop app here, so I ran the toggle logic from
useMcpToggle(packages/app/src/providers/connect/mcp.ts) in a unit test, with the MCP connect and the status refresh succeeding:connected.This happens because the toggle waits for the resource catalog refresh before it checks the connection status. One caveat: servers that don't support resources are skipped, and slow listings are cut off by a timeout. So in practice the problem shows up with slow servers that do support resources, or when the refresh fails.
The script below reproduces both cases. Copy it to
packages/app/src/providers/connect/in adevcheckout and run it withbun testfrompackages/app.Reproduction script (mcp-repro.test.ts)
Run it:
bun mcp-repro.test.tsmapix-etnc commentedon Nov 2, 2026