Skip to content

fix(auth): explain OAuth client-registration failures (e.g. Figma 403) instead of leaking SDK JSON parse errors#297

Closed
az-catch wants to merge 1 commit into
apify:mainfrom
az-catch:fix/oauth-dcr-forbidden-error
Closed

fix(auth): explain OAuth client-registration failures (e.g. Figma 403) instead of leaking SDK JSON parse errors#297
az-catch wants to merge 1 commit into
apify:mainfrom
az-catch:fix/oauth-dcr-forbidden-error

Conversation

@az-catch

@az-catch az-catch commented Jul 3, 2026

Copy link
Copy Markdown

Problem

Logging in to a server that refuses Dynamic Client Registration (DCR) produces a cryptic, non-actionable error. Figma's remote MCP server is the clearest reproduction — mcpc login https://mcp.figma.com/mcp fails with:

✗ HTTP 403: Invalid OAuth error response: SyntaxError: Unexpected token 'F', "Forbidden" is not valid JSON. Raw body: Forbidden

Root cause (confirmed against the live server)

  1. Discovery succeeds. Figma's protected-resource metadata points at auth server https://api.figma.com, whose metadata advertises registration_endpoint: https://api.figma.com/v1/oauth/mcp/register.
  2. The SDK performs DCR against that endpoint. Figma returns a 403 with a plain-text body Forbidden (despite Content-Type: application/json). Per Figma's own forum, the endpoint "will return a 403 for any client not on [the approved] list" — MCP access is limited to approved clients.
  3. The MCP SDK tries to JSON.parse("Forbidden"), fails, and surfaces the raw SyntaxError.

This is not Figma-specific — any allow-list-only or DCR-less server (opencode, Cline, Codex users have reported the same wall) produces the same opaque error.

mcpc cannot make Figma's login succeed (that is a provider allow-list policy), but it can tell the user what actually happened and what to do next.

Change

A new exported helper explainOAuthRegistrationFailure() inspects failures that occur before the authorization redirect (tracked via a new reachedAuthorization flag) and rewrites registration-phase failures — HTTP 403/401, a malformed non-JSON OAuth error, or a missing registration_endpoint — into actionable guidance:

✗ mcp.figma.com refused to register mcpc as an OAuth client (HTTP 403).
Some MCP servers only accept a fixed allow-list of approved clients and reject
Dynamic Client Registration from everyone else (Figma's remote MCP server behaves
this way). When that happens there is no self-service client for mcpc to register.

To authenticate, supply a client the server already recognises:
  • pre-registered client:  mcpc login mcp.figma.com --client-id <id> [--client-secret <secret>]
  • custom CIMD document:   mcpc login mcp.figma.com --client-metadata-url <https-url>

If the server restricts MCP access to specific approved clients, check with the
provider whether third-party clients such as mcpc are supported.

Failures after the authorization redirect (token exchange, user cancellation, network faults) are passed through unchanged. The original SDK error is preserved in details.originalError for --verbose / --json.

Testing

  • pnpm run build (tsc) — clean
  • pnpm run format:check and eslint on changed files — clean
  • pnpm exec vitest run838 passed, including 5 new tests covering: the Figma non-JSON 403, a server with no registration_endpoint, a bare Forbidden, post-authorization pass-through, and unrelated-error pass-through.
  • Manually verified end-to-end against https://mcp.figma.com/mcp in both human and --json output modes.

…ing SDK JSON parse errors

When a server refuses Dynamic Client Registration, the MCP SDK surfaces an
opaque error. Figma's remote MCP server is the clearest example: its
registration endpoint (https://api.figma.com/v1/oauth/mcp/register) returns a
bare `403 Forbidden` for any client not on its approved allow-list, and because
that body is not JSON the SDK reports:

    HTTP 403: Invalid OAuth error response: SyntaxError: Unexpected token 'F',
    "Forbidden" is not valid JSON. Raw body: Forbidden

That leaves the user with no idea what went wrong or what to do next, and
retrying/re-authenticating cannot help.

Detect registration-phase failures (403/401, malformed non-JSON OAuth error, or
a missing registration_endpoint) that occur before the authorization redirect
and replace them with actionable guidance: supply a pre-registered client via
--client-id/--client-secret or a custom CIMD via --client-metadata-url, and
check whether the provider allow-lists clients. Failures after the authorization
redirect (token exchange, cancellation, network) are passed through unchanged.

Adds unit tests covering the Figma 403 case, the no-registration-endpoint case,
bare Forbidden, post-authorization pass-through, and unrelated-error
pass-through.
jancurn added a commit that referenced this pull request Jul 4, 2026
…03) instead of leaking SDK JSON parse errors (#298)

`mcpc login` against a server that refuses Dynamic Client Registration
(e.g. Figma's remote MCP server, which returns a plain-text `403
Forbidden` for clients not on its allow-list) surfaced the SDK's raw
parse error (`HTTP 403: Invalid OAuth error response: SyntaxError: ...
Raw body: Forbidden`). Registration-phase failures are now rewritten
into actionable guidance pointing at `--client-id` /
`--client-metadata-url`.

- Registration phase is detected via the SDK's typed `OAuthError` plus a
`reachedAuthorization` flag; post-redirect errors pass through unchanged
- Allow-list wording only for 401/403 or OAuth-coded client rejections;
other registration failures (5xx outages, rejected metadata) are
reported honestly
- The server's underlying response stays visible in the message
(truncated), since `login` prints only `error.message`

Fixes #296, supersedes #297.

https://claude.ai/code/session_01AP2pmUcWvR8rNypXqNEqtk

---
_Generated by [Claude
Code](https://claude.ai/code/session_01AP2pmUcWvR8rNypXqNEqtk)_

Co-authored-by: Claude <noreply@anthropic.com>
@jancurn

jancurn commented Jul 4, 2026

Copy link
Copy Markdown
Member

Thanks for this PR — the root-cause analysis was spot on and shaped the final fix. We reimplemented it in #298 rather than merging because review found issues in the detection approach:

  • Over-broad matching: the message regexes also fired on 5xx outages and WAF blocks, mislabeling them as allow-list rejections. #298 keys off the SDK's typed OAuthError instead, and only claims an allow-list for 401/403 or OAuth-coded client rejections.
  • Lost diagnostics: login only prints error.message, so details.originalError was never shown anywhere. #298 appends the server's response to the message itself.
  • Fragile to SDK upgrades: tests asserted the same hardcoded strings the regexes matched, so an SDK rewording would silently break the feature. Typed errors also cover 400/422 registration rejections the regexes missed.
    The reachedAuthorization flag and message design carried over from your PR nearly unchanged — thanks again!

@jancurn jancurn closed this Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants