From ecd4f32f00345d6060839467ba713a9106b09aca Mon Sep 17 00:00:00 2001 From: Alby Hernandez Date: Fri, 22 May 2026 00:49:31 +0100 Subject: [PATCH] fix(mcp): run OAuth dance inside startAuth The MCP SDK's connect() resolves before initiating OAuth, which made startAuth() return with authorizationUrl="" and authenticate() then called defs() on an unauthenticated client, failing with "Failed to get tools" before the browser was ever opened. Force a listTools() right after connect() so the SDK runs the auth dance and onRedirect populates capturedUrl in time. --- packages/opencode/src/mcp/index.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 70d1019573b7..ccfe561e2c71 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -801,7 +801,25 @@ export const layer = Layer.effect( const client = new Client({ name: "opencode", version: InstallationVersion }) return client .connect(transport) - .then(() => ({ authorizationUrl: "", oauthState, client }) satisfies AuthResult) + .then(async () => { + // connect() resolves before the SDK starts the OAuth dance, + // so force a real request to make it run and let onRedirect + // populate capturedUrl before we return. + try { + await client.listTools(undefined, { timeout: 15_000 }) + return { authorizationUrl: "", oauthState, client } satisfies AuthResult + } catch (err) { + const start = Date.now() + while (!capturedUrl && Date.now() - start < 5_000) { + await new Promise((r) => setTimeout(r, 50)) + } + if (capturedUrl) { + pendingOAuthTransports.set(mcpName, transport) + return { authorizationUrl: capturedUrl.toString(), oauthState } satisfies AuthResult + } + throw err + } + }) }, catch: (error) => error, }).pipe(