Skip to content

Send Render's pre-registered OAuth client ID - #2

Merged
jacobprall merged 1 commit into
mainfrom
oauth-client-id
Aug 5, 2026
Merged

Send Render's pre-registered OAuth client ID#2
jacobprall merged 1 commit into
mainfrom
oauth-client-id

Conversation

@jacobprall

Copy link
Copy Markdown
Collaborator

Found during T-1 clean-room testing. Draft: must not merge until the pi client ID is confirmed registered on Render's side — see the gate below.

The defect

v0.1.0's default and recommended auth path cannot work for anyone. A first run with no credentials fails at startup:

MCP: Failed to connect to render: Incompatible auth server: does not support dynamic
client registration — probe: endpoint returned an untyped response (401) —
this URL does not appear to speak MCP

Cause

Render's MCP server advertises OAuth correctly: mcp.render.com/.well-known/oauth-protected-resource returns 200 and names https://api.render.com as the authorization server. But that server's metadata publishes only authorization_code and refresh_token with S256 PKCE and no registration_endpoint, so dynamic client registration is impossible.

pi-mcp-adapter attempts registration precisely when oauth.clientId is omitted, and buildRenderMcpConfig omitted it. Render instead pre-registers one public PKCE client per integration — claude, cursor, codex — which is why the Claude Code plugin ships oauth.clientId: "claude" rather than relying on registration.

This is the same gap misc/kimi-plugin-spec.md §4.2 documented for Kimi, with the same root cause.

Change

Send oauth.clientId: "pi" on the OAuth path. The bearer path is untouched, since it never runs an OAuth flow. The ID is public and carries no client secret.

docs/SPEC.md §5 already described the mechanism accurately — "the adapter performs dynamic client registration when oauth.clientId is omitted" — but concluded it matched the Claude Code plugin, the one case that does not rely on registration. That reasoning is corrected and the field is marked required.

Merge gate

  • Confirm Render has registered a public PKCE client with ID pi for https://mcp.render.com/mcp. If the registered ID differs, change the constant to match.
  • Confirm the registered client accepts dynamic loopback redirect URIs. Claude Code sets no redirectUri, which suggests it does; if not, add oauth.redirectUri.
  • Complete a clean-account OAuth flow end to end: /mcp-auth render, then list services.
  • Confirm an unauthenticated first run no longer surfaces the startup error.

Verification so far

npm run verify passes with 67 tests (64 before, plus 3). The new unit tests assert the client ID is present on the OAuth path, absent on the bearer path, and that no client secret is ever serialized. tests/extension/loads.test.ts pins the exact adapter config, so it caught this change and was updated deliberately.

Not yet verified: the live OAuth flow, which is what the merge gate covers.

Clean-room testing found that the default auth path cannot work for anyone. A
first run with no credentials fails at startup with:

    MCP: Failed to connect to render: Incompatible auth server: does not support
    dynamic client registration — probe: endpoint returned an untyped response
    (401) — this URL does not appear to speak MCP

Render's MCP server advertises its authorization server correctly, but that
server (https://api.render.com) publishes only authorization_code and
refresh_token grants with S256 PKCE, and no registration_endpoint. Dynamic
client registration is impossible against it.

pi-mcp-adapter attempts registration precisely when `oauth.clientId` is omitted,
and buildRenderMcpConfig omitted it. Render instead pre-registers one public PKCE
client per integration, which is why the Claude Code plugin ships
`oauth.clientId: "claude"`. This package needs the equivalent.

Send `oauth.clientId: "pi"` on the OAuth path and leave the bearer path alone,
since it never runs an OAuth flow. The ID is public and carries no secret.

SPEC §5 already described the mechanism — "the adapter performs dynamic client
registration when `oauth.clientId` is omitted" — but concluded that this matched
the Claude Code plugin, which is the one case that does not rely on
registration. Correct that reasoning and mark the field required.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jacobprall
jacobprall marked this pull request as ready for review August 5, 2026 21:19
@jacobprall
jacobprall merged commit d0b477d into main Aug 5, 2026
1 check passed
@jacobprall
jacobprall deleted the oauth-client-id branch August 5, 2026 21:19
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.

1 participant