MCP OAuth: portless redirect_uris in client-metadata document fail against Notion MCP (RFC 8252 loopback port matching not honored)

Resolved 💬 2 comments Opened Apr 24, 2026 by yxorama Closed Apr 24, 2026

Summary

Authenticating the hosted Notion MCP server (https://mcp.notion.com/mcp) in Claude Code fails at Notion's consent step with "Invalid redirect_uri for OAuth client". The failure is structural: Claude Code's OAuth client-metadata document declares portless loopback redirect URIs, but the authorize request sends a runtime ephemeral port. Notion's OAuth validator does strict string matching and rejects the mismatch.

Per RFC 8252 §7.3, OAuth servers MUST ignore the port component when matching loopback redirect URIs (localhost, 127.0.0.1, [::1]) precisely to support ephemeral ports for native public clients. Notion doesn't appear to honor this, so the current Claude Code metadata shape is effectively incompatible with Notion's MCP OAuth.

Environment

  • Claude Code: 2.1.119
  • OS: macOS (Darwin 25.4.0)
  • Local session (not remote/SSH)
  • Target MCP: https://mcp.notion.com/mcp

Reproduction

  1. Install Notion MCP in Claude Code (pointing at https://mcp.notion.com/mcp).
  2. Invoke mcp__notion__authenticate.
  3. Open the returned authorize URL, sign in, approve workspace access.
  4. Click "Authenticate" on Notion's consent page.

Expected

Browser redirects to http://localhost:<port>/callback?code=…&state=…; Claude Code's in-process listener completes the exchange; Notion tools become available.

Actual

Browser lands on a Notion error page: "Invalid redirect_uri for OAuth client".

Attempting the manual fallback — pasting that URL into mcp__notion__complete_authentication — fails with "OAuth state mismatch - possible CSRF attack", because the URL is Notion's error page, not a /callback?... URL.

Root cause

The client-metadata document at https://claude.ai/oauth/claude-code-client-metadata declares:

{
  "client_id": "https://claude.ai/oauth/claude-code-client-metadata",
  "client_name": "Claude Code",
  "client_uri": "https://claude.ai",
  "redirect_uris": [
    "http://localhost/callback",
    "http://127.0.0.1/callback"
  ],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

The actual authorize request sends:

redirect_uri=http://localhost:<ephemeral-port>/callback

Notion's OAuth server rejects because the strings differ. RFC 8252 §7.3 requires port-agnostic matching for loopback redirect URIs, but Notion isn't doing it, and Claude Code isn't compensating.

Evidence this isn't client-side state

Reproduced on a fully clean slate:

  • All prior localhost Custom entries revoked on the Notion side (Settings → MCP connections).
  • All Claude Code integrations removed workspace-wide.
  • Keychain Access searched for notion / claude code — no matching MCP entries.
  • ~/.claude/mcp-needs-auth-cache.json cleared.
  • Fresh mcp__notion__authenticate call produced a new ephemeral port (65175 in this run) → same "Invalid redirect_uri" error.

Suggested fixes (Claude Code side)

Options, roughly by cost:

  1. Fall back to classic Dynamic Client Registration (RFC 7591) when the target server advertises a registration_endpoint in its OAuth metadata. DCR lets Claude Code register the concrete http://localhost:<port>/callback each flow, sidestepping strict-matching servers. Notion's MCP OAuth server does appear to support DCR (it accepted it for the client that produced three stale localhost:<port> entries previously seen in this workspace's history).
  2. Detect servers that don't honor RFC 8252 loopback port matching and either DCR there or degrade to a user-visible error explaining the interop gap, rather than a silent failure at the consent step.
  3. Coordinate with Notion to implement RFC 8252 §7.3 loopback matching in their MCP OAuth validator.

Related

  • makenotion/notion-mcp-server#268 (Notion MCP OAuth state issues)
  • anthropics/claude-code#10439 (MCP OAuth redirect_uri issues for remote MCP)
  • anthropics/claude-code#3273 (Claude Code requires DCR support from MCP servers)

Workaround for other users

Use the self-hosted @notionhq/notion-mcp-server stdio MCP with a Notion internal integration token — bypasses OAuth entirely.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗