Problem
Zoo Code's MCP OAuth client includes urn:ietf:params:oauth:grant-type:jwt-bearer in the grant_types array during dynamic client registration (RFC 7591). PostHog's registration endpoint rejects this grant type even though it appears in their server metadata.
The error returned:
{"error":"invalid_client_metadata","error_description":"{'grant_types': {2: [ErrorDetail(string='\"urn:ietf:params:oauth:grant-type:jwt-bearer\" is not a valid choice.', code='invalid_choice')]}}"}
Root cause
Zoo Code reads grant_types_supported from the authorization server's /.well-known/oauth-authorization-server metadata and includes all of them in the registration request. PostHog advertises jwt-bearer in their metadata but their registration endpoint only accepts authorization_code and refresh_token for dynamically registered clients. jwt-bearer is reserved for a separate agent identity flow (ID-JAG) that Zoo Code does not use.
Reproduction
# Fails — jwt-bearer in grant_types
curl -X POST https://oauth.posthog.com/oauth/register/ -H "Content-Type: application/json" -d '{
"client_name": "zoo-code",
"redirect_uris": ["http://localhost:12345/callback"],
"grant_types": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}'
# Succeeds — jwt-bearer removed
curl -X POST https://oauth.posthog.com/oauth/register/ -H "Content-Type: application/json" -d '{
"client_name": "zoo-code",
"redirect_uris": ["http://localhost:12345/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}'
Fix
When building the grant_types array for a dynamic client registration request, only include grant types the client actually uses. Filter out urn:ietf:params:oauth:grant-type:jwt-bearer (and any other non-standard types from grant_types_supported) before sending the registration payload.
The safe allowlist for dynamic registration is:
authorization_code
refresh_token (only if the client will use refresh tokens)
TDD approach
Write tests first, then implement the fix.
Test 1 — registration payload does not include jwt-bearer
Given an authorization server that advertises jwt-bearer in grant_types_supported,
when Zoo Code builds the dynamic client registration request,
then the grant_types array must not contain urn:ietf:params:oauth:grant-type:jwt-bearer.
Test 2 — registration succeeds against a server that rejects jwt-bearer
Mock a registration endpoint that returns invalid_client_metadata when jwt-bearer is present and 200 otherwise.
Confirm Zoo Code's registration call succeeds.
Test 3 — non-standard grant types are filtered generally
Given a server that advertises an unknown grant type urn:example:grant-type:foo,
the registration payload must only contain types from the safe allowlist.
Server metadata reference
GET https://oauth.posthog.com/.well-known/oauth-authorization-server
grant_types_supported: ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"]
The registration endpoint at https://oauth.posthog.com/oauth/register/ silently strips jwt-bearer on some paths and returns invalid_client_metadata on others, so the fix must be client-side.
Problem
Zoo Code's MCP OAuth client includes
urn:ietf:params:oauth:grant-type:jwt-bearerin thegrant_typesarray during dynamic client registration (RFC 7591). PostHog's registration endpoint rejects this grant type even though it appears in their server metadata.The error returned:
{"error":"invalid_client_metadata","error_description":"{'grant_types': {2: [ErrorDetail(string='\"urn:ietf:params:oauth:grant-type:jwt-bearer\" is not a valid choice.', code='invalid_choice')]}}"}Root cause
Zoo Code reads
grant_types_supportedfrom the authorization server's/.well-known/oauth-authorization-servermetadata and includes all of them in the registration request. PostHog advertisesjwt-bearerin their metadata but their registration endpoint only acceptsauthorization_codeandrefresh_tokenfor dynamically registered clients.jwt-beareris reserved for a separate agent identity flow (ID-JAG) that Zoo Code does not use.Reproduction
Fix
When building the
grant_typesarray for a dynamic client registration request, only include grant types the client actually uses. Filter outurn:ietf:params:oauth:grant-type:jwt-bearer(and any other non-standard types fromgrant_types_supported) before sending the registration payload.The safe allowlist for dynamic registration is:
authorization_coderefresh_token(only if the client will use refresh tokens)TDD approach
Write tests first, then implement the fix.
Test 1 — registration payload does not include jwt-bearer
Given an authorization server that advertises
jwt-beareringrant_types_supported,when Zoo Code builds the dynamic client registration request,
then the
grant_typesarray must not containurn:ietf:params:oauth:grant-type:jwt-bearer.Test 2 — registration succeeds against a server that rejects jwt-bearer
Mock a registration endpoint that returns
invalid_client_metadatawhenjwt-beareris present and200otherwise.Confirm Zoo Code's registration call succeeds.
Test 3 — non-standard grant types are filtered generally
Given a server that advertises an unknown grant type
urn:example:grant-type:foo,the registration payload must only contain types from the safe allowlist.
Server metadata reference
The registration endpoint at
https://oauth.posthog.com/oauth/register/silently stripsjwt-beareron some paths and returnsinvalid_client_metadataon others, so the fix must be client-side.