Skip to content

Conversation

@wdawson
Copy link

@wdawson wdawson commented Feb 11, 2026

Implement OIDC-flavored refresh token guidance (modelcontextprotocol/modelcontextprotocol#2207) in the Python SDK client.

Motivation and Context

MCP clients interacting with OIDC-flavored Authorization Servers often don't receive refresh tokens because they aren't requesting offline_access. This leads to poor UX (frequent re-authentication). SEP-2207 provides guidance for how MCP clients should handle this.

The SDK currently expects the caller to supply the supported grant_types for the client. The example already shows providing refresh_token, and callers are encouraged to do so as well.

This PR:

  • Extracts scope selection into an exported, testable determineScope() helper that follows the MCP Scope Selection Strategy
  • Automatically appends offline_access to the authorization request scope when the AS advertises it in scopes_supported and the client's grant_types includes refresh_token

No changes are needed on the server side — the SDK's example AS already includes offline_access in its scopes_supported, and the example middleware already omits it from WWW-Authenticate (both compliant with the SEP).

How Has This Been Tested?

  • 14 new unit tests for determineScope() covering:
    • MCP spec scope selection priority (explicit scope, PRM scopes_supported, clientMetadata.scope fallback, omit)
    • SEP-2207 augmentation (adds offline_access when conditions met)
    • Guard conditions (no augmentation when AS doesn't support it, when already present in clientMetadata.scope or non-compliant PRM, when grant_types omits refresh_token, when grant_types is undefined, when no scopes are present)

Breaking Changes

None. The offline_access augmentation only activates when:

  1. The AS metadata includes offline_access in scopes_supported
  2. The client's grant_types includes refresh_token

Clients that don't set grant_types or don't include refresh_token are unaffected. Authorization Servers that don't recognize offline_access will simply ignore it per OAuth 2.1. The newly exported determineScope() function is additive.

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

  • determineScope() now encodes the MCP Scope Selection Strategy as a testable, exported function. It accepts requestedScope, resourceMetadata, authServerMetadata, and clientMetadata, and returns the effective scope string (or undefined to omit the parameter).
  • The existing startAuthorization() already handles adding prompt=consent when offline_access is in scope, per the OIDC Core spec. No changes were needed there.
  • grant_types defaulting was intentionally not added at the SDK level. The OAuthClientMetadata type is shared between client creation (DCR) and server-side parsing (CIMD), so schema-level defaults would violate OAuth spec defaults for CIMD. The example clients already include refresh_token in grant_types.
  • The 403 insufficient_scope handler in streamableHttp.ts already passes scope through to auth(), which now delegates to determineScope() — no additional changes needed for step-up authorization flows.

@changeset-bot
Copy link

changeset-bot bot commented Feb 11, 2026

⚠️ No Changeset found

Latest commit: 0ea0a0a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 11, 2026

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 0ea0a0a

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