From b209d98074bc680d0bda58ee61075dcbaf4d9026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Tue, 13 May 2025 15:25:49 +0200 Subject: [PATCH 1/2] Add port sourcing from existing client information --- src/lib/utils.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 572550c..1f60c5e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -3,6 +3,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' +import { OAuthClientInformationFull, OAuthClientInformationFullSchema } from '@modelcontextprotocol/sdk/shared/auth.js' // Connection constants export const REASON_AUTH_NEEDED = 'authentication-needed' @@ -11,6 +12,7 @@ export const REASON_TRANSPORT_FALLBACK = 'falling-back-to-alternate-transport' // Transport strategy types export type TransportStrategy = 'sse-only' | 'http-only' | 'sse-first' | 'http-first' import { OAuthCallbackServerOptions } from './types' +import { readJsonFile } from './mcp-auth-config' import express from 'express' import net from 'net' import crypto from 'crypto' @@ -352,6 +354,21 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { return { server, authCode, waitForAuthCode } } +async function findExistingClientPort(serverUrl: string): Promise { + const serverUrlHash = getServerUrlHash(serverUrl) + const clientInfo = await readJsonFile(serverUrlHash, 'client_info.json', OAuthClientInformationFullSchema) + if (!clientInfo) { + return undefined + } + + const localhostRedirectUri = clientInfo.redirect_uris.map((uri) => new URL(uri)).find(({ hostname }) => hostname === 'localhost') + if (!localhostRedirectUri) { + throw new Error('Cannot find localhost callback URI from existing client information') + } + + return parseInt(localhostRedirectUri.port) +} + /** * Finds an available port on the local machine * @param preferredPort Optional preferred port to try first @@ -440,11 +457,14 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, process.exit(1) } - // Use the specified port, or find an available one - const callbackPort = specifiedPort || (await findAvailablePort(defaultPort)) + // Use the specified port, or the existing client port or fallback to find an available one + const [existingClientPort, availablePort] = await Promise.all([findExistingClientPort(serverUrl), findAvailablePort(defaultPort)]) + const callbackPort = specifiedPort || existingClientPort || availablePort if (specifiedPort) { log(`Using specified callback port: ${callbackPort}`) + } else if (existingClientPort) { + log(`Using existing client port: ${existingClientPort}`) } else { log(`Using automatically selected callback port: ${callbackPort}`) } From bd6df4222f5bdeaccadad2691c9404b90233ca34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Tue, 13 May 2025 15:26:18 +0200 Subject: [PATCH 2/2] Fix schema on clientInformation() --- src/lib/node-oauth-client-provider.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index 806f3af..0826844 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -1,9 +1,8 @@ import open from 'open' import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js' import { - OAuthClientInformation, OAuthClientInformationFull, - OAuthClientInformationSchema, + OAuthClientInformationFullSchema, OAuthTokens, OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' @@ -57,9 +56,9 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * Gets the client information if it exists * @returns The client information or undefined */ - async clientInformation(): Promise { + async clientInformation(): Promise { // log('Reading client info') - return readJsonFile(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema) + return readJsonFile(this.serverUrlHash, 'client_info.json', OAuthClientInformationFullSchema) } /**