@@ -4,19 +4,19 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
4
4
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
5
5
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
6
6
import { OAuthClientInformationFull , OAuthClientInformationFullSchema } from '@modelcontextprotocol/sdk/shared/auth.js'
7
+ import { OAuthCallbackServerOptions } from './types'
8
+ import { getConfigFilePath , readJsonFile } from './mcp-auth-config'
9
+ import express from 'express'
10
+ import net from 'net'
11
+ import crypto from 'crypto'
12
+ import fs from 'fs/promises'
7
13
8
14
// Connection constants
9
15
export const REASON_AUTH_NEEDED = 'authentication-needed'
10
16
export const REASON_TRANSPORT_FALLBACK = 'falling-back-to-alternate-transport'
11
17
12
18
// Transport strategy types
13
19
export type TransportStrategy = 'sse-only' | 'http-only' | 'sse-first' | 'http-first'
14
- import { OAuthCallbackServerOptions } from './types'
15
- import { getConfigFilePath , readJsonFile } from './mcp-auth-config'
16
- import express from 'express'
17
- import net from 'net'
18
- import crypto from 'crypto'
19
- import fs from 'fs/promises'
20
20
21
21
// Package version from package.json
22
22
export const MCP_REMOTE_VERSION = require ( '../../package.json' ) . version
@@ -355,8 +355,7 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) {
355
355
return { server, authCode, waitForAuthCode }
356
356
}
357
357
358
- async function findExistingClientPort ( serverUrl : string ) : Promise < number | undefined > {
359
- const serverUrlHash = getServerUrlHash ( serverUrl )
358
+ async function findExistingClientPort ( serverUrlHash : string ) : Promise < number | undefined > {
360
359
const clientInfo = await readJsonFile < OAuthClientInformationFull > ( serverUrlHash , 'client_info.json' , OAuthClientInformationFullSchema )
361
360
if ( ! clientInfo ) {
362
361
return undefined
@@ -370,6 +369,13 @@ async function findExistingClientPort(serverUrl: string): Promise<number | undef
370
369
return parseInt ( localhostRedirectUri . port )
371
370
}
372
371
372
+ function calculateDefaultPort ( serverUrlHash : string ) : number {
373
+ // Convert the first 4 bytes of the serverUrlHash into a port offset
374
+ const offset = parseInt ( serverUrlHash . substring ( 0 , 4 ) , 16 )
375
+ // Pick a consistent but random-seeming port from 3335 to 49151
376
+ return 3335 + ( offset % 45816 )
377
+ }
378
+
373
379
/**
374
380
* Finds an available port on the local machine
375
381
* @param preferredPort Optional preferred port to try first
@@ -403,11 +409,10 @@ export async function findAvailablePort(preferredPort?: number): Promise<number>
403
409
/**
404
410
* Parses command line arguments for MCP clients and proxies
405
411
* @param args Command line arguments
406
- * @param defaultPort Default port for the callback server if specified port is unavailable
407
412
* @param usage Usage message to show on error
408
413
* @returns A promise that resolves to an object with parsed serverUrl, callbackPort and headers
409
414
*/
410
- export async function parseCommandLineArgs ( args : string [ ] , defaultPort : number , usage : string ) {
415
+ export async function parseCommandLineArgs ( args : string [ ] , usage : string ) {
411
416
// Process headers
412
417
const headers : Record < string , string > = { }
413
418
let i = 0
@@ -457,17 +462,19 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number,
457
462
log ( usage )
458
463
process . exit ( 1 )
459
464
}
465
+ const serverUrlHash = getServerUrlHash ( serverUrl )
466
+ const defaultPort = calculateDefaultPort ( serverUrlHash )
460
467
461
468
// Use the specified port, or the existing client port or fallback to find an available one
462
- const [ existingClientPort , availablePort ] = await Promise . all ( [ findExistingClientPort ( serverUrl ) , findAvailablePort ( defaultPort ) ] )
469
+ const [ existingClientPort , availablePort ] = await Promise . all ( [ findExistingClientPort ( serverUrlHash ) , findAvailablePort ( defaultPort ) ] )
463
470
let callbackPort : number
464
471
465
472
if ( specifiedPort ) {
466
473
if ( existingClientPort && specifiedPort !== existingClientPort ) {
467
474
log (
468
475
`Warning! Specified callback port of ${ specifiedPort } , which conflicts with existing client registration port ${ existingClientPort } . Deleting existing client data to force reregistration.` ,
469
476
)
470
- await fs . rm ( getConfigFilePath ( getServerUrlHash ( serverUrl ) , 'client_info.json' ) )
477
+ await fs . rm ( getConfigFilePath ( serverUrlHash , 'client_info.json' ) )
471
478
}
472
479
log ( `Using specified callback port: ${ specifiedPort } ` )
473
480
callbackPort = specifiedPort
0 commit comments