Bug
On Windows, all OAuth flows silently fail because the authorization URL is truncated at the first & query parameter.
openExternal in src/.../oauth.ts spawns:
const child = launch('cmd', ['/c', 'start', '""', url], { stdio, detached: true });
Node.js spawn joins the arguments into a command line for cmd.exe, which interprets & as a command separator. The browser only receives:
https://example.com/oauth/authorize?response_type=code
Everything after the first & is lost — client_id, redirect_uri, code_challenge, etc. The OAuth server returns "The requested OAuth 2.0 Client
does not exist" because there's no client_id.
Problematic code
const { spawn } = require('child_process');
// Current (broken) — browser receives only a=1
spawn('cmd', ['/c', 'start', '""', 'https://httpbin.org/get?a=1&b=2&c=3'],
{ stdio: 'ignore', detached: true });
// Fixed — browser receives a=1, b=2, c=3
spawn('cmd', ['/s', '/c', 'start "" "https://httpbin.org/get?a=1&b=2&c=3"'],
{ stdio: 'ignore', detached: true, windowsVerbatimArguments: true });
Fix
const child = launch('cmd', ['/s', '/c', start "" "${url}"], {
stdio,
detached: true,
windowsVerbatimArguments: true,
});
- windowsVerbatimArguments: true — tells Node.js to pass arguments as-is to CreateProcess, no escaping
- /s — tells cmd.exe to correctly strip outer quotes after /c, preserving the inner "url" quotes that protect &
Impact
- All OAuth flows on Windows are broken for every MCP server
- macOS (open) and Linux (xdg-open) are unaffected — they pass the URL as a direct process argument
Environment
- Windows 11 Pro
- Node.js v24.9.0
- mcporter 0.8.1
- Tested against Glean MCP
I applied the fix directly on the js code and it worked fine. I will submit a PR for this.
Bug
On Windows, all OAuth flows silently fail because the authorization URL is truncated at the first & query parameter.
openExternal in src/.../oauth.ts spawns:
const child = launch('cmd', ['/c', 'start', '""', url], { stdio, detached: true });
Node.js spawn joins the arguments into a command line for cmd.exe, which interprets & as a command separator. The browser only receives:
https://example.com/oauth/authorize?response_type=code
Everything after the first & is lost — client_id, redirect_uri, code_challenge, etc. The OAuth server returns "The requested OAuth 2.0 Client
does not exist" because there's no client_id.
Problematic code
const { spawn } = require('child_process');
// Current (broken) — browser receives only a=1
spawn('cmd', ['/c', 'start', '""', 'https://httpbin.org/get?a=1&b=2&c=3'],
{ stdio: 'ignore', detached: true });
// Fixed — browser receives a=1, b=2, c=3
spawn('cmd', ['/s', '/c', 'start "" "https://httpbin.org/get?a=1&b=2&c=3"'],
{ stdio: 'ignore', detached: true, windowsVerbatimArguments: true });
Fix
const child = launch('cmd', ['/s', '/c',
start "" "${url}"], {stdio,
detached: true,
windowsVerbatimArguments: true,
});
Impact
Environment
I applied the fix directly on the js code and it worked fine. I will submit a PR for this.