Describe the bug
When an HTTP MCP server is configured with auth.redirectPort and has no cached OAuth token, the CLI eagerly initiates the OAuth flow at session startup and binds the configured local callback port (e.g. 127.0.0.1:19878). The pending listener stays bound for ~5 minutes waiting for the browser callback.
If the user later runs /mcp → server → Authenticate during that window, the re-auth path tries to bind the same auth.redirectPort again and immediately fails with:
Authentication failed: MCPOAuthError: listen EADDRINUSE: address already in use 127.0.0.1:19878
The user has no way to recover the URL from the original eager flow either (it briefly appears on startup before the TUI renders over it, and is not surfaced anywhere in /mcp). The only workarounds are: wait ~5 minutes for the eager flow to time out, or remove the server from mcp-config.json and restart.
This appears to be a regression introduced by the fix for #3418 — that fix made /mcp re-auth honor auth.redirectPort (it previously used a random ephemeral port). Now both code paths fight over the same port within a single process.
Affected version
GitHub Copilot CLI 1.0.52-1
Steps to reproduce the behavior
- Configure an HTTP MCP server with
auth.redirectPort in ~/.copilot/mcp-config.json, with no cached token:
{
"mcpServers": {
"linear": {
"type": "http",
"url": "https://mcp.linear.app/mcp",
"auth": { "redirectPort": 19878 }
}
}
}
- Start Copilot CLI. Eager OAuth flow kicks off and binds
127.0.0.1:19878 (the URL is printed briefly before the TUI renders, easily missed on remote/headless sessions).
- Within ~5 minutes, run
/mcp → linear → Authenticate.
- See:
Authentication failed: MCPOAuthError: listen EADDRINUSE: address already in use 127.0.0.1:19878.
Verify via another shell that the eager listener is still alive throughout:
ss -tlnp | grep 127.0.0.1:19878
# LISTEN ... MainThread,pid=<copilot PID>,fd=XX
/restart and exit+resume do not help, because the new process re-runs the eager flow within ~200ms of startup and re-binds the same port (verified by polling ss -tlnp).
Expected behavior
Either of these alone would fix the bug; both together would be ideal:
- Lazy auth on startup. Don't auto-initiate OAuth for servers requiring auth at session start. Only kick off the flow when the user explicitly authenticates via
/mcp (or invokes a tool from that server). This guarantees the URL is born at the moment the user is looking at the TUI.
- Detect-and-reuse pending flows. If
/mcp → server → Authenticate is invoked while a flow is already pending for that server (listener already bound, URL+state already generated), surface the existing pending URL instead of trying to start a second listener. Something like:
A pending authentication flow already exists for linear. Open this URL to complete it: https://...
No second listener, no EADDRINUSE.
Describe the bug
When an HTTP MCP server is configured with
auth.redirectPortand has no cached OAuth token, the CLI eagerly initiates the OAuth flow at session startup and binds the configured local callback port (e.g.127.0.0.1:19878). The pending listener stays bound for ~5 minutes waiting for the browser callback.If the user later runs
/mcp→ server → Authenticate during that window, the re-auth path tries to bind the sameauth.redirectPortagain and immediately fails with:The user has no way to recover the URL from the original eager flow either (it briefly appears on startup before the TUI renders over it, and is not surfaced anywhere in
/mcp). The only workarounds are: wait ~5 minutes for the eager flow to time out, or remove the server frommcp-config.jsonand restart.This appears to be a regression introduced by the fix for #3418 — that fix made
/mcpre-auth honorauth.redirectPort(it previously used a random ephemeral port). Now both code paths fight over the same port within a single process.Affected version
GitHub Copilot CLI 1.0.52-1Steps to reproduce the behavior
auth.redirectPortin~/.copilot/mcp-config.json, with no cached token:{ "mcpServers": { "linear": { "type": "http", "url": "https://mcp.linear.app/mcp", "auth": { "redirectPort": 19878 } } } }127.0.0.1:19878(the URL is printed briefly before the TUI renders, easily missed on remote/headless sessions)./mcp→ linear → Authenticate.Authentication failed: MCPOAuthError: listen EADDRINUSE: address already in use 127.0.0.1:19878.Verify via another shell that the eager listener is still alive throughout:
/restartand exit+resume do not help, because the new process re-runs the eager flow within ~200ms of startup and re-binds the same port (verified by pollingss -tlnp).Expected behavior
Either of these alone would fix the bug; both together would be ideal:
/mcp(or invokes a tool from that server). This guarantees the URL is born at the moment the user is looking at the TUI./mcp→ server → Authenticate is invoked while a flow is already pending for that server (listener already bound, URL+state already generated), surface the existing pending URL instead of trying to start a second listener. Something like: