Version: copilot-cli v1.0.51
Summary
Two related issues in the /mcp slash command's interactive "Authenticate" action, both stemming from the same single callsite of authenticate() in the /mcp UI's React component:
auth.redirectPort is ignored (regression of v1.0.49 fix on this code path). Same MCP server config that pins port 19876 via auto-connect ends up on a random port via /mcp UI re-auth.
- OAuth authorize URL is not surfaced inline. The TUI shows "Waiting for authorization in browser..." but never prints the URL. Users on remote dev environments (Codespaces, Coder workspaces) have no way to retrieve the URL without inspecting logs.
Both fix together with ~10 LOC in a single React component — the pattern already exists elsewhere in the codebase.
Repro
- Configure an HTTP MCP server with
auth.redirectPort in ~/.copilot/mcp-config.json:
{
"mcpServers": {
"linear": {
"type": "http",
"url": "https://mcp.linear.app/mcp",
"auth": { "redirectPort": 19876 }
}
}
}
- Delete any cached OAuth state for that server in
~/.copilot/mcp-oauth-config/.
- Start a Copilot session. Don't touch
/mcp. Auto-connect triggers OAuth.
- Observe the printed
authorize?...&redirect_uri=... URL → port 19876 ✓
- Wipe OAuth state again. Restart Copilot.
- Open
/mcp, select the server, click Authenticate.
- Observe the printed URL → random ephemeral port ✗
- Also observe: the TUI shows "Waiting for authorization in browser..." but never displays the URL itself anywhere — only place it surfaces is via the env-configured
$BROWSER invocation, which is hidden when stdout is captured by the TUI on remote/headless setups.
Expected
/mcp UI re-auth honors auth.redirectPort, same as auto-connect already does.
/mcp UI re-auth shows the URL inline in the TUI (the way the URL-elicitation component already does for MCP-server-requested URLs).
Actual
The /mcp UI's authenticate() callsite is missing both redirectPort and onAuthorizationUrl options:
// Current /mcp UI:
await provider.authenticate(serverUrl, {
staticClientConfig,
forceReauth,
forceDeviceCode,
signal,
clientName,
callbackSuccessMessage,
onStatusChange,
// ❌ MISSING: redirectPort
// ❌ MISSING: skipBrowserOpen / onAuthorizationUrl
});
Both code paths that DO work correctly already exist in the codebase:
MSt.getAuthProvider (auto-connect via mcp.oauth_required event handler): passes redirectPort: r.auth?.redirectPort.
Foo.login (the AI-callable mcp.oauth.login tool): passes both redirectPort: u.auth?.redirectPort and skipBrowserOpen: true + onAuthorizationUrl: S => f(S).
Only the /mcp UI re-auth path is missing them.
Impact
Users on remote dev environments where only specific ports are forwarded to the laptop cannot complete OAuth via /mcp re-auth. Scenarios that hit this:
- Switching accounts
- Re-auth after token revocation
- Re-auth after refresh token expiry where the client wasn't proactively refreshed
Auto-connect at session start works (path #1 above). The bug is silent: first-time native config works, then the next time the user re-auths through /mcp, it suddenly fails. Hard to diagnose without reading the code.
Suggested fix
Add redirectPort + onAuthorizationUrl to the /mcp UI's authenticate() callsite, plus a small <Box> block in the same React component to render the URL when present. The "copy this URL" UI block is already implemented in the URL-elicitation component (the one that handles MCP-server-requested URL elicitations) — extract it into a shared <OAuthUrlPrompt> component and reuse it here.
Approximate diff:
const [authUrl, setAuthUrl] = useState<string | undefined>();
const authenticate = async (serverName, url, forceReauth = false, forceDeviceCode = false) => {
// ...
await provider.authenticate(url, {
staticClientConfig,
forceReauth,
forceDeviceCode,
signal: abortController.signal,
clientName,
callbackSuccessMessage,
onStatusChange,
+ redirectPort: serverConfig?.auth?.redirectPort,
+ onAuthorizationUrl: (url) => setAuthUrl(url),
});
};
return (
<Box flexDirection="column" gap={1}>
<Text bold color={statusColor}>{statusMessage}</Text>
+ {authUrl && (
+ <Box flexDirection="column" borderStyle="round" paddingX={1}>
+ <Text>If your browser didn't open, copy this URL manually:</Text>
+ <Text>{terminalHyperlink(chalk.underline(authUrl), authUrl)}</Text>
+ </Box>
+ )}
{/* rest of UI */}
</Box>
);
Estimated total change: ~10 LOC, single file.
Workaround
Pre-register an OAuth client out-of-band (RFC 7591 dynamic client registration) with the desired redirect_uris, then seed ~/.copilot/mcp-oauth-config/<sha>.json with the resulting clientId + redirectUri. Copilot's OAuth provider parses the saved redirectUri's port and uses it via the existing fallback in authenticate():
let V = A ?? 0, F = V > 0;
if (!F && T?.redirectUri) try {
V = parseInt(new URL(T.redirectUri).port, 10) || 0;
} catch {}
So pre-seeding the cache file effectively patches the /mcp UI bug without modifying Copilot CLI. Not great UX (relies on internal cache layout — filename hash is pbkdf2(serverUrl, "copilot-mcp-oauth-store-key-v1", 210000, 32, sha256)), but it works.
Version: copilot-cli
v1.0.51Summary
Two related issues in the
/mcpslash command's interactive "Authenticate" action, both stemming from the same single callsite ofauthenticate()in the/mcpUI's React component:auth.redirectPortis ignored (regression of v1.0.49 fix on this code path). Same MCP server config that pins port 19876 via auto-connect ends up on a random port via/mcpUI re-auth.Both fix together with ~10 LOC in a single React component — the pattern already exists elsewhere in the codebase.
Repro
auth.redirectPortin~/.copilot/mcp-config.json:{ "mcpServers": { "linear": { "type": "http", "url": "https://mcp.linear.app/mcp", "auth": { "redirectPort": 19876 } } } }~/.copilot/mcp-oauth-config/./mcp. Auto-connect triggers OAuth.authorize?...&redirect_uri=...URL → port 19876 ✓/mcp, select the server, click Authenticate.$BROWSERinvocation, which is hidden when stdout is captured by the TUI on remote/headless setups.Expected
/mcpUI re-auth honorsauth.redirectPort, same as auto-connect already does./mcpUI re-auth shows the URL inline in the TUI (the way the URL-elicitation component already does for MCP-server-requested URLs).Actual
The
/mcpUI'sauthenticate()callsite is missing bothredirectPortandonAuthorizationUrloptions:Both code paths that DO work correctly already exist in the codebase:
MSt.getAuthProvider(auto-connect viamcp.oauth_requiredevent handler): passesredirectPort: r.auth?.redirectPort.Foo.login(the AI-callablemcp.oauth.logintool): passes bothredirectPort: u.auth?.redirectPortandskipBrowserOpen: true+onAuthorizationUrl: S => f(S).Only the
/mcpUI re-auth path is missing them.Impact
Users on remote dev environments where only specific ports are forwarded to the laptop cannot complete OAuth via
/mcpre-auth. Scenarios that hit this:Auto-connect at session start works (path #1 above). The bug is silent: first-time native config works, then the next time the user re-auths through
/mcp, it suddenly fails. Hard to diagnose without reading the code.Suggested fix
Add
redirectPort+onAuthorizationUrlto the/mcpUI'sauthenticate()callsite, plus a small<Box>block in the same React component to render the URL when present. The "copy this URL" UI block is already implemented in the URL-elicitation component (the one that handles MCP-server-requested URL elicitations) — extract it into a shared<OAuthUrlPrompt>component and reuse it here.Approximate diff:
const [authUrl, setAuthUrl] = useState<string | undefined>(); const authenticate = async (serverName, url, forceReauth = false, forceDeviceCode = false) => { // ... await provider.authenticate(url, { staticClientConfig, forceReauth, forceDeviceCode, signal: abortController.signal, clientName, callbackSuccessMessage, onStatusChange, + redirectPort: serverConfig?.auth?.redirectPort, + onAuthorizationUrl: (url) => setAuthUrl(url), }); }; return ( <Box flexDirection="column" gap={1}> <Text bold color={statusColor}>{statusMessage}</Text> + {authUrl && ( + <Box flexDirection="column" borderStyle="round" paddingX={1}> + <Text>If your browser didn't open, copy this URL manually:</Text> + <Text>{terminalHyperlink(chalk.underline(authUrl), authUrl)}</Text> + </Box> + )} {/* rest of UI */} </Box> );Estimated total change: ~10 LOC, single file.
Workaround
Pre-register an OAuth client out-of-band (RFC 7591 dynamic client registration) with the desired
redirect_uris, then seed~/.copilot/mcp-oauth-config/<sha>.jsonwith the resultingclientId+redirectUri. Copilot's OAuth provider parses the savedredirectUri's port and uses it via the existing fallback inauthenticate():So pre-seeding the cache file effectively patches the
/mcpUI bug without modifying Copilot CLI. Not great UX (relies on internal cache layout — filename hash ispbkdf2(serverUrl, "copilot-mcp-oauth-store-key-v1", 210000, 32, sha256)), but it works.