-
Notifications
You must be signed in to change notification settings - Fork 3.1k
fix(mcp): skip OAuth browser flow in non-interactive mode #6662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -901,6 +901,7 @@ export async function connectAndDiscover( | |
| debugMode, | ||
| workspaceContext, | ||
| sendSdkMcpMessage, | ||
| cliConfig.isInteractive(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The new test calls — GPT-5 via Qwen Code /review |
||
| ); | ||
|
|
||
| mcpClient.onerror = (error) => { | ||
|
|
@@ -1291,6 +1292,7 @@ export function hasNetworkTransport(config: MCPServerConfig): boolean { | |
| * @param mcpServerName The name of the MCP server, used for logging and identification. | ||
| * @param mcpServerConfig The configuration specifying how to connect to the server. | ||
| * @param sendSdkMcpMessage Optional callback for SDK MCP servers to route messages via control plane. | ||
| * @param interactive When false (non-interactive/headless `-p` mode), skip OAuth flows that open a browser; the connection is rejected instead of blocking on a callback. | ||
| * @returns A promise that resolves to a connected MCP `Client` instance. | ||
| * @throws An error if the connection fails or the configuration is invalid. | ||
| */ | ||
|
|
@@ -1300,6 +1302,10 @@ export async function connectToMcpServer( | |
| debugMode: boolean, | ||
| workspaceContext: WorkspaceContext, | ||
| sendSdkMcpMessage?: SendSdkMcpMessage, | ||
| // When false (non-interactive `-p` mode), never open a browser for MCP OAuth. | ||
| // A server that would require an interactive OAuth flow is skipped instead of | ||
| // blocking startup on a browser callback. | ||
| interactive: boolean = true, | ||
| ): Promise<Client> { | ||
| const mcpClient = new Client({ | ||
| name: 'qwen-code-mcp-client', | ||
|
|
@@ -1439,6 +1445,18 @@ export async function connectToMcpServer( | |
| throw new Error(oauthMessage); | ||
| } | ||
|
|
||
| // In non-interactive mode (`-p`) we must never open a browser for OAuth, | ||
| // as it would block startup on a callback the user can't complete. Skip | ||
| // this server instead — it surfaces via the normal failed-connection path. | ||
| if (!interactive) { | ||
| const oauthMessage = | ||
| `The MCP server '${mcpServerName}' requires OAuth authentication, ` + | ||
| `but Qwen Code is running in non-interactive mode. Skipping this server. ` + | ||
| getMcpOAuthDialogInstruction('authenticate', mcpServerName); | ||
| debugLogger.warn(oauthMessage); | ||
| throw new Error(oauthMessage); | ||
| } | ||
|
|
||
| // Try to extract www-authenticate header from the error | ||
| let wwwAuthenticate = extractWWWAuthenticateHeader(errorString); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Critical]
isInteractive()is also false for ACP /stream-jsonsessions, not just one-shot-pruns. Those modes still support user interaction: the existing permission gates explicitly exemptgetExperimentalZedIntegration()andInputFormat.STREAM_JSON(for examplecoreToolScheduler.ts:2500-2503). With an HTTP MCP server that returns 401 and needs first-time OAuth, this call now passesfalse; the new guard throws beforeauthenticate()can open the browser. The server remains disconnected for every ACP/stream-json user although this flow worked before the change. Derive this flag from whether the session can handle interaction (isInteractive || experimental Zed || stream-json) and cover that configuration path in the test.