Skip to content

feat(client): SEP-837/2207 — application_type heuristic, grant_types default, https token-endpoint guard#2345

Open
felixweinberger wants to merge 1 commit into
fweinberger/auth-1-sep2468-clientfrom
fweinberger/auth-2-sep837-2207
Open

feat(client): SEP-837/2207 — application_type heuristic, grant_types default, https token-endpoint guard#2345
felixweinberger wants to merge 1 commit into
fweinberger/auth-1-sep2468-clientfrom
fweinberger/auth-2-sep837-2207

Conversation

@felixweinberger

Copy link
Copy Markdown
Contributor

7ee1ad038

registerClient() defaults application_type from the redirect URI (loopback/localhost → 'native', else 'web'; consumer-set value always wins) and defaults grant_types to ['authorization_code', 'refresh_token'] when omitted. auth()/exchangeAuthorization()/refreshAuthorization() reject a non-https: token endpoint (loopback exempt). New exported RegistrationRejectedError carrying the AS error and the submitted metadata.

Motivation and Context

SEP-837 (modelcontextprotocol/modelcontextprotocol#837) and SEP-2207 (modelcontextprotocol/modelcontextprotocol#2207).

Normative sentences implemented:

MCP clients MUST specify an appropriate application_type during Dynamic Client Registration. … Native applications … SHOULD use application_type: "native"; Web applications … SHOULD use application_type: "web".
basic/authorization/client-registration.mdx (SEP-837)

MCP clients MUST be prepared to handle registration failures due to redirect URI constraints … When a registration request is rejected, clients SHOULD surface a meaningful error.
basic/authorization/client-registration.mdx (SEP-837)

SHOULD include refresh_token in their grant_types client metadata.
basic/authorization/index.mdx §Refresh Tokens (SEP-2207)

MUST keep refresh tokens confidential in transit and storage as specified in OAuth 2.1 Section 4.3.
basic/authorization/index.mdx §Refresh Tokens (SEP-2207) — the in-transit clause is enforced here by rejecting non-https token endpoints; in-storage remains a consumer obligation.

How Has This Been Tested?

  • Unit: application_type heuristic table (loopback v4/v6, localhost, custom-scheme, https web host, explicit override).
  • E2E: client-auth:dcr:{application_type-heuristic, application_type-override, grant_types-default, registration-rejected-error}, client-auth:https-token-endpoint-guard.
  • Conformance: burns the 11-scenario SEP-837 block (auth/metadata-*, auth/scope-from-*, auth/token-endpoint-auth-*, auth/offline-access-not-supported) on the 2026 leg.

Breaking Changes

Yes — DCR body and token-endpoint guard.

  • The Dynamic Client Registration request body now includes application_type (heuristic default) and grant_types: ['authorization_code', 'refresh_token'] when the consumer's clientMetadata omits them. ASes that reject unknown fields or that constrain redirect URIs by application_type may now reject registrations that previously succeeded. Consumers can override both fields explicitly; RegistrationRejectedError carries the submitted metadata to support the spec's MAY-retry-with-adjusted-type.
  • auth() and the low-level token helpers throw on an http: token endpoint (loopback hosts exempt). Hosts pointed at a plaintext token endpoint will fail closed; this enforces a spec MUST and there is no opt-out.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

The application_type heuristic is an OAuthClientMetadataSchema default, not a rewrite — a consumer-set value (including undefined set explicitly) is never overwritten. SEP-2207's RS-operator offline_access SHOULD-NOTs and the in-storage confidentiality clause are documented as consumer obligations in migration.md (PR 7); they are not SDK code.


@felixweinberger felixweinberger requested a review from a team as a code owner June 23, 2026 13:18
@changeset-bot

changeset-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b445a38

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@modelcontextprotocol/client Minor
@modelcontextprotocol/core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@2345

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@2345

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@2345

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server-legacy@2345

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@2345

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@2345

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@2345

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@2345

commit: b445a38

Comment thread docs/migration.md
Comment thread packages/core/src/auth/errors.ts
Comment thread docs/migration.md Outdated

@pcarleton pcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 change otherwise lgtm

Comment thread packages/client/src/client/auth.ts Outdated
authServerMetadata?.scopes_supported?.includes('offline_access') &&
!effectiveScope.split(' ').includes('offline_access') &&
clientMetadata.grant_types?.includes('refresh_token')
(clientMetadata.grant_types ?? DEFAULT_DCR_GRANT_TYPES).includes('refresh_token')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems a bit strange to me to check it here, vs. filling in the defaults in clientMetadata

Comment thread packages/client/src/client/auth.ts Outdated
authServerMetadata?.scopes_supported?.includes('offline_access') &&
!effectiveScope.split(' ').includes('offline_access') &&
clientMetadata.grant_types?.includes('refresh_token')
(clientMetadata.grant_types ?? DEFAULT_DCR_GRANT_TYPES).includes('refresh_token')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should apply this when getting client metadata from the provider, and then pass it around from there. it feels strange to me to have a sneaky default check fairly removed from where we got the data

@felixweinberger felixweinberger force-pushed the fweinberger/auth-1-sep2468-client branch from afb76ea to c843d7e Compare June 23, 2026 19:03
@felixweinberger felixweinberger force-pushed the fweinberger/auth-2-sep837-2207 branch from 99fbb49 to 5156ed7 Compare June 23, 2026 19:03
Comment thread packages/client/src/client/auth.ts
Comment thread packages/client/src/client/auth.ts
@felixweinberger felixweinberger force-pushed the fweinberger/auth-1-sep2468-client branch from c843d7e to 5dfc2be Compare June 23, 2026 21:20
@felixweinberger felixweinberger force-pushed the fweinberger/auth-2-sep837-2207 branch from 5156ed7 to 409363a Compare June 23, 2026 21:20
Comment thread .changeset/auth-dcr-hygiene.md Outdated
Comment thread test/e2e/scenarios/client-auth.test.ts
@felixweinberger felixweinberger force-pushed the fweinberger/auth-2-sep837-2207 branch from 409363a to 32d3af6 Compare June 23, 2026 21:50
OAuthServerInfo
} from './client/auth.js';
export {
assertSecureTokenEndpoint,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New public export assertSecureTokenEndpoint (packages/client/src/index.ts) is the only new symbol in this PR not mentioned in the changeset or migration docs, and no in-PR consumer imports it from the package barrel (internal callers use ./auth.js). Either drop the barrel export to keep the helper internal, or add it to .changeset/auth-dcr-hygiene.md / the migration docs alongside the other new auth surface so the release notes match the shipped public API.

Extended reasoning...

What's inconsistent. packages/client/src/index.ts:21 adds assertSecureTokenEndpoint to the public barrel of @modelcontextprotocol/client. Every other new public symbol introduced by this PR — resolveClientMetadata, RegistrationRejectedError, InsecureTokenEndpointError, OAuthClientMetadata.application_type, OAuthErrorCode.InvalidRedirectUri — is explicitly named in .changeset/auth-dcr-hygiene.md, and the guard behavior is described in both docs/migration.md and docs/migration-SKILL.md. The exported helper itself, however, appears nowhere in the changeset or either migration doc.

No barrel consumer exists in the PR. A repo-wide search shows the only references are: the definition and internal use in packages/client/src/client/auth.ts (executeTokenRequest), the internal uses in packages/client/src/client/crossAppAccess.ts (requestJwtAuthorizationGrant / exchangeJwtAuthGrant) — both of which import it from ./auth.js, not the barrel — and the unit test, which imports from ../../src/client/auth.js directly. Nothing in the PR exercises or requires the index.ts export.

Why this matters under repo conventions. CLAUDE.md's Public API Exports section says adding a symbol to a package index.ts makes it public API and must be done intentionally, and the review checklist treats every new export as something that must be deliberate and documented. Once shipped in a minor release the export becomes surface that semver discourages removing, so the decision should be explicit now rather than discovered later.

Step-by-step view of the gap. (1) A consumer reads the v-next release notes generated from .changeset/auth-dcr-hygiene.md: they learn about InsecureTokenEndpointError and the guarded functions, but never that a reusable assertSecureTokenEndpoint(tokenEndpoint) helper is now exported. (2) Conversely, a consumer who discovers the export via autocomplete finds no changeset entry, no migration-doc mention, and no example showing intended use — so the symbol is shipped-but-unadvertised public API. (3) Meanwhile every internal caller resolves the helper through ./auth.js, so removing the barrel line would change nothing inside the SDK or its tests.

Impact. None at runtime — this is purely API-surface / release-notes hygiene. Exporting the guard is a defensible choice (consumers hand-rolling token requests could reuse it to get the same SEP-2207 behavior), which is why this is a nit rather than a bug.

How to fix. Pick one: (a) remove assertSecureTokenEndpoint from packages/client/src/index.ts and keep it internal — internal callers and tests are unaffected since they import from ./auth.js; or (b) keep the export and add one clause to .changeset/auth-dcr-hygiene.md (and optionally the migration docs' SEP-2207 section) noting the exported helper, so the release notes enumerate the full new auth surface.

Comment thread packages/client/src/client/auth.ts
@felixweinberger felixweinberger force-pushed the fweinberger/auth-1-sep2468-client branch from 5dfc2be to e2cecde Compare June 23, 2026 22:57
@felixweinberger felixweinberger force-pushed the fweinberger/auth-2-sep837-2207 branch from 32d3af6 to b629c06 Compare June 23, 2026 22:57
@felixweinberger felixweinberger force-pushed the fweinberger/auth-2-sep837-2207 branch from b629c06 to b445a38 Compare June 23, 2026 23:05
@felixweinberger felixweinberger force-pushed the fweinberger/auth-1-sep2468-client branch from e2cecde to 7c38bad Compare June 23, 2026 23:05
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.

2 participants