modelcontextprotocol / modelcontextprotocol/typescript-sdk

Support RFC 8252 dynamic loopback port selection for native OAuth clients

Open
#1,316 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auth enhancement needs decision P3
Dominant language
TypeScript
Stars
13.4k
Forks
2.2k
Avg merge
3d 15h
Merged PRs (30d)
4

Description

Summary

The current OAuthClientProvider interface requires redirectUrl to be a synchronous getter, which makes it difficult for native apps to implement RFC 8252-compliant dynamic port selection for loopback redirect URIs.

Background

RFC 8252 Section 7.3 specifies best practices for OAuth 2.0 in native applications:

Native apps that are able to open a port on the loopback network interface [...] can use the loopback interface to receive the OAuth redirect.

Loopback redirect URIs use the "http" scheme and are constructed with the loopback IP literal and whatever port the client is listening on.

The authorization server MUST allow any port to be specified at the time of the request for loopback IP redirect URIs, to accommodate clients that obtain an available ephemeral port from the operating system at the time of the request.

Key requirements:

  • Use 127.0.0.1 or [::1] (not localhost)
  • Let OS assign an available ephemeral port (server.listen(0))
  • Authorization servers must accept any port for loopback URIs

Current Limitation

The OAuthClientProvider interface defines redirectUrl as a synchronous getter:

// src/client/auth.ts
export interface OAuthClientProvider {
  get redirectUrl(): string | URL | undefined;
  // ...
}

This is accessed in startAuthorization() before the authorization flow begins:

// src/client/auth.ts:514-518
const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
  // ...
  redirectUrl: provider.redirectUrl,  // <-- sync getter called here
  // ...
});

The problem: To get a dynamic port, native apps need to:

  1. Start a local HTTP server with server.listen(0, '127.0.0.1')
  2. Wait for the listening callback to get the assigned port
  3. Return the redirect URL with the actual port

Step 2 is async, but redirectUrl is a sync getter.

Current Workaround

Native app developers must start the callback server before creating the OAuthClientProvider, even if OAuth might not be needed (e.g., cached tokens are valid). This is wasteful and goes against the "start server only when needed" pattern shown in simpleOAuthClient.ts.

Proposed Solution

Consider making redirectUrl support async resolution. Options:

Option A: Allow async getter

get redirectUrl(): string | URL | undefined | Promise<string | URL | undefined>;

Option B: Callback-based lazy initialization

interface OAuthClientProvider {
  // Called when redirect URL is needed, provider can start server here
  prepareForAuthorization?(): Promise<void>;
  get redirectUrl(): string | URL | undefined;
}

Option B might be cleanest as it gives providers a hook to do async setup before redirectUrl is accessed.

Impact

This would enable native MCP clients to:

  • Avoid port conflicts when running multiple instances
  • Follow RFC 8252 security best practices
  • Start callback servers lazily (only when authorization is actually needed)

Related Files

  • src/client/auth.ts - OAuthClientProvider interface and startAuthorization()
  • src/examples/client/simpleOAuthClient.ts - Example that currently uses hardcoded port

References

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 with the OAuthClientProvider interface and startAuthorization() in src/client/auth.ts, then review src/examples/client/simpleOAuthClient.ts. Compare the proposed async getter and preparation-hook approaches against the RFC 8252 requirements and existing authorization flow. Done means native clients can lazily obtain an OS-assigned loopback port without starting the callback server before authorization is needed.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.