Merge branch 'main' into main

This commit is contained in:
SteveHuy 2025-05-14 20:55:40 +10:00 committed by GitHub
commit baac4b5b79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 26 additions and 6 deletions

View file

@ -1,9 +1,8 @@
import open from 'open' import open from 'open'
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js' import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'
import { import {
OAuthClientInformation,
OAuthClientInformationFull, OAuthClientInformationFull,
OAuthClientInformationSchema, OAuthClientInformationFullSchema,
OAuthTokens, OAuthTokens,
OAuthTokensSchema, OAuthTokensSchema,
} from '@modelcontextprotocol/sdk/shared/auth.js' } from '@modelcontextprotocol/sdk/shared/auth.js'
@ -57,9 +56,9 @@ export class NodeOAuthClientProvider implements OAuthClientProvider {
* Gets the client information if it exists * Gets the client information if it exists
* @returns The client information or undefined * @returns The client information or undefined
*/ */
async clientInformation(): Promise<OAuthClientInformation | undefined> { async clientInformation(): Promise<OAuthClientInformationFull | undefined> {
// log('Reading client info') // log('Reading client info')
return readJsonFile<OAuthClientInformation>(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema) return readJsonFile<OAuthClientInformationFull>(this.serverUrlHash, 'client_info.json', OAuthClientInformationFullSchema)
} }
/** /**

View file

@ -6,6 +6,8 @@ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import os from 'os' import os from 'os'
import { OAuthClientInformationFull, OAuthClientInformationFullSchema } from '@modelcontextprotocol/sdk/shared/auth.js'
// Connection constants // Connection constants
export const REASON_AUTH_NEEDED = 'authentication-needed' export const REASON_AUTH_NEEDED = 'authentication-needed'
@ -15,6 +17,7 @@ export const SHORT_TIMEOUT_DURATION = 50000
// Transport strategy types // Transport strategy types
export type TransportStrategy = 'sse-only' | 'http-only' | 'sse-first' | 'http-first' export type TransportStrategy = 'sse-only' | 'http-only' | 'sse-first' | 'http-first'
import { OAuthCallbackServerOptions } from './types' import { OAuthCallbackServerOptions } from './types'
import { readJsonFile } from './mcp-auth-config'
import express from 'express' import express from 'express'
import net from 'net' import net from 'net'
import crypto from 'crypto' import crypto from 'crypto'
@ -399,6 +402,21 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) {
return { server, authCode, waitForAuthCode } return { server, authCode, waitForAuthCode }
} }
async function findExistingClientPort(serverUrl: string): Promise<number | undefined> {
const serverUrlHash = getServerUrlHash(serverUrl)
const clientInfo = await readJsonFile<OAuthClientInformationFull>(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 * Finds an available port on the local machine
* @param preferredPort Optional preferred port to try first * @param preferredPort Optional preferred port to try first
@ -488,11 +506,14 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number,
process.exit(1) process.exit(1)
} }
// Use the specified port, or find an available one // Use the specified port, or the existing client port or fallback to find an available one
const callbackPort = specifiedPort || (await findAvailablePort(defaultPort)) const [existingClientPort, availablePort] = await Promise.all([findExistingClientPort(serverUrl), findAvailablePort(defaultPort)])
const callbackPort = specifiedPort || existingClientPort || availablePort
if (specifiedPort) { if (specifiedPort) {
log(`Using specified callback port: ${callbackPort}`) log(`Using specified callback port: ${callbackPort}`)
} else if (existingClientPort) {
log(`Using existing client port: ${existingClientPort}`)
} else { } else {
log(`Using automatically selected callback port: ${callbackPort}`) log(`Using automatically selected callback port: ${callbackPort}`)
} }