Skip to content

Conversation

@grimmerk
Copy link
Contributor

The 30-day default for clientSecretExpirySeconds caused production issues where MCP clients fail with "Client secret has expired" after 30 days.

Motivation and Context

MCP servers using the SDK's default settings issue client secrets that expire in 30 days. Some MCP clients (e.g., ChatGPT certified connectors) don't automatically re-register when secrets expire, causing users to see cryptic "invalid_client: Client secret has expired" errors.

This change aligns TypeScript SDK with Python SDK behavior, which defaults to None (no expiration). Per RFC 7591, client_secret_expires_at is optional - servers that want expiring secrets can still explicitly set clientSecretExpirySeconds.

How Has This Been Tested?

  • All existing tests pass
  • Added new test case for default undefined behavior
  • Verified in production environment (Fireflies.ai MCP server) where this issue was discovered

Breaking Changes

None. This is a relaxation of default behavior:

  • Existing code explicitly setting clientSecretExpirySeconds works unchanged
  • New default (no expiry) is more permissive than old default (30 days)

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

Behavior after this change:

clientSecretExpirySeconds client_secret_expires_at
undefined (new default) omitted (no expiry)
0 0 (no expiry per RFC 7591)
> 0 now + seconds

Related: Python SDK already defaults to None in src/mcp/server/auth/settings.py

…ion)

The 30-day default caused MCP clients to fail with "Client secret has expired"
after 30 days. Aligns with Python SDK behavior (defaults to None).

- undefined: omit client_secret_expires_at (no expiry)
- 0: set to 0 (no expiry per RFC 7591)
- positive: set to now + seconds
@grimmerk grimmerk requested a review from a team as a code owner December 17, 2025 15:01
@grimmerk grimmerk requested a review from a team as a code owner December 17, 2025 15:20
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 17, 2025

Open in StackBlitz

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

commit: dfa31e2

@felixweinberger felixweinberger added the auth Issues and PRs related to Authentication / OAuth label Dec 18, 2025
Copy link
Contributor

@felixweinberger felixweinberger left a comment

Choose a reason for hiding this comment

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

It's true that this aligns behavior with Python which defaults to None:

See: https://github.com/modelcontextprotocol/python-sdk/blob/8ac0cab98c3243e3369e283ddf4cee5c2d63629b/src/mcp/server/auth/settings.py#L6, and https://github.com/modelcontextprotocol/python-sdk/blob/8ac0cab98c3243e3369e283ddf4cee5c2d63629b/src/mcp/server/auth/handlers/register.py#L97-L101

However - the actual RFC suggests that client_secret_expires_at is REQUIRED if client_secret is issued, with 0 meaning "no expiry":

Source: https://www.rfc-editor.org/rfc/rfc7591.html#section-3.2.1

That suggests to me we should actually be tightening up the Python SDK to have a default.

Debatable whether the default should be 30 days though - maybe that's the right thing to align on here - will defer to @pcarleton who has more context on auth here.

Comment on lines +91 to +102
// - undefined: omit client_secret_expires_at (no expiration)
// - 0: set to 0 (no expiration per RFC 7591)
// - positive number: set to now + seconds
let clientSecretExpiresAt: number | undefined;
if (!isPublicClient) {
if (clientSecretExpirySeconds !== undefined && clientSecretExpirySeconds > 0) {
clientSecretExpiresAt = clientIdIssuedAt + clientSecretExpirySeconds;
} else if (clientSecretExpirySeconds === 0) {
clientSecretExpiresAt = 0;
}
// else: undefined - omit from response (no expiration)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: these comments don't add a ton of value imo, would remove

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Issues and PRs related to Authentication / OAuth

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants