fix(auth): loopback redirect URIs must match on any port (RFC 8252 §7.3) #5

Closed
opened 2026-08-25 02:10:38 +00:00 by jlxq0 · 0 comments
Owner

Symptom

Claude Code CLI cannot complete Dynamic Client Registration:

Dynamic Client Registration rejected (HTTP 400): unregistered redirect_uri

Observed against caldav-mcp and diagnosed there; src/oauth_redirect.rs is copy-pasted across all five Rust MCP servers, so this repo has the same defect.

Cause

is_allowed_redirect_uri (src/oauth_redirect.rs:23) requires exact string equality against the allowlist. The allowlist carries http://localhost:8787/callback. Claude Code CLI does not use 8787: it binds a random free port per session (the observed attempt used http://localhost:3118/callback) and only consults a fixed port if every random draw fails. MCP_OAUTH_CALLBACK_PORT overrides it, but nothing sets that by default. No static entry can match, so native loopback clients are permanently locked out.

RFC 8252 §7.3:

the authorization server MUST allow any port to be specified at the time of the request for loopback IP redirect URIs

validate_redirect_uri already implements the loopback carve-out for the scheme check — cleartext http is permitted on loopback hosts only. The port half of the same rule was never written.

Fix

When an allowlist entry is a loopback http URI, compare scheme + host + path and ignore the port. Non-loopback entries keep exact matching: the port is a meaningful part of an https or private-scheme callback and relaxing it there would be a real hole.

Tests:

  • http://localhost:8787/callback allowlisted accepts http://localhost:3118/callback
  • and rejects http://localhost:3118/other — path still matters
  • and does not accept http://127.0.0.1:3118/callback unless 127.0.0.1 is listed separately — RFC 8252 relaxes the port, not the host
  • https://claude.ai/api/mcp/auth_callback still rejects https://claude.ai:8443/api/mcp/auth_callback

Before trusting the new tests, break the matcher and watch them go red.

Scope

Sibling issue with the full diagnosis: jlxq0/caldav-mcp#2
Also affected: jmap-mcp, m365-mcp, typst-mcp, caldav-mcp.

No deployment manifest change is required — the existing allowlists already carry a loopback entry, which starts matching once the rule is right.

## Symptom Claude Code CLI cannot complete Dynamic Client Registration: ``` Dynamic Client Registration rejected (HTTP 400): unregistered redirect_uri ``` Observed against `caldav-mcp` and diagnosed there; `src/oauth_redirect.rs` is copy-pasted across all five Rust MCP servers, so this repo has the same defect. ## Cause `is_allowed_redirect_uri` (`src/oauth_redirect.rs:23`) requires exact string equality against the allowlist. The allowlist carries `http://localhost:8787/callback`. Claude Code CLI does **not** use 8787: it binds a random free port per session (the observed attempt used `http://localhost:3118/callback`) and only consults a fixed port if every random draw fails. `MCP_OAUTH_CALLBACK_PORT` overrides it, but nothing sets that by default. No static entry can match, so native loopback clients are permanently locked out. RFC 8252 §7.3: > the authorization server MUST allow any port to be specified at the time of the request for loopback IP redirect URIs `validate_redirect_uri` already implements the loopback carve-out for the *scheme* check — cleartext `http` is permitted on loopback hosts only. The port half of the same rule was never written. ## Fix When an allowlist entry is a loopback `http` URI, compare scheme + host + path and ignore the port. Non-loopback entries keep exact matching: the port is a meaningful part of an `https` or private-scheme callback and relaxing it there would be a real hole. Tests: - `http://localhost:8787/callback` allowlisted accepts `http://localhost:3118/callback` - and rejects `http://localhost:3118/other` — path still matters - and does not accept `http://127.0.0.1:3118/callback` unless `127.0.0.1` is listed separately — RFC 8252 relaxes the port, not the host - `https://claude.ai/api/mcp/auth_callback` still rejects `https://claude.ai:8443/api/mcp/auth_callback` Before trusting the new tests, break the matcher and watch them go red. ## Scope Sibling issue with the full diagnosis: https://forge.oddie.app/jlxq0/caldav-mcp/issues/2 Also affected: jmap-mcp, m365-mcp, typst-mcp, caldav-mcp. No deployment manifest change is required — the existing allowlists already carry a loopback entry, which starts matching once the rule is right.
jlxq0 closed this issue 2026-08-25 04:49:31 +00:00
Sign in to join this conversation.
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/carddav-mcp#5
No description provided.