modelcontextprotocol / modelcontextprotocol/typescript-sdk
Support RFC 8252 dynamic loopback port selection for native OAuth clients
Nobody has claimed this yet.
- 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.1or[::1](notlocalhost) - 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:
- Start a local HTTP server with
server.listen(0, '127.0.0.1') - Wait for the
listeningcallback to get the assigned port - 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-OAuthClientProviderinterface andstartAuthorization()src/examples/client/simpleOAuthClient.ts- Example that currently uses hardcoded port
References
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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