Summary
Strict OAuth clients (anything using oauth4webapi, or following RFC 8414 §3) fail token exchange against https://mcp.onkernel.com/mcp with:
Failed to process authorization code response: unexpected JWT "iss" (issuer) claim value
DCR succeeds. The user signs in. /callback fires. /token returns 200 with a body. The client rejects on the id_token's iss claim.
Root cause: your AS metadata advertises one issuer; the id_token returned by /token is Clerk-signed with a different one.
curl -s https://mcp.onkernel.com/.well-known/oauth-authorization-server/mcp | jq '.issuer, .jwks_uri'
# "https://mcp.onkernel.com"
# "https://clerk.onkernel.com/.well-known/jwks.json"
Decode the id_token from a real token-exchange response and iss is https://clerk.onkernel.com, not https://mcp.onkernel.com. RFC 8414 §3 requires the two to match; strict clients enforce.
Context
We hit this while onboarding kernel/ onto Smithery. The flow is past DCR (after working around a separate 200-vs-201 issue at /register client-side) and dies during token exchange in oauth4webapi's processAuthorizationCodeResponse. Same family from another client: jlowin/fastmcp#2453, open, no fix.
Fix options
| Option |
Where it lives |
Tradeoff |
1. Clerk JWT template that mints session tokens with iss=https://mcp.onkernel.com, used when minting through your /token route |
Clerk dashboard + src/app/token/route.ts |
Self-contained, no re-signing. Doesn't change AS identity. |
2. Re-sign the id_token in src/app/token/route.ts with iss=https://mcp.onkernel.com before returning |
src/app/token/route.ts |
Full control over claims. Need a signing key, JWKs endpoint you own, and to update jwks_uri in your AS metadata. |
3. Change AS metadata issuer to https://clerk.onkernel.com so it matches what's in the token |
src/app/.well-known/oauth-authorization-server/route.ts |
Works. Couples your AS identity to Clerk's domain. |
1 or 2.
Reproduction
- Hit
https://mcp.onkernel.com/register and walk a real OAuth flow.
- Capture the token response body.
echo "$id_token" | cut -d. -f2 | base64 -d | jq .iss
- Compare to
.issuer from https://mcp.onkernel.com/.well-known/oauth-authorization-server/mcp.
They differ today. Either fix above aligns them.
Summary
Strict OAuth clients (anything using
oauth4webapi, or following RFC 8414 §3) fail token exchange againsthttps://mcp.onkernel.com/mcpwith:DCR succeeds. The user signs in.
/callbackfires./tokenreturns 200 with a body. The client rejects on theid_token'sissclaim.Root cause: your AS metadata advertises one issuer; the
id_tokenreturned by/tokenis Clerk-signed with a different one.Decode the
id_tokenfrom a real token-exchange response andissishttps://clerk.onkernel.com, nothttps://mcp.onkernel.com. RFC 8414 §3 requires the two to match; strict clients enforce.Context
We hit this while onboarding
kernel/onto Smithery. The flow is past DCR (after working around a separate 200-vs-201 issue at/registerclient-side) and dies during token exchange inoauth4webapi'sprocessAuthorizationCodeResponse. Same family from another client:jlowin/fastmcp#2453, open, no fix.Fix options
iss=https://mcp.onkernel.com, used when minting through your/tokenroutesrc/app/token/route.tsid_tokeninsrc/app/token/route.tswithiss=https://mcp.onkernel.combefore returningsrc/app/token/route.tsjwks_uriin your AS metadata.issuertohttps://clerk.onkernel.comso it matches what's in the tokensrc/app/.well-known/oauth-authorization-server/route.ts1 or 2.
Reproduction
https://mcp.onkernel.com/registerand walk a real OAuth flow.echo "$id_token" | cut -d. -f2 | base64 -d | jq .iss.issuerfromhttps://mcp.onkernel.com/.well-known/oauth-authorization-server/mcp.They differ today. Either fix above aligns them.