github / github/copilot-cli

MCP OAuth re-auth repeatedly fails on Windows when the cached loopback redirect port is in an excluded TCP port range

Abierto
#3,973 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:authentication area:mcp area:platform-windows
Lenguaje dominante
Shell
Estrellas
11.2k
Forks
1.9k
Merge medio
14 h 16 min
PR fusionados (30 d)
6

Descripción

On Windows, an HTTP MCP server using OAuth can get into a sticky re-auth failure state: repeated `/mcp` re-auth attempts fail with the same local listener bind error until the cached OAuth registration is manually removed. The observed failure mode is that the CLI reuses a cached, dynamically registered client whose loopback redirect URI contains a **fixed port** (e.g. `http://127.0.0.1:60909/`). On my machine that port was inside a Windows **excluded TCP port range**, so the local loopback listener could not bind and failed with `EACCES`. Because the same cached redirect URI is reused on each retry, every re-auth attempt hits the same unbindable port, with no self-healing path.

Transient error shown in the TUI during `/mcp` re-auth:

```
Authenticating: foundry-mcp
Authentication failed: MCPOAuthError: listen EACCES: permission denied 127.0.0.1:60909
```

I could not find this bind error persisted in the CLI log/cache locations I checked; I only observed it transiently in the TUI, which made it difficult to diagnose after the fact.

Evidence that the failure is the local listener bind, not the server/token/config:
- Cached registration `~/.copilot/mcp-oauth-config/.json` contained `"redirectUri": "http://127.0.0.1:60909/"` (dynamic registration, `isStatic:false`).
- `netsh interface ipv4 show excludedportrange protocol=tcp` showed an excluded range `60868-60967`; `60909` is inside it.
- `Get-NetTCPConnection -LocalPort 60909 -State Listen` returned nothing — the port is reserved by the OS, not occupied by a listener (consistent with `EACCES` rather than `EADDRINUSE`; see [KB 3039044](https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/error-10013-wsaeacces-is-returned), where `tcpip.sys` marks excluded-range ports RESERVED and `bind()` returns WSAEACCES/10013).
- Using the still-valid cached access token, a manual `POST` of an MCP `initialize` request returned HTTP 200 with a valid response. That indicates the server endpoint, network path, cached token, and MCP config were not the cause.

### Affected version

`GitHub Copilot CLI 1.0.66-2`

### Steps to reproduce the behavior

1. Run on Windows where one or more excluded TCP port ranges exist. Confirm with: `netsh interface ipv4 show excludedportrange protocol=tcp`
2. Configure an HTTP MCP server that uses OAuth in `~/.copilot/mcp-config.json`.
3. Let the CLI perform dynamic client registration and cache a loopback redirect URI in `~/.copilot/mcp-oauth-config/.json`.
4. If the cached redirect URI's port is inside an excluded range, run `/mcp` and re-authenticate the server.
5. Observe repeated failure: `Authentication failed: MCPOAuthError: listen EACCES: permission denied 127.0.0.1:`

In my repro the cached redirect URI used port `60909`, and Windows reported an excluded range of `60868-60967`.

### Expected behavior

The CLI should recover automatically instead of looping on an unbindable pinned port:

1. **Validate/recover the loopback port for dynamically registered clients.** Before reusing a cached loopback redirect port, attempt to bind it; on `EACCES`/`EADDRINUSE`, discard or refresh that cached registration and register a new redirect URI on a bindable port. A robust approach is to bind an OS-selected loopback port first (bind to port `0`), read back the assigned port, then use it when constructing the authorization request / dynamic registration. This matches [RFC 8252 §7.3](https://www.rfc-editor.org/rfc/rfc8252.html#section-7.3), whose expected pattern is for native-app clients to obtain an *ephemeral* loopback port from the OS at request time — the spec's normative "MUST allow any port" is placed on the **authorization server** specifically to enable this. §8.4 further notes loopback redirects match on everything except the port. Re-registering with a different loopback port is permitted by Dynamic Client Registration ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591.html)). Pinning a fixed loopback port is fragile on platforms where a given port may be OS-excluded.
2. **Persist the bind failure** (MCP server name, address, port, and errno/code) to the diagnostic log, so a local listener-bind failure can be distinguished from an actual auth-provider failure.

### Additional context

- **OS / environment:** Windows (`win32-x64`). Reproduced on a Microsoft Dev Box / Windows 365 Cloud PC (`Manufacturer: Microsoft Corporation, Model: Virtual Machine`) with `hns`, `vmcompute`, `winnat`, and `WslService` running and three excluded TCP ranges present (`49723-49822`, `50000-50059`, `60868-60967`). I did not independently determine which component created the range containing `60909`.
- **Trigger scope:** Excluded port ranges are common on Windows developer environments that use virtualization/container networking (Hyper-V, WSL2, Docker, HNS, WinNAT). They appear to be common on Microsoft Dev Box / Windows 365 Cloud PC images where these features are enabled by default. When the condition is hit, the failure is sticky and non-obvious: the server stays failed and re-auth loops until the hidden cached registration files are removed.
- **Manual workaround that fixed it:** delete the server's `.json` registration and `.tokens.json` from `~/.copilot/mcp-oauth-config/`, then re-auth — the CLI re-registers on a new (bindable) port and authentication succeeds.
- I found related but apparently distinct OAuth issues (#3393, #3039 headless/SSH browser; #3838 reauth not attached). I did not find one covering a Windows excluded-port listener bind failure.

Guía de contribución

Abrir la guía de contribución

Línea de trabajo

Comienza con el flujo de reautenticación de `/mcp` y los archivos almacenados en caché bajo `~/.copilot/mcp-oauth-config/`, y reproduce el problema en Windows usando `netsh interface ipv4 show excludedportrange protocol=tcp`. Traza el registro dinámico y el enlace del listener de loopback, incluidos los registros de diagnóstico. Se considera terminado cuando la reautenticación se recupera de un puerto almacenado en caché excluido u ocupado y el fallo de enlace se persiste con su servidor, dirección, puerto y código de error.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Área
authentication, cli, networking, operating-systems
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Tranquilo
Claridad
Bastante claro
Aptitud para principiantes
52/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.