Skip to content

MCP OAuth cannot complete for any RFC 9207 client: /oauth2/authorize 302s to clerk.higgsfield.ai with the client's redirect_uri, so the callback iss contradicts the advertised issuer #67

Description

@TeamRoseyCo

Summary

MCP OAuth against https://mcp.higgsfield.ai/mcp can no longer be completed by any RFC 9207-compliant client. The browser flow looks like it succeeds — Higgsfield renders "Authentication successful", so the user believes they are connected — but the client discards the authorization code and never stores a token, because the iss on the callback contradicts the issuer Higgsfield advertises in its own discovery documents.

The cause is that /oauth2/authorize does not act as an authorization server at all. It issues a bare 302 to clerk.higgsfield.ai/oauth/authorize with the client's loopback redirect_uri passed through unchanged. Clerk therefore owns the browser leg and correctly stamps its own identity, iss=https://clerk.higgsfield.ai — while /.well-known/oauth-authorization-server claims issuer: https://mcp.higgsfield.ai. Per MCP's authorization spec (SEP-2468) the client MUST abort on that mismatch.

This is not a client bug and there is no client-side workaround: the documented escape hatch in the SDK (skipIssuerMetadataValidation) explicitly does not suppress the runtime iss check, and Claude Code exposes no flag for it either way.

Environment

Error

From ~/Library/Caches/claude-cli-nodejs/<cwd>/mcp-logs-higgsfield/*.jsonl:

{"debug":"Returning cached discovery state (authServer: https://mcp.higgsfield.ai)"}
{"debug":"No access token in storage"}
{"debug":"Redirecting to authorization URL"}
{"debug":"Initial auth result: REDIRECT"}
{"debug":"Completing auth flow with authorization code"}
{"debug":"Error during auth completion: Issuer mismatch in authorization response (RFC 9207):
          expected \"https://mcp.higgsfield.ai\", received \"https://clerk.higgsfield.ai\""}

The server then stays parked in needs-auth for every subsequent session ("Skipping connection (cached needs-auth)"), so the only tools it exposes are authenticate and complete_authentication. All ~80 generation tools are unreachable.

Reproduction — no account or credentials needed

The delegation is unconditional, so two curl calls demonstrate the contradiction. Note the deliberately invalid client_id: the 302 happens before any client validation, which is itself worth a look.

1. The advertised issuer is mcp.higgsfield.ai:

curl -s https://mcp.higgsfield.ai/.well-known/oauth-authorization-server | jq '{issuer, authorization_endpoint, token_endpoint}'
{
  "issuer": "https://mcp.higgsfield.ai",
  "authorization_endpoint": "https://mcp.higgsfield.ai/oauth2/authorize",
  "token_endpoint": "https://mcp.higgsfield.ai/oauth2/token"
}

2. But /oauth2/authorize hands the whole flow — including the client's redirect_uri — to a different issuer:

curl -s -o /dev/null -D - \
  "https://mcp.higgsfield.ai/oauth2/authorize?response_type=code&client_id=NOT_A_REAL_CLIENT&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost%3A8899%2Fcallback&state=teststate&scope=openid+email+offline_access&resource=https%3A%2F%2Fmcp.higgsfield.ai%2Fmcp"
HTTP/2 302
location: https://clerk.higgsfield.ai/oauth/authorize?response_type=code&client_id=NOT_A_REAL_CLIENT&...&redirect_uri=http%3A%2F%2Flocalhost%3A8899%2Fcallback&...

Because redirect_uri still points at the client's loopback listener, Clerk — not Higgsfield — terminates the flow, and stamps iss=https://clerk.higgsfield.ai. https://clerk.higgsfield.ai/.well-known/oauth-authorization-server self-reports "issuer":"https://clerk.higgsfield.ai", so the two documents are simply inconsistent about who the authorization server is.

Your own /.well-known/oauth-protected-resource already names the split explicitly:

"authorization_servers": ["https://mcp.higgsfield.ai", "https://fnf-device-auth.higgsfield.ai"],
"higgsfield_auth_hints": {
  "options": [{
    "flow": "authorization_code_pkce",
    "authorization_server": "https://mcp.higgsfield.ai",
    "upstream_authorization_server": "https://clerk.higgsfield.ai",
    "potential_clients": ["anthropic","claude","claude-ai","claude-code"]
  }]
}

So the flow you designate for Claude Code is exactly the one that cannot complete.

Why this started without any change on your side

MCP made the check mandatory and default-on. modelcontextprotocol/typescript-sdk#2344 (SEP-2468, merged 2026-06-24) implements:

On receiving the authorization response, MCP clients MUST apply the validation in RFC9207 Section 2.4 before transmitting the authorization code to any token endpoint.

If issuer validation fails, the client MUST treat the response as invalid and abort the authorization flow.

Comparison is exact — the spec forbids host case folding, trailing-slash and percent-encoding normalization — so clerk.higgsfield.ai can never satisfy an expectation of mcp.higgsfield.ai.

Other hosted MCP servers with the same front-a-different-auth-host topology broke on the same rollout, which may be useful prior art:

Suggested fixes

Preferred — terminate the callback yourself. Register a redirect URI at Clerk that points back to Higgsfield (e.g. https://mcp.higgsfield.ai/oauth2/callback) instead of forwarding the client's loopback URI upstream. Handle Clerk's callback server-side, then redirect to the client's redirect_uri with your own code plus iss=https://mcp.higgsfield.ai. This makes the advertised issuer truthful, and it is also the only option under which your own /oauth2/token endpoint is meaningful rather than a pass-through.

Alternative — stop advertising yourself as the authorization server. Publish https://clerk.higgsfield.ai in authorization_servers in /.well-known/oauth-protected-resource instead of https://mcp.higgsfield.ai. Clerk's metadata already echoes its own issuer correctly, so the discovered issuer would then match the iss it stamps. Worth checking that resource/audience binding for https://mcp.higgsfield.ai/mcp still holds if clients register directly with Clerk.

Either way, please also confirm /oauth2/authorize rejects an unregistered client_id before redirecting rather than after.

Impact

Every RFC 9207-compliant MCP client is affected, not just Claude Code, and it fails in the most confusing possible way: the browser says authentication succeeded, so users conclude their client is broken and file against the client instead of here. Existing users are hit as soon as their stored token expires and a silent re-auth is attempted. Verified reachable via the CLI in the meantime, so the outage is confined to the MCP surface.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions