diff --git a/package.json b/package.json index 4c7a40d..b7f3f7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.1.4", + "version": "0.1.5", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", @@ -32,7 +32,7 @@ "open": "^10.1.0" }, "devDependencies": { - "@modelcontextprotocol/sdk": "^1.10.2", + "@modelcontextprotocol/sdk": "^1.11.2", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "prettier": "^3.5.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a987cf4..d0720bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,8 +16,8 @@ importers: version: 10.1.0 devDependencies: '@modelcontextprotocol/sdk': - specifier: ^1.10.2 - version: 1.10.2 + specifier: ^1.11.2 + version: 1.11.2 '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -211,8 +211,8 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@modelcontextprotocol/sdk@1.10.2': - resolution: {integrity: sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==} + '@modelcontextprotocol/sdk@1.11.2': + resolution: {integrity: sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==} engines: {node: '>=18'} '@pkgjs/parseargs@0.11.0': @@ -1206,7 +1206,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@modelcontextprotocol/sdk@1.10.2': + '@modelcontextprotocol/sdk@1.11.2': dependencies: content-type: 1.0.5 cors: 2.8.5 diff --git a/src/client.ts b/src/client.ts index 4e0c14c..d87599c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -151,7 +151,7 @@ async function runClient( } // Parse command-line arguments and run the client -parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [callback-port]') +parseCommandLineArgs(process.argv.slice(2), 'Usage: npx tsx client.ts [callback-port]') .then(({ serverUrl, callbackPort, headers, transportStrategy }) => { return runClient(serverUrl, callbackPort, headers, transportStrategy) }) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c580769..7a36bda 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -7,6 +7,12 @@ import fs from 'fs' import path from 'path' import os from 'os' import { OAuthClientInformationFull, OAuthClientInformationFullSchema } from '@modelcontextprotocol/sdk/shared/auth.js' +import { OAuthCallbackServerOptions } from './types' +import { getConfigFilePath, readJsonFile } from './mcp-auth-config' +import express from 'express' +import net from 'net' +import crypto from 'crypto' +import fs from 'fs/promises' // Connection constants export const REASON_AUTH_NEEDED = 'authentication-needed' @@ -15,11 +21,6 @@ export const SHORT_TIMEOUT_DURATION = 50000 // 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' // Package version from package.json export const MCP_REMOTE_VERSION = require('../../package.json').version @@ -401,8 +402,7 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { return { server, authCode, waitForAuthCode } } -async function findExistingClientPort(serverUrl: string): Promise { - const serverUrlHash = getServerUrlHash(serverUrl) +async function findExistingClientPort(serverUrlHash: string): Promise { const clientInfo = await readJsonFile(serverUrlHash, 'client_info.json', OAuthClientInformationFullSchema) if (!clientInfo) { return undefined @@ -416,6 +416,13 @@ async function findExistingClientPort(serverUrl: string): Promise /** * Parses command line arguments for MCP clients and proxies * @param args Command line arguments - * @param defaultPort Default port for the callback server if specified port is unavailable * @param usage Usage message to show on error * @returns A promise that resolves to an object with parsed serverUrl, callbackPort and headers */ -export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { +export async function parseCommandLineArgs(args: string[], usage: string) { // Process headers const headers: Record = {} let i = 0 @@ -504,17 +510,28 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, log(usage) process.exit(1) } + const serverUrlHash = getServerUrlHash(serverUrl) + const defaultPort = calculateDefaultPort(serverUrlHash) // 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 + const [existingClientPort, availablePort] = await Promise.all([findExistingClientPort(serverUrlHash), findAvailablePort(defaultPort)]) + let callbackPort: number if (specifiedPort) { - log(`Using specified callback port: ${callbackPort}`) + if (existingClientPort && specifiedPort !== existingClientPort) { + log( + `Warning! Specified callback port of ${specifiedPort}, which conflicts with existing client registration port ${existingClientPort}. Deleting existing client data to force reregistration.`, + ) + await fs.rm(getConfigFilePath(serverUrlHash, 'client_info.json')) + } + log(`Using specified callback port: ${specifiedPort}`) + callbackPort = specifiedPort } else if (existingClientPort) { log(`Using existing client port: ${existingClientPort}`) + callbackPort = existingClientPort } else { - log(`Using automatically selected callback port: ${callbackPort}`) + log(`Using automatically selected callback port: ${availablePort}`) + callbackPort = availablePort } if (Object.keys(headers).length > 0) {