modelcontextprotocol / modelcontextprotocol/typescript-sdk

authExtensions docs say custom claims override reserved JWT claims, but implementation keeps reserved claims authoritative

Open Beginner friendly
#1,914 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug documentation fix proposed P3 ready for work
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

Summary

PrivateKeyJwtProviderOptions.claims currently documents overlapping custom claims as taking precedence over the SDK's standard JWT claims, but the implementation keeps the reserved claims authoritative.

This looks like a docs / contract mismatch rather than a runtime bug or security issue.

Current docs

packages/client/src/client/authExtensions.ts says:

These are merged with the standard claims (iss, sub, aud, exp, iat, jti), with custom claims taking precedence for any overlapping keys.

Actual behavior

createPrivateKeyJwtAuth() constructs claims from { ...baseClaims, ...options.claims }, but then immediately calls:

  • .setIssuer(options.issuer)
  • .setSubject(options.subject)
  • .setAudience(audience)
  • .setIssuedAt(now)
  • .setExpirationTime(now + lifetimeSeconds)
  • .setJti(jti)

Those setters overwrite overlapping values from options.claims, so the SDK's reserved claims remain authoritative.

Minimal reproduction

const addClientAuth = createPrivateKeyJwtAuth({
  issuer: 'client-id',
  subject: 'client-id',
  privateKey: 'a-string-secret-at-least-256-bits-long',
  alg: 'HS256',
  audience: 'https://aud.example.com',
  claims: {
    iss: 'override-issuer',
    sub: 'override-subject',
    aud: 'https://override.example.com',
    tenant_id: 'org-123'
  }
});

Decoding the resulting JWT shows:

  • iss === 'client-id'
  • sub === 'client-id'
  • aud === 'https://aud.example.com'
  • tenant_id === 'org-123'

So additional custom claims are included, but overlapping reserved claims are not overridden.

Why this matters

This can mislead users into thinking they can customize reserved JWT claims through claims, when in practice only non-overlapping claims are honored.

Suggested resolution

I think the smallest fix is to align the docs/tests with the current runtime behavior:

  • clarify that additional custom claims are included
  • clarify that reserved standard claims are still set explicitly by the SDK and are not overridden
  • add a regression test showing the current behavior

If maintainers prefer the opposite behavior, that would likely deserve a separate design discussion because it changes runtime semantics.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/client/src/client/authExtensions.ts and read the PrivateKeyJwtProviderOptions.claims documentation alongside createPrivateKeyJwtAuth(). Locate the related auth extension tests, then update the wording to explain that additional claims are included while reserved standard claims remain SDK-controlled. Done means the documentation matches runtime behavior and a regression test demonstrates the reserved-claim behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
authentication, documentation
Issue type
Documentation
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.