From 56d9117ce0e49a82b85f9b02e2cdebaa026892c1 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 09:49:44 +0000 Subject: [PATCH 001/104] fix types and pass build --- package.json | 16 ++++ src/react/index.ts | 222 +++++++++++++++++++++++---------------------- tsconfig.json | 12 +-- tsup.config.ts | 10 -- 4 files changed, 136 insertions(+), 124 deletions(-) delete mode 100644 tsup.config.ts diff --git a/package.json b/package.json index d09e160..2093274 100644 --- a/package.json +++ b/package.json @@ -32,5 +32,21 @@ "tsup": "^8.4.0", "tsx": "^4.19.3", "typescript": "^5.8.2" + }, + "tsup": { + "entry": [ + "src/cli/client.ts", + "src/cli/proxy.ts", + "src/react/index.ts" + ], + "format": [ + "esm" + ], + "dts": true, + "clean": true, + "outDir": "dist", + "external": [ + "react" + ] } } diff --git a/src/react/index.ts b/src/react/index.ts index 1dc3d25..9538c8f 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -5,6 +5,12 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { discoverOAuthMetadata, exchangeAuthorization, startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js' import { OAuthClientInformation, OAuthMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js' +function assert(condition: unknown, message: string): asserts condition { + if (!condition) { + throw new Error(message) + } +} + export type UseMcpOptions = { /** The /sse URL of your remote MCP server */ url: string @@ -259,7 +265,6 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { const codeVerifierRef = useRef(undefined) const connectingRef = useRef(false) const isInitialMount = useRef(true) - let handleAuthentication: () => Promise // Set up default options const { @@ -338,6 +343,114 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { setError(undefined) }, [addLog]) + // Start the auth flow and get the auth URL + const startAuthFlow = useCallback(async (): Promise => { + if (!authProviderRef.current || !metadataRef.current) { + throw new Error('Auth provider or metadata not available') + } + + addLog('info', 'Starting authentication flow...') + + // Check if we have client info + let clientInfo = await authProviderRef.current.clientInformation() + + if (!clientInfo) { + // Register client dynamically + addLog('info', 'No client information found, registering...') + // Note: In a complete implementation, you'd register the client here + // This would be done server-side in a real application + throw new Error('Dynamic client registration not implemented in this example') + } + + // Start authorization flow + addLog('info', 'Preparing authorization...') + const { authorizationUrl, codeVerifier } = await startAuthorization(url, { + metadata: metadataRef.current, + clientInformation: clientInfo, + redirectUrl: authProviderRef.current.redirectUrl, + }) + + // Save code verifier and auth URL for later use + await authProviderRef.current.saveCodeVerifier(codeVerifier) + codeVerifierRef.current = codeVerifier + authUrlRef.current = authorizationUrl + setAuthUrl(authorizationUrl.toString()) + + return authorizationUrl + }, [url, addLog]) + + // Handle authentication flow + const handleAuthentication = useCallback(async () => { + if (!authProviderRef.current) { + throw new Error('Auth provider not available') + } + + // Get or create the auth URL + if (!authUrlRef.current) { + try { + await startAuthFlow() + } catch (err) { + addLog('error', `Failed to start auth flow: ${err instanceof Error ? err.message : String(err)}`) + throw err + } + } + + if (!authUrlRef.current) { + throw new Error('Failed to create authorization URL') + } + + // Set up listener for post-auth message + const authPromise = new Promise((resolve, reject) => { + const timeoutId = setTimeout( + () => { + window.removeEventListener('message', messageHandler) + reject(new Error('Authentication timeout after 5 minutes')) + }, + 5 * 60 * 1000, + ) + + const messageHandler = (event: MessageEvent) => { + // Verify origin for security + if (event.origin !== window.location.origin) return + + if (event.data && event.data.type === 'mcp_auth_callback' && event.data.code) { + window.removeEventListener('message', messageHandler) + clearTimeout(timeoutId) + + // TODO: not this, obviously + // reload window, we should find the token in local storage + window.location.reload() + // resolve(event.data.code) + } + } + + window.addEventListener('message', messageHandler) + }) + + // Redirect to authorization + addLog('info', 'Opening authorization window...') + assert(metadataRef.current, 'Metadata not available') + const redirectResult = await authProviderRef.current.redirectToAuthorization(authUrlRef.current, metadataRef.current, { + popupFeatures, + }) + + if (!redirectResult.success) { + // Popup was blocked + setState('failed') + setError('Authentication popup was blocked by the browser. Please click the link to authenticate in a new window.') + setAuthUrl(redirectResult.url) + addLog('warn', 'Authentication popup was blocked. User needs to manually authorize.') + throw new Error('Authentication popup blocked') + } + + // Wait for auth to complete + addLog('info', 'Waiting for authorization...') + const code = await authPromise + addLog('info', 'Authorization code received') + + return code + }, [url, addLog, popupFeatures, startAuthFlow]) + // Initialize connection to MCP server const connect = useCallback(async () => { // Prevent multiple simultaneous connection attempts @@ -387,11 +500,13 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { const serverUrl = new URL(url) transportRef.current = new SSEClientTransport(serverUrl, { + // @ts-expect-error TODO: fix this type, expect BrowserOAuthClientProvider authProvider: authProviderRef.current, }) // Set up transport handlers transportRef.current.onmessage = (message: JSONRPCMessage) => { + // @ts-expect-error TODO: fix this type addLog('debug', `Received message: ${message.method || message.id}`) } @@ -507,111 +622,6 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { return undefined }, []) - // Start the auth flow and get the auth URL - const startAuthFlow = useCallback(async (): Promise => { - if (!authProviderRef.current || !metadataRef.current) { - throw new Error('Auth provider or metadata not available') - } - - addLog('info', 'Starting authentication flow...') - - // Check if we have client info - let clientInfo = await authProviderRef.current.clientInformation() - - if (!clientInfo) { - // Register client dynamically - addLog('info', 'No client information found, registering...') - // Note: In a complete implementation, you'd register the client here - // This would be done server-side in a real application - throw new Error('Dynamic client registration not implemented in this example') - } - - // Start authorization flow - addLog('info', 'Preparing authorization...') - const { authorizationUrl, codeVerifier } = await startAuthorization(url, { - metadata: metadataRef.current, - clientInformation: clientInfo, - redirectUrl: authProviderRef.current.redirectUrl, - }) - - // Save code verifier and auth URL for later use - await authProviderRef.current.saveCodeVerifier(codeVerifier) - codeVerifierRef.current = codeVerifier - authUrlRef.current = authorizationUrl - setAuthUrl(authorizationUrl.toString()) - - return authorizationUrl - }, [url, addLog]) - - // Handle authentication flow - handleAuthentication = useCallback(async () => { - if (!authProviderRef.current) { - throw new Error('Auth provider not available') - } - - // Get or create the auth URL - if (!authUrlRef.current) { - try { - await startAuthFlow() - } catch (err) { - addLog('error', `Failed to start auth flow: ${err instanceof Error ? err.message : String(err)}`) - throw err - } - } - - if (!authUrlRef.current) { - throw new Error('Failed to create authorization URL') - } - - // Set up listener for post-auth message - const authPromise = new Promise((resolve, reject) => { - const timeoutId = setTimeout( - () => { - window.removeEventListener('message', messageHandler) - reject(new Error('Authentication timeout after 5 minutes')) - }, - 5 * 60 * 1000, - ) - - const messageHandler = (event: MessageEvent) => { - // Verify origin for security - if (event.origin !== window.location.origin) return - - if (event.data && event.data.type === 'mcp_auth_callback' && event.data.code) { - window.removeEventListener('message', messageHandler) - clearTimeout(timeoutId) - - // TODO: not this, obviously - // reload window, we should find the token in local storage - window.location.reload() - // resolve(event.data.code) - } - } - - window.addEventListener('message', messageHandler) - }) - - // Redirect to authorization - addLog('info', 'Opening authorization window...') - const redirectResult = await authProviderRef.current.redirectToAuthorization(authUrlRef.current, metadataRef.current, { popupFeatures }) - - if (!redirectResult.success) { - // Popup was blocked - setState('failed') - setError('Authentication popup was blocked by the browser. Please click the link to authenticate in a new window.') - setAuthUrl(redirectResult.url) - addLog('warn', 'Authentication popup was blocked. User needs to manually authorize.') - throw new Error('Authentication popup blocked') - } - - // Wait for auth to complete - addLog('info', 'Waiting for authorization...') - const code = await authPromise - addLog('info', 'Authorization code received') - - return code - }, [url, addLog, popupFeatures, startAuthFlow]) - // Handle auth completion - this is called when we receive a message from the popup const handleAuthCompletion = useCallback( async (code: string) => { diff --git a/tsconfig.json b/tsconfig.json index b562002..cd9cfa1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,14 @@ { "compilerOptions": { "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "outDir": "./build", - "rootDir": "./src", + "module": "ES2022", + "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, - + "noEmit": true, "lib": ["ES2022", "DOM"], "types": ["node", "react"], "forceConsistentCasingInFileNames": true, "resolveJsonModule": true - }, - "include": ["*.ts","src/**/*"], - "exclude": ["node_modules", "packages", "**/*.spec.ts"] + } } diff --git a/tsup.config.ts b/tsup.config.ts deleted file mode 100644 index a203cbb..0000000 --- a/tsup.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from 'tsup' - -export default defineConfig({ - entry: ['src/cli/client.ts', 'src/cli/proxy.ts', 'src/react/index.ts'], - format: ['esm'], - dts: true, - clean: true, - outDir: 'dist', - external: ['react'], -}) From 18130306fdaa18b0a894bf77038be1da346189c3 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 10:00:11 +0000 Subject: [PATCH 002/104] fix bin field --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d09e160..415510e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "mcp-remote", "version": "0.0.5-0", "type": "module", - "bin": "dist/cli/proxy.js", + "bin": { + "mcp-remote": "dist/cli/proxy.js" + }, "files": [ "dist", "README.md", From 99046031684997070501463293f2e357962ba65f Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 10:01:31 +0000 Subject: [PATCH 003/104] 0.0.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 735ad80..76d1b41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.5-0", + "version": "0.0.5", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 65e55c38237d633dbbeda80fea956088bfc4dfa2 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 24 Mar 2025 21:31:50 +1100 Subject: [PATCH 004/104] Thank you Claude --- src/react/index.ts | 73 +++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/react/index.ts b/src/react/index.ts index 9538c8f..1a7fef7 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -471,13 +471,6 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { }) } - // Discover OAuth metadata if not already discovered - if (!metadataRef.current) { - addLog('info', 'Discovering OAuth metadata...') - metadataRef.current = await discoverOAuthMetadata(url) - addLog('debug', `OAuth metadata: ${metadataRef.current ? 'Found' : 'Not available'}`) - } - // Create MCP client clientRef.current = new Client( { @@ -491,10 +484,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { }, ) - // Set up auth flow - check if we have tokens - const tokens = await authProviderRef.current.tokens() - - // Create SSE transport + // Create SSE transport - try connecting without auth first setState('connecting') addLog('info', 'Creating transport...') @@ -514,13 +504,8 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { addLog('error', `Transport error: ${err.message}`) if (err.message.includes('Unauthorized')) { - setState('authenticating') - handleAuthentication().catch((authErr) => { - addLog('error', `Authentication error: ${authErr.message}`) - setState('failed') - setError(`Authentication failed: ${authErr.message}`) - connectingRef.current = false - }) + // Only discover OAuth metadata and authenticate if we get a 401 + discoverOAuthAndAuthenticate(err) } else { setState('failed') setError(`Connection error: ${err.message}`) @@ -540,7 +525,38 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { } } - // Connect transport + // Helper function to handle OAuth discovery and authentication + const discoverOAuthAndAuthenticate = async (error: Error) => { + try { + // Discover OAuth metadata now that we know we need it + if (!metadataRef.current) { + addLog('info', 'Discovering OAuth metadata...') + metadataRef.current = await discoverOAuthMetadata(url) + addLog('debug', `OAuth metadata: ${metadataRef.current ? 'Found' : 'Not available'}`) + } + + // If metadata is found, start auth flow + if (metadataRef.current) { + setState('authenticating') + // Start authentication process + await handleAuthentication() + // After successful auth, retry connection + return connect() + } else { + // No OAuth metadata available + setState('failed') + setError(`Authentication required but no OAuth metadata found: ${error.message}`) + connectingRef.current = false + } + } catch (oauthErr) { + addLog('error', `OAuth discovery error: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) + setState('failed') + setError(`Authentication setup failed: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) + connectingRef.current = false + } + } + + // Try connecting transport first without OAuth discovery try { addLog('info', 'Starting transport...') // await transportRef.current.start() @@ -548,11 +564,8 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { addLog('error', `Transport start error: ${err instanceof Error ? err.message : String(err)}`) if (err instanceof Error && err.message.includes('Unauthorized')) { - setState('authenticating') - // Start authentication process - await handleAuthentication() - // After successful auth, retry connection - return connect() + // Only discover OAuth and authenticate if we get a 401 + await discoverOAuthAndAuthenticate(err) } else { setState('failed') setError(`Connection error: ${err instanceof Error ? err.message : String(err)}`) @@ -586,9 +599,15 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { } } catch (connectErr) { addLog('error', `Client connect error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) - setState('failed') - setError(`Connection error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) - connectingRef.current = false + + if (connectErr instanceof Error && connectErr.message.includes('Unauthorized')) { + // Only discover OAuth and authenticate if we get a 401 + await discoverOAuthAndAuthenticate(connectErr) + } else { + setState('failed') + setError(`Connection error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) + connectingRef.current = false + } } } catch (err) { addLog('error', `Unexpected error: ${err instanceof Error ? err.message : String(err)}`) From d3322733553d74e475876d9b41a219f77174cb99 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 10:48:46 +0000 Subject: [PATCH 005/104] format --- .prettierignore | 1 + README.md | 6 +----- package.json | 3 ++- src/cli/proxy.ts | 9 ++------- src/cli/shared.ts | 4 ++-- src/lib/types.ts | 4 ++-- src/lib/utils.ts | 19 +++++++++---------- src/react/index.ts | 4 ++-- 8 files changed, 21 insertions(+), 29 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..eeeb7be --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +pnpm-lock.yaml \ No newline at end of file diff --git a/README.md b/README.md index 99b1e8a..7aebd26 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,7 @@ E.g: Claude Desktop or Windsurf "mcpServers": { "remote-example": { "command": "npx", - "args": [ - "mcp-remote", - "https://remote.mcp.server/sse" - ] + "args": ["mcp-remote", "https://remote.mcp.server/sse"] } } } @@ -23,4 +20,3 @@ E.g: Claude Desktop or Windsurf Cursor: ![image](https://github.com/user-attachments/assets/14338bfa-a779-4e8a-a477-71f72cc5d99d) - diff --git a/package.json b/package.json index 76d1b41..7b5b680 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ } }, "scripts": { - "build": "tsup" + "build": "tsup", + "check": "prettier --check . && tsc" }, "dependencies": { "express": "^4.21.2", diff --git a/src/cli/proxy.ts b/src/cli/proxy.ts index 763a9b1..60f4966 100644 --- a/src/cli/proxy.ts +++ b/src/cli/proxy.ts @@ -11,13 +11,8 @@ import { EventEmitter } from 'events' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { - NodeOAuthClientProvider, - setupOAuthCallbackServer, - parseCommandLineArgs, - setupSignalHandlers, -} from './shared.js' -import {connectToRemoteServer, mcpProxy} from "../lib/utils.js"; +import { NodeOAuthClientProvider, setupOAuthCallbackServer, parseCommandLineArgs, setupSignalHandlers } from './shared.js' +import { connectToRemoteServer, mcpProxy } from '../lib/utils.js' /** * Main function to run the proxy diff --git a/src/cli/shared.ts b/src/cli/shared.ts index 28bc1b1..a4101aa 100644 --- a/src/cli/shared.ts +++ b/src/cli/shared.ts @@ -10,7 +10,7 @@ import path from 'path' import os from 'os' import crypto from 'crypto' import net from 'net' -import {OAuthClientProvider} from '@modelcontextprotocol/sdk/client/auth.js' +import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js' import { OAuthClientInformation, OAuthClientInformationFull, @@ -18,7 +18,7 @@ import { OAuthTokens, OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' -import {OAuthCallbackServerOptions, OAuthProviderOptions} from "../lib/types.js"; +import { OAuthCallbackServerOptions, OAuthProviderOptions } from '../lib/types.js' /** * Implements the OAuthClientProvider interface for Node.js environments. diff --git a/src/lib/types.ts b/src/lib/types.ts index 2201eb5..188fccb 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,4 +1,4 @@ -import {EventEmitter} from "events"; +import { EventEmitter } from 'events' /** * Options for creating an OAuth client provider @@ -28,4 +28,4 @@ export interface OAuthCallbackServerOptions { path: string /** Event emitter to signal when auth code is received */ events: EventEmitter -} \ No newline at end of file +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2a51a47..0340315 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,24 +1,23 @@ -import { OAuthClientProvider, UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"; -import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; -import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' +import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' +import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' /** * Creates a bidirectional proxy between two transports * @param params The transport connections to proxy between */ -export function mcpProxy({transportToClient, transportToServer}: { - transportToClient: Transport; - transportToServer: Transport -}) { +export function mcpProxy({ transportToClient, transportToServer }: { transportToClient: Transport; transportToServer: Transport }) { let transportToClientClosed = false let transportToServerClosed = false transportToClient.onmessage = (message) => { + // @ts-expect-error TODO console.error('[Local→Remote]', message.method || message.id) transportToServer.send(message).catch(onServerError) } transportToServer.onmessage = (message) => { + // @ts-expect-error TODO: fix this type console.error('[Remote→Local]', message.method || message.id) transportToClient.send(message).catch(onClientError) } @@ -66,7 +65,7 @@ export async function connectToRemoteServer( ): Promise { console.error('Connecting to remote server:', serverUrl) const url = new URL(serverUrl) - const transport = new SSEClientTransport(url, {authProvider}) + const transport = new SSEClientTransport(url, { authProvider }) try { await transport.start() @@ -84,7 +83,7 @@ export async function connectToRemoteServer( await transport.finishAuth(code) // Create a new transport after auth - const newTransport = new SSEClientTransport(url, {authProvider}) + const newTransport = new SSEClientTransport(url, { authProvider }) await newTransport.start() console.error('Connected to remote server after authentication') return newTransport @@ -97,4 +96,4 @@ export async function connectToRemoteServer( throw error } } -} \ No newline at end of file +} diff --git a/src/react/index.ts b/src/react/index.ts index 1a7fef7..40948c2 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -534,7 +534,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { metadataRef.current = await discoverOAuthMetadata(url) addLog('debug', `OAuth metadata: ${metadataRef.current ? 'Found' : 'Not available'}`) } - + // If metadata is found, start auth flow if (metadataRef.current) { setState('authenticating') @@ -599,7 +599,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { } } catch (connectErr) { addLog('error', `Client connect error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) - + if (connectErr instanceof Error && connectErr.message.includes('Unauthorized')) { // Only discover OAuth and authenticate if we get a 401 await discoverOAuthAndAuthenticate(connectErr) From 3b98cfdc8ca18a57bc4adfd8b1fb43ef80a3781a Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 10:49:20 +0000 Subject: [PATCH 006/104] 0.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b5b680..044b649 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.5", + "version": "0.0.6", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 81f69497348cb77e1b6d729935decb400250a443 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 10:58:01 +0000 Subject: [PATCH 007/104] 0.0.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 044b649..b25e641 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.6", + "version": "0.0.7", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 29d71fb270f7abf61b0d9a8d0f4de92cb222ee95 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 24 Mar 2025 22:53:47 +1100 Subject: [PATCH 008/104] trying moving the SDK into a normal dep --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b25e641..823ddb5 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,11 @@ "check": "prettier --check . && tsc" }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.7.0", "express": "^4.21.2", "open": "^10.1.0" }, "devDependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "@types/react": "^19.0.12", From 9c2e670abc32045ba47610c1cb55d4f22310e0a4 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 24 Mar 2025 22:53:55 +1100 Subject: [PATCH 009/104] 0.0.8-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 823ddb5..2c0ee90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.7", + "version": "0.0.8-0", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From b475270ef417c78e152eb1b75f43d9928977ab22 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 14:19:41 +0000 Subject: [PATCH 010/104] Update pnpm-lock.yaml --- pnpm-lock.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf2b644..74a123e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.7.0 + version: 1.7.0 express: specifier: ^4.21.2 version: 4.21.2 @@ -15,9 +18,6 @@ importers: specifier: ^10.1.0 version: 10.1.0 devDependencies: - '@modelcontextprotocol/sdk': - specifier: ^1.7.0 - version: 1.7.0 '@types/express': specifier: ^5.0.0 version: 5.0.0 From 81d65e84ed3811ad889d5fc2d6990dcdfdf88f95 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Mon, 24 Mar 2025 14:19:45 +0000 Subject: [PATCH 011/104] 0.0.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2c0ee90..1dd36a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.8-0", + "version": "0.0.8", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From b217e0800739674caa896ad1191f1348029dfe95 Mon Sep 17 00:00:00 2001 From: Jeremy Morrell Date: Mon, 24 Mar 2025 12:08:33 -0700 Subject: [PATCH 012/104] Add VPN warning --- README.md | 30 ++++++++++++++++++++++++++++++ package.json | 1 + src/cli/proxy.ts | 22 ++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/README.md b/README.md index 7aebd26..12f14e1 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,33 @@ E.g: Claude Desktop or Windsurf Cursor: ![image](https://github.com/user-attachments/assets/14338bfa-a779-4e8a-a477-71f72cc5d99d) + + +## Debugging + +### Restart Claude + +When modifying `claude_desktop_config.json` it can helpful to completely restart Claude + +### VPN Certs + +You may run into issues if you are behind a VPN, you can try setting the `NODE_EXTRA_CA_CERTS` +environment variable to point to the CA certificate file. If using `claude_desktop_config.json`, +this might look like: + +```json +{ + "mcpServers": { + "remote-example": { + "command": "npx", + "args": [ + "mcp-remote", + "https://remote.mcp.server/sse" + ], + "env": { + "NODE_EXTRA_CA_CERTS": "\${your CA certificate file path}.pem" + } + } + } +} +``` diff --git a/package.json b/package.json index 1dd36a3..5cf09a2 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ } }, "scripts": { + "dev": "tsup --watch", "build": "tsup", "check": "prettier --check . && tsc" }, diff --git a/src/cli/proxy.ts b/src/cli/proxy.ts index 60f4966..d705b35 100644 --- a/src/cli/proxy.ts +++ b/src/cli/proxy.ts @@ -63,6 +63,28 @@ async function runProxy(serverUrl: string, callbackPort: number) { setupSignalHandlers(cleanup) } catch (error) { console.error('Fatal error:', error) + if (error instanceof Error && error.message.includes('self-signed certificate in certificate chain')) { + console.error(`You may be behind a VPN! + +If you are behind a VPN, you can try setting the NODE_EXTRA_CA_CERTS environment variable to point +to the CA certificate file. If using claude_desktop_config.json, this might look like: + +{ + "mcpServers": { + "\${mcpServerName}": { + "command": "npx", + "args": [ + "mcp-remote", + "https://remote.mcp.server/sse" + ], + "env": { + "NODE_EXTRA_CA_CERTS": "\${your CA certificate file path}.pem" + } + } + } +} + `) + } server.close() process.exit(1) } From 1cc1bd67a7dfb658bdbd0b9412d1ad1d5cf7b2fe Mon Sep 17 00:00:00 2001 From: Jeremy Morrell Date: Mon, 24 Mar 2025 12:09:41 -0700 Subject: [PATCH 013/104] tweak --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12f14e1..9f28a6a 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ this might look like: "https://remote.mcp.server/sse" ], "env": { - "NODE_EXTRA_CA_CERTS": "\${your CA certificate file path}.pem" + "NODE_EXTRA_CA_CERTS": "{your CA certificate file path}.pem" } } } From 429cc5aa1e5a80a3350913e78236b43f9f9bd12f Mon Sep 17 00:00:00 2001 From: Jeremy Morrell Date: Mon, 24 Mar 2025 12:26:07 -0700 Subject: [PATCH 014/104] Add warning about older versions of Node --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9f28a6a..0c299fa 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ Cursor: ## Debugging +### Check your Node version + +Make sure that the version of Node you have installed is [16 or higher](https://modelcontextprotocol.io/quickstart/server). + ### Restart Claude When modifying `claude_desktop_config.json` it can helpful to completely restart Claude From da0f298ce126b5ad4e50be728e13391eed5854d7 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 08:33:06 +1100 Subject: [PATCH 015/104] Fleshing out readme --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c299fa..0e5502b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,19 @@ Connect an MCP Client that only supports local (stdio) servers to a Remote MCP Server, with auth support: -**Note: this is a working proof-of-concept** but should be considered **experimental** +**Note: this is a working proof-of-concept** but should be considered **experimental**. + +## Why is this necessary? + +So far, the majority of MCP servers in the wild are installed locally, using the stdio transport. This has some benefits: both the client and the server can implicitly trust each other as the user has granted them both permission to run. Adding secrets like API keys can be done using environment variables and never leave your machine. And building on `npx` and `uvx` has allowed users to avoid explicit install steps, too. + +But there's a reason most software that _could_ be moved to the web _did_ get moved to the web: it's so much easier to find and fix bugs & iterate on new features when you can push updates to all your users with a single deploy. + +With the MCP [Authorization specification](https://spec.modelcontextprotocol.io/specification/draft/basic/authorization/) nearing completion, we now have a secure way of sharing our MCP servers with the world _without_ running code on user's laptops. Or at least, you would, if all the popular MCP _clients_ supported it yet. Most are stdio-only, and those that _do_ support HTTP+SSE don't yet support the OAuth flows required. + +That's where `mcp-remote` comes in. As soon as your chosen MCP client supports remote, authorized servers, you can remove it. Until that time, drop in this one liner and dress for the MCP clients you want! + +## Configuration E.g: Claude Desktop or Windsurf @@ -21,7 +33,6 @@ Cursor: ![image](https://github.com/user-attachments/assets/14338bfa-a779-4e8a-a477-71f72cc5d99d) - ## Debugging ### Check your Node version From 4e94da52fecf4ea5646b23c4a905c9b0a59cad5a Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 09:44:30 +1100 Subject: [PATCH 016/104] adding a PID to the authorize step, we're definitely getting two processes --- src/lib/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 0340315..2b09f57 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -2,6 +2,8 @@ import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sd import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' +const pid = process.pid + /** * Creates a bidirectional proxy between two transports * @param params The transport connections to proxy between @@ -63,7 +65,7 @@ export async function connectToRemoteServer( authProvider: OAuthClientProvider, waitForAuthCode: () => Promise, ): Promise { - console.error('Connecting to remote server:', serverUrl) + console.error(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) const transport = new SSEClientTransport(url, { authProvider }) From f1cfe3621b926afde52f088c460c802c2fc9793d Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 09:55:11 +1100 Subject: [PATCH 017/104] Few links back to CF resources for the readme --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e5502b..7207790 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ With the MCP [Authorization specification](https://spec.modelcontextprotocol.io/ That's where `mcp-remote` comes in. As soon as your chosen MCP client supports remote, authorized servers, you can remove it. Until that time, drop in this one liner and dress for the MCP clients you want! -## Configuration +## Usage E.g: Claude Desktop or Windsurf @@ -33,6 +33,23 @@ Cursor: ![image](https://github.com/user-attachments/assets/14338bfa-a779-4e8a-a477-71f72cc5d99d) +## Building Remote MCP Servers + +For instructions on building & deploying remote MCP servers, including acting as a valid OAuth client, see the following resources: + +* https://developers.cloudflare.com/agents/guides/remote-mcp-server/ + +In particular, see: + +* https://github.com/cloudflare/workers-oauth-provider for defining an MCP-comlpiant OAuth server in Cloudflare Workers +* https://github.com/cloudflare/agents/tree/main/examples/mcp for defining an `McpAgent` using the [`agents`](https://npmjs.com/package/agents) framework. + +For more information about testing these servers, see also: + +* https://developers.cloudflare.com/agents/guides/test-remote-mcp-server/ + +Know of more resources you'd like to share? Please add them to this Readme and send a PR! + ## Debugging ### Check your Node version From 752caf199a1c2847626c192be2b5faeb2af111db Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 09:55:16 +1100 Subject: [PATCH 018/104] 0.0.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5cf09a2..5ba0f68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.8", + "version": "0.0.9", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 2cce763c77a0f34e39ae47586a9150153d9da31a Mon Sep 17 00:00:00 2001 From: Jeremy Morrell Date: Mon, 24 Mar 2025 18:24:38 -0700 Subject: [PATCH 019/104] Add more detailed instructions to README --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7207790..b0b07c3 100644 --- a/README.md +++ b/README.md @@ -16,22 +16,77 @@ That's where `mcp-remote` comes in. As soon as your chosen MCP client supports r ## Usage -E.g: Claude Desktop or Windsurf +### Claude Desktop + +[Official Docs](https://modelcontextprotocol.io/quickstart/user) + +In order to add an MCP server to Claude Desktop you need to edit the configuration file located at: + +macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` +Windows: `%APPDATA%\Claude\claude_desktop_config.json` + +If it does not exist yet, [you may need to enable it under Settings > Developer](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server). ```json { "mcpServers": { "remote-example": { "command": "npx", - "args": ["mcp-remote", "https://remote.mcp.server/sse"] + "args": [ + "-y", + "mcp-remote", + "https://remote.mcp.server/sse" + ] } } } ``` -Cursor: +Restart Claude Desktop to pick up the changes in the configuration file. +Upon restarting, you should see a hammer icon in the bottom right corner +of the input box. -![image](https://github.com/user-attachments/assets/14338bfa-a779-4e8a-a477-71f72cc5d99d) +### Cursor + +[Official Docs](https://docs.cursor.com/context/model-context-protocol) + +Add the following configuration to `~/.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "remote-example": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://remote.mcp.server/sse" + ] + } + } +} +``` + +### Windsurf + +[Official Docs](https://docs.codeium.com/windsurf/mcp) + +Add the following configuration to `~/.codeium/windsurf/mcp_config.json`: + +```json +{ + "mcpServers": { + "remote-example": { + "command": "npx", + "args": [ + "-y", + "mcp-remote", + "https://remote.mcp.server/sse" + ] + } + } +} +``` ## Building Remote MCP Servers @@ -54,7 +109,10 @@ Know of more resources you'd like to share? Please add them to this Readme and s ### Check your Node version -Make sure that the version of Node you have installed is [16 or higher](https://modelcontextprotocol.io/quickstart/server). +Make sure that the version of Node you have installed is [16 or +higher](https://modelcontextprotocol.io/quickstart/server). Claude +Desktop will use your system version of Node, even if you have a newer +version installed elsewhere. ### Restart Claude From 88d9909ebc4d36b07d9c9133520b6d4f0fd1b5ae Mon Sep 17 00:00:00 2001 From: Jeremy Morrell Date: Mon, 24 Mar 2025 18:25:54 -0700 Subject: [PATCH 020/104] add a space --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b0b07c3..7acaa85 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ That's where `mcp-remote` comes in. As soon as your chosen MCP client supports r In order to add an MCP server to Claude Desktop you need to edit the configuration file located at: macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` + Windows: `%APPDATA%\Claude\claude_desktop_config.json` If it does not exist yet, [you may need to enable it under Settings > Developer](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server). From 6faec1e3f9baee4f3038ce84dc0b4d9e408f7016 Mon Sep 17 00:00:00 2001 From: Jeremy Morrell Date: Mon, 24 Mar 2025 19:49:14 -0700 Subject: [PATCH 021/104] Add instructions for following Claude Desktop logs --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 7acaa85..3775d21 100644 --- a/README.md +++ b/README.md @@ -141,3 +141,19 @@ this might look like: } } ``` + +### Check the logs + +[Follow Claude Desktop logs in real-time](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop) + +MacOS / Linux: + +`tail -n 20 -F ~/Library/Logs/Claude/mcp*.log` + +For bash on WSL: + +`tail -n 20 -f "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log"` + +or Powershell: + +`Get-Content "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log" -Wait -Tail 20` From 1f186a6cb4ba2244c148bdeb5f98d664b2a56f4b Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 11:40:27 +1100 Subject: [PATCH 022/104] added clearStorage --- src/react/index.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/react/index.ts b/src/react/index.ts index 40948c2..2d95e6c 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -70,6 +70,10 @@ export type UseMcpResult = { * @returns Auth URL that can be used to manually open a new window */ authenticate: () => Promise + /** + * Clear all localStorage items for this server + */ + clearStorage: () => void } type StoredState = { @@ -120,6 +124,44 @@ class BrowserOAuthClientProvider { } } + /** + * Clears all storage items related to this server + * @returns The number of items cleared + */ + clearStorage(): number { + const prefix = `${this.storageKeyPrefix}_${this.serverUrlHash}`; + const keysToRemove = []; + + // Find all keys that match the prefix + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith(prefix)) { + keysToRemove.push(key); + } + } + + // Also check for any state keys + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith(`${this.storageKeyPrefix}:state_`)) { + // Load state to check if it's for this server + try { + const state = JSON.parse(localStorage.getItem(key) || '{}'); + if (state.serverUrlHash === this.serverUrlHash) { + keysToRemove.push(key); + } + } catch (e) { + // Ignore JSON parse errors + } + } + } + + // Remove all matching keys + keysToRemove.forEach(key => localStorage.removeItem(key)); + + return keysToRemove.length; + } + private hashString(str: string): string { // Simple hash function for browser environments let hash = 0 @@ -722,6 +764,25 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { } }, [disconnect]) + // Clear all localStorage items for this server + const clearStorage = useCallback(() => { + if (!authProviderRef.current) { + addLog('warn', 'Cannot clear storage: auth provider not initialized'); + return; + } + + // Use the provider's method to clear storage + const clearedCount = authProviderRef.current.clearStorage(); + + // Clear auth-related state in the hook + authUrlRef.current = undefined; + setAuthUrl(undefined); + metadataRef.current = undefined; + codeVerifierRef.current = undefined; + + addLog('info', `Cleared ${clearedCount} storage items for server`); + }, [addLog]); + return { state, tools, @@ -732,6 +793,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { retry, disconnect, authenticate, + clearStorage, } } From 97fc62dbd8130508e6eff7c02bfe069b79ffbcc6 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 11:45:52 +1100 Subject: [PATCH 023/104] 0.0.10-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ba0f68..82f9e99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.9", + "version": "0.0.10-0", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 90b375bfdc5a7d153a48871a8734d0bc18a788d7 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 11:55:43 +1100 Subject: [PATCH 024/104] Switched from "localhost" to "127.0.0.1" for local redirect --- src/cli/shared.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/shared.ts b/src/cli/shared.ts index a4101aa..b146abd 100644 --- a/src/cli/shared.ts +++ b/src/cli/shared.ts @@ -44,7 +44,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { } get redirectUrl(): string { - return `http://localhost:${this.options.callbackPort}${this.callbackPath}` + return `http://127.0.0.1:${this.options.callbackPort}${this.callbackPath}` } get clientMetadata() { @@ -229,7 +229,7 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { }) const server = app.listen(options.port, () => { - console.error(`OAuth callback server running at http://localhost:${options.port}`) + console.error(`OAuth callback server running at http://127.0.0.1:${options.port}`) }) /** @@ -299,7 +299,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, } const url = new URL(serverUrl) - const isLocalhost = url.hostname === 'localhost' && url.protocol === 'http:' + const isLocalhost = (url.hostname === 'localhost' || url.hostname === '127.0.0.1') && url.protocol === 'http:' if (!(url.protocol == 'https:' || isLocalhost)) { console.error(usage) From c4779d082a840d1a827f69cb9088fb235f39e5a0 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 11:56:26 +1100 Subject: [PATCH 025/104] 0.0.10-1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82f9e99..1bef2a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.10-0", + "version": "0.0.10-1", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 397fcd3e1b92c921523f1883924ba6ac7dea86c8 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 16:11:01 +1100 Subject: [PATCH 026/104] adding polling fallback for tokens --- src/react/index.ts | 94 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/src/react/index.ts b/src/react/index.ts index 2d95e6c..fa72917 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -129,37 +129,37 @@ class BrowserOAuthClientProvider { * @returns The number of items cleared */ clearStorage(): number { - const prefix = `${this.storageKeyPrefix}_${this.serverUrlHash}`; - const keysToRemove = []; - + const prefix = `${this.storageKeyPrefix}_${this.serverUrlHash}` + const keysToRemove = [] + // Find all keys that match the prefix for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); + const key = localStorage.key(i) if (key && key.startsWith(prefix)) { - keysToRemove.push(key); + keysToRemove.push(key) } } - + // Also check for any state keys for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); + const key = localStorage.key(i) if (key && key.startsWith(`${this.storageKeyPrefix}:state_`)) { // Load state to check if it's for this server try { - const state = JSON.parse(localStorage.getItem(key) || '{}'); + const state = JSON.parse(localStorage.getItem(key) || '{}') if (state.serverUrlHash === this.serverUrlHash) { - keysToRemove.push(key); + keysToRemove.push(key) } } catch (e) { // Ignore JSON parse errors } } } - + // Remove all matching keys - keysToRemove.forEach(key => localStorage.removeItem(key)); - - return keysToRemove.length; + keysToRemove.forEach((key) => localStorage.removeItem(key)) + + return keysToRemove.length } private hashString(str: string): string { @@ -173,7 +173,7 @@ class BrowserOAuthClientProvider { return Math.abs(hash).toString(16) } - private getKey(key: string): string { + getKey(key: string): string { return `${this.storageKeyPrefix}_${this.serverUrlHash}_${key}` } @@ -443,9 +443,12 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { // Set up listener for post-auth message const authPromise = new Promise((resolve, reject) => { + let pollIntervalId: number | undefined + const timeoutId = setTimeout( () => { window.removeEventListener('message', messageHandler) + if (pollIntervalId) clearTimeout(pollIntervalId) reject(new Error('Authentication timeout after 5 minutes')) }, 5 * 60 * 1000, @@ -458,15 +461,48 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { if (event.data && event.data.type === 'mcp_auth_callback' && event.data.code) { window.removeEventListener('message', messageHandler) clearTimeout(timeoutId) + if (pollIntervalId) clearTimeout(pollIntervalId) // TODO: not this, obviously - // reload window, we should find the token in local storage - window.location.reload() - // resolve(event.data.code) + resolve(event.data.code) } } window.addEventListener('message', messageHandler) + + // Add polling fallback to check for tokens in localStorage + const pollForTokens = () => { + try { + // Check if tokens have appeared in localStorage + const tokensKey = authProviderRef.current!.getKey('tokens') + const storedTokens = localStorage.getItem(tokensKey) + + if (storedTokens) { + // Tokens found, clean up and resolve + window.removeEventListener('message', messageHandler) + clearTimeout(timeoutId) + if (pollIntervalId) clearTimeout(pollIntervalId) + + // Parse tokens to make sure they're valid + const tokens = JSON.parse(storedTokens) + if (tokens.access_token) { + addLog('info', 'Found tokens in localStorage via polling') + resolve(tokens.access_token) + } + } + } catch (err) { + // Error during polling, continue anyway + console.error(err) + } + } + + // Start polling every 500ms using setTimeout for recursive polling + const poll = () => { + pollForTokens() + pollIntervalId = setTimeout(poll, 500) as unknown as number + } + + poll() // Start the polling }) // Redirect to authorization @@ -767,21 +803,21 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { // Clear all localStorage items for this server const clearStorage = useCallback(() => { if (!authProviderRef.current) { - addLog('warn', 'Cannot clear storage: auth provider not initialized'); - return; + addLog('warn', 'Cannot clear storage: auth provider not initialized') + return } - + // Use the provider's method to clear storage - const clearedCount = authProviderRef.current.clearStorage(); - + const clearedCount = authProviderRef.current.clearStorage() + // Clear auth-related state in the hook - authUrlRef.current = undefined; - setAuthUrl(undefined); - metadataRef.current = undefined; - codeVerifierRef.current = undefined; - - addLog('info', `Cleared ${clearedCount} storage items for server`); - }, [addLog]); + authUrlRef.current = undefined + setAuthUrl(undefined) + metadataRef.current = undefined + codeVerifierRef.current = undefined + + addLog('info', `Cleared ${clearedCount} storage items for server`) + }, [addLog]) return { state, From 71da5a974edccf0edeea8ce96abfced58bffd886 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 16:11:14 +1100 Subject: [PATCH 027/104] 0.0.10-2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1bef2a8..2ff9059 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.10-1", + "version": "0.0.10-2", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 6a2bf90f5a57bb8b6e0358b8a0b8e0b5623385bc Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 17:43:19 +1100 Subject: [PATCH 028/104] Refactored to avoid the useCallback soup --- src/react/index.ts | 936 +++++++++++++++++++++++++++------------------ 1 file changed, 565 insertions(+), 371 deletions(-) diff --git a/src/react/index.ts b/src/react/index.ts index fa72917..d1062f4 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -2,7 +2,12 @@ import { CallToolResultSchema, JSONRPCMessage, ListToolsResultSchema, Tool } fro import { useCallback, useEffect, useRef, useState } from 'react' import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { Client } from '@modelcontextprotocol/sdk/client/index.js' -import { discoverOAuthMetadata, exchangeAuthorization, startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js' +import { + OAuthClientProvider, + discoverOAuthMetadata, + exchangeAuthorization, + startAuthorization, +} from '@modelcontextprotocol/sdk/client/auth.js' import { OAuthClientInformation, OAuthMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js' function assert(condition: unknown, message: string): asserts condition { @@ -86,7 +91,7 @@ type StoredState = { /** * Browser-compatible OAuth client provider for MCP */ -class BrowserOAuthClientProvider { +class BrowserOAuthClientProvider implements OAuthClientProvider { private storageKeyPrefix: string private serverUrlHash: string private clientName: string @@ -288,156 +293,401 @@ class BrowserOAuthClientProvider { } /** - * useMcp is a React hook that connects to a remote MCP server, negotiates auth - * (including opening a popup window or new tab to complete the OAuth flow), - * and enables passing a list of tools (once loaded) to ai-sdk (using `useChat`). + * Class to encapsulate all MCP client functionality, + * including authentication flow and connection management */ -export function useMcp(options: UseMcpOptions): UseMcpResult { - const [state, setState] = useState('discovering') - const [tools, setTools] = useState([]) - const [error, setError] = useState(undefined) - const [log, setLog] = useState([]) - const [authUrl, setAuthUrl] = useState(undefined) +class McpClient { + // State + private _state: UseMcpResult['state'] = 'discovering' + private _error?: string + private _tools: Tool[] = [] + private _log: UseMcpResult['log'] = [] + private _authUrl?: string - const clientRef = useRef(null) - const transportRef = useRef(null) - const authProviderRef = useRef(null) - const metadataRef = useRef(undefined) - const authUrlRef = useRef(undefined) - const codeVerifierRef = useRef(undefined) - const connectingRef = useRef(false) - const isInitialMount = useRef(true) + // Client and transport + private client: Client | null = null + private transport: SSEClientTransport | null = null + private authProvider: BrowserOAuthClientProvider | null = null - // Set up default options - const { - url, - clientName = 'MCP React Client', - clientUri = window.location.origin, - callbackUrl = new URL('/oauth/callback', window.location.origin).toString(), - storageKeyPrefix = 'mcp:auth', - clientConfig = { - name: 'mcp-react-client', - version: '0.1.0', + // Authentication state + private metadata?: OAuthMetadata + private authUrlRef?: URL + private codeVerifier?: string + private connecting = false + + // Update callbacks + private onStateChange: (state: UseMcpResult['state']) => void + private onToolsChange: (tools: Tool[]) => void + private onErrorChange: (error?: string) => void + private onLogChange: (log: UseMcpResult['log']) => void + private onAuthUrlChange: (authUrl?: string) => void + + constructor( + private url: string, + private options: { + clientName?: string + clientUri?: string + callbackUrl?: string + storageKeyPrefix?: string + clientConfig?: { + name?: string + version?: string + } + debug?: boolean + autoRetry?: boolean | number + autoReconnect?: boolean | number + popupFeatures?: string }, - debug = false, - autoRetry = false, - autoReconnect = 3000, - popupFeatures = 'width=600,height=700,resizable=yes,scrollbars=yes', - } = options - - // Add to log - const addLog = useCallback( - (level: 'debug' | 'info' | 'warn' | 'error', message: string) => { - if (level === 'debug' && !debug) return - setLog((prevLog) => [...prevLog, { level, message }]) + callbacks: { + onStateChange: (state: UseMcpResult['state']) => void + onToolsChange: (tools: Tool[]) => void + onErrorChange: (error?: string) => void + onLogChange: (log: UseMcpResult['log']) => void + onAuthUrlChange: (authUrl?: string) => void }, - [debug], - ) + ) { + // Initialize callbacks + this.onStateChange = callbacks.onStateChange + this.onToolsChange = callbacks.onToolsChange + this.onErrorChange = callbacks.onErrorChange + this.onLogChange = callbacks.onLogChange + this.onAuthUrlChange = callbacks.onAuthUrlChange - // Call a tool on the MCP server - const callTool = useCallback( - async (name: string, args?: Record) => { - if (!clientRef.current || state !== 'ready') { - throw new Error('MCP client not ready') + // Initialize auth provider + this.initAuthProvider() + } + + get state(): UseMcpResult['state'] { + return this._state + } + + get tools(): Tool[] { + return this._tools + } + + get error(): string | undefined { + return this._error + } + + get log(): UseMcpResult['log'] { + return this._log + } + + get authUrl(): string | undefined { + return this._authUrl + } + + /** + * Initialize the auth provider + */ + private initAuthProvider(): void { + if (!this.authProvider) { + this.authProvider = new BrowserOAuthClientProvider(this.url, { + storageKeyPrefix: this.options.storageKeyPrefix, + clientName: this.options.clientName, + clientUri: this.options.clientUri, + callbackUrl: this.options.callbackUrl, + }) + } + } + + /** + * Add a log entry + */ + private addLog(level: 'debug' | 'info' | 'warn' | 'error', message: string): void { + if (level === 'debug' && !this.options.debug) return + this._log = [...this._log, { level, message }] + this.onLogChange(this._log) + } + + /** + * Update the state + */ + private setState(state: UseMcpResult['state']): void { + this._state = state + this.onStateChange(state) + } + + /** + * Update the error + */ + private setError(error?: string): void { + this._error = error + this.onErrorChange(error) + } + + /** + * Update the tools + */ + private setTools(tools: Tool[]): void { + this._tools = tools + this.onToolsChange(tools) + } + + /** + * Update the auth URL + */ + private setAuthUrl(authUrl?: string): void { + this._authUrl = authUrl + this.onAuthUrlChange(authUrl) + } + + /** + * Handle OAuth discovery and authentication + */ + private async discoverOAuthAndAuthenticate(error: Error): Promise { + try { + // Discover OAuth metadata now that we know we need it + if (!this.metadata) { + this.addLog('info', 'Discovering OAuth metadata...') + this.metadata = await discoverOAuthMetadata(this.url) + this.addLog('debug', `OAuth metadata: ${this.metadata ? 'Found' : 'Not available'}`) } - try { - console.log('CALLING TOOL') - const result = await clientRef.current.request( - { - method: 'tools/call', - params: { name, arguments: args }, + // If metadata is found, start auth flow + if (this.metadata) { + this.setState('authenticating') + + try { + // Start authentication process + await this.handleAuthentication() + + // After successful auth, retry connection + // Important: We need to fully disconnect and reconnect + await this.disconnect() + await this.connect() + } catch (authErr) { + this.addLog('error', `Authentication error: ${authErr instanceof Error ? authErr.message : String(authErr)}`) + this.setState('failed') + this.setError(`Authentication failed: ${authErr instanceof Error ? authErr.message : String(authErr)}`) + this.connecting = false + } + } else { + // No OAuth metadata available + this.setState('failed') + this.setError(`Authentication required but no OAuth metadata found: ${error.message}`) + this.connecting = false + } + } catch (oauthErr) { + this.addLog('error', `OAuth discovery error: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) + this.setState('failed') + this.setError(`Authentication setup failed: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) + this.connecting = false + } + } + + /** + * Connect to the MCP server + */ + async connect(): Promise { + // Prevent multiple simultaneous connection attempts + if (this.connecting) return + this.connecting = true + + try { + this.setState('discovering') + this.setError(undefined) + + // Create MCP client + this.client = new Client( + { + name: this.options.clientConfig?.name || 'mcp-react-client', + version: this.options.clientConfig?.version || '0.1.0', + }, + { + capabilities: { + sampling: {}, }, - CallToolResultSchema, - ) - return result - } catch (err) { - addLog('error', `Error calling tool ${name}: ${err instanceof Error ? err.message : String(err)}`) - throw err - } - }, - [state, addLog], - ) + }, + ) - // Disconnect from the MCP server - const disconnect = useCallback(async () => { - if (clientRef.current) { - try { - await clientRef.current.close() - } catch (err) { - addLog('error', `Error closing client: ${err instanceof Error ? err.message : String(err)}`) + // Create SSE transport + this.setState('connecting') + this.addLog('info', 'Creating transport...') + + const serverUrl = new URL(this.url) + this.transport = new SSEClientTransport(serverUrl, { + authProvider: this.authProvider, + }) + + // Set up transport handlers + this.transport.onmessage = (message: JSONRPCMessage) => { + // @ts-expect-error TODO: fix this type + this.addLog('debug', `Received message: ${message.method || message.id}`) } - clientRef.current = null + + this.transport.onerror = (err: Error) => { + this.addLog('error', `Transport error: ${err.message}`) + + if (err.message.includes('Unauthorized')) { + // Only discover OAuth metadata and authenticate if we get a 401 + this.discoverOAuthAndAuthenticate(err) + } else { + this.setState('failed') + this.setError(`Connection error: ${err.message}`) + this.connecting = false + } + } + + this.transport.onclose = () => { + this.addLog('info', 'Connection closed') + // If we were previously connected, try to reconnect + if (this.state === 'ready' && this.options.autoReconnect) { + const delay = typeof this.options.autoReconnect === 'number' ? this.options.autoReconnect : 3000 + this.addLog('info', `Will reconnect in ${delay}ms...`) + setTimeout(() => { + this.disconnect().then(() => this.connect()) + }, delay) + } + } + + // Try connecting transport + try { + this.addLog('info', 'Starting transport...') + // await this.transport.start() + } catch (err) { + this.addLog('error', `Transport start error: ${err instanceof Error ? err.message : String(err)}`) + + if (err instanceof Error && err.message.includes('Unauthorized')) { + // Only discover OAuth and authenticate if we get a 401 + await this.discoverOAuthAndAuthenticate(err) + return // Important: Return here to avoid proceeding with the unauthorized connection + } else { + this.setState('failed') + this.setError(`Connection error: ${err instanceof Error ? err.message : String(err)}`) + this.connecting = false + return + } + } + + // Connect client + try { + this.addLog('info', 'Connecting client...') + this.setState('loading') + await this.client.connect(this.transport) + this.addLog('info', 'Client connected') + + // Load tools + try { + this.addLog('info', 'Loading tools...') + const toolsResponse = await this.client.request({ method: 'tools/list' }, ListToolsResultSchema) + this.setTools(toolsResponse.tools) + this.addLog('info', `Loaded ${toolsResponse.tools.length} tools`) + + // Connection completed successfully + this.setState('ready') + this.connecting = false + } catch (toolErr) { + this.addLog('error', `Error loading tools: ${toolErr instanceof Error ? toolErr.message : String(toolErr)}`) + // We're still connected, just couldn't load tools + this.setState('ready') + this.connecting = false + } + } catch (connectErr) { + this.addLog('error', `Client connect error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) + + if (connectErr instanceof Error && connectErr.message.includes('Unauthorized')) { + // Only discover OAuth and authenticate if we get a 401 + await this.discoverOAuthAndAuthenticate(connectErr) + } else { + this.setState('failed') + this.setError(`Connection error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) + this.connecting = false + } + } + } catch (err) { + this.addLog('error', `Unexpected error: ${err instanceof Error ? err.message : String(err)}`) + this.setState('failed') + this.setError(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`) + this.connecting = false + } + } + + /** + * Disconnect from the MCP server + */ + async disconnect(): Promise { + if (this.client) { + try { + await this.client.close() + } catch (err) { + this.addLog('error', `Error closing client: ${err instanceof Error ? err.message : String(err)}`) + } + this.client = null } - if (transportRef.current) { + if (this.transport) { try { - await transportRef.current.close() + await this.transport.close() } catch (err) { - addLog('error', `Error closing transport: ${err instanceof Error ? err.message : String(err)}`) + this.addLog('error', `Error closing transport: ${err instanceof Error ? err.message : String(err)}`) } - transportRef.current = null + this.transport = null } - connectingRef.current = false - setState('discovering') - setTools([]) - setError(undefined) - }, [addLog]) + this.connecting = false + this.setState('discovering') + this.setTools([]) + this.setError(undefined) + } - // Start the auth flow and get the auth URL - const startAuthFlow = useCallback(async (): Promise => { - if (!authProviderRef.current || !metadataRef.current) { + /** + * Start the auth flow and get the auth URL + */ + async startAuthFlow(): Promise { + if (!this.authProvider || !this.metadata) { throw new Error('Auth provider or metadata not available') } - addLog('info', 'Starting authentication flow...') + this.addLog('info', 'Starting authentication flow...') // Check if we have client info - let clientInfo = await authProviderRef.current.clientInformation() + let clientInfo = await this.authProvider.clientInformation() if (!clientInfo) { // Register client dynamically - addLog('info', 'No client information found, registering...') + this.addLog('info', 'No client information found, registering...') // Note: In a complete implementation, you'd register the client here // This would be done server-side in a real application throw new Error('Dynamic client registration not implemented in this example') } // Start authorization flow - addLog('info', 'Preparing authorization...') - const { authorizationUrl, codeVerifier } = await startAuthorization(url, { - metadata: metadataRef.current, + this.addLog('info', 'Preparing authorization...') + const { authorizationUrl, codeVerifier } = await startAuthorization(this.url, { + metadata: this.metadata, clientInformation: clientInfo, - redirectUrl: authProviderRef.current.redirectUrl, + redirectUrl: this.authProvider.redirectUrl, }) // Save code verifier and auth URL for later use - await authProviderRef.current.saveCodeVerifier(codeVerifier) - codeVerifierRef.current = codeVerifier - authUrlRef.current = authorizationUrl - setAuthUrl(authorizationUrl.toString()) + await this.authProvider.saveCodeVerifier(codeVerifier) + this.codeVerifier = codeVerifier + this.authUrlRef = authorizationUrl + this.setAuthUrl(authorizationUrl.toString()) return authorizationUrl - }, [url, addLog]) + } - // Handle authentication flow - const handleAuthentication = useCallback(async () => { - if (!authProviderRef.current) { + /** + * Handle authentication flow + */ + async handleAuthentication(): Promise { + if (!this.authProvider) { throw new Error('Auth provider not available') } // Get or create the auth URL - if (!authUrlRef.current) { + if (!this.authUrlRef) { try { - await startAuthFlow() + await this.startAuthFlow() } catch (err) { - addLog('error', `Failed to start auth flow: ${err instanceof Error ? err.message : String(err)}`) + this.addLog('error', `Failed to start auth flow: ${err instanceof Error ? err.message : String(err)}`) throw err } } - if (!authUrlRef.current) { + if (!this.authUrlRef) { throw new Error('Failed to create authorization URL') } @@ -463,7 +713,6 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { clearTimeout(timeoutId) if (pollIntervalId) clearTimeout(pollIntervalId) - // TODO: not this, obviously resolve(event.data.code) } } @@ -474,7 +723,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { const pollForTokens = () => { try { // Check if tokens have appeared in localStorage - const tokensKey = authProviderRef.current!.getKey('tokens') + const tokensKey = this.authProvider!.getKey('tokens') const storedTokens = localStorage.getItem(tokensKey) if (storedTokens) { @@ -486,7 +735,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { // Parse tokens to make sure they're valid const tokens = JSON.parse(storedTokens) if (tokens.access_token) { - addLog('info', 'Found tokens in localStorage via polling') + this.addLog('info', 'Found tokens in localStorage via polling') resolve(tokens.access_token) } } @@ -506,263 +755,217 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { }) // Redirect to authorization - addLog('info', 'Opening authorization window...') - assert(metadataRef.current, 'Metadata not available') - const redirectResult = await authProviderRef.current.redirectToAuthorization(authUrlRef.current, metadataRef.current, { - popupFeatures, + this.addLog('info', 'Opening authorization window...') + assert(this.metadata, 'Metadata not available') + const redirectResult = await this.authProvider.redirectToAuthorization(this.authUrlRef, this.metadata, { + popupFeatures: this.options.popupFeatures, }) if (!redirectResult.success) { // Popup was blocked - setState('failed') - setError('Authentication popup was blocked by the browser. Please click the link to authenticate in a new window.') - setAuthUrl(redirectResult.url) - addLog('warn', 'Authentication popup was blocked. User needs to manually authorize.') + this.setState('failed') + this.setError('Authentication popup was blocked by the browser. Please click the link to authenticate in a new window.') + this.setAuthUrl(redirectResult.url) + this.addLog('warn', 'Authentication popup was blocked. User needs to manually authorize.') throw new Error('Authentication popup blocked') } // Wait for auth to complete - addLog('info', 'Waiting for authorization...') + this.addLog('info', 'Waiting for authorization...') const code = await authPromise - addLog('info', 'Authorization code received') + this.addLog('info', 'Authorization code received') return code - }, [url, addLog, popupFeatures, startAuthFlow]) + } - // Initialize connection to MCP server - const connect = useCallback(async () => { - // Prevent multiple simultaneous connection attempts - if (connectingRef.current) return - connectingRef.current = true + /** + * Handle authentication completion + */ + async handleAuthCompletion(code: string): Promise { + if (!this.authProvider || !this.transport) { + throw new Error('Authentication context not available') + } try { - setState('discovering') - setError(undefined) + this.addLog('info', 'Finishing authorization...') + await this.transport.finishAuth(code) + this.addLog('info', 'Authorization completed') - // Create auth provider if not already created - if (!authProviderRef.current) { - authProviderRef.current = new BrowserOAuthClientProvider(url, { - storageKeyPrefix, - clientName, - clientUri, - callbackUrl, - }) - } + // Reset auth URL state + this.authUrlRef = undefined + this.setAuthUrl(undefined) - // Create MCP client - clientRef.current = new Client( - { - name: clientConfig.name || 'mcp-react-client', - version: clientConfig.version || '0.1.0', - }, - { - capabilities: { - sampling: {}, - }, - }, - ) - - // Create SSE transport - try connecting without auth first - setState('connecting') - addLog('info', 'Creating transport...') - - const serverUrl = new URL(url) - transportRef.current = new SSEClientTransport(serverUrl, { - // @ts-expect-error TODO: fix this type, expect BrowserOAuthClientProvider - authProvider: authProviderRef.current, - }) - - // Set up transport handlers - transportRef.current.onmessage = (message: JSONRPCMessage) => { - // @ts-expect-error TODO: fix this type - addLog('debug', `Received message: ${message.method || message.id}`) - } - - transportRef.current.onerror = (err: Error) => { - addLog('error', `Transport error: ${err.message}`) - - if (err.message.includes('Unauthorized')) { - // Only discover OAuth metadata and authenticate if we get a 401 - discoverOAuthAndAuthenticate(err) - } else { - setState('failed') - setError(`Connection error: ${err.message}`) - connectingRef.current = false - } - } - - transportRef.current.onclose = () => { - addLog('info', 'Connection closed') - // If we were previously connected, try to reconnect - if (state === 'ready' && autoReconnect) { - const delay = typeof autoReconnect === 'number' ? autoReconnect : 3000 - addLog('info', `Will reconnect in ${delay}ms...`) - setTimeout(() => { - disconnect().then(() => connect()) - }, delay) - } - } - - // Helper function to handle OAuth discovery and authentication - const discoverOAuthAndAuthenticate = async (error: Error) => { - try { - // Discover OAuth metadata now that we know we need it - if (!metadataRef.current) { - addLog('info', 'Discovering OAuth metadata...') - metadataRef.current = await discoverOAuthMetadata(url) - addLog('debug', `OAuth metadata: ${metadataRef.current ? 'Found' : 'Not available'}`) - } - - // If metadata is found, start auth flow - if (metadataRef.current) { - setState('authenticating') - // Start authentication process - await handleAuthentication() - // After successful auth, retry connection - return connect() - } else { - // No OAuth metadata available - setState('failed') - setError(`Authentication required but no OAuth metadata found: ${error.message}`) - connectingRef.current = false - } - } catch (oauthErr) { - addLog('error', `OAuth discovery error: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) - setState('failed') - setError(`Authentication setup failed: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) - connectingRef.current = false - } - } - - // Try connecting transport first without OAuth discovery - try { - addLog('info', 'Starting transport...') - // await transportRef.current.start() - } catch (err) { - addLog('error', `Transport start error: ${err instanceof Error ? err.message : String(err)}`) - - if (err instanceof Error && err.message.includes('Unauthorized')) { - // Only discover OAuth and authenticate if we get a 401 - await discoverOAuthAndAuthenticate(err) - } else { - setState('failed') - setError(`Connection error: ${err instanceof Error ? err.message : String(err)}`) - connectingRef.current = false - return - } - } - - // Connect client - try { - addLog('info', 'Connecting client...') - setState('loading') - await clientRef.current.connect(transportRef.current) - addLog('info', 'Client connected') - - // Load tools - try { - addLog('info', 'Loading tools...') - const toolsResponse = await clientRef.current.request({ method: 'tools/list' }, ListToolsResultSchema) - setTools(toolsResponse.tools) - addLog('info', `Loaded ${toolsResponse.tools.length} tools`) - - // Connection completed successfully - setState('ready') - connectingRef.current = false - } catch (toolErr) { - addLog('error', `Error loading tools: ${toolErr instanceof Error ? toolErr.message : String(toolErr)}`) - // We're still connected, just couldn't load tools - setState('ready') - connectingRef.current = false - } - } catch (connectErr) { - addLog('error', `Client connect error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) - - if (connectErr instanceof Error && connectErr.message.includes('Unauthorized')) { - // Only discover OAuth and authenticate if we get a 401 - await discoverOAuthAndAuthenticate(connectErr) - } else { - setState('failed') - setError(`Connection error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) - connectingRef.current = false - } - } + // Reconnect with the new auth token - important to do a full disconnect/connect cycle + await this.disconnect() + await this.connect() } catch (err) { - addLog('error', `Unexpected error: ${err instanceof Error ? err.message : String(err)}`) - setState('failed') - setError(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`) - connectingRef.current = false + this.addLog('error', `Auth completion error: ${err instanceof Error ? err.message : String(err)}`) + this.setState('failed') + this.setError(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`) } - }, [ - url, - clientName, - clientUri, - callbackUrl, - storageKeyPrefix, - clientConfig, - debug, - autoReconnect, - addLog, - handleAuthentication, - disconnect, - ]) + } - // Provide public authenticate method - const authenticate = useCallback(async (): Promise => { - if (!authUrlRef.current) { - await startAuthFlow() + /** + * Call a tool on the MCP server + */ + async callTool(name: string, args?: Record): Promise { + if (!this.client || this.state !== 'ready') { + throw new Error('MCP client not ready') } - if (authUrlRef.current) { - return authUrlRef.current.toString() + try { + const result = await this.client.request( + { + method: 'tools/call', + params: { name, arguments: args }, + }, + CallToolResultSchema, + ) + return result + } catch (err) { + this.addLog('error', `Error calling tool ${name}: ${err instanceof Error ? err.message : String(err)}`) + throw err + } + } + + /** + * Retry connection + */ + retry(): void { + if (this.state === 'failed') { + this.disconnect().then(() => this.connect()) + } + } + + /** + * Manually trigger authentication + */ + async authenticate(): Promise { + if (!this.authUrlRef) { + await this.startAuthFlow() + } + + if (this.authUrlRef) { + return this.authUrlRef.toString() } return undefined - }, []) + } - // Handle auth completion - this is called when we receive a message from the popup - const handleAuthCompletion = useCallback( - async (code: string) => { - if (!authProviderRef.current || !transportRef.current) { - throw new Error('Authentication context not available') - } - - try { - addLog('info', 'Finishing authorization...') - await transportRef.current.finishAuth(code) - addLog('info', 'Authorization completed') - - // Reset auth URL state - authUrlRef.current = undefined - setAuthUrl(undefined) - - // Reconnect with the new auth token - await disconnect() - connect() - } catch (err) { - addLog('error', `Auth completion error: ${err instanceof Error ? err.message : String(err)}`) - setState('failed') - setError(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`) - } - }, - [addLog, disconnect, connect], - ) - - // Retry connection - const retry = useCallback(() => { - if (state === 'failed') { - disconnect().then(() => connect()) + /** + * Clear all localStorage items for this server + */ + clearStorage(): number { + if (!this.authProvider) { + this.addLog('warn', 'Cannot clear storage: auth provider not initialized') + return 0 } - }, [state, disconnect, connect]) + + // Use the provider's method to clear storage + const clearedCount = this.authProvider.clearStorage() + + // Clear auth-related state in the class + this.authUrlRef = undefined + this.setAuthUrl(undefined) + this.metadata = undefined + this.codeVerifier = undefined + + this.addLog('info', `Cleared ${clearedCount} storage items for server`) + + return clearedCount + } +} + +/** + * useMcp is a React hook that connects to a remote MCP server, negotiates auth + * (including opening a popup window or new tab to complete the OAuth flow), + * and enables passing a list of tools (once loaded) to ai-sdk (using `useChat`). + */ +export function useMcp(options: UseMcpOptions): UseMcpResult { + const [state, setState] = useState('discovering') + const [tools, setTools] = useState([]) + const [error, setError] = useState(undefined) + const [log, setLog] = useState([]) + const [authUrl, setAuthUrl] = useState(undefined) + + // Use a ref to maintain a single instance of the McpClient + const clientRef = useRef(null) + const isInitialMount = useRef(true) + + // Initialize the client if it doesn't exist yet + const getClient = useCallback(() => { + if (!clientRef.current) { + clientRef.current = new McpClient( + options.url, + { + clientName: options.clientName || 'MCP React Client', + clientUri: options.clientUri || window.location.origin, + callbackUrl: options.callbackUrl || new URL('/oauth/callback', window.location.origin).toString(), + storageKeyPrefix: options.storageKeyPrefix || 'mcp:auth', + clientConfig: options.clientConfig || { + name: 'mcp-react-client', + version: '0.1.0', + }, + debug: options.debug || false, + autoRetry: options.autoRetry || false, + autoReconnect: options.autoReconnect || 3000, + popupFeatures: options.popupFeatures || 'width=600,height=700,resizable=yes,scrollbars=yes', + }, + { + onStateChange: setState, + onToolsChange: setTools, + onErrorChange: setError, + onLogChange: setLog, + onAuthUrlChange: setAuthUrl, + }, + ) + } + return clientRef.current + }, [ + options.url, + options.clientName, + options.clientUri, + options.callbackUrl, + options.storageKeyPrefix, + options.clientConfig, + options.debug, + options.autoRetry, + options.autoReconnect, + options.popupFeatures, + ]) + + // Connect on initial mount + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false + const client = getClient() + client.connect() + } + }, [getClient]) + + // Auto-retry on failure + useEffect(() => { + if (state === 'failed' && options.autoRetry) { + const delay = typeof options.autoRetry === 'number' ? options.autoRetry : 5000 + const timeoutId = setTimeout(() => { + const client = getClient() + client.retry() + }, delay) + + return () => { + clearTimeout(timeoutId) + } + } + }, [state, options.autoRetry, getClient]) // Set up message listener for auth callback useEffect(() => { const messageHandler = (event: MessageEvent) => { - // Verify origin for security if (event.origin !== window.location.origin) return if (event.data && event.data.type === 'mcp_auth_callback' && event.data.code) { - handleAuthCompletion(event.data.code).catch((err) => { - addLog('error', `Auth callback error: ${err.message}`) + const client = getClient() + client.handleAuthCompletion(event.data.code).catch((err) => { + console.error('Auth callback error:', err) }) } } @@ -771,53 +974,45 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { return () => { window.removeEventListener('message', messageHandler) } - }, [handleAuthCompletion, addLog]) - - // Initial connection and auto-retry - useEffect(() => { - if (isInitialMount.current) { - isInitialMount.current = false - connect() - } else if (state === 'failed' && autoRetry) { - const delay = typeof autoRetry === 'number' ? autoRetry : 5000 - const timeoutId = setTimeout(() => { - addLog('info', 'Auto-retrying connection...') - disconnect().then(() => connect()) - }, delay) - - return () => { - clearTimeout(timeoutId) - } - } - }, [state, autoRetry, connect, disconnect, addLog]) + }, [getClient]) // Clean up on unmount useEffect(() => { return () => { - if (clientRef.current || transportRef.current) { - disconnect() + if (clientRef.current) { + clientRef.current.disconnect() } } - }, [disconnect]) + }, []) + + // Public methods - proxied to the client + const callTool = useCallback( + async (name: string, args?: Record) => { + const client = getClient() + return client.callTool(name, args) + }, + [getClient], + ) + + const retry = useCallback(() => { + const client = getClient() + client.retry() + }, [getClient]) + + const disconnect = useCallback(async () => { + const client = getClient() + await client.disconnect() + }, [getClient]) + + const authenticate = useCallback(async (): Promise => { + const client = getClient() + return client.authenticate() + }, [getClient]) - // Clear all localStorage items for this server const clearStorage = useCallback(() => { - if (!authProviderRef.current) { - addLog('warn', 'Cannot clear storage: auth provider not initialized') - return - } - - // Use the provider's method to clear storage - const clearedCount = authProviderRef.current.clearStorage() - - // Clear auth-related state in the hook - authUrlRef.current = undefined - setAuthUrl(undefined) - metadataRef.current = undefined - codeVerifierRef.current = undefined - - addLog('info', `Cleared ${clearedCount} storage items for server`) - }, [addLog]) + const client = getClient() + client.clearStorage() + }, [getClient]) return { state, @@ -864,8 +1059,6 @@ export async function onMcpAuthorization( } // Find the matching auth state in localStorage - // const storageKeys = Object.keys(localStorage).filter((key) => key.includes('_auth_state') && localStorage.getItem(key) === state) - const stateKey = `${storageKeyPrefix}:state_${state}` const storedState = localStorage.getItem(stateKey) console.log({ stateKey, storedState }) @@ -913,6 +1106,7 @@ export async function onMcpAuthorization( window.opener.postMessage( { type: 'mcp_auth_callback', + code: code, }, window.location.origin, ) From 2b7fcf4725e5819dfad4a99d178170662cd790de Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 17:54:20 +1100 Subject: [PATCH 029/104] Successfully logging in using popup. Types busted though --- src/react/index.ts | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/react/index.ts b/src/react/index.ts index d1062f4..c75ddee 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -307,7 +307,7 @@ class McpClient { // Client and transport private client: Client | null = null private transport: SSEClientTransport | null = null - private authProvider: BrowserOAuthClientProvider | null = null + private authProvider: BrowserOAuthClientProvider | undefined = undefined // Authentication state private metadata?: OAuthMetadata @@ -736,7 +736,9 @@ class McpClient { const tokens = JSON.parse(storedTokens) if (tokens.access_token) { this.addLog('info', 'Found tokens in localStorage via polling') - resolve(tokens.access_token) + // Resolve with an object that indicates tokens are already available + // This will signal to handleAuthCompletion that no token exchange is needed + resolve({ tokensAlreadyExchanged: true }) } } } catch (err) { @@ -747,8 +749,8 @@ class McpClient { // Start polling every 500ms using setTimeout for recursive polling const poll = () => { - pollForTokens() pollIntervalId = setTimeout(poll, 500) as unknown as number + pollForTokens() } poll() // Start the polling @@ -780,15 +782,27 @@ class McpClient { /** * Handle authentication completion + * @param codeOrResult - Either the authorization code or an object indicating tokens are already available */ - async handleAuthCompletion(code: string): Promise { + async handleAuthCompletion(codeOrResult: string | { tokensAlreadyExchanged: boolean }): Promise { if (!this.authProvider || !this.transport) { throw new Error('Authentication context not available') } try { - this.addLog('info', 'Finishing authorization...') - await this.transport.finishAuth(code) + // Check if we received an object indicating tokens are already available + if (typeof codeOrResult === 'object' && codeOrResult.tokensAlreadyExchanged) { + this.addLog('info', 'Using already exchanged tokens from localStorage') + // No need to exchange tokens, they're already in localStorage + } else if (typeof codeOrResult === 'string') { + // We received an authorization code that needs to be exchanged + this.addLog('info', 'Finishing authorization with code exchange...') + await this.transport.finishAuth(codeOrResult) + this.addLog('info', 'Authorization code exchanged for tokens') + } else { + throw new Error('Invalid authentication result') + } + this.addLog('info', 'Authorization completed') // Reset auth URL state @@ -962,11 +976,20 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { const messageHandler = (event: MessageEvent) => { if (event.origin !== window.location.origin) return - if (event.data && event.data.type === 'mcp_auth_callback' && event.data.code) { + if (event.data && event.data.type === 'mcp_auth_callback') { const client = getClient() - client.handleAuthCompletion(event.data.code).catch((err) => { - console.error('Auth callback error:', err) - }) + + // If code is provided, use it; otherwise, assume tokens are already in localStorage + if (event.data.code) { + client.handleAuthCompletion(event.data.code).catch((err) => { + console.error('Auth callback error:', err) + }) + } else { + // Tokens were already exchanged by the popup + client.handleAuthCompletion({ tokensAlreadyExchanged: true }).catch((err) => { + console.error('Auth callback error:', err) + }) + } } } @@ -1106,7 +1129,8 @@ export async function onMcpAuthorization( window.opener.postMessage( { type: 'mcp_auth_callback', - code: code, + // Don't send the code back since we've already done the token exchange + // This signals to the main window that tokens are already in localStorage }, window.location.origin, ) From 5f189c23b32e5c4665ab8bf5cf64bad949d5fbf5 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 18:08:45 +1100 Subject: [PATCH 030/104] Now it's working! --- src/react/index.ts | 48 +++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/react/index.ts b/src/react/index.ts index c75ddee..134d454 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -97,6 +97,8 @@ class BrowserOAuthClientProvider implements OAuthClientProvider { private clientName: string private clientUri: string private callbackUrl: string + // Store additional options for popup windows + private popupFeatures: string constructor( readonly serverUrl: string, @@ -105,6 +107,7 @@ class BrowserOAuthClientProvider implements OAuthClientProvider { clientName?: string clientUri?: string callbackUrl?: string + popupFeatures?: string } = {}, ) { this.storageKeyPrefix = options.storageKeyPrefix || 'mcp:auth' @@ -112,6 +115,7 @@ class BrowserOAuthClientProvider implements OAuthClientProvider { this.clientName = options.clientName || 'MCP Browser Client' this.clientUri = options.clientUri || window.location.origin this.callbackUrl = options.callbackUrl || new URL('/oauth/callback', window.location.origin).toString() + this.popupFeatures = options.popupFeatures || 'width=600,height=700,resizable=yes,scrollbars=yes' } get redirectUrl(): string { @@ -216,12 +220,21 @@ class BrowserOAuthClientProvider implements OAuthClientProvider { localStorage.setItem(key, JSON.stringify(tokens)) } - async redirectToAuthorization( + /** + * Redirect method that matches the interface expected by OAuthClientProvider + */ + async redirectToAuthorization(authorizationUrl: URL): Promise { + // Simply open the URL in the current window + console.log('WE WERE ABOUT TO REDIRECT BUT WE DONT DO THAT HERE') + // window.location.href = authorizationUrl.toString() + } + + /** + * Extended popup-based authorization method specific to browser environments + */ + async openAuthorizationPopup( authorizationUrl: URL, metadata: OAuthMetadata, - options?: { - popupFeatures?: string - }, ): Promise<{ success: boolean; popupBlocked?: boolean; url: string }> { // Store the auth state for the popup flow const state = Math.random().toString(36).substring(2) @@ -238,14 +251,13 @@ class BrowserOAuthClientProvider implements OAuthClientProvider { authorizationUrl.searchParams.set('state', state) const authUrl = authorizationUrl.toString() - const popupFeatures = options?.popupFeatures || 'width=600,height=700,resizable=yes,scrollbars=yes' // Store the auth URL in case we need it for manual authentication localStorage.setItem(this.getKey('auth_url'), authUrl) try { // Open the authorization URL in a popup window - const popup = window.open(authUrl, 'mcp_auth', popupFeatures) + const popup = window.open(authUrl, 'mcp_auth', this.popupFeatures) // Check if popup was blocked or closed immediately if (!popup || popup.closed || popup.closed === undefined) { @@ -735,10 +747,10 @@ class McpClient { // Parse tokens to make sure they're valid const tokens = JSON.parse(storedTokens) if (tokens.access_token) { - this.addLog('info', 'Found tokens in localStorage via polling') + console.log('Found tokens in localStorage via polling') // Resolve with an object that indicates tokens are already available // This will signal to handleAuthCompletion that no token exchange is needed - resolve({ tokensAlreadyExchanged: true }) + resolve('TOKENS_ALREADY_EXCHANGED') } } } catch (err) { @@ -759,9 +771,7 @@ class McpClient { // Redirect to authorization this.addLog('info', 'Opening authorization window...') assert(this.metadata, 'Metadata not available') - const redirectResult = await this.authProvider.redirectToAuthorization(this.authUrlRef, this.metadata, { - popupFeatures: this.options.popupFeatures, - }) + const redirectResult = await this.authProvider.openAuthorizationPopup(this.authUrlRef, this.metadata) if (!redirectResult.success) { // Popup was blocked @@ -782,25 +792,23 @@ class McpClient { /** * Handle authentication completion - * @param codeOrResult - Either the authorization code or an object indicating tokens are already available + * @param code - The authorization code or special token indicator */ - async handleAuthCompletion(codeOrResult: string | { tokensAlreadyExchanged: boolean }): Promise { + async handleAuthCompletion(code: string): Promise { if (!this.authProvider || !this.transport) { throw new Error('Authentication context not available') } try { - // Check if we received an object indicating tokens are already available - if (typeof codeOrResult === 'object' && codeOrResult.tokensAlreadyExchanged) { + // Check if this is our special token indicator + if (code === 'TOKENS_ALREADY_EXCHANGED') { this.addLog('info', 'Using already exchanged tokens from localStorage') // No need to exchange tokens, they're already in localStorage - } else if (typeof codeOrResult === 'string') { + } else { // We received an authorization code that needs to be exchanged this.addLog('info', 'Finishing authorization with code exchange...') - await this.transport.finishAuth(codeOrResult) + await this.transport.finishAuth(code) this.addLog('info', 'Authorization code exchanged for tokens') - } else { - throw new Error('Invalid authentication result') } this.addLog('info', 'Authorization completed') @@ -986,7 +994,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { }) } else { // Tokens were already exchanged by the popup - client.handleAuthCompletion({ tokensAlreadyExchanged: true }).catch((err) => { + client.handleAuthCompletion('TOKENS_ALREADY_EXCHANGED').catch((err) => { console.error('Auth callback error:', err) }) } From cad8a757422409311749e43340f86baebb20641f Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 19:13:49 +1100 Subject: [PATCH 031/104] 0.0.10-3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ff9059..b9bf3dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.10-2", + "version": "0.0.10-3", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From 17ee6e0bc5219d3628733770546d7cebf139bf54 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 20:04:56 +1100 Subject: [PATCH 032/104] If popups are blocked, the link should now work --- src/react/index.ts | 95 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/src/react/index.ts b/src/react/index.ts index 134d454..43f8a4f 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -93,7 +93,7 @@ type StoredState = { */ class BrowserOAuthClientProvider implements OAuthClientProvider { private storageKeyPrefix: string - private serverUrlHash: string + serverUrlHash: string private clientName: string private clientUri: string private callbackUrl: string @@ -236,19 +236,27 @@ class BrowserOAuthClientProvider implements OAuthClientProvider { authorizationUrl: URL, metadata: OAuthMetadata, ): Promise<{ success: boolean; popupBlocked?: boolean; url: string }> { - // Store the auth state for the popup flow - const state = Math.random().toString(36).substring(2) - const stateKey = `${this.storageKeyPrefix}:state_${state}` - localStorage.setItem( - stateKey, - JSON.stringify({ - authorizationUrl: authorizationUrl.toString(), - metadata, - serverUrlHash: this.serverUrlHash, - expiry: +new Date() + 1000 * 60 * 5 /* 5 minutes */, - } as StoredState), - ) - authorizationUrl.searchParams.set('state', state) + // Use existing state parameter if it exists in the URL + const existingState = authorizationUrl.searchParams.get('state') + + if (!existingState) { + // This should not happen as startAuthFlow should've added state + // But if it doesn't exist, add it as a fallback + const state = Math.random().toString(36).substring(2) + const stateKey = `${this.storageKeyPrefix}:state_${state}` + + localStorage.setItem( + stateKey, + JSON.stringify({ + authorizationUrl: authorizationUrl.toString(), + metadata, + serverUrlHash: this.serverUrlHash, + expiry: +new Date() + 1000 * 60 * 5 /* 5 minutes */, + } as StoredState), + ) + + authorizationUrl.searchParams.set('state', state) + } const authUrl = authorizationUrl.toString() @@ -324,6 +332,7 @@ class McpClient { // Authentication state private metadata?: OAuthMetadata private authUrlRef?: URL + private authState?: string private codeVerifier?: string private connecting = false @@ -675,7 +684,30 @@ class McpClient { // Save code verifier and auth URL for later use await this.authProvider.saveCodeVerifier(codeVerifier) this.codeVerifier = codeVerifier + + // Generate state parameter that will be used for both popup and manual flows + const state = Math.random().toString(36).substring(2) + const stateKey = `${this.options.storageKeyPrefix}:state_${state}` + + // Store state for later retrieval + localStorage.setItem( + stateKey, + JSON.stringify({ + authorizationUrl: authorizationUrl.toString(), + metadata: this.metadata, + serverUrlHash: this.authProvider.serverUrlHash, + expiry: +new Date() + 1000 * 60 * 5 /* 5 minutes */, + } as StoredState), + ) + + // Add state to the URL + authorizationUrl.searchParams.set('state', state) + + // Store the state and URL for later use + this.authState = state this.authUrlRef = authorizationUrl + + // Set manual auth URL (already includes state parameter) this.setAuthUrl(authorizationUrl.toString()) return authorizationUrl @@ -863,14 +895,41 @@ class McpClient { * Manually trigger authentication */ async authenticate(): Promise { - if (!this.authUrlRef) { - await this.startAuthFlow() + if (!this.authProvider) { + try { + // Discover OAuth metadata if we don't have it yet + this.addLog('info', 'Discovering OAuth metadata...') + this.metadata = await discoverOAuthMetadata(this.url) + this.addLog('debug', `OAuth metadata: ${this.metadata ? 'Found' : 'Not available'}`) + + if (!this.metadata) { + throw new Error('No OAuth metadata available') + } + + // Initialize the auth provider now that we have metadata + this.initAuthProvider() + } catch (err) { + this.addLog('error', `Failed to discover OAuth metadata: ${err instanceof Error ? err.message : String(err)}`) + return undefined + } } - if (this.authUrlRef) { + try { + // If we don't have an auth URL yet with state param, start a new flow + if (!this.authUrlRef || !this.authUrlRef.searchParams.get('state')) { + await this.startAuthFlow() + } + + if (!this.authUrlRef) { + throw new Error('Failed to create authorization URL') + } + + // The URL already has the state parameter from startAuthFlow return this.authUrlRef.toString() + } catch (err) { + this.addLog('error', `Error preparing manual authentication: ${err instanceof Error ? err.message : String(err)}`) + return undefined } - return undefined } /** From 140f30c6f909d8ba6f420d18a9bb179b5502de03 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 25 Mar 2025 20:05:07 +1100 Subject: [PATCH 033/104] 0.0.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b9bf3dc..e9f0be6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.10-3", + "version": "0.0.10", "type": "module", "bin": { "mcp-remote": "dist/cli/proxy.js" From d41e92812f033dc381cf3971211cf497a0fcdce2 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 14:32:41 +1100 Subject: [PATCH 034/104] moving things around now that /react is moving to use-mcp --- package.json | 27 +- src/{cli => }/client.ts | 10 +- src/{cli => }/proxy.ts | 4 +- src/react/index.ts | 1243 --------------------------------------- src/{cli => }/shared.ts | 2 +- 5 files changed, 22 insertions(+), 1264 deletions(-) rename src/{cli => }/client.ts (96%) rename src/{cli => }/proxy.ts (96%) delete mode 100644 src/react/index.ts rename src/{cli => }/shared.ts (99%) diff --git a/package.json b/package.json index e9f0be6..a7c0e02 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,25 @@ { "name": "mcp-remote", "version": "0.0.10", + "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", + "keywords": [ + "mcp", + "stdio", + "sse", + "remote", + "oauth" + ], + "author": "Glen Maddern ", + "repository": "https://github.com/geelen/remote-mcp", "type": "module", - "bin": { - "mcp-remote": "dist/cli/proxy.js" - }, "files": [ "dist", "README.md", "LICENSE" ], - "exports": { - "./react": { - "types": "./dist/react/index.d.ts", - "require": "./dist/react/index.js", - "import": "./dist/react/index.js" - } + "main": "dist/index.js", + "bin": { + "mcp-remote": "dist/cli/proxy.js" }, "scripts": { "dev": "tsup --watch", @@ -39,9 +43,8 @@ }, "tsup": { "entry": [ - "src/cli/client.ts", - "src/cli/proxy.ts", - "src/react/index.ts" + "src/client.ts", + "src/proxy.ts" ], "format": [ "esm" diff --git a/src/cli/client.ts b/src/client.ts similarity index 96% rename from src/cli/client.ts rename to src/client.ts index f79e917..192c178 100644 --- a/src/cli/client.ts +++ b/src/client.ts @@ -14,7 +14,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' -import { NodeOAuthClientProvider, setupOAuthCallbackServer, parseCommandLineArgs, setupSignalHandlers } from './shared.js' +import { NodeOAuthClientProvider, setupOAuthCallbackServer, parseCommandLineArgs, setupSignalHandlers } from './shared' /** * Main function to run the client @@ -33,13 +33,11 @@ async function runClient(serverUrl: string, callbackPort: number) { // Create the client const client = new Client( { - name: 'mcp-cli', - version: '0.1.0', + name: 'mcp-remote', + version: require('../package.json').version, }, { - capabilities: { - sampling: {}, - }, + capabilities: {}, }, ) diff --git a/src/cli/proxy.ts b/src/proxy.ts similarity index 96% rename from src/cli/proxy.ts rename to src/proxy.ts index d705b35..d2b7e4a 100644 --- a/src/cli/proxy.ts +++ b/src/proxy.ts @@ -11,8 +11,8 @@ import { EventEmitter } from 'events' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { NodeOAuthClientProvider, setupOAuthCallbackServer, parseCommandLineArgs, setupSignalHandlers } from './shared.js' -import { connectToRemoteServer, mcpProxy } from '../lib/utils.js' +import { NodeOAuthClientProvider, setupOAuthCallbackServer, parseCommandLineArgs, setupSignalHandlers } from './shared' +import { connectToRemoteServer, mcpProxy } from './lib/utils' /** * Main function to run the proxy diff --git a/src/react/index.ts b/src/react/index.ts deleted file mode 100644 index 43f8a4f..0000000 --- a/src/react/index.ts +++ /dev/null @@ -1,1243 +0,0 @@ -import { CallToolResultSchema, JSONRPCMessage, ListToolsResultSchema, Tool } from '@modelcontextprotocol/sdk/types.js' -import { useCallback, useEffect, useRef, useState } from 'react' -import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' -import { Client } from '@modelcontextprotocol/sdk/client/index.js' -import { - OAuthClientProvider, - discoverOAuthMetadata, - exchangeAuthorization, - startAuthorization, -} from '@modelcontextprotocol/sdk/client/auth.js' -import { OAuthClientInformation, OAuthMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js' - -function assert(condition: unknown, message: string): asserts condition { - if (!condition) { - throw new Error(message) - } -} - -export type UseMcpOptions = { - /** The /sse URL of your remote MCP server */ - url: string - /** OAuth client name for registration */ - clientName?: string - /** OAuth client URI for registration */ - clientUri?: string - /** Custom callback URL for OAuth redirect (defaults to /oauth/callback on the current origin) */ - callbackUrl?: string - /** Storage key prefix for OAuth data (defaults to "mcp_auth") */ - storageKeyPrefix?: string - /** Custom configuration for the MCP client */ - clientConfig?: { - name?: string - version?: string - } - /** Whether to enable debug logging */ - debug?: boolean - /** Auto retry connection if it fails, with delay in ms (default: false) */ - autoRetry?: boolean | number - /** Auto reconnect if connection is lost, with delay in ms (default: 3000) */ - autoReconnect?: boolean | number - /** Popup window features (dimensions and behavior) for OAuth */ - popupFeatures?: string -} - -export type UseMcpResult = { - tools: Tool[] - /** - * The current state of the MCP connection. This will be one of: - * - 'discovering': Finding out whether there is in fact a server at that URL, and what its capabilities are - * - 'authenticating': The server has indicated we must authenticate, so we can't proceed until that's complete - * - 'connecting': The connection to the MCP server is being established. This happens before we know whether we need to authenticate or not, and then again once we have credentials - * - 'loading': We're connected to the MCP server, and now we're loading its resources/prompts/tools - * - 'ready': The MCP server is connected and ready to be used - * - 'failed': The connection to the MCP server failed - * */ - state: 'discovering' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed' - /** If the state is 'failed', this will be the error message */ - error?: string - /** - * If authorization was blocked, this will contain the URL to authorize manually - * The app can render this as a link with target="_blank" so the user can complete - * authorization without leaving the app - */ - authUrl?: string - /** All internal log messages */ - log: { level: 'debug' | 'info' | 'warn' | 'error'; message: string }[] - /** Call a tool on the MCP server */ - callTool: (name: string, args?: Record) => Promise - /** Manually retry connection if it's in a failed state */ - retry: () => void - /** Manually disconnect from the MCP server */ - disconnect: () => void - /** - * Manually trigger authentication - * @returns Auth URL that can be used to manually open a new window - */ - authenticate: () => Promise - /** - * Clear all localStorage items for this server - */ - clearStorage: () => void -} - -type StoredState = { - authorizationUrl: string - metadata: OAuthMetadata - serverUrlHash: string - expiry: number -} - -/** - * Browser-compatible OAuth client provider for MCP - */ -class BrowserOAuthClientProvider implements OAuthClientProvider { - private storageKeyPrefix: string - serverUrlHash: string - private clientName: string - private clientUri: string - private callbackUrl: string - // Store additional options for popup windows - private popupFeatures: string - - constructor( - readonly serverUrl: string, - options: { - storageKeyPrefix?: string - clientName?: string - clientUri?: string - callbackUrl?: string - popupFeatures?: string - } = {}, - ) { - this.storageKeyPrefix = options.storageKeyPrefix || 'mcp:auth' - this.serverUrlHash = this.hashString(serverUrl) - this.clientName = options.clientName || 'MCP Browser Client' - this.clientUri = options.clientUri || window.location.origin - this.callbackUrl = options.callbackUrl || new URL('/oauth/callback', window.location.origin).toString() - this.popupFeatures = options.popupFeatures || 'width=600,height=700,resizable=yes,scrollbars=yes' - } - - get redirectUrl(): string { - return this.callbackUrl - } - - get clientMetadata() { - return { - redirect_uris: [this.redirectUrl], - token_endpoint_auth_method: 'none', - grant_types: ['authorization_code', 'refresh_token'], - response_types: ['code'], - client_name: this.clientName, - client_uri: this.clientUri, - } - } - - /** - * Clears all storage items related to this server - * @returns The number of items cleared - */ - clearStorage(): number { - const prefix = `${this.storageKeyPrefix}_${this.serverUrlHash}` - const keysToRemove = [] - - // Find all keys that match the prefix - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i) - if (key && key.startsWith(prefix)) { - keysToRemove.push(key) - } - } - - // Also check for any state keys - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i) - if (key && key.startsWith(`${this.storageKeyPrefix}:state_`)) { - // Load state to check if it's for this server - try { - const state = JSON.parse(localStorage.getItem(key) || '{}') - if (state.serverUrlHash === this.serverUrlHash) { - keysToRemove.push(key) - } - } catch (e) { - // Ignore JSON parse errors - } - } - } - - // Remove all matching keys - keysToRemove.forEach((key) => localStorage.removeItem(key)) - - return keysToRemove.length - } - - private hashString(str: string): string { - // Simple hash function for browser environments - let hash = 0 - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i) - hash = (hash << 5) - hash + char - hash = hash & hash // Convert to 32bit integer - } - return Math.abs(hash).toString(16) - } - - getKey(key: string): string { - return `${this.storageKeyPrefix}_${this.serverUrlHash}_${key}` - } - - async clientInformation(): Promise { - const key = this.getKey('client_info') - const data = localStorage.getItem(key) - if (!data) return undefined - - try { - return JSON.parse(data) as OAuthClientInformation - } catch (e) { - return undefined - } - } - - async saveClientInformation(clientInformation: OAuthClientInformation): Promise { - const key = this.getKey('client_info') - localStorage.setItem(key, JSON.stringify(clientInformation)) - } - - async tokens(): Promise { - const key = this.getKey('tokens') - const data = localStorage.getItem(key) - if (!data) return undefined - - try { - return JSON.parse(data) as OAuthTokens - } catch (e) { - return undefined - } - } - - async saveTokens(tokens: OAuthTokens): Promise { - const key = this.getKey('tokens') - localStorage.setItem(key, JSON.stringify(tokens)) - } - - /** - * Redirect method that matches the interface expected by OAuthClientProvider - */ - async redirectToAuthorization(authorizationUrl: URL): Promise { - // Simply open the URL in the current window - console.log('WE WERE ABOUT TO REDIRECT BUT WE DONT DO THAT HERE') - // window.location.href = authorizationUrl.toString() - } - - /** - * Extended popup-based authorization method specific to browser environments - */ - async openAuthorizationPopup( - authorizationUrl: URL, - metadata: OAuthMetadata, - ): Promise<{ success: boolean; popupBlocked?: boolean; url: string }> { - // Use existing state parameter if it exists in the URL - const existingState = authorizationUrl.searchParams.get('state') - - if (!existingState) { - // This should not happen as startAuthFlow should've added state - // But if it doesn't exist, add it as a fallback - const state = Math.random().toString(36).substring(2) - const stateKey = `${this.storageKeyPrefix}:state_${state}` - - localStorage.setItem( - stateKey, - JSON.stringify({ - authorizationUrl: authorizationUrl.toString(), - metadata, - serverUrlHash: this.serverUrlHash, - expiry: +new Date() + 1000 * 60 * 5 /* 5 minutes */, - } as StoredState), - ) - - authorizationUrl.searchParams.set('state', state) - } - - const authUrl = authorizationUrl.toString() - - // Store the auth URL in case we need it for manual authentication - localStorage.setItem(this.getKey('auth_url'), authUrl) - - try { - // Open the authorization URL in a popup window - const popup = window.open(authUrl, 'mcp_auth', this.popupFeatures) - - // Check if popup was blocked or closed immediately - if (!popup || popup.closed || popup.closed === undefined) { - console.warn('Popup blocked. Returning error.') - return { success: false, popupBlocked: true, url: authUrl } - } - - // Try to access the popup to confirm it's not blocked - try { - // Just accessing any property will throw if popup is blocked - const popupLocation = popup.location - // If we can read location.href, the popup is definitely working - if (popupLocation.href) { - // Successfully opened popup - return { success: true, url: authUrl } - } - } catch (e) { - // Access to the popup was denied, indicating it's blocked - console.warn('Popup blocked (security exception).') - return { success: false, popupBlocked: true, url: authUrl } - } - - // If we got here, popup is working - return { success: true, url: authUrl } - } catch (e) { - // Error opening popup - console.warn('Error opening popup:', e) - return { success: false, popupBlocked: true, url: authUrl } - } - } - - async saveCodeVerifier(codeVerifier: string): Promise { - const key = this.getKey('code_verifier') - localStorage.setItem(key, codeVerifier) - } - - async codeVerifier(): Promise { - const key = this.getKey('code_verifier') - const verifier = localStorage.getItem(key) - if (!verifier) { - throw new Error('No code verifier found in storage') - } - return verifier - } -} - -/** - * Class to encapsulate all MCP client functionality, - * including authentication flow and connection management - */ -class McpClient { - // State - private _state: UseMcpResult['state'] = 'discovering' - private _error?: string - private _tools: Tool[] = [] - private _log: UseMcpResult['log'] = [] - private _authUrl?: string - - // Client and transport - private client: Client | null = null - private transport: SSEClientTransport | null = null - private authProvider: BrowserOAuthClientProvider | undefined = undefined - - // Authentication state - private metadata?: OAuthMetadata - private authUrlRef?: URL - private authState?: string - private codeVerifier?: string - private connecting = false - - // Update callbacks - private onStateChange: (state: UseMcpResult['state']) => void - private onToolsChange: (tools: Tool[]) => void - private onErrorChange: (error?: string) => void - private onLogChange: (log: UseMcpResult['log']) => void - private onAuthUrlChange: (authUrl?: string) => void - - constructor( - private url: string, - private options: { - clientName?: string - clientUri?: string - callbackUrl?: string - storageKeyPrefix?: string - clientConfig?: { - name?: string - version?: string - } - debug?: boolean - autoRetry?: boolean | number - autoReconnect?: boolean | number - popupFeatures?: string - }, - callbacks: { - onStateChange: (state: UseMcpResult['state']) => void - onToolsChange: (tools: Tool[]) => void - onErrorChange: (error?: string) => void - onLogChange: (log: UseMcpResult['log']) => void - onAuthUrlChange: (authUrl?: string) => void - }, - ) { - // Initialize callbacks - this.onStateChange = callbacks.onStateChange - this.onToolsChange = callbacks.onToolsChange - this.onErrorChange = callbacks.onErrorChange - this.onLogChange = callbacks.onLogChange - this.onAuthUrlChange = callbacks.onAuthUrlChange - - // Initialize auth provider - this.initAuthProvider() - } - - get state(): UseMcpResult['state'] { - return this._state - } - - get tools(): Tool[] { - return this._tools - } - - get error(): string | undefined { - return this._error - } - - get log(): UseMcpResult['log'] { - return this._log - } - - get authUrl(): string | undefined { - return this._authUrl - } - - /** - * Initialize the auth provider - */ - private initAuthProvider(): void { - if (!this.authProvider) { - this.authProvider = new BrowserOAuthClientProvider(this.url, { - storageKeyPrefix: this.options.storageKeyPrefix, - clientName: this.options.clientName, - clientUri: this.options.clientUri, - callbackUrl: this.options.callbackUrl, - }) - } - } - - /** - * Add a log entry - */ - private addLog(level: 'debug' | 'info' | 'warn' | 'error', message: string): void { - if (level === 'debug' && !this.options.debug) return - this._log = [...this._log, { level, message }] - this.onLogChange(this._log) - } - - /** - * Update the state - */ - private setState(state: UseMcpResult['state']): void { - this._state = state - this.onStateChange(state) - } - - /** - * Update the error - */ - private setError(error?: string): void { - this._error = error - this.onErrorChange(error) - } - - /** - * Update the tools - */ - private setTools(tools: Tool[]): void { - this._tools = tools - this.onToolsChange(tools) - } - - /** - * Update the auth URL - */ - private setAuthUrl(authUrl?: string): void { - this._authUrl = authUrl - this.onAuthUrlChange(authUrl) - } - - /** - * Handle OAuth discovery and authentication - */ - private async discoverOAuthAndAuthenticate(error: Error): Promise { - try { - // Discover OAuth metadata now that we know we need it - if (!this.metadata) { - this.addLog('info', 'Discovering OAuth metadata...') - this.metadata = await discoverOAuthMetadata(this.url) - this.addLog('debug', `OAuth metadata: ${this.metadata ? 'Found' : 'Not available'}`) - } - - // If metadata is found, start auth flow - if (this.metadata) { - this.setState('authenticating') - - try { - // Start authentication process - await this.handleAuthentication() - - // After successful auth, retry connection - // Important: We need to fully disconnect and reconnect - await this.disconnect() - await this.connect() - } catch (authErr) { - this.addLog('error', `Authentication error: ${authErr instanceof Error ? authErr.message : String(authErr)}`) - this.setState('failed') - this.setError(`Authentication failed: ${authErr instanceof Error ? authErr.message : String(authErr)}`) - this.connecting = false - } - } else { - // No OAuth metadata available - this.setState('failed') - this.setError(`Authentication required but no OAuth metadata found: ${error.message}`) - this.connecting = false - } - } catch (oauthErr) { - this.addLog('error', `OAuth discovery error: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) - this.setState('failed') - this.setError(`Authentication setup failed: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`) - this.connecting = false - } - } - - /** - * Connect to the MCP server - */ - async connect(): Promise { - // Prevent multiple simultaneous connection attempts - if (this.connecting) return - this.connecting = true - - try { - this.setState('discovering') - this.setError(undefined) - - // Create MCP client - this.client = new Client( - { - name: this.options.clientConfig?.name || 'mcp-react-client', - version: this.options.clientConfig?.version || '0.1.0', - }, - { - capabilities: { - sampling: {}, - }, - }, - ) - - // Create SSE transport - this.setState('connecting') - this.addLog('info', 'Creating transport...') - - const serverUrl = new URL(this.url) - this.transport = new SSEClientTransport(serverUrl, { - authProvider: this.authProvider, - }) - - // Set up transport handlers - this.transport.onmessage = (message: JSONRPCMessage) => { - // @ts-expect-error TODO: fix this type - this.addLog('debug', `Received message: ${message.method || message.id}`) - } - - this.transport.onerror = (err: Error) => { - this.addLog('error', `Transport error: ${err.message}`) - - if (err.message.includes('Unauthorized')) { - // Only discover OAuth metadata and authenticate if we get a 401 - this.discoverOAuthAndAuthenticate(err) - } else { - this.setState('failed') - this.setError(`Connection error: ${err.message}`) - this.connecting = false - } - } - - this.transport.onclose = () => { - this.addLog('info', 'Connection closed') - // If we were previously connected, try to reconnect - if (this.state === 'ready' && this.options.autoReconnect) { - const delay = typeof this.options.autoReconnect === 'number' ? this.options.autoReconnect : 3000 - this.addLog('info', `Will reconnect in ${delay}ms...`) - setTimeout(() => { - this.disconnect().then(() => this.connect()) - }, delay) - } - } - - // Try connecting transport - try { - this.addLog('info', 'Starting transport...') - // await this.transport.start() - } catch (err) { - this.addLog('error', `Transport start error: ${err instanceof Error ? err.message : String(err)}`) - - if (err instanceof Error && err.message.includes('Unauthorized')) { - // Only discover OAuth and authenticate if we get a 401 - await this.discoverOAuthAndAuthenticate(err) - return // Important: Return here to avoid proceeding with the unauthorized connection - } else { - this.setState('failed') - this.setError(`Connection error: ${err instanceof Error ? err.message : String(err)}`) - this.connecting = false - return - } - } - - // Connect client - try { - this.addLog('info', 'Connecting client...') - this.setState('loading') - await this.client.connect(this.transport) - this.addLog('info', 'Client connected') - - // Load tools - try { - this.addLog('info', 'Loading tools...') - const toolsResponse = await this.client.request({ method: 'tools/list' }, ListToolsResultSchema) - this.setTools(toolsResponse.tools) - this.addLog('info', `Loaded ${toolsResponse.tools.length} tools`) - - // Connection completed successfully - this.setState('ready') - this.connecting = false - } catch (toolErr) { - this.addLog('error', `Error loading tools: ${toolErr instanceof Error ? toolErr.message : String(toolErr)}`) - // We're still connected, just couldn't load tools - this.setState('ready') - this.connecting = false - } - } catch (connectErr) { - this.addLog('error', `Client connect error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) - - if (connectErr instanceof Error && connectErr.message.includes('Unauthorized')) { - // Only discover OAuth and authenticate if we get a 401 - await this.discoverOAuthAndAuthenticate(connectErr) - } else { - this.setState('failed') - this.setError(`Connection error: ${connectErr instanceof Error ? connectErr.message : String(connectErr)}`) - this.connecting = false - } - } - } catch (err) { - this.addLog('error', `Unexpected error: ${err instanceof Error ? err.message : String(err)}`) - this.setState('failed') - this.setError(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`) - this.connecting = false - } - } - - /** - * Disconnect from the MCP server - */ - async disconnect(): Promise { - if (this.client) { - try { - await this.client.close() - } catch (err) { - this.addLog('error', `Error closing client: ${err instanceof Error ? err.message : String(err)}`) - } - this.client = null - } - - if (this.transport) { - try { - await this.transport.close() - } catch (err) { - this.addLog('error', `Error closing transport: ${err instanceof Error ? err.message : String(err)}`) - } - this.transport = null - } - - this.connecting = false - this.setState('discovering') - this.setTools([]) - this.setError(undefined) - } - - /** - * Start the auth flow and get the auth URL - */ - async startAuthFlow(): Promise { - if (!this.authProvider || !this.metadata) { - throw new Error('Auth provider or metadata not available') - } - - this.addLog('info', 'Starting authentication flow...') - - // Check if we have client info - let clientInfo = await this.authProvider.clientInformation() - - if (!clientInfo) { - // Register client dynamically - this.addLog('info', 'No client information found, registering...') - // Note: In a complete implementation, you'd register the client here - // This would be done server-side in a real application - throw new Error('Dynamic client registration not implemented in this example') - } - - // Start authorization flow - this.addLog('info', 'Preparing authorization...') - const { authorizationUrl, codeVerifier } = await startAuthorization(this.url, { - metadata: this.metadata, - clientInformation: clientInfo, - redirectUrl: this.authProvider.redirectUrl, - }) - - // Save code verifier and auth URL for later use - await this.authProvider.saveCodeVerifier(codeVerifier) - this.codeVerifier = codeVerifier - - // Generate state parameter that will be used for both popup and manual flows - const state = Math.random().toString(36).substring(2) - const stateKey = `${this.options.storageKeyPrefix}:state_${state}` - - // Store state for later retrieval - localStorage.setItem( - stateKey, - JSON.stringify({ - authorizationUrl: authorizationUrl.toString(), - metadata: this.metadata, - serverUrlHash: this.authProvider.serverUrlHash, - expiry: +new Date() + 1000 * 60 * 5 /* 5 minutes */, - } as StoredState), - ) - - // Add state to the URL - authorizationUrl.searchParams.set('state', state) - - // Store the state and URL for later use - this.authState = state - this.authUrlRef = authorizationUrl - - // Set manual auth URL (already includes state parameter) - this.setAuthUrl(authorizationUrl.toString()) - - return authorizationUrl - } - - /** - * Handle authentication flow - */ - async handleAuthentication(): Promise { - if (!this.authProvider) { - throw new Error('Auth provider not available') - } - - // Get or create the auth URL - if (!this.authUrlRef) { - try { - await this.startAuthFlow() - } catch (err) { - this.addLog('error', `Failed to start auth flow: ${err instanceof Error ? err.message : String(err)}`) - throw err - } - } - - if (!this.authUrlRef) { - throw new Error('Failed to create authorization URL') - } - - // Set up listener for post-auth message - const authPromise = new Promise((resolve, reject) => { - let pollIntervalId: number | undefined - - const timeoutId = setTimeout( - () => { - window.removeEventListener('message', messageHandler) - if (pollIntervalId) clearTimeout(pollIntervalId) - reject(new Error('Authentication timeout after 5 minutes')) - }, - 5 * 60 * 1000, - ) - - const messageHandler = (event: MessageEvent) => { - // Verify origin for security - if (event.origin !== window.location.origin) return - - if (event.data && event.data.type === 'mcp_auth_callback' && event.data.code) { - window.removeEventListener('message', messageHandler) - clearTimeout(timeoutId) - if (pollIntervalId) clearTimeout(pollIntervalId) - - resolve(event.data.code) - } - } - - window.addEventListener('message', messageHandler) - - // Add polling fallback to check for tokens in localStorage - const pollForTokens = () => { - try { - // Check if tokens have appeared in localStorage - const tokensKey = this.authProvider!.getKey('tokens') - const storedTokens = localStorage.getItem(tokensKey) - - if (storedTokens) { - // Tokens found, clean up and resolve - window.removeEventListener('message', messageHandler) - clearTimeout(timeoutId) - if (pollIntervalId) clearTimeout(pollIntervalId) - - // Parse tokens to make sure they're valid - const tokens = JSON.parse(storedTokens) - if (tokens.access_token) { - console.log('Found tokens in localStorage via polling') - // Resolve with an object that indicates tokens are already available - // This will signal to handleAuthCompletion that no token exchange is needed - resolve('TOKENS_ALREADY_EXCHANGED') - } - } - } catch (err) { - // Error during polling, continue anyway - console.error(err) - } - } - - // Start polling every 500ms using setTimeout for recursive polling - const poll = () => { - pollIntervalId = setTimeout(poll, 500) as unknown as number - pollForTokens() - } - - poll() // Start the polling - }) - - // Redirect to authorization - this.addLog('info', 'Opening authorization window...') - assert(this.metadata, 'Metadata not available') - const redirectResult = await this.authProvider.openAuthorizationPopup(this.authUrlRef, this.metadata) - - if (!redirectResult.success) { - // Popup was blocked - this.setState('failed') - this.setError('Authentication popup was blocked by the browser. Please click the link to authenticate in a new window.') - this.setAuthUrl(redirectResult.url) - this.addLog('warn', 'Authentication popup was blocked. User needs to manually authorize.') - throw new Error('Authentication popup blocked') - } - - // Wait for auth to complete - this.addLog('info', 'Waiting for authorization...') - const code = await authPromise - this.addLog('info', 'Authorization code received') - - return code - } - - /** - * Handle authentication completion - * @param code - The authorization code or special token indicator - */ - async handleAuthCompletion(code: string): Promise { - if (!this.authProvider || !this.transport) { - throw new Error('Authentication context not available') - } - - try { - // Check if this is our special token indicator - if (code === 'TOKENS_ALREADY_EXCHANGED') { - this.addLog('info', 'Using already exchanged tokens from localStorage') - // No need to exchange tokens, they're already in localStorage - } else { - // We received an authorization code that needs to be exchanged - this.addLog('info', 'Finishing authorization with code exchange...') - await this.transport.finishAuth(code) - this.addLog('info', 'Authorization code exchanged for tokens') - } - - this.addLog('info', 'Authorization completed') - - // Reset auth URL state - this.authUrlRef = undefined - this.setAuthUrl(undefined) - - // Reconnect with the new auth token - important to do a full disconnect/connect cycle - await this.disconnect() - await this.connect() - } catch (err) { - this.addLog('error', `Auth completion error: ${err instanceof Error ? err.message : String(err)}`) - this.setState('failed') - this.setError(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`) - } - } - - /** - * Call a tool on the MCP server - */ - async callTool(name: string, args?: Record): Promise { - if (!this.client || this.state !== 'ready') { - throw new Error('MCP client not ready') - } - - try { - const result = await this.client.request( - { - method: 'tools/call', - params: { name, arguments: args }, - }, - CallToolResultSchema, - ) - return result - } catch (err) { - this.addLog('error', `Error calling tool ${name}: ${err instanceof Error ? err.message : String(err)}`) - throw err - } - } - - /** - * Retry connection - */ - retry(): void { - if (this.state === 'failed') { - this.disconnect().then(() => this.connect()) - } - } - - /** - * Manually trigger authentication - */ - async authenticate(): Promise { - if (!this.authProvider) { - try { - // Discover OAuth metadata if we don't have it yet - this.addLog('info', 'Discovering OAuth metadata...') - this.metadata = await discoverOAuthMetadata(this.url) - this.addLog('debug', `OAuth metadata: ${this.metadata ? 'Found' : 'Not available'}`) - - if (!this.metadata) { - throw new Error('No OAuth metadata available') - } - - // Initialize the auth provider now that we have metadata - this.initAuthProvider() - } catch (err) { - this.addLog('error', `Failed to discover OAuth metadata: ${err instanceof Error ? err.message : String(err)}`) - return undefined - } - } - - try { - // If we don't have an auth URL yet with state param, start a new flow - if (!this.authUrlRef || !this.authUrlRef.searchParams.get('state')) { - await this.startAuthFlow() - } - - if (!this.authUrlRef) { - throw new Error('Failed to create authorization URL') - } - - // The URL already has the state parameter from startAuthFlow - return this.authUrlRef.toString() - } catch (err) { - this.addLog('error', `Error preparing manual authentication: ${err instanceof Error ? err.message : String(err)}`) - return undefined - } - } - - /** - * Clear all localStorage items for this server - */ - clearStorage(): number { - if (!this.authProvider) { - this.addLog('warn', 'Cannot clear storage: auth provider not initialized') - return 0 - } - - // Use the provider's method to clear storage - const clearedCount = this.authProvider.clearStorage() - - // Clear auth-related state in the class - this.authUrlRef = undefined - this.setAuthUrl(undefined) - this.metadata = undefined - this.codeVerifier = undefined - - this.addLog('info', `Cleared ${clearedCount} storage items for server`) - - return clearedCount - } -} - -/** - * useMcp is a React hook that connects to a remote MCP server, negotiates auth - * (including opening a popup window or new tab to complete the OAuth flow), - * and enables passing a list of tools (once loaded) to ai-sdk (using `useChat`). - */ -export function useMcp(options: UseMcpOptions): UseMcpResult { - const [state, setState] = useState('discovering') - const [tools, setTools] = useState([]) - const [error, setError] = useState(undefined) - const [log, setLog] = useState([]) - const [authUrl, setAuthUrl] = useState(undefined) - - // Use a ref to maintain a single instance of the McpClient - const clientRef = useRef(null) - const isInitialMount = useRef(true) - - // Initialize the client if it doesn't exist yet - const getClient = useCallback(() => { - if (!clientRef.current) { - clientRef.current = new McpClient( - options.url, - { - clientName: options.clientName || 'MCP React Client', - clientUri: options.clientUri || window.location.origin, - callbackUrl: options.callbackUrl || new URL('/oauth/callback', window.location.origin).toString(), - storageKeyPrefix: options.storageKeyPrefix || 'mcp:auth', - clientConfig: options.clientConfig || { - name: 'mcp-react-client', - version: '0.1.0', - }, - debug: options.debug || false, - autoRetry: options.autoRetry || false, - autoReconnect: options.autoReconnect || 3000, - popupFeatures: options.popupFeatures || 'width=600,height=700,resizable=yes,scrollbars=yes', - }, - { - onStateChange: setState, - onToolsChange: setTools, - onErrorChange: setError, - onLogChange: setLog, - onAuthUrlChange: setAuthUrl, - }, - ) - } - return clientRef.current - }, [ - options.url, - options.clientName, - options.clientUri, - options.callbackUrl, - options.storageKeyPrefix, - options.clientConfig, - options.debug, - options.autoRetry, - options.autoReconnect, - options.popupFeatures, - ]) - - // Connect on initial mount - useEffect(() => { - if (isInitialMount.current) { - isInitialMount.current = false - const client = getClient() - client.connect() - } - }, [getClient]) - - // Auto-retry on failure - useEffect(() => { - if (state === 'failed' && options.autoRetry) { - const delay = typeof options.autoRetry === 'number' ? options.autoRetry : 5000 - const timeoutId = setTimeout(() => { - const client = getClient() - client.retry() - }, delay) - - return () => { - clearTimeout(timeoutId) - } - } - }, [state, options.autoRetry, getClient]) - - // Set up message listener for auth callback - useEffect(() => { - const messageHandler = (event: MessageEvent) => { - if (event.origin !== window.location.origin) return - - if (event.data && event.data.type === 'mcp_auth_callback') { - const client = getClient() - - // If code is provided, use it; otherwise, assume tokens are already in localStorage - if (event.data.code) { - client.handleAuthCompletion(event.data.code).catch((err) => { - console.error('Auth callback error:', err) - }) - } else { - // Tokens were already exchanged by the popup - client.handleAuthCompletion('TOKENS_ALREADY_EXCHANGED').catch((err) => { - console.error('Auth callback error:', err) - }) - } - } - } - - window.addEventListener('message', messageHandler) - return () => { - window.removeEventListener('message', messageHandler) - } - }, [getClient]) - - // Clean up on unmount - useEffect(() => { - return () => { - if (clientRef.current) { - clientRef.current.disconnect() - } - } - }, []) - - // Public methods - proxied to the client - const callTool = useCallback( - async (name: string, args?: Record) => { - const client = getClient() - return client.callTool(name, args) - }, - [getClient], - ) - - const retry = useCallback(() => { - const client = getClient() - client.retry() - }, [getClient]) - - const disconnect = useCallback(async () => { - const client = getClient() - await client.disconnect() - }, [getClient]) - - const authenticate = useCallback(async (): Promise => { - const client = getClient() - return client.authenticate() - }, [getClient]) - - const clearStorage = useCallback(() => { - const client = getClient() - client.clearStorage() - }, [getClient]) - - return { - state, - tools, - error, - log, - authUrl, - callTool, - retry, - disconnect, - authenticate, - clearStorage, - } -} - -/** - * onMcpAuthorization is invoked when the oauth flow completes. This is usually mounted - * on /oauth/callback, and passed the entire URL query parameters. This first uses the state - * parameter to look up in LocalStorage the context for the current auth flow, and then - * completes the flow by exchanging the authorization code for an access token. - * - * Once it's updated LocalStorage with the auth token, it will post a message back to the original - * window to inform any running `useMcp` hooks that the auth flow is complete. - */ -export async function onMcpAuthorization( - query: Record, - { - storageKeyPrefix = 'mcp:auth', - }: { - storageKeyPrefix?: string - } = {}, -) { - try { - // Extract the authorization code and state - const code = query.code - const state = query.state - - if (!code) { - throw new Error('No authorization code received') - } - - if (!state) { - throw new Error('No state parameter received') - } - - // Find the matching auth state in localStorage - const stateKey = `${storageKeyPrefix}:state_${state}` - const storedState = localStorage.getItem(stateKey) - console.log({ stateKey, storedState }) - if (!storedState) { - throw new Error('No matching auth state found in storage') - } - const { authorizationUrl, serverUrlHash, metadata, expiry } = JSON.parse(storedState) - if (expiry < Date.now()) { - throw new Error('Auth state has expired') - } - - // Find all related auth data with the same prefix and server hash - const clientInfoKey = `${storageKeyPrefix}_${serverUrlHash}_client_info` - const codeVerifierKey = `${storageKeyPrefix}_${serverUrlHash}_code_verifier` - console.log({ authorizationUrl, clientInfoKey, codeVerifierKey }) - - const clientInfoStr = localStorage.getItem(clientInfoKey) - const codeVerifier = localStorage.getItem(codeVerifierKey) - - if (!clientInfoStr) { - throw new Error('No client information found in storage') - } - - if (!codeVerifier) { - throw new Error('No code verifier found in storage') - } - - // Parse client info - const clientInfo = JSON.parse(clientInfoStr) as OAuthClientInformation - - const tokens = await exchangeAuthorization(new URL('/', authorizationUrl), { - metadata, - clientInformation: clientInfo, - authorizationCode: code, - codeVerifier, - }) - - // Save the tokens - const tokensKey = `${storageKeyPrefix}_${serverUrlHash}_tokens` - console.log({ tokensKey, tokens }) - localStorage.setItem(tokensKey, JSON.stringify(tokens)) - - // Post message back to the parent window - if (window.opener && !window.opener.closed) { - window.opener.postMessage( - { - type: 'mcp_auth_callback', - // Don't send the code back since we've already done the token exchange - // This signals to the main window that tokens are already in localStorage - }, - window.location.origin, - ) - // Close the popup - window.close() - } else { - // If no parent window, we're in a redirect flow - // Redirect back to the main page - window.location.href = '/' - } - - return { success: true } - } catch (error) { - console.error('Error in MCP authorization:', error) - - // Create a readable error message for display - const errorMessage = error instanceof Error ? error.message : String(error) - - // If the popup is still open, show the error - const errorHtml = ` - - - Authentication Error - - - -

Authentication Error

-
-

${errorMessage}

-
-

You can close this window and try again.

- - - ` - - document.body.innerHTML = errorHtml - - return { success: false, error: errorMessage } - } -} diff --git a/src/cli/shared.ts b/src/shared.ts similarity index 99% rename from src/cli/shared.ts rename to src/shared.ts index b146abd..91a8e80 100644 --- a/src/cli/shared.ts +++ b/src/shared.ts @@ -18,7 +18,7 @@ import { OAuthTokens, OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' -import { OAuthCallbackServerOptions, OAuthProviderOptions } from '../lib/types.js' +import { OAuthCallbackServerOptions, OAuthProviderOptions } from './lib/types' /** * Implements the OAuthClientProvider interface for Node.js environments. From 1dd99edc0d2a32f432239d228ab6c814988addf5 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 14:38:15 +1100 Subject: [PATCH 035/104] breaking shared apart --- src/client.ts | 3 +- .../node-oauth-client-provider.ts} | 143 +----------------- src/lib/utils.ts | 131 ++++++++++++++++ src/proxy.ts | 4 +- 4 files changed, 139 insertions(+), 142 deletions(-) rename src/{shared.ts => lib/node-oauth-client-provider.ts} (60%) diff --git a/src/client.ts b/src/client.ts index 192c178..48d6ea4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -14,7 +14,8 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' -import { NodeOAuthClientProvider, setupOAuthCallbackServer, parseCommandLineArgs, setupSignalHandlers } from './shared' +import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' +import { parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers } from './lib/utils' /** * Main function to run the client diff --git a/src/shared.ts b/src/lib/node-oauth-client-provider.ts similarity index 60% rename from src/shared.ts rename to src/lib/node-oauth-client-provider.ts index 91a8e80..920b8e5 100644 --- a/src/shared.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -1,15 +1,8 @@ -/** - * Shared utilities for MCP OAuth clients and proxies. - * Contains common functionality for authentication, file storage, and proxying. - */ - -import express from 'express' -import open from 'open' -import fs from 'fs/promises' +import crypto from 'crypto' import path from 'path' import os from 'os' -import crypto from 'crypto' -import net from 'net' +import fs from 'fs/promises' +import open from 'open' import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js' import { OAuthClientInformation, @@ -18,7 +11,7 @@ import { OAuthTokens, OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' -import { OAuthCallbackServerOptions, OAuthProviderOptions } from './lib/types' +import type { OAuthProviderOptions } from './types' /** * Implements the OAuthClientProvider interface for Node.js environments. @@ -204,131 +197,3 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { return await this.readTextFile('code_verifier.txt') } } - -/** - * Sets up an Express server to handle OAuth callbacks - * @param options The server options - * @returns An object with the server, authCode, and waitForAuthCode function - */ -export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { - let authCode: string | null = null - const app = express() - - app.get(options.path, (req, res) => { - const code = req.query.code as string | undefined - if (!code) { - res.status(400).send('Error: No authorization code received') - return - } - - authCode = code - res.send('Authorization successful! You may close this window and return to the CLI.') - - // Notify main flow that auth code is available - options.events.emit('auth-code-received', code) - }) - - const server = app.listen(options.port, () => { - console.error(`OAuth callback server running at http://127.0.0.1:${options.port}`) - }) - - /** - * Waits for the OAuth authorization code - * @returns A promise that resolves with the authorization code - */ - const waitForAuthCode = (): Promise => { - return new Promise((resolve) => { - if (authCode) { - resolve(authCode) - return - } - - options.events.once('auth-code-received', (code) => { - resolve(code) - }) - }) - } - - return { server, authCode, waitForAuthCode } -} - -/** - * Finds an available port on the local machine - * @param preferredPort Optional preferred port to try first - * @returns A promise that resolves to an available port number - */ -export async function findAvailablePort(preferredPort?: number): Promise { - return new Promise((resolve, reject) => { - const server = net.createServer() - - server.on('error', (err: NodeJS.ErrnoException) => { - if (err.code === 'EADDRINUSE') { - // If preferred port is in use, get a random port - server.listen(0) - } else { - reject(err) - } - }) - - server.on('listening', () => { - const { port } = server.address() as net.AddressInfo - server.close(() => { - resolve(port) - }) - }) - - // Try preferred port first, or get a random port - server.listen(preferredPort || 0) - }) -} - -/** - * 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 and callbackPort - */ -export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { - const serverUrl = args[0] - const specifiedPort = args[1] ? parseInt(args[1]) : undefined - - if (!serverUrl) { - console.error(usage) - process.exit(1) - } - - const url = new URL(serverUrl) - const isLocalhost = (url.hostname === 'localhost' || url.hostname === '127.0.0.1') && url.protocol === 'http:' - - if (!(url.protocol == 'https:' || isLocalhost)) { - console.error(usage) - process.exit(1) - } - - // Use the specified port, or find an available one - const callbackPort = specifiedPort || (await findAvailablePort(defaultPort)) - - if (specifiedPort) { - console.error(`Using specified callback port: ${callbackPort}`) - } else { - console.error(`Using automatically selected callback port: ${callbackPort}`) - } - - return { serverUrl, callbackPort } -} - -/** - * Sets up signal handlers for graceful shutdown - * @param cleanup Cleanup function to run on shutdown - */ -export function setupSignalHandlers(cleanup: () => Promise) { - process.on('SIGINT', async () => { - console.error('\nShutting down...') - await cleanup() - process.exit(0) - }) - - // Keep the process alive - process.stdin.resume() -} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2b09f57..33e8685 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,9 @@ import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' +import { OAuthCallbackServerOptions } from './types' +import express from 'express' +import net from 'net' const pid = process.pid @@ -99,3 +102,131 @@ export async function connectToRemoteServer( } } } + +/** + * Sets up an Express server to handle OAuth callbacks + * @param options The server options + * @returns An object with the server, authCode, and waitForAuthCode function + */ +export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { + let authCode: string | null = null + const app = express() + + app.get(options.path, (req, res) => { + const code = req.query.code as string | undefined + if (!code) { + res.status(400).send('Error: No authorization code received') + return + } + + authCode = code + res.send('Authorization successful! You may close this window and return to the CLI.') + + // Notify main flow that auth code is available + options.events.emit('auth-code-received', code) + }) + + const server = app.listen(options.port, () => { + console.error(`OAuth callback server running at http://127.0.0.1:${options.port}`) + }) + + /** + * Waits for the OAuth authorization code + * @returns A promise that resolves with the authorization code + */ + const waitForAuthCode = (): Promise => { + return new Promise((resolve) => { + if (authCode) { + resolve(authCode) + return + } + + options.events.once('auth-code-received', (code) => { + resolve(code) + }) + }) + } + + return { server, authCode, waitForAuthCode } +} + +/** + * Finds an available port on the local machine + * @param preferredPort Optional preferred port to try first + * @returns A promise that resolves to an available port number + */ +export async function findAvailablePort(preferredPort?: number): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer() + + server.on('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EADDRINUSE') { + // If preferred port is in use, get a random port + server.listen(0) + } else { + reject(err) + } + }) + + server.on('listening', () => { + const { port } = server.address() as net.AddressInfo + server.close(() => { + resolve(port) + }) + }) + + // Try preferred port first, or get a random port + server.listen(preferredPort || 0) + }) +} + +/** + * 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 and callbackPort + */ +export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { + const serverUrl = args[0] + const specifiedPort = args[1] ? parseInt(args[1]) : undefined + + if (!serverUrl) { + console.error(usage) + process.exit(1) + } + + const url = new URL(serverUrl) + const isLocalhost = (url.hostname === 'localhost' || url.hostname === '127.0.0.1') && url.protocol === 'http:' + + if (!(url.protocol == 'https:' || isLocalhost)) { + console.error(usage) + process.exit(1) + } + + // Use the specified port, or find an available one + const callbackPort = specifiedPort || (await findAvailablePort(defaultPort)) + + if (specifiedPort) { + console.error(`Using specified callback port: ${callbackPort}`) + } else { + console.error(`Using automatically selected callback port: ${callbackPort}`) + } + + return { serverUrl, callbackPort } +} + +/** + * Sets up signal handlers for graceful shutdown + * @param cleanup Cleanup function to run on shutdown + */ +export function setupSignalHandlers(cleanup: () => Promise) { + process.on('SIGINT', async () => { + console.error('\nShutting down...') + await cleanup() + process.exit(0) + }) + + // Keep the process alive + process.stdin.resume() +} diff --git a/src/proxy.ts b/src/proxy.ts index d2b7e4a..23eaafe 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -11,8 +11,8 @@ import { EventEmitter } from 'events' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { NodeOAuthClientProvider, setupOAuthCallbackServer, parseCommandLineArgs, setupSignalHandlers } from './shared' -import { connectToRemoteServer, mcpProxy } from './lib/utils' +import { connectToRemoteServer, mcpProxy, parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers } from './lib/utils' +import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' /** * Main function to run the proxy From a32681e154d7d443ed6a154edffdea8d0ab217b6 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 14:42:19 +1100 Subject: [PATCH 036/104] extracted the config directory access --- src/lib/mcp-auth-config.ts | 146 ++++++++++++++++++++++++++ src/lib/node-oauth-client-provider.ts | 121 ++++----------------- 2 files changed, 169 insertions(+), 98 deletions(-) create mode 100644 src/lib/mcp-auth-config.ts diff --git a/src/lib/mcp-auth-config.ts b/src/lib/mcp-auth-config.ts new file mode 100644 index 0000000..8f06d41 --- /dev/null +++ b/src/lib/mcp-auth-config.ts @@ -0,0 +1,146 @@ +import crypto from 'crypto' +import path from 'path' +import os from 'os' +import fs from 'fs/promises' + +/** + * MCP Remote Authentication Configuration + * + * This module handles the storage and retrieval of authentication-related data for MCP Remote. + * + * Configuration directory structure: + * - The config directory is determined by MCP_REMOTE_CONFIG_DIR env var or defaults to ~/.mcp-auth + * - Each file is prefixed with a hash of the server URL to separate configurations for different servers + * + * Files stored in the config directory: + * - {server_hash}_client_info.json: Contains OAuth client registration information + * - Format: OAuthClientInformation object with client_id and other registration details + * - {server_hash}_tokens.json: Contains OAuth access and refresh tokens + * - Format: OAuthTokens object with access_token, refresh_token, and expiration information + * - {server_hash}_code_verifier.txt: Contains the PKCE code verifier for the current OAuth flow + * - Format: Plain text string used for PKCE verification + * + * All JSON files are stored with 2-space indentation for readability. + */ + +/** + * Gets the configuration directory path + * @returns The path to the configuration directory + */ +export function getConfigDir(): string { + return process.env.MCP_REMOTE_CONFIG_DIR || path.join(os.homedir(), '.mcp-auth') +} + +/** + * Ensures the configuration directory exists + */ +export async function ensureConfigDir(): Promise { + try { + const configDir = getConfigDir() + await fs.mkdir(configDir, { recursive: true }) + } catch (error) { + console.error('Error creating config directory:', error) + throw error + } +} + +/** + * Generates a hash for the server URL to use in filenames + * @param serverUrl The server URL to hash + * @returns The hashed server URL + */ +export function getServerUrlHash(serverUrl: string): string { + return crypto.createHash('md5').update(serverUrl).digest('hex') +} + +/** + * Reads a JSON file and parses it with the provided schema + * @param serverUrlHash The hash of the server URL + * @param filename The name of the file to read + * @param schema The schema to validate against + * @returns The parsed file content or undefined if the file doesn't exist + */ +export async function readJsonFile( + serverUrlHash: string, + filename: string, + schema: any +): Promise { + try { + await ensureConfigDir() + const configDir = getConfigDir() + const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + const content = await fs.readFile(filePath, 'utf-8') + return await schema.parseAsync(JSON.parse(content)) + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return undefined + } + return undefined + } +} + +/** + * Writes a JSON object to a file + * @param serverUrlHash The hash of the server URL + * @param filename The name of the file to write + * @param data The data to write + */ +export async function writeJsonFile( + serverUrlHash: string, + filename: string, + data: any +): Promise { + try { + await ensureConfigDir() + const configDir = getConfigDir() + const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8') + } catch (error) { + console.error(`Error writing ${filename}:`, error) + throw error + } +} + +/** + * Reads a text file + * @param serverUrlHash The hash of the server URL + * @param filename The name of the file to read + * @param errorMessage Optional custom error message + * @returns The file content as a string + */ +export async function readTextFile( + serverUrlHash: string, + filename: string, + errorMessage?: string +): Promise { + try { + await ensureConfigDir() + const configDir = getConfigDir() + const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + return await fs.readFile(filePath, 'utf-8') + } catch (error) { + throw new Error(errorMessage || `Error reading ${filename}`) + } +} + +/** + * Writes a text string to a file + * @param serverUrlHash The hash of the server URL + * @param filename The name of the file to write + * @param text The text to write + */ +export async function writeTextFile( + serverUrlHash: string, + filename: string, + text: string +): Promise { + try { + await ensureConfigDir() + const configDir = getConfigDir() + const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + await fs.writeFile(filePath, text, 'utf-8') + } catch (error) { + console.error(`Error writing ${filename}:`, error) + throw error + } +} \ No newline at end of file diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index 920b8e5..c97743e 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -1,7 +1,3 @@ -import crypto from 'crypto' -import path from 'path' -import os from 'os' -import fs from 'fs/promises' import open from 'open' import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js' import { @@ -12,13 +8,19 @@ import { OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' import type { OAuthProviderOptions } from './types' +import { + getServerUrlHash, + readJsonFile, + writeJsonFile, + readTextFile, + writeTextFile, +} from './mcp-auth-config' /** * Implements the OAuthClientProvider interface for Node.js environments. * Handles OAuth flow and token storage for MCP clients. */ export class NodeOAuthClientProvider implements OAuthClientProvider { - private configDir: string private serverUrlHash: string private callbackPath: string private clientName: string @@ -29,8 +31,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param options Configuration options for the provider */ constructor(readonly options: OAuthProviderOptions) { - this.serverUrlHash = crypto.createHash('md5').update(options.serverUrl).digest('hex') - this.configDir = options.configDir || path.join(os.homedir(), '.mcp-auth') + this.serverUrlHash = getServerUrlHash(options.serverUrl) this.callbackPath = options.callbackPath || '/oauth/callback' this.clientName = options.clientName || 'MCP CLI Client' this.clientUri = options.clientUri || 'https://github.com/modelcontextprotocol/mcp-cli' @@ -51,96 +52,16 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { } } - /** - * Ensures the configuration directory exists - * @private - */ - private async ensureConfigDir() { - try { - await fs.mkdir(this.configDir, { recursive: true }) - } catch (error) { - console.error('Error creating config directory:', error) - throw error - } - } - - /** - * Reads a JSON file and parses it with the provided schema - * @param filename The name of the file to read - * @param schema The schema to validate against - * @returns The parsed file content or undefined if the file doesn't exist - * @private - */ - private async readFile(filename: string, schema: any): Promise { - try { - await this.ensureConfigDir() - const filePath = path.join(this.configDir, `${this.serverUrlHash}_${filename}`) - const content = await fs.readFile(filePath, 'utf-8') - return await schema.parseAsync(JSON.parse(content)) - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - return undefined - } - return undefined - } - } - - /** - * Writes a JSON object to a file - * @param filename The name of the file to write - * @param data The data to write - * @private - */ - private async writeFile(filename: string, data: any) { - try { - await this.ensureConfigDir() - const filePath = path.join(this.configDir, `${this.serverUrlHash}_${filename}`) - await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8') - } catch (error) { - console.error(`Error writing ${filename}:`, error) - throw error - } - } - - /** - * Writes a text string to a file - * @param filename The name of the file to write - * @param text The text to write - * @private - */ - private async writeTextFile(filename: string, text: string) { - try { - await this.ensureConfigDir() - const filePath = path.join(this.configDir, `${this.serverUrlHash}_${filename}`) - await fs.writeFile(filePath, text, 'utf-8') - } catch (error) { - console.error(`Error writing ${filename}:`, error) - throw error - } - } - - /** - * Reads text from a file - * @param filename The name of the file to read - * @returns The file content as a string - * @private - */ - private async readTextFile(filename: string): Promise { - try { - await this.ensureConfigDir() - const filePath = path.join(this.configDir, `${this.serverUrlHash}_${filename}`) - return await fs.readFile(filePath, 'utf-8') - } catch (error) { - throw new Error('No code verifier saved for session') - } - } - /** * Gets the client information if it exists * @returns The client information or undefined */ async clientInformation(): Promise { - return this.readFile('client_info.json', OAuthClientInformationSchema) + return readJsonFile( + this.serverUrlHash, + 'client_info.json', + OAuthClientInformationSchema + ) } /** @@ -148,7 +69,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param clientInformation The client information to save */ async saveClientInformation(clientInformation: OAuthClientInformationFull): Promise { - await this.writeFile('client_info.json', clientInformation) + await writeJsonFile(this.serverUrlHash, 'client_info.json', clientInformation) } /** @@ -156,7 +77,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The OAuth tokens or undefined */ async tokens(): Promise { - return this.readFile('tokens.json', OAuthTokensSchema) + return readJsonFile(this.serverUrlHash, 'tokens.json', OAuthTokensSchema) } /** @@ -164,7 +85,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param tokens The tokens to save */ async saveTokens(tokens: OAuthTokens): Promise { - await this.writeFile('tokens.json', tokens) + await writeJsonFile(this.serverUrlHash, 'tokens.json', tokens) } /** @@ -186,7 +107,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param codeVerifier The code verifier to save */ async saveCodeVerifier(codeVerifier: string): Promise { - await this.writeTextFile('code_verifier.txt', codeVerifier) + await writeTextFile(this.serverUrlHash, 'code_verifier.txt', codeVerifier) } /** @@ -194,6 +115,10 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The code verifier */ async codeVerifier(): Promise { - return await this.readTextFile('code_verifier.txt') + return await readTextFile( + this.serverUrlHash, + 'code_verifier.txt', + 'No code verifier saved for session' + ) } -} +} \ No newline at end of file From 027007030e6400beadbdeab94e4f17243fd2ff29 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 15:02:27 +1100 Subject: [PATCH 037/104] adding the --clean flag --- src/client.ts | 14 +++-- src/lib/mcp-auth-config.ts | 82 +++++++++++++++++++++++---- src/lib/node-oauth-client-provider.ts | 21 ++++++- src/lib/types.ts | 2 + src/lib/utils.ts | 17 +++++- src/proxy.ts | 14 +++-- 6 files changed, 125 insertions(+), 25 deletions(-) diff --git a/src/client.ts b/src/client.ts index 48d6ea4..451325c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4,7 +4,10 @@ * MCP Client with OAuth support * A command-line client that connects to an MCP server using SSE with OAuth authentication. * - * Run with: npx tsx client.ts https://example.remote/server [callback-port] + * Run with: npx tsx client.ts [--clean] https://example.remote/server [callback-port] + * + * Options: + * --clean: Deletes stored configuration before reading, ensuring a fresh session * * If callback-port is not specified, an available port will be automatically selected. */ @@ -20,7 +23,7 @@ import { parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers } f /** * Main function to run the client */ -async function runClient(serverUrl: string, callbackPort: number) { +async function runClient(serverUrl: string, callbackPort: number, clean: boolean = false) { // Set up event emitter for auth flow const events = new EventEmitter() @@ -29,6 +32,7 @@ async function runClient(serverUrl: string, callbackPort: number) { serverUrl, callbackPort, clientName: 'MCP CLI Client', + clean, }) // Create the client @@ -147,9 +151,9 @@ async function runClient(serverUrl: string, callbackPort: number) { } // Parse command-line arguments and run the client -parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [callback-port]') - .then(({ serverUrl, callbackPort }) => { - return runClient(serverUrl, callbackPort) +parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [--clean] [callback-port]') + .then(({ serverUrl, callbackPort, clean }) => { + return runClient(serverUrl, callbackPort, clean) }) .catch((error) => { console.error('Fatal error:', error) diff --git a/src/lib/mcp-auth-config.ts b/src/lib/mcp-auth-config.ts index 8f06d41..f52fc64 100644 --- a/src/lib/mcp-auth-config.ts +++ b/src/lib/mcp-auth-config.ts @@ -23,6 +23,26 @@ import fs from 'fs/promises' * All JSON files are stored with 2-space indentation for readability. */ +/** + * Known configuration file names that might need to be cleaned + */ +export const knownConfigFiles = [ + 'client_info.json', + 'tokens.json', + 'code_verifier.txt', +]; + +/** + * Deletes all known configuration files for a specific server + * @param serverUrlHash The hash of the server URL + */ +export async function cleanServerConfig(serverUrlHash: string): Promise { + console.error(`Cleaning configuration files for server: ${serverUrlHash}`) + for (const filename of knownConfigFiles) { + await deleteConfigFile(serverUrlHash, filename) + } +} + /** * Gets the configuration directory path * @returns The path to the configuration directory @@ -53,22 +73,58 @@ export function getServerUrlHash(serverUrl: string): string { return crypto.createHash('md5').update(serverUrl).digest('hex') } +/** + * Gets the file path for a config file + * @param serverUrlHash The hash of the server URL + * @param filename The name of the file + * @returns The absolute file path + */ +export function getConfigFilePath(serverUrlHash: string, filename: string): string { + const configDir = getConfigDir() + return path.join(configDir, `${serverUrlHash}_${filename}`) +} + +/** + * Deletes a config file if it exists + * @param serverUrlHash The hash of the server URL + * @param filename The name of the file to delete + */ +export async function deleteConfigFile(serverUrlHash: string, filename: string): Promise { + try { + const filePath = getConfigFilePath(serverUrlHash, filename) + await fs.unlink(filePath) + } catch (error) { + // Ignore if file doesn't exist + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + console.error(`Error deleting ${filename}:`, error) + } + } +} + /** * Reads a JSON file and parses it with the provided schema * @param serverUrlHash The hash of the server URL * @param filename The name of the file to read * @param schema The schema to validate against + * @param clean Whether to clean (delete) before reading * @returns The parsed file content or undefined if the file doesn't exist */ export async function readJsonFile( serverUrlHash: string, filename: string, - schema: any + schema: any, + clean: boolean = false ): Promise { try { await ensureConfigDir() - const configDir = getConfigDir() - const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + + // If clean flag is set, delete the file before trying to read it + if (clean) { + await deleteConfigFile(serverUrlHash, filename) + return undefined + } + + const filePath = getConfigFilePath(serverUrlHash, filename) const content = await fs.readFile(filePath, 'utf-8') return await schema.parseAsync(JSON.parse(content)) } catch (error) { @@ -92,8 +148,7 @@ export async function writeJsonFile( ): Promise { try { await ensureConfigDir() - const configDir = getConfigDir() - const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + const filePath = getConfigFilePath(serverUrlHash, filename) await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8') } catch (error) { console.error(`Error writing ${filename}:`, error) @@ -106,17 +161,25 @@ export async function writeJsonFile( * @param serverUrlHash The hash of the server URL * @param filename The name of the file to read * @param errorMessage Optional custom error message + * @param clean Whether to clean (delete) before reading * @returns The file content as a string */ export async function readTextFile( serverUrlHash: string, filename: string, - errorMessage?: string + errorMessage?: string, + clean: boolean = false ): Promise { try { await ensureConfigDir() - const configDir = getConfigDir() - const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + + // If clean flag is set, delete the file before trying to read it + if (clean) { + await deleteConfigFile(serverUrlHash, filename) + throw new Error('File deleted due to clean flag') + } + + const filePath = getConfigFilePath(serverUrlHash, filename) return await fs.readFile(filePath, 'utf-8') } catch (error) { throw new Error(errorMessage || `Error reading ${filename}`) @@ -136,8 +199,7 @@ export async function writeTextFile( ): Promise { try { await ensureConfigDir() - const configDir = getConfigDir() - const filePath = path.join(configDir, `${serverUrlHash}_${filename}`) + const filePath = getConfigFilePath(serverUrlHash, filename) await fs.writeFile(filePath, text, 'utf-8') } catch (error) { console.error(`Error writing ${filename}:`, error) diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index c97743e..ed6caed 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -14,6 +14,7 @@ import { writeJsonFile, readTextFile, writeTextFile, + cleanServerConfig, } from './mcp-auth-config' /** @@ -35,6 +36,13 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { this.callbackPath = options.callbackPath || '/oauth/callback' this.clientName = options.clientName || 'MCP CLI Client' this.clientUri = options.clientUri || 'https://github.com/modelcontextprotocol/mcp-cli' + + // If clean flag is set, proactively clean all config files for this server + if (options.clean) { + cleanServerConfig(this.serverUrlHash).catch(err => { + console.error('Error cleaning server config:', err) + }) + } } get redirectUrl(): string { @@ -60,7 +68,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { return readJsonFile( this.serverUrlHash, 'client_info.json', - OAuthClientInformationSchema + OAuthClientInformationSchema, + this.options.clean ) } @@ -77,7 +86,12 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The OAuth tokens or undefined */ async tokens(): Promise { - return readJsonFile(this.serverUrlHash, 'tokens.json', OAuthTokensSchema) + return readJsonFile( + this.serverUrlHash, + 'tokens.json', + OAuthTokensSchema, + this.options.clean + ) } /** @@ -118,7 +132,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { return await readTextFile( this.serverUrlHash, 'code_verifier.txt', - 'No code verifier saved for session' + 'No code verifier saved for session', + this.options.clean ) } } \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts index 188fccb..e719905 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -16,6 +16,8 @@ export interface OAuthProviderOptions { clientName?: string /** Client URI to use for OAuth registration */ clientUri?: string + /** Whether to clean stored configuration before reading */ + clean?: boolean } /** diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 33e8685..e9b4bf3 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -185,9 +185,18 @@ export async function findAvailablePort(preferredPort?: number): Promise * @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 and callbackPort + * @returns A promise that resolves to an object with parsed serverUrl, callbackPort, and clean flag */ export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { + // Check for --clean flag + const cleanIndex = args.indexOf('--clean') + const clean = cleanIndex !== -1 + + // Remove the flag from args if it exists + if (clean) { + args.splice(cleanIndex, 1) + } + const serverUrl = args[0] const specifiedPort = args[1] ? parseInt(args[1]) : undefined @@ -212,8 +221,12 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, } else { console.error(`Using automatically selected callback port: ${callbackPort}`) } + + if (clean) { + console.error('Clean mode enabled: config files will be reset before reading') + } - return { serverUrl, callbackPort } + return { serverUrl, callbackPort, clean } } /** diff --git a/src/proxy.ts b/src/proxy.ts index 23eaafe..dff2e9e 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -4,7 +4,10 @@ * MCP Proxy with OAuth support * A bidirectional proxy between a local STDIO MCP server and a remote SSE server with OAuth authentication. * - * Run with: npx tsx proxy.ts https://example.remote/server [callback-port] + * Run with: npx tsx proxy.ts [--clean] https://example.remote/server [callback-port] + * + * Options: + * --clean: Deletes stored configuration before reading, ensuring a fresh session * * If callback-port is not specified, an available port will be automatically selected. */ @@ -17,7 +20,7 @@ import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' /** * Main function to run the proxy */ -async function runProxy(serverUrl: string, callbackPort: number) { +async function runProxy(serverUrl: string, callbackPort: number, clean: boolean = false) { // Set up event emitter for auth flow const events = new EventEmitter() @@ -26,6 +29,7 @@ async function runProxy(serverUrl: string, callbackPort: number) { serverUrl, callbackPort, clientName: 'MCP CLI Proxy', + clean, }) // Create the STDIO transport for local connections @@ -91,9 +95,9 @@ to the CA certificate file. If using claude_desktop_config.json, this might look } // Parse command-line arguments and run the proxy -parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [callback-port]') - .then(({ serverUrl, callbackPort }) => { - return runProxy(serverUrl, callbackPort) +parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [--clean] [callback-port]') + .then(({ serverUrl, callbackPort, clean }) => { + return runProxy(serverUrl, callbackPort, clean) }) .catch((error) => { console.error('Fatal error:', error) From bcdf520af5dd850c17169fde9bf98c9362517fa8 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 15:30:54 +1100 Subject: [PATCH 038/104] updated readme --- README.md | 118 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 3775d21..5d42804 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,7 @@ That's where `mcp-remote` comes in. As soon as your chosen MCP client supports r ## Usage -### Claude Desktop - -[Official Docs](https://modelcontextprotocol.io/quickstart/user) - -In order to add an MCP server to Claude Desktop you need to edit the configuration file located at: - -macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - -Windows: `%APPDATA%\Claude\claude_desktop_config.json` - -If it does not exist yet, [you may need to enable it under Settings > Developer](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server). +All the most popular MCP clients (Claude Desktop, Cursor & Windsurf) use the following config format: ```json { @@ -34,7 +24,6 @@ If it does not exist yet, [you may need to enable it under Settings > Developer] "remote-example": { "command": "npx", "args": [ - "-y", "mcp-remote", "https://remote.mcp.server/sse" ] @@ -43,51 +32,74 @@ If it does not exist yet, [you may need to enable it under Settings > Developer] } ``` +### Flags + +* If `npx` is producing errors, consider adding `-y` as the first argument to auto-accept the installation of the `mcp-remote` package. + +```json + "command": "npx", + "args": [ + "-y" + "mcp-remote", + "https://remote.mcp.server/sse" + ] +``` + +* To force `npx` to always check for an updated version of `mcp-remote`, add the `@latest` flag: + +```json + "args": [ + "mcp-remote@latest", + "https://remote.mcp.server/sse" + ] +``` + +* To force `mcp-remote` to ignore any existing access tokens and begin the authorization flow anew, pass `--clean`. + +```json + "args": [ + "mcp-remote", + "https://remote.mcp.server/sse", + "--clean" + ] +``` + +* To change which port `mcp-remote` listens for an OAuth redirect (by default `3334`), add an additional argument after the server URL. Note that whatever port you specify, if it is unavailable an open port will be chosen at random. + +```json + "args": [ + "mcp-remote", + "https://remote.mcp.server/sse", + "9696" + ] +``` + +* To ensure that no + +### Claude Desktop + +[Official Docs](https://modelcontextprotocol.io/quickstart/user) + +In order to add an MCP server to Claude Desktop you need to edit the configuration file located at: + +* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` +* Windows: `%APPDATA%\Claude\claude_desktop_config.json` + +If it does not exist yet, [you may need to enable it under Settings > Developer](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server). + Restart Claude Desktop to pick up the changes in the configuration file. Upon restarting, you should see a hammer icon in the bottom right corner of the input box. ### Cursor -[Official Docs](https://docs.cursor.com/context/model-context-protocol) +[Official Docs](https://docs.cursor.com/context/model-context-protocol). The configuration file is located at `~/.cursor/mcp.json`. -Add the following configuration to `~/.cursor/mcp.json`: - -```json -{ - "mcpServers": { - "remote-example": { - "command": "npx", - "args": [ - "-y", - "mcp-remote", - "https://remote.mcp.server/sse" - ] - } - } -} -``` +As of version `0.48.0`, Cursor supports unauthed SSE servers directly. If your MCP server is using the official MCP OAuth authorization protocol, you still need to add a **"command"** server and call `mcp-remote`. ### Windsurf -[Official Docs](https://docs.codeium.com/windsurf/mcp) - -Add the following configuration to `~/.codeium/windsurf/mcp_config.json`: - -```json -{ - "mcpServers": { - "remote-example": { - "command": "npx", - "args": [ - "-y", - "mcp-remote", - "https://remote.mcp.server/sse" - ] - } - } -} -``` +[Official Docs](https://docs.codeium.com/windsurf/mcp). The configuration file is located at `~/.codeium/windsurf/mcp_config.json`. ## Building Remote MCP Servers @@ -106,7 +118,17 @@ For more information about testing these servers, see also: Know of more resources you'd like to share? Please add them to this Readme and send a PR! -## Debugging +## Troubleshooting + +### Wipe your `~/.mcp-auth` directory + +`mcp-remote` stores all the credential information inside `~/.mcp-auth` (or wherever your `MCP_REMOTE_CONFIG_DIR` points to). If you're having persistent issues, try running: + +```sh +rm -rf ~/.mcp-auth +``` + +Then restarting your MCP client. ### Check your Node version From d10063463da412967782779677cc34cb9bb6c63a Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 15:35:52 +1100 Subject: [PATCH 039/104] 0.0.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a7c0e02..0ff649e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.10", + "version": "0.0.11", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From eaed1f31e85856912cdfb18bd27bf7b96271b1ad Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 15:41:59 +1100 Subject: [PATCH 040/104] exposing both CLI tools --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ff649e..a605ed2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ ], "main": "dist/index.js", "bin": { - "mcp-remote": "dist/cli/proxy.js" + "mcp-remote": "dist/proxy.js", + "mcp-remote-client": "dist/client.js" }, "scripts": { "dev": "tsup --watch", From 3f1664cdbad25013b053c17d18fb4818af66172a Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 15:42:02 +1100 Subject: [PATCH 041/104] 0.0.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a605ed2..bb808ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.11", + "version": "0.0.12", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From a97fd9e5c635f3e4726497df5558eb7d23e3b76d Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 16:10:12 +1100 Subject: [PATCH 042/104] added instructions for running the client --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5d42804..4d373c2 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,6 @@ All the most popular MCP clients (Claude Desktop, Cursor & Windsurf) use the fol ] ``` -* To ensure that no - ### Claude Desktop [Official Docs](https://modelcontextprotocol.io/quickstart/user) @@ -120,7 +118,7 @@ Know of more resources you'd like to share? Please add them to this Readme and s ## Troubleshooting -### Wipe your `~/.mcp-auth` directory +### Clear your `~/.mcp-auth` directory `mcp-remote` stores all the credential information inside `~/.mcp-auth` (or wherever your `MCP_REMOTE_CONFIG_DIR` points to). If you're having persistent issues, try running: @@ -166,16 +164,17 @@ this might look like: ### Check the logs -[Follow Claude Desktop logs in real-time](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop) +* [Follow Claude Desktop logs in real-time](https://modelcontextprotocol.io/docs/tools/debugging#debugging-in-claude-desktop) +* MacOS / Linux:
`tail -n 20 -F ~/Library/Logs/Claude/mcp*.log` +* For bash on WSL:
`tail -n 20 -f "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log"` +* Powershell:
`Get-Content "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log" -Wait -Tail 20` -MacOS / Linux: +### "Client" mode -`tail -n 20 -F ~/Library/Logs/Claude/mcp*.log` +Run the following on the command line (not from an MCP server): -For bash on WSL: +```shell +npx -p mcp-remote@latest mcp-remote-client https://remote.mcp.server/sse +``` -`tail -n 20 -f "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log"` - -or Powershell: - -`Get-Content "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log" -Wait -Tail 20` +This will run through the entire authorization flow and attempt to list the tools & resources at the remote URL. Pair this with `--clean` or after running `rm -rf ~/.mcp-auth` to see if stale credentials are your problem, otherwise hopefully the issue will be more obvious in these logs than those in your MCP client. From 1382827ebd03297d3f3fd0a4beffce18cdcd0029 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 16:21:34 +1100 Subject: [PATCH 043/104] Versioning storage based on mcp-auth version number to let us iterate on storage format --- src/client.ts | 4 +-- src/lib/mcp-auth-config.ts | 51 ++++++++++++++++---------------------- src/lib/utils.ts | 8 +++--- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/client.ts b/src/client.ts index 451325c..c76a538 100644 --- a/src/client.ts +++ b/src/client.ts @@ -18,7 +18,7 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' -import { parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers } from './lib/utils' +import { parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers, MCP_REMOTE_VERSION } from './lib/utils' /** * Main function to run the client @@ -39,7 +39,7 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean const client = new Client( { name: 'mcp-remote', - version: require('../package.json').version, + version: MCP_REMOTE_VERSION, }, { capabilities: {}, diff --git a/src/lib/mcp-auth-config.ts b/src/lib/mcp-auth-config.ts index f52fc64..e35badc 100644 --- a/src/lib/mcp-auth-config.ts +++ b/src/lib/mcp-auth-config.ts @@ -2,16 +2,17 @@ import crypto from 'crypto' import path from 'path' import os from 'os' import fs from 'fs/promises' +import { MCP_REMOTE_VERSION } from './utils' /** * MCP Remote Authentication Configuration - * + * * This module handles the storage and retrieval of authentication-related data for MCP Remote. - * + * * Configuration directory structure: * - The config directory is determined by MCP_REMOTE_CONFIG_DIR env var or defaults to ~/.mcp-auth * - Each file is prefixed with a hash of the server URL to separate configurations for different servers - * + * * Files stored in the config directory: * - {server_hash}_client_info.json: Contains OAuth client registration information * - Format: OAuthClientInformation object with client_id and other registration details @@ -19,18 +20,14 @@ import fs from 'fs/promises' * - Format: OAuthTokens object with access_token, refresh_token, and expiration information * - {server_hash}_code_verifier.txt: Contains the PKCE code verifier for the current OAuth flow * - Format: Plain text string used for PKCE verification - * + * * All JSON files are stored with 2-space indentation for readability. */ /** * Known configuration file names that might need to be cleaned */ -export const knownConfigFiles = [ - 'client_info.json', - 'tokens.json', - 'code_verifier.txt', -]; +export const knownConfigFiles = ['client_info.json', 'tokens.json', 'code_verifier.txt'] /** * Deletes all known configuration files for a specific server @@ -48,7 +45,9 @@ export async function cleanServerConfig(serverUrlHash: string): Promise { * @returns The path to the configuration directory */ export function getConfigDir(): string { - return process.env.MCP_REMOTE_CONFIG_DIR || path.join(os.homedir(), '.mcp-auth') + const baseConfigDir = process.env.MCP_REMOTE_CONFIG_DIR || path.join(os.homedir(), '.mcp-auth') + // Add a version subdirectory so we don't need to worry about backwards/forwards compatibility yet + return path.join(baseConfigDir, `mcp-remote-${MCP_REMOTE_VERSION}`) } /** @@ -110,20 +109,20 @@ export async function deleteConfigFile(serverUrlHash: string, filename: string): * @returns The parsed file content or undefined if the file doesn't exist */ export async function readJsonFile( - serverUrlHash: string, - filename: string, + serverUrlHash: string, + filename: string, schema: any, - clean: boolean = false + clean: boolean = false, ): Promise { try { await ensureConfigDir() - + // If clean flag is set, delete the file before trying to read it if (clean) { await deleteConfigFile(serverUrlHash, filename) return undefined } - + const filePath = getConfigFilePath(serverUrlHash, filename) const content = await fs.readFile(filePath, 'utf-8') return await schema.parseAsync(JSON.parse(content)) @@ -141,11 +140,7 @@ export async function readJsonFile( * @param filename The name of the file to write * @param data The data to write */ -export async function writeJsonFile( - serverUrlHash: string, - filename: string, - data: any -): Promise { +export async function writeJsonFile(serverUrlHash: string, filename: string, data: any): Promise { try { await ensureConfigDir() const filePath = getConfigFilePath(serverUrlHash, filename) @@ -165,20 +160,20 @@ export async function writeJsonFile( * @returns The file content as a string */ export async function readTextFile( - serverUrlHash: string, + serverUrlHash: string, filename: string, errorMessage?: string, - clean: boolean = false + clean: boolean = false, ): Promise { try { await ensureConfigDir() - + // If clean flag is set, delete the file before trying to read it if (clean) { await deleteConfigFile(serverUrlHash, filename) throw new Error('File deleted due to clean flag') } - + const filePath = getConfigFilePath(serverUrlHash, filename) return await fs.readFile(filePath, 'utf-8') } catch (error) { @@ -192,11 +187,7 @@ export async function readTextFile( * @param filename The name of the file to write * @param text The text to write */ -export async function writeTextFile( - serverUrlHash: string, - filename: string, - text: string -): Promise { +export async function writeTextFile(serverUrlHash: string, filename: string, text: string): Promise { try { await ensureConfigDir() const filePath = getConfigFilePath(serverUrlHash, filename) @@ -205,4 +196,4 @@ export async function writeTextFile( console.error(`Error writing ${filename}:`, error) throw error } -} \ No newline at end of file +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e9b4bf3..3cbb1f6 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -191,12 +191,12 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, // Check for --clean flag const cleanIndex = args.indexOf('--clean') const clean = cleanIndex !== -1 - + // Remove the flag from args if it exists if (clean) { args.splice(cleanIndex, 1) } - + const serverUrl = args[0] const specifiedPort = args[1] ? parseInt(args[1]) : undefined @@ -221,7 +221,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, } else { console.error(`Using automatically selected callback port: ${callbackPort}`) } - + if (clean) { console.error('Clean mode enabled: config files will be reset before reading') } @@ -243,3 +243,5 @@ export function setupSignalHandlers(cleanup: () => Promise) { // Keep the process alive process.stdin.resume() } + +export const MCP_REMOTE_VERSION = require('../../package.json').version From eee7b1b8d532be0d257eb95f2e7b01b37ebbccaf Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 16:29:45 +1100 Subject: [PATCH 044/104] Added process.pid to all logs so we can deal with claude code forking mulitple instances --- src/lib/mcp-auth-config.ts | 12 +++---- src/lib/node-oauth-client-provider.ts | 45 +++++++-------------------- src/lib/utils.ts | 40 +++++++++++++----------- src/proxy.ts | 14 ++++----- 4 files changed, 47 insertions(+), 64 deletions(-) diff --git a/src/lib/mcp-auth-config.ts b/src/lib/mcp-auth-config.ts index e35badc..a71782a 100644 --- a/src/lib/mcp-auth-config.ts +++ b/src/lib/mcp-auth-config.ts @@ -2,7 +2,7 @@ import crypto from 'crypto' import path from 'path' import os from 'os' import fs from 'fs/promises' -import { MCP_REMOTE_VERSION } from './utils' +import { log, MCP_REMOTE_VERSION } from './utils' /** * MCP Remote Authentication Configuration @@ -34,7 +34,7 @@ export const knownConfigFiles = ['client_info.json', 'tokens.json', 'code_verifi * @param serverUrlHash The hash of the server URL */ export async function cleanServerConfig(serverUrlHash: string): Promise { - console.error(`Cleaning configuration files for server: ${serverUrlHash}`) + log(`Cleaning configuration files for server: ${serverUrlHash}`) for (const filename of knownConfigFiles) { await deleteConfigFile(serverUrlHash, filename) } @@ -58,7 +58,7 @@ export async function ensureConfigDir(): Promise { const configDir = getConfigDir() await fs.mkdir(configDir, { recursive: true }) } catch (error) { - console.error('Error creating config directory:', error) + log('Error creating config directory:', error) throw error } } @@ -95,7 +95,7 @@ export async function deleteConfigFile(serverUrlHash: string, filename: string): } catch (error) { // Ignore if file doesn't exist if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { - console.error(`Error deleting ${filename}:`, error) + log(`Error deleting ${filename}:`, error) } } } @@ -146,7 +146,7 @@ export async function writeJsonFile(serverUrlHash: string, filename: string, dat const filePath = getConfigFilePath(serverUrlHash, filename) await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8') } catch (error) { - console.error(`Error writing ${filename}:`, error) + log(`Error writing ${filename}:`, error) throw error } } @@ -193,7 +193,7 @@ export async function writeTextFile(serverUrlHash: string, filename: string, tex const filePath = getConfigFilePath(serverUrlHash, filename) await fs.writeFile(filePath, text, 'utf-8') } catch (error) { - console.error(`Error writing ${filename}:`, error) + log(`Error writing ${filename}:`, error) throw error } } diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index ed6caed..0a05d47 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -8,14 +8,8 @@ import { OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' import type { OAuthProviderOptions } from './types' -import { - getServerUrlHash, - readJsonFile, - writeJsonFile, - readTextFile, - writeTextFile, - cleanServerConfig, -} from './mcp-auth-config' +import { getServerUrlHash, readJsonFile, writeJsonFile, readTextFile, writeTextFile, cleanServerConfig } from './mcp-auth-config' +import { log } from './utils' /** * Implements the OAuthClientProvider interface for Node.js environments. @@ -36,11 +30,11 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { this.callbackPath = options.callbackPath || '/oauth/callback' this.clientName = options.clientName || 'MCP CLI Client' this.clientUri = options.clientUri || 'https://github.com/modelcontextprotocol/mcp-cli' - + // If clean flag is set, proactively clean all config files for this server if (options.clean) { - cleanServerConfig(this.serverUrlHash).catch(err => { - console.error('Error cleaning server config:', err) + cleanServerConfig(this.serverUrlHash).catch((err) => { + log('Error cleaning server config:', err) }) } } @@ -65,12 +59,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The client information or undefined */ async clientInformation(): Promise { - return readJsonFile( - this.serverUrlHash, - 'client_info.json', - OAuthClientInformationSchema, - this.options.clean - ) + return readJsonFile(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema, this.options.clean) } /** @@ -86,12 +75,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The OAuth tokens or undefined */ async tokens(): Promise { - return readJsonFile( - this.serverUrlHash, - 'tokens.json', - OAuthTokensSchema, - this.options.clean - ) + return readJsonFile(this.serverUrlHash, 'tokens.json', OAuthTokensSchema, this.options.clean) } /** @@ -107,12 +91,12 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param authorizationUrl The URL to redirect to */ async redirectToAuthorization(authorizationUrl: URL): Promise { - console.error(`\nPlease authorize this client by visiting:\n${authorizationUrl.toString()}\n`) + log(`\nPlease authorize this client by visiting:\n${authorizationUrl.toString()}\n`) try { await open(authorizationUrl.toString()) - console.error('Browser opened automatically.') + log('Browser opened automatically.') } catch (error) { - console.error('Could not open browser automatically. Please copy and paste the URL above into your browser.') + log('Could not open browser automatically. Please copy and paste the URL above into your browser.') } } @@ -129,11 +113,6 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The code verifier */ async codeVerifier(): Promise { - return await readTextFile( - this.serverUrlHash, - 'code_verifier.txt', - 'No code verifier saved for session', - this.options.clean - ) + return await readTextFile(this.serverUrlHash, 'code_verifier.txt', 'No code verifier saved for session', this.options.clean) } -} \ No newline at end of file +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 3cbb1f6..08a6f5d 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -6,6 +6,10 @@ import express from 'express' import net from 'net' const pid = process.pid +export function log(str: string, ...rest: unknown[]) { + // Using stderr so that it doesn't interfere with stdout + console.error(`[${pid}] ${str}`, ...rest) +} /** * Creates a bidirectional proxy between two transports @@ -17,13 +21,13 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo transportToClient.onmessage = (message) => { // @ts-expect-error TODO - console.error('[Local→Remote]', message.method || message.id) + log('[Local→Remote]', message.method || message.id) transportToServer.send(message).catch(onServerError) } transportToServer.onmessage = (message) => { // @ts-expect-error TODO: fix this type - console.error('[Remote→Local]', message.method || message.id) + log('[Remote→Local]', message.method || message.id) transportToClient.send(message).catch(onClientError) } @@ -48,11 +52,11 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo transportToServer.onerror = onServerError function onClientError(error: Error) { - console.error('Error from local client:', error) + log('Error from local client:', error) } function onServerError(error: Error) { - console.error('Error from remote server:', error) + log('Error from remote server:', error) } } @@ -68,36 +72,36 @@ export async function connectToRemoteServer( authProvider: OAuthClientProvider, waitForAuthCode: () => Promise, ): Promise { - console.error(`[${pid}] Connecting to remote server: ${serverUrl}`) + log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) const transport = new SSEClientTransport(url, { authProvider }) try { await transport.start() - console.error('Connected to remote server') + log('Connected to remote server') return transport } catch (error) { if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { - console.error('Authentication required. Waiting for authorization...') + log('Authentication required. Waiting for authorization...') // Wait for the authorization code from the callback const code = await waitForAuthCode() try { - console.error('Completing authorization...') + log('Completing authorization...') await transport.finishAuth(code) // Create a new transport after auth const newTransport = new SSEClientTransport(url, { authProvider }) await newTransport.start() - console.error('Connected to remote server after authentication') + log('Connected to remote server after authentication') return newTransport } catch (authError) { - console.error('Authorization error:', authError) + log('Authorization error:', authError) throw authError } } else { - console.error('Connection error:', error) + log('Connection error:', error) throw error } } @@ -127,7 +131,7 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { }) const server = app.listen(options.port, () => { - console.error(`OAuth callback server running at http://127.0.0.1:${options.port}`) + log(`OAuth callback server running at http://127.0.0.1:${options.port}`) }) /** @@ -201,7 +205,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const specifiedPort = args[1] ? parseInt(args[1]) : undefined if (!serverUrl) { - console.error(usage) + log(usage) process.exit(1) } @@ -209,7 +213,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const isLocalhost = (url.hostname === 'localhost' || url.hostname === '127.0.0.1') && url.protocol === 'http:' if (!(url.protocol == 'https:' || isLocalhost)) { - console.error(usage) + log(usage) process.exit(1) } @@ -217,13 +221,13 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const callbackPort = specifiedPort || (await findAvailablePort(defaultPort)) if (specifiedPort) { - console.error(`Using specified callback port: ${callbackPort}`) + log(`Using specified callback port: ${callbackPort}`) } else { - console.error(`Using automatically selected callback port: ${callbackPort}`) + log(`Using automatically selected callback port: ${callbackPort}`) } if (clean) { - console.error('Clean mode enabled: config files will be reset before reading') + log('Clean mode enabled: config files will be reset before reading') } return { serverUrl, callbackPort, clean } @@ -235,7 +239,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, */ export function setupSignalHandlers(cleanup: () => Promise) { process.on('SIGINT', async () => { - console.error('\nShutting down...') + log('\nShutting down...') await cleanup() process.exit(0) }) diff --git a/src/proxy.ts b/src/proxy.ts index dff2e9e..b549018 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -14,7 +14,7 @@ import { EventEmitter } from 'events' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { connectToRemoteServer, mcpProxy, parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers } from './lib/utils' +import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers } from './lib/utils' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' /** @@ -54,9 +54,9 @@ async function runProxy(serverUrl: string, callbackPort: number, clean: boolean // Start the local STDIO server await localTransport.start() - console.error('Local STDIO server running') - console.error('Proxy established successfully between local STDIO and remote SSE') - console.error('Press Ctrl+C to exit') + log('Local STDIO server running') + log('Proxy established successfully between local STDIO and remote SSE') + log('Press Ctrl+C to exit') // Setup cleanup handler const cleanup = async () => { @@ -66,9 +66,9 @@ async function runProxy(serverUrl: string, callbackPort: number, clean: boolean } setupSignalHandlers(cleanup) } catch (error) { - console.error('Fatal error:', error) + log('Fatal error:', error) if (error instanceof Error && error.message.includes('self-signed certificate in certificate chain')) { - console.error(`You may be behind a VPN! + log(`You may be behind a VPN! If you are behind a VPN, you can try setting the NODE_EXTRA_CA_CERTS environment variable to point to the CA certificate file. If using claude_desktop_config.json, this might look like: @@ -100,6 +100,6 @@ parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [--cl return runProxy(serverUrl, callbackPort, clean) }) .catch((error) => { - console.error('Fatal error:', error) + log('Fatal error:', error) process.exit(1) }) From e793ed5b04a8df6111644df63053cb0669d03408 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 16:31:41 +1100 Subject: [PATCH 045/104] 0.0.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb808ae..8d27591 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.12", + "version": "0.0.13", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 412b5d9486b54e2383f67bbbacc82f08735afad3 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 19:38:52 +1100 Subject: [PATCH 046/104] most of the implementation looking ok but sharing the token the wrong way --- src/client.ts | 71 +++++----- src/lib/coordination.ts | 181 ++++++++++++++++++++++++++ src/lib/mcp-auth-config.ts | 66 ++++++++-- src/lib/node-oauth-client-provider.ts | 4 +- src/lib/utils.ts | 86 +++++++++++- src/proxy.ts | 18 +-- 6 files changed, 364 insertions(+), 62 deletions(-) create mode 100644 src/lib/coordination.ts diff --git a/src/client.ts b/src/client.ts index c76a538..80b2dd4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -18,7 +18,8 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' -import { parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers, MCP_REMOTE_VERSION } from './lib/utils' +import { parseCommandLineArgs, setupSignalHandlers, log, MCP_REMOTE_VERSION, getServerUrlHash } from './lib/utils' +import { coordinateAuth } from './lib/coordination' /** * Main function to run the client @@ -27,6 +28,12 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean // Set up event emitter for auth flow const events = new EventEmitter() + // Get the server URL hash for lockfile operations + const serverUrlHash = getServerUrlHash(serverUrl) + + // Coordinate authentication with other instances + const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events) + // Create the OAuth client provider const authProvider = new NodeOAuthClientProvider({ serverUrl, @@ -35,6 +42,11 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean clean, }) + // If we got auth from another instance, pre-populate with the received code + if (skipBrowserAuth) { + log('Using auth code from another instance') + } + // Create the client const client = new Client( { @@ -53,15 +65,15 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean // Set up message and error handlers transport.onmessage = (message) => { - console.log('Received message:', JSON.stringify(message, null, 2)) + log('Received message:', JSON.stringify(message, null, 2)) } transport.onerror = (error) => { - console.error('Transport error:', error) + log('Transport error:', error) } transport.onclose = () => { - console.log('Connection closed.') + log('Connection closed.') process.exit(0) } return transport @@ -69,16 +81,9 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean const transport = initTransport() - // Set up an HTTP server to handle OAuth callback - const { server, waitForAuthCode } = setupOAuthCallbackServer({ - port: callbackPort, - path: '/oauth/callback', - events, - }) - // Set up cleanup handler const cleanup = async () => { - console.log('\nClosing connection...') + log('\nClosing connection...') await client.close() server.close() } @@ -86,44 +91,44 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean // Try to connect try { - console.log('Connecting to server...') + log('Connecting to server...') await client.connect(transport) - console.log('Connected successfully!') + log('Connected successfully!') } catch (error) { if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { - console.log('Authentication required. Waiting for authorization...') + log('Authentication required. Waiting for authorization...') - // Wait for the authorization code from the callback + // Wait for the authorization code from the callback or another instance const code = await waitForAuthCode() try { - console.log('Completing authorization...') + log('Completing authorization...') await transport.finishAuth(code) // Reconnect after authorization with a new transport - console.log('Connecting after authorization...') + log('Connecting after authorization...') await client.connect(initTransport()) - console.log('Connected successfully!') + log('Connected successfully!') // Request tools list after auth - console.log('Requesting tools list...') + log('Requesting tools list...') const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) - console.log('Tools:', JSON.stringify(tools, null, 2)) + log('Tools:', JSON.stringify(tools, null, 2)) // Request resources list after auth - console.log('Requesting resource list...') + log('Requesting resource list...') const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) - console.log('Resources:', JSON.stringify(resources, null, 2)) + log('Resources:', JSON.stringify(resources, null, 2)) - console.log('Listening for messages. Press Ctrl+C to exit.') + log('Listening for messages. Press Ctrl+C to exit.') } catch (authError) { - console.error('Authorization error:', authError) + log('Authorization error:', authError) server.close() process.exit(1) } } else { - console.error('Connection error:', error) + log('Connection error:', error) server.close() process.exit(1) } @@ -131,23 +136,23 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean try { // Request tools list - console.log('Requesting tools list...') + log('Requesting tools list...') const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) - console.log('Tools:', JSON.stringify(tools, null, 2)) + log('Tools:', JSON.stringify(tools, null, 2)) } catch (e) { - console.log('Error requesting tools list:', e) + log('Error requesting tools list:', e) } try { // Request resources list - console.log('Requesting resource list...') + log('Requesting resource list...') const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) - console.log('Resources:', JSON.stringify(resources, null, 2)) + log('Resources:', JSON.stringify(resources, null, 2)) } catch (e) { - console.log('Error requesting resources list:', e) + log('Error requesting resources list:', e) } - console.log('Listening for messages. Press Ctrl+C to exit.') + log('Listening for messages. Press Ctrl+C to exit.') } // Parse command-line arguments and run the client diff --git a/src/lib/coordination.ts b/src/lib/coordination.ts new file mode 100644 index 0000000..a0568d7 --- /dev/null +++ b/src/lib/coordination.ts @@ -0,0 +1,181 @@ +import { checkLockfile, createLockfile, deleteLockfile, getConfigFilePath, LockfileData } from './mcp-auth-config' +import { EventEmitter } from 'events' +import { Server } from 'http' +import express from 'express' +import { AddressInfo } from 'net' +import { log, setupOAuthCallbackServerWithLongPoll } from './utils' + +/** + * Checks if a process with the given PID is running + * @param pid The process ID to check + * @returns True if the process is running, false otherwise + */ +export async function isPidRunning(pid: number): Promise { + try { + process.kill(pid, 0) // Doesn't kill the process, just checks if it exists + return true + } catch { + return false + } +} + +/** + * Checks if a lockfile is valid (process running and endpoint accessible) + * @param lockData The lockfile data + * @returns True if the lockfile is valid, false otherwise + */ +export async function isLockValid(lockData: LockfileData): Promise { + // Check if the lockfile is too old (over 30 minutes) + const MAX_LOCK_AGE = 30 * 60 * 1000 // 30 minutes + if (Date.now() - lockData.timestamp > MAX_LOCK_AGE) { + log('Lockfile is too old') + return false + } + + // Check if the process is still running + if (!(await isPidRunning(lockData.pid))) { + log('Process from lockfile is not running') + return false + } + + // Check if the endpoint is accessible + try { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), 1000) + + const response = await fetch(`http://127.0.0.1:${lockData.port}/wait-for-auth?poll=false`, { + signal: controller.signal, + }) + + clearTimeout(timeout) + return response.status === 200 || response.status === 202 + } catch (error) { + log(`Error connecting to auth server: ${(error as Error).message}`) + return false + } +} + +/** + * Waits for authentication from another server instance + * @param port The port to connect to + * @returns The auth code if successful, false otherwise + */ +export async function waitForAuthentication(port: number): Promise { + log(`Waiting for authentication from the server on port ${port}...`) + + try { + while (true) { + const url = `http://127.0.0.1:${port}/wait-for-auth` + log(`Querying: ${url}`) + const response = await fetch(url) + + if (response.status === 200) { + const code = await response.text() + log(`Received code: ${code}`) + return code // Return the auth code + } else if (response.status === 202) { + // do nothing, loop + } else { + log(`Unexpected response status: ${response.status}`) + return false + } + } + } catch (error) { + log(`Error waiting for authentication: ${(error as Error).message}`) + return false + } +} + +/** + * Coordinates authentication between multiple instances of the client/proxy + * @param serverUrlHash The hash of the server URL + * @param callbackPort The port to use for the callback server + * @param events The event emitter to use for signaling + * @returns An object with the server, waitForAuthCode function, and a flag indicating if browser auth can be skipped + */ +export async function coordinateAuth( + serverUrlHash: string, + callbackPort: number, + events: EventEmitter, +): Promise<{ server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean; authCode?: string }> { + // Check for a lockfile + const lockData = await checkLockfile(serverUrlHash) + + // If there's a valid lockfile, try to use the existing auth process + if (lockData && (await isLockValid(lockData))) { + log(`Another instance is handling authentication on port ${lockData.port}`) + + try { + // Try to wait for the authentication to complete + const code = await waitForAuthentication(lockData.port) + if (code) { + log('Authentication completed by another instance') + + // Setup a dummy server and return a pre-resolved promise for the auth code + const dummyServer = express().listen(0) // Listen on any available port + const dummyWaitForAuthCode = () => Promise.resolve(code) + + return { + server: dummyServer, + waitForAuthCode: dummyWaitForAuthCode, + skipBrowserAuth: true, + authCode: code, + } + } else { + log('Taking over authentication process...') + } + } catch (error) { + log(`Error waiting for authentication: ${error}`) + } + + // If we get here, the other process didn't complete auth successfully + await deleteLockfile(serverUrlHash) + } else if (lockData) { + // Invalid lockfile, delete its + log('Found invalid lockfile, deleting it') + await deleteLockfile(serverUrlHash) + } + + // Create our own lockfile + const { server, waitForAuthCode, authCompletedPromise } = setupOAuthCallbackServerWithLongPoll({ + port: callbackPort, + path: '/oauth/callback', + events, + }) + + // Get the actual port the server is running on + const address = server.address() as AddressInfo + const actualPort = address.port + + log(`Creating lockfile for server ${serverUrlHash} with process ${process.pid} on port ${actualPort}`) + await createLockfile(serverUrlHash, process.pid, actualPort) + + // Make sure lockfile is deleted on process exit + const cleanupHandler = async () => { + try { + log(`Cleaning up lockfile for server ${serverUrlHash}`) + await deleteLockfile(serverUrlHash) + } catch (error) { + log(`Error cleaning up lockfile: ${error}`) + } + } + + process.once('exit', () => { + try { + // Synchronous version for 'exit' event since we can't use async here + const configPath = getConfigFilePath(serverUrlHash, 'lock.json') + require('fs').unlinkSync(configPath) + } catch {} + }) + + // Also handle SIGINT separately + process.once('SIGINT', async () => { + await cleanupHandler() + }) + + return { + server, + waitForAuthCode, + skipBrowserAuth: false, + } +} diff --git a/src/lib/mcp-auth-config.ts b/src/lib/mcp-auth-config.ts index a71782a..7782096 100644 --- a/src/lib/mcp-auth-config.ts +++ b/src/lib/mcp-auth-config.ts @@ -1,4 +1,3 @@ -import crypto from 'crypto' import path from 'path' import os from 'os' import fs from 'fs/promises' @@ -27,7 +26,61 @@ import { log, MCP_REMOTE_VERSION } from './utils' /** * Known configuration file names that might need to be cleaned */ -export const knownConfigFiles = ['client_info.json', 'tokens.json', 'code_verifier.txt'] +export const knownConfigFiles = ['client_info.json', 'tokens.json', 'code_verifier.txt', 'lock.json'] + +/** + * Lockfile data structure + */ +export interface LockfileData { + pid: number + port: number + timestamp: number +} + +/** + * Creates a lockfile for the given server + * @param serverUrlHash The hash of the server URL + * @param pid The process ID + * @param port The port the server is running on + */ +export async function createLockfile(serverUrlHash: string, pid: number, port: number): Promise { + const lockData: LockfileData = { + pid, + port, + timestamp: Date.now(), + } + await writeJsonFile(serverUrlHash, 'lock.json', lockData) +} + +/** + * Checks if a lockfile exists for the given server + * @param serverUrlHash The hash of the server URL + * @returns The lockfile data or null if it doesn't exist + */ +export async function checkLockfile(serverUrlHash: string): Promise { + try { + const lockfile = await readJsonFile(serverUrlHash, 'lock.json', { + async parseAsync(data: any) { + if (typeof data !== 'object' || data === null) return null + if (typeof data.pid !== 'number' || typeof data.port !== 'number' || typeof data.timestamp !== 'number') { + return null + } + return data as LockfileData + }, + }) + return lockfile || null + } catch { + return null + } +} + +/** + * Deletes the lockfile for the given server + * @param serverUrlHash The hash of the server URL + */ +export async function deleteLockfile(serverUrlHash: string): Promise { + await deleteConfigFile(serverUrlHash, 'lock.json') +} /** * Deletes all known configuration files for a specific server @@ -63,15 +116,6 @@ export async function ensureConfigDir(): Promise { } } -/** - * Generates a hash for the server URL to use in filenames - * @param serverUrl The server URL to hash - * @returns The hashed server URL - */ -export function getServerUrlHash(serverUrl: string): string { - return crypto.createHash('md5').update(serverUrl).digest('hex') -} - /** * Gets the file path for a config file * @param serverUrlHash The hash of the server URL diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index 0a05d47..f8203fc 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -8,8 +8,8 @@ import { OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' import type { OAuthProviderOptions } from './types' -import { getServerUrlHash, readJsonFile, writeJsonFile, readTextFile, writeTextFile, cleanServerConfig } from './mcp-auth-config' -import { log } from './utils' +import { readJsonFile, writeJsonFile, readTextFile, writeTextFile, cleanServerConfig } from './mcp-auth-config' +import { getServerUrlHash, log } from './utils' /** * Implements the OAuthClientProvider interface for Node.js environments. diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 08a6f5d..fa0360e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,6 +4,10 @@ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' import { OAuthCallbackServerOptions } from './types' 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 const pid = process.pid export function log(str: string, ...rest: unknown[]) { @@ -65,12 +69,14 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo * @param serverUrl The URL of the remote server * @param authProvider The OAuth client provider * @param waitForAuthCode Function to wait for the auth code + * @param skipBrowserAuth Whether to skip browser auth and use shared auth * @returns The connected SSE client transport */ export async function connectToRemoteServer( serverUrl: string, authProvider: OAuthClientProvider, waitForAuthCode: () => Promise, + skipBrowserAuth: boolean = false, ): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) @@ -82,7 +88,11 @@ export async function connectToRemoteServer( return transport } catch (error) { if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { - log('Authentication required. Waiting for authorization...') + if (skipBrowserAuth) { + log('Authentication required but skipping browser auth - using shared auth') + } else { + log('Authentication required. Waiting for authorization...') + } // Wait for the authorization code from the callback const code = await waitForAuthCode() @@ -112,10 +122,56 @@ export async function connectToRemoteServer( * @param options The server options * @returns An object with the server, authCode, and waitForAuthCode function */ -export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { +export function setupOAuthCallbackServerWithLongPoll(options: OAuthCallbackServerOptions) { let authCode: string | null = null const app = express() + // Create a promise to track when auth is completed + let authCompletedResolve: (code: string) => void + const authCompletedPromise = new Promise((resolve) => { + authCompletedResolve = resolve + }) + + // Long-polling endpoint + app.get('/wait-for-auth', (req, res) => { + if (authCode) { + // Auth already completed + log('Auth already completed, returning immediately') + res.status(200).send(authCode) + return + } + + if (req.query.poll === 'false') { + log('Client requested no long poll, responding with 202') + res.status(202).send('Authentication in progress') + return + } + + // Long poll - wait for up to 30 seconds + const longPollTimeout = setTimeout(() => { + log('Long poll timeout reached, responding with 202') + res.status(202).send('Authentication in progress') + }, 30000) + + // If auth completes while we're waiting, send the response immediately + authCompletedPromise + .then((code) => { + clearTimeout(longPollTimeout) + if (!res.headersSent) { + log('Auth completed during long poll, responding with 200') + res.status(200).send(code) + } + }) + .catch(() => { + clearTimeout(longPollTimeout) + if (!res.headersSent) { + log('Auth failed during long poll, responding with 500') + res.status(500).send('Authentication failed') + } + }) + }) + + // OAuth callback endpoint app.get(options.path, (req, res) => { const code = req.query.code as string | undefined if (!code) { @@ -124,6 +180,9 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { } authCode = code + log('Auth code received, resolving promise') + authCompletedResolve(code) + res.send('Authorization successful! You may close this window and return to the CLI.') // Notify main flow that auth code is available @@ -134,10 +193,6 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { log(`OAuth callback server running at http://127.0.0.1:${options.port}`) }) - /** - * Waits for the OAuth authorization code - * @returns A promise that resolves with the authorization code - */ const waitForAuthCode = (): Promise => { return new Promise((resolve) => { if (authCode) { @@ -151,6 +206,16 @@ export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { }) } + return { server, authCode, waitForAuthCode, authCompletedPromise } +} + +/** + * Sets up an Express server to handle OAuth callbacks + * @param options The server options + * @returns An object with the server, authCode, and waitForAuthCode function + */ +export function setupOAuthCallbackServer(options: OAuthCallbackServerOptions) { + const { server, authCode, waitForAuthCode } = setupOAuthCallbackServerWithLongPoll(options) return { server, authCode, waitForAuthCode } } @@ -248,4 +313,11 @@ export function setupSignalHandlers(cleanup: () => Promise) { process.stdin.resume() } -export const MCP_REMOTE_VERSION = require('../../package.json').version +/** + * Generates a hash for the server URL to use in filenames + * @param serverUrl The server URL to hash + * @returns The hashed server URL + */ +export function getServerUrlHash(serverUrl: string): string { + return crypto.createHash('md5').update(serverUrl).digest('hex') +} diff --git a/src/proxy.ts b/src/proxy.ts index b549018..d578abd 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -14,8 +14,9 @@ import { EventEmitter } from 'events' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupOAuthCallbackServer, setupSignalHandlers } from './lib/utils' +import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupSignalHandlers, getServerUrlHash } from './lib/utils' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' +import { coordinateAuth } from './lib/coordination' /** * Main function to run the proxy @@ -24,6 +25,12 @@ async function runProxy(serverUrl: string, callbackPort: number, clean: boolean // Set up event emitter for auth flow const events = new EventEmitter() + // Get the server URL hash for lockfile operations + const serverUrlHash = getServerUrlHash(serverUrl) + + // Coordinate authentication with other instances + const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events) + // Create the OAuth client provider const authProvider = new NodeOAuthClientProvider({ serverUrl, @@ -35,16 +42,9 @@ async function runProxy(serverUrl: string, callbackPort: number, clean: boolean // Create the STDIO transport for local connections const localTransport = new StdioServerTransport() - // Set up an HTTP server to handle OAuth callback - const { server, waitForAuthCode } = setupOAuthCallbackServer({ - port: callbackPort, - path: '/oauth/callback', - events, - }) - try { // Connect to remote server with authentication - const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, waitForAuthCode) + const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, waitForAuthCode, skipBrowserAuth) // Set up bidirectional proxy between local and remote transports mcpProxy({ From 9fbba0509c2efb9987eb39c9525771fc7b96bf49 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 21:16:21 +1100 Subject: [PATCH 047/104] Changing to returning a boolean, but tokens still not being loaded --- src/client.ts | 4 ++-- src/lib/coordination.ts | 33 ++++++++++++++++++++------------- src/lib/utils.ts | 11 ++++++----- src/proxy.ts | 5 +++++ 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/client.ts b/src/client.ts index 80b2dd4..7cf4db0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -42,9 +42,9 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean clean, }) - // If we got auth from another instance, pre-populate with the received code + // If auth was completed by another instance, just log that we'll use the auth from disk if (skipBrowserAuth) { - log('Using auth code from another instance') + log('Authentication was completed by another instance - will use tokens from disk') } // Create the client diff --git a/src/lib/coordination.ts b/src/lib/coordination.ts index a0568d7..c173e7a 100644 --- a/src/lib/coordination.ts +++ b/src/lib/coordination.ts @@ -58,9 +58,9 @@ export async function isLockValid(lockData: LockfileData): Promise { /** * Waits for authentication from another server instance * @param port The port to connect to - * @returns The auth code if successful, false otherwise + * @returns True if authentication completed successfully, false otherwise */ -export async function waitForAuthentication(port: number): Promise { +export async function waitForAuthentication(port: number): Promise { log(`Waiting for authentication from the server on port ${port}...`) try { @@ -70,11 +70,13 @@ export async function waitForAuthentication(port: number): Promise setTimeout(resolve, 1000)) } else { log(`Unexpected response status: ${response.status}`) return false @@ -97,7 +99,7 @@ export async function coordinateAuth( serverUrlHash: string, callbackPort: number, events: EventEmitter, -): Promise<{ server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean; authCode?: string }> { +): Promise<{ server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean }> { // Check for a lockfile const lockData = await checkLockfile(serverUrlHash) @@ -107,19 +109,24 @@ export async function coordinateAuth( try { // Try to wait for the authentication to complete - const code = await waitForAuthentication(lockData.port) - if (code) { + const authCompleted = await waitForAuthentication(lockData.port) + if (authCompleted) { log('Authentication completed by another instance') - // Setup a dummy server and return a pre-resolved promise for the auth code + // Setup a dummy server - the client will use tokens directly from disk const dummyServer = express().listen(0) // Listen on any available port - const dummyWaitForAuthCode = () => Promise.resolve(code) + + // This shouldn't actually be called in normal operation, but provide it for API compatibility + const dummyWaitForAuthCode = () => { + log('WARNING: waitForAuthCode called in secondary instance - this is unexpected') + // Return a promise that never resolves - the client should use the tokens from disk instead + return new Promise(() => {}) + } return { server: dummyServer, waitForAuthCode: dummyWaitForAuthCode, skipBrowserAuth: true, - authCode: code, } } else { log('Taking over authentication process...') @@ -176,6 +183,6 @@ export async function coordinateAuth( return { server, waitForAuthCode, - skipBrowserAuth: false, + skipBrowserAuth: false } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index fa0360e..a37fe61 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -135,9 +135,10 @@ export function setupOAuthCallbackServerWithLongPoll(options: OAuthCallbackServe // Long-polling endpoint app.get('/wait-for-auth', (req, res) => { if (authCode) { - // Auth already completed - log('Auth already completed, returning immediately') - res.status(200).send(authCode) + // Auth already completed - just return 200 without the actual code + // Secondary instances will read tokens from disk + log('Auth already completed, returning 200') + res.status(200).send('Authentication completed') return } @@ -155,11 +156,11 @@ export function setupOAuthCallbackServerWithLongPoll(options: OAuthCallbackServe // If auth completes while we're waiting, send the response immediately authCompletedPromise - .then((code) => { + .then(() => { clearTimeout(longPollTimeout) if (!res.headersSent) { log('Auth completed during long poll, responding with 200') - res.status(200).send(code) + res.status(200).send('Authentication completed') } }) .catch(() => { diff --git a/src/proxy.ts b/src/proxy.ts index d578abd..cbaaf5d 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -38,6 +38,11 @@ async function runProxy(serverUrl: string, callbackPort: number, clean: boolean clientName: 'MCP CLI Proxy', clean, }) + + // If auth was completed by another instance, just log that we'll use the auth from disk + if (skipBrowserAuth) { + log('Authentication was completed by another instance - will use tokens from disk') + } // Create the STDIO transport for local connections const localTransport = new StdioServerTransport() From d5f5b3178640db9de934e0a4751171a68d98398b Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 22:28:10 +1100 Subject: [PATCH 048/104] Added a wait for the token exchange --- src/client.ts | 5 ++++- src/lib/mcp-auth-config.ts | 6 +++++- src/lib/node-oauth-client-provider.ts | 7 +++++++ src/proxy.ts | 5 ++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index 7cf4db0..5b908c3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -44,7 +44,10 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean // If auth was completed by another instance, just log that we'll use the auth from disk if (skipBrowserAuth) { - log('Authentication was completed by another instance - will use tokens from disk') + log('Authentication was completed by another instance - will use tokens from disk...') + // TODO: remove, the callback is happening before the tokens are exchanged + // so we're slightly too early + await new Promise((res) => setTimeout(res, 1_000)) } // Create the client diff --git a/src/lib/mcp-auth-config.ts b/src/lib/mcp-auth-config.ts index 7782096..fce4219 100644 --- a/src/lib/mcp-auth-config.ts +++ b/src/lib/mcp-auth-config.ts @@ -169,11 +169,15 @@ export async function readJsonFile( const filePath = getConfigFilePath(serverUrlHash, filename) const content = await fs.readFile(filePath, 'utf-8') - return await schema.parseAsync(JSON.parse(content)) + const result = await schema.parseAsync(JSON.parse(content)) + // console.log({ filename: result }) + return result } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + // console.log(`File ${filename} does not exist`) return undefined } + log(`Error reading ${filename}:`, error) return undefined } } diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index f8203fc..c02ce97 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -59,6 +59,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The client information or undefined */ async clientInformation(): Promise { + // log('Reading client info') return readJsonFile(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema, this.options.clean) } @@ -67,6 +68,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param clientInformation The client information to save */ async saveClientInformation(clientInformation: OAuthClientInformationFull): Promise { + // log('Saving client info') await writeJsonFile(this.serverUrlHash, 'client_info.json', clientInformation) } @@ -75,6 +77,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The OAuth tokens or undefined */ async tokens(): Promise { + // log('Reading tokens') + // console.log(new Error().stack) return readJsonFile(this.serverUrlHash, 'tokens.json', OAuthTokensSchema, this.options.clean) } @@ -83,6 +87,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param tokens The tokens to save */ async saveTokens(tokens: OAuthTokens): Promise { + // log('Saving tokens') await writeJsonFile(this.serverUrlHash, 'tokens.json', tokens) } @@ -105,6 +110,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @param codeVerifier The code verifier to save */ async saveCodeVerifier(codeVerifier: string): Promise { + // log('Saving code verifier') await writeTextFile(this.serverUrlHash, 'code_verifier.txt', codeVerifier) } @@ -113,6 +119,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { * @returns The code verifier */ async codeVerifier(): Promise { + // log('Reading code verifier') return await readTextFile(this.serverUrlHash, 'code_verifier.txt', 'No code verifier saved for session', this.options.clean) } } diff --git a/src/proxy.ts b/src/proxy.ts index cbaaf5d..4c8d75c 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -38,10 +38,13 @@ async function runProxy(serverUrl: string, callbackPort: number, clean: boolean clientName: 'MCP CLI Proxy', clean, }) - + // If auth was completed by another instance, just log that we'll use the auth from disk if (skipBrowserAuth) { log('Authentication was completed by another instance - will use tokens from disk') + // TODO: remove, the callback is happening before the tokens are exchanged + // so we're slightly too early + await new Promise((res) => setTimeout(res, 1_000)) } // Create the STDIO transport for local connections From 03e56ea31b39135363063acc94da88a8cb3c4768 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 22:28:18 +1100 Subject: [PATCH 049/104] 0.0.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d27591..93d6b36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.13", + "version": "0.0.14", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 7eac41c561f0b9238ec62ed15f7bfa2c3264ed45 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 22:30:04 +1100 Subject: [PATCH 050/104] fixing github url --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93d6b36..8a25440 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "oauth" ], "author": "Glen Maddern ", - "repository": "https://github.com/geelen/remote-mcp", + "repository": "https://github.com/geelen/mcp-remote", "type": "module", "files": [ "dist", From bd3610d87b268b91a05dc51d37e5880022be6260 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 31 Mar 2025 22:30:18 +1100 Subject: [PATCH 051/104] 0.0.15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a25440..b1edf20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.14", + "version": "0.0.15", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 88fa18b1d1a3510b276db2ea04b9174cbf178839 Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Mon, 31 Mar 2025 07:58:18 -0700 Subject: [PATCH 052/104] Document ~/.mcp-auth (#11) To help people who get stuck with `mcp-remote` Co-authored-by: Jeremy Morrell (Cloudflare) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 4d373c2..bcb15bd 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,17 @@ this might look like: * For bash on WSL:
`tail -n 20 -f "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log"` * Powershell:
`Get-Content "C:\Users\YourUsername\AppData\Local\Claude\Logs\mcp.log" -Wait -Tail 20` +## Debugging + +If you encounter the following error, returned by the `/callback` URL: + +``` +Authentication Error +Token exchange failed: HTTP 400 +``` + +You can run `rm -rf ~/.mcp-auth` to clear any locally stored state and tokens. + ### "Client" mode Run the following on the command line (not from an MCP server): From 684320a04cddff286d62b4550cdc6ee725acd581 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Fri, 4 Apr 2025 14:44:56 +1100 Subject: [PATCH 053/104] Disabling the multi-popup-prevention logic for now on windows as it appears not to be reliable --- README.md | 2 +- src/lib/coordination.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bcb15bd..ac30c88 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Then restarting your MCP client. ### Check your Node version -Make sure that the version of Node you have installed is [16 or +Make sure that the version of Node you have installed is [18 or higher](https://modelcontextprotocol.io/quickstart/server). Claude Desktop will use your system version of Node, even if you have a newer version installed elsewhere. diff --git a/src/lib/coordination.ts b/src/lib/coordination.ts index c173e7a..ad1d2f6 100644 --- a/src/lib/coordination.ts +++ b/src/lib/coordination.ts @@ -76,7 +76,7 @@ export async function waitForAuthentication(port: number): Promise { } else if (response.status === 202) { // Continue polling log(`Authentication still in progress`) - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)) } else { log(`Unexpected response status: ${response.status}`) return false @@ -100,8 +100,8 @@ export async function coordinateAuth( callbackPort: number, events: EventEmitter, ): Promise<{ server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean }> { - // Check for a lockfile - const lockData = await checkLockfile(serverUrlHash) + // Check for a lockfile (disabled on Windows for the time being) + const lockData = process.platform === 'win32' ? null : await checkLockfile(serverUrlHash) // If there's a valid lockfile, try to use the existing auth process if (lockData && (await isLockValid(lockData))) { @@ -115,7 +115,7 @@ export async function coordinateAuth( // Setup a dummy server - the client will use tokens directly from disk const dummyServer = express().listen(0) // Listen on any available port - + // This shouldn't actually be called in normal operation, but provide it for API compatibility const dummyWaitForAuthCode = () => { log('WARNING: waitForAuthCode called in secondary instance - this is unexpected') @@ -183,6 +183,6 @@ export async function coordinateAuth( return { server, waitForAuthCode, - skipBrowserAuth: false + skipBrowserAuth: false, } } From d125df49a2999af0544a4738d7154393c263bcba Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Fri, 4 Apr 2025 14:45:41 +1100 Subject: [PATCH 054/104] 0.0.16 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b1edf20..d8ab40f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.15", + "version": "0.0.16", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From a3b4906afd9e8eb1e396ffc10d31a365c03100b8 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 10 Apr 2025 11:27:25 +1000 Subject: [PATCH 055/104] Added --header CLI args support This can include ${ENV_VAR} strings that are replaced with the values in process.env --- README.md | 23 +++++++++++++++++++++++ package.json | 2 +- src/client.ts | 8 ++++---- src/lib/utils.ts | 44 ++++++++++++++++++++++++++++++++++++++++---- src/proxy.ts | 8 ++++---- 5 files changed, 72 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ac30c88..ded8937 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,29 @@ All the most popular MCP clients (Claude Desktop, Cursor & Windsurf) use the fol } ``` +### Custom Headers + +To bypass authentication, or to emit custom headers on all requests to your remote server, pass `--header` CLI arguments: + +```json +{ + "mcpServers": { + "remote-example": { + "command": "npx", + "args": [ + "mcp-remote", + "https://remote.mcp.server/sse", + "--header", + "Authorization: Bearer ${AUTH_TOKEN}" + ] + }, + "env": { + "AUTH_TOKEN": "..." + } + } +} +``` + ### Flags * If `npx` is producing errors, consider adding `-y` as the first argument to auto-accept the installation of the `mcp-remote` package. diff --git a/package.json b/package.json index d8ab40f..26c26bf 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "mcp-remote-client": "dist/client.js" }, "scripts": { - "dev": "tsup --watch", "build": "tsup", + "build:watch": "tsup --watch", "check": "prettier --check . && tsc" }, "dependencies": { diff --git a/src/client.ts b/src/client.ts index 5b908c3..9a4b7c4 100644 --- a/src/client.ts +++ b/src/client.ts @@ -24,7 +24,7 @@ import { coordinateAuth } from './lib/coordination' /** * Main function to run the client */ -async function runClient(serverUrl: string, callbackPort: number, clean: boolean = false) { +async function runClient(serverUrl: string, callbackPort: number, headers: Record, clean: boolean = false) { // Set up event emitter for auth flow const events = new EventEmitter() @@ -64,7 +64,7 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean // Create the transport factory const url = new URL(serverUrl) function initTransport() { - const transport = new SSEClientTransport(url, { authProvider }) + const transport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) // Set up message and error handlers transport.onmessage = (message) => { @@ -160,8 +160,8 @@ async function runClient(serverUrl: string, callbackPort: number, clean: boolean // Parse command-line arguments and run the client parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [--clean] [callback-port]') - .then(({ serverUrl, callbackPort, clean }) => { - return runClient(serverUrl, callbackPort, clean) + .then(({ serverUrl, callbackPort, clean, headers }) => { + return runClient(serverUrl, callbackPort, headers, clean) }) .catch((error) => { console.error('Fatal error:', error) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a37fe61..30688af 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -68,6 +68,7 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo * Creates and connects to a remote SSE server with OAuth authentication * @param serverUrl The URL of the remote server * @param authProvider The OAuth client provider + * @param headers Additional headers to send with the request * @param waitForAuthCode Function to wait for the auth code * @param skipBrowserAuth Whether to skip browser auth and use shared auth * @returns The connected SSE client transport @@ -75,12 +76,13 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo export async function connectToRemoteServer( serverUrl: string, authProvider: OAuthClientProvider, + headers: Record, waitForAuthCode: () => Promise, skipBrowserAuth: boolean = false, ): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) - const transport = new SSEClientTransport(url, { authProvider }) + const transport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) try { await transport.start() @@ -102,7 +104,7 @@ export async function connectToRemoteServer( await transport.finishAuth(code) // Create a new transport after auth - const newTransport = new SSEClientTransport(url, { authProvider }) + const newTransport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) await newTransport.start() log('Connected to remote server after authentication') return newTransport @@ -255,7 +257,7 @@ export async function findAvailablePort(preferredPort?: number): Promise * @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 clean flag + * @returns A promise that resolves to an object with parsed serverUrl, callbackPort, clean flag, and headers */ export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { // Check for --clean flag @@ -267,6 +269,21 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, args.splice(cleanIndex, 1) } + // Process headers + const headers: Record = {} + args.forEach((arg, i) => { + if (arg === '--header' && i < args.length - 1) { + const value = args[i + 1] + const match = value.match(/^([A-Za-z0-9_-]+):(.*)$/) + if (match) { + headers[match[1]] = match[2] + } else { + log(`Warning: ignoring invalid header argument: ${value}`) + } + args.splice(i, 2) + } + }) + const serverUrl = args[0] const specifiedPort = args[1] ? parseInt(args[1]) : undefined @@ -296,7 +313,26 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, log('Clean mode enabled: config files will be reset before reading') } - return { serverUrl, callbackPort, clean } + if (Object.keys(headers).length > 0) { + log(`Using custom headers: ${JSON.stringify(headers)}`) + } + // Replace environment variables in headers + // example `Authorization: Bearer ${TOKEN}` will read process.env.TOKEN + for (const [key, value] of Object.entries(headers)) { + headers[key] = value.replace(/\$\{([^}]+)}/g, (match, envVarName) => { + const envVarValue = process.env[envVarName] + + if (envVarValue !== undefined) { + log(`Replacing ${match} with environment value in header '${key}'`) + return envVarValue + } else { + log(`Warning: Environment variable '${envVarName}' not found for header '${key}'.`) + return '' + } + }) + } + + return { serverUrl, callbackPort, clean, headers } } /** diff --git a/src/proxy.ts b/src/proxy.ts index 4c8d75c..f9a415f 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -21,7 +21,7 @@ import { coordinateAuth } from './lib/coordination' /** * Main function to run the proxy */ -async function runProxy(serverUrl: string, callbackPort: number, clean: boolean = false) { +async function runProxy(serverUrl: string, callbackPort: number, headers: Record, clean: boolean = false) { // Set up event emitter for auth flow const events = new EventEmitter() @@ -52,7 +52,7 @@ async function runProxy(serverUrl: string, callbackPort: number, clean: boolean try { // Connect to remote server with authentication - const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, waitForAuthCode, skipBrowserAuth) + const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth) // Set up bidirectional proxy between local and remote transports mcpProxy({ @@ -104,8 +104,8 @@ to the CA certificate file. If using claude_desktop_config.json, this might look // Parse command-line arguments and run the proxy parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [--clean] [callback-port]') - .then(({ serverUrl, callbackPort, clean }) => { - return runProxy(serverUrl, callbackPort, clean) + .then(({ serverUrl, callbackPort, clean, headers }) => { + return runProxy(serverUrl, callbackPort, headers, clean) }) .catch((error) => { log('Fatal error:', error) From fcf9ed8583c2d46db85349baeed12181a324860a Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 10 Apr 2025 11:56:06 +1000 Subject: [PATCH 056/104] 0.0.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26c26bf..6c85f9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.16", + "version": "0.0.17", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From cf459b1a0d526505940d7a7e53414032ecbb8b29 Mon Sep 17 00:00:00 2001 From: Jon Slominski Date: Thu, 10 Apr 2025 20:56:07 -0500 Subject: [PATCH 057/104] Ensure Authorization header is passed to SSE connections When using Bearer token authentication, the Authorization header was not being properly forwarded to SSE connections, causing 401 Unauthorized errors. This fix adds a custom EventSource initialization that explicitly includes the Authorization header in all SSE requests, allowing proper authentication with remote servers. --- src/lib/utils.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 30688af..fd60028 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -26,12 +26,16 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo transportToClient.onmessage = (message) => { // @ts-expect-error TODO log('[Local→Remote]', message.method || message.id) + // Log full outgoing request details + log('[Local→Remote Full]', JSON.stringify(message, null, 2)) transportToServer.send(message).catch(onServerError) } transportToServer.onmessage = (message) => { // @ts-expect-error TODO: fix this type log('[Remote→Local]', message.method || message.id) + // Log full response details + log('[Remote→Local Full]', JSON.stringify(message, null, 2)) transportToClient.send(message).catch(onClientError) } @@ -82,7 +86,25 @@ export async function connectToRemoteServer( ): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) - const transport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) + + // Create transport with eventSourceInit to pass Authorization header if present + const eventSourceInit = { + fetch: (url: string | URL, init: RequestInit | undefined) => { + return fetch(url, { + ...init, + headers: { + ...init?.headers, + ...headers, + }, + }) + }, + } + + const transport = new SSEClientTransport(url, { + authProvider, + requestInit: { headers }, + eventSourceInit, + }) try { await transport.start() From 3c4600a92019ac78eedc1dd58d5c66615f7a7a64 Mon Sep 17 00:00:00 2001 From: Jon Slominski Date: Thu, 10 Apr 2025 21:00:12 -0500 Subject: [PATCH 058/104] Update utils.ts removed debug logging --- src/lib/utils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index fd60028..b53d3b0 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -26,16 +26,12 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo transportToClient.onmessage = (message) => { // @ts-expect-error TODO log('[Local→Remote]', message.method || message.id) - // Log full outgoing request details - log('[Local→Remote Full]', JSON.stringify(message, null, 2)) transportToServer.send(message).catch(onServerError) } transportToServer.onmessage = (message) => { // @ts-expect-error TODO: fix this type log('[Remote→Local]', message.method || message.id) - // Log full response details - log('[Remote→Local Full]', JSON.stringify(message, null, 2)) transportToClient.send(message).catch(onClientError) } From 14da1b44d4a0f7f439213315cbd743e15ca6e3f0 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Fri, 11 Apr 2025 13:23:12 +1000 Subject: [PATCH 059/104] 0.0.18 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c85f9f..f586b76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.17", + "version": "0.0.18", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From c542f78288aa35d4f726740ad4ed27a50f4a1fb8 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Fri, 11 Apr 2025 13:45:03 +1000 Subject: [PATCH 060/104] removing --clean flag as it sent things into a reauth loop --- README.md | 12 +------ src/client.ts | 14 +++------ src/lib/mcp-auth-config.ts | 45 ++------------------------- src/lib/node-oauth-client-provider.ts | 15 +++------ src/lib/types.ts | 2 -- src/lib/utils.ts | 17 ++-------- src/proxy.ts | 14 +++------ 7 files changed, 19 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index ded8937..c9cdfc7 100644 --- a/README.md +++ b/README.md @@ -77,16 +77,6 @@ To bypass authentication, or to emit custom headers on all requests to your remo ] ``` -* To force `mcp-remote` to ignore any existing access tokens and begin the authorization flow anew, pass `--clean`. - -```json - "args": [ - "mcp-remote", - "https://remote.mcp.server/sse", - "--clean" - ] -``` - * To change which port `mcp-remote` listens for an OAuth redirect (by default `3334`), add an additional argument after the server URL. Note that whatever port you specify, if it is unavailable an open port will be chosen at random. ```json @@ -211,4 +201,4 @@ Run the following on the command line (not from an MCP server): npx -p mcp-remote@latest mcp-remote-client https://remote.mcp.server/sse ``` -This will run through the entire authorization flow and attempt to list the tools & resources at the remote URL. Pair this with `--clean` or after running `rm -rf ~/.mcp-auth` to see if stale credentials are your problem, otherwise hopefully the issue will be more obvious in these logs than those in your MCP client. +This will run through the entire authorization flow and attempt to list the tools & resources at the remote URL. Try this after running `rm -rf ~/.mcp-auth` to see if stale credentials are your problem, otherwise hopefully the issue will be more obvious in these logs than those in your MCP client. diff --git a/src/client.ts b/src/client.ts index 9a4b7c4..d620884 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4,10 +4,7 @@ * MCP Client with OAuth support * A command-line client that connects to an MCP server using SSE with OAuth authentication. * - * Run with: npx tsx client.ts [--clean] https://example.remote/server [callback-port] - * - * Options: - * --clean: Deletes stored configuration before reading, ensuring a fresh session + * Run with: npx tsx client.ts https://example.remote/server [callback-port] * * If callback-port is not specified, an available port will be automatically selected. */ @@ -24,7 +21,7 @@ import { coordinateAuth } from './lib/coordination' /** * Main function to run the client */ -async function runClient(serverUrl: string, callbackPort: number, headers: Record, clean: boolean = false) { +async function runClient(serverUrl: string, callbackPort: number, headers: Record) { // Set up event emitter for auth flow const events = new EventEmitter() @@ -39,7 +36,6 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor serverUrl, callbackPort, clientName: 'MCP CLI Client', - clean, }) // If auth was completed by another instance, just log that we'll use the auth from disk @@ -159,9 +155,9 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor } // Parse command-line arguments and run the client -parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [--clean] [callback-port]') - .then(({ serverUrl, callbackPort, clean, headers }) => { - return runClient(serverUrl, callbackPort, headers, clean) +parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [callback-port]') + .then(({ serverUrl, callbackPort, headers }) => { + return runClient(serverUrl, callbackPort, headers) }) .catch((error) => { console.error('Fatal error:', error) diff --git a/src/lib/mcp-auth-config.ts b/src/lib/mcp-auth-config.ts index fce4219..1286b03 100644 --- a/src/lib/mcp-auth-config.ts +++ b/src/lib/mcp-auth-config.ts @@ -23,11 +23,6 @@ import { log, MCP_REMOTE_VERSION } from './utils' * All JSON files are stored with 2-space indentation for readability. */ -/** - * Known configuration file names that might need to be cleaned - */ -export const knownConfigFiles = ['client_info.json', 'tokens.json', 'code_verifier.txt', 'lock.json'] - /** * Lockfile data structure */ @@ -82,17 +77,6 @@ export async function deleteLockfile(serverUrlHash: string): Promise { await deleteConfigFile(serverUrlHash, 'lock.json') } -/** - * Deletes all known configuration files for a specific server - * @param serverUrlHash The hash of the server URL - */ -export async function cleanServerConfig(serverUrlHash: string): Promise { - log(`Cleaning configuration files for server: ${serverUrlHash}`) - for (const filename of knownConfigFiles) { - await deleteConfigFile(serverUrlHash, filename) - } -} - /** * Gets the configuration directory path * @returns The path to the configuration directory @@ -149,24 +133,12 @@ export async function deleteConfigFile(serverUrlHash: string, filename: string): * @param serverUrlHash The hash of the server URL * @param filename The name of the file to read * @param schema The schema to validate against - * @param clean Whether to clean (delete) before reading * @returns The parsed file content or undefined if the file doesn't exist */ -export async function readJsonFile( - serverUrlHash: string, - filename: string, - schema: any, - clean: boolean = false, -): Promise { +export async function readJsonFile(serverUrlHash: string, filename: string, schema: any): Promise { try { await ensureConfigDir() - // If clean flag is set, delete the file before trying to read it - if (clean) { - await deleteConfigFile(serverUrlHash, filename) - return undefined - } - const filePath = getConfigFilePath(serverUrlHash, filename) const content = await fs.readFile(filePath, 'utf-8') const result = await schema.parseAsync(JSON.parse(content)) @@ -204,24 +176,11 @@ export async function writeJsonFile(serverUrlHash: string, filename: string, dat * @param serverUrlHash The hash of the server URL * @param filename The name of the file to read * @param errorMessage Optional custom error message - * @param clean Whether to clean (delete) before reading * @returns The file content as a string */ -export async function readTextFile( - serverUrlHash: string, - filename: string, - errorMessage?: string, - clean: boolean = false, -): Promise { +export async function readTextFile(serverUrlHash: string, filename: string, errorMessage?: string): Promise { try { await ensureConfigDir() - - // If clean flag is set, delete the file before trying to read it - if (clean) { - await deleteConfigFile(serverUrlHash, filename) - throw new Error('File deleted due to clean flag') - } - const filePath = getConfigFilePath(serverUrlHash, filename) return await fs.readFile(filePath, 'utf-8') } catch (error) { diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index c02ce97..d6f2955 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -8,7 +8,7 @@ import { OAuthTokensSchema, } from '@modelcontextprotocol/sdk/shared/auth.js' import type { OAuthProviderOptions } from './types' -import { readJsonFile, writeJsonFile, readTextFile, writeTextFile, cleanServerConfig } from './mcp-auth-config' +import { readJsonFile, writeJsonFile, readTextFile, writeTextFile } from './mcp-auth-config' import { getServerUrlHash, log } from './utils' /** @@ -30,13 +30,6 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { this.callbackPath = options.callbackPath || '/oauth/callback' this.clientName = options.clientName || 'MCP CLI Client' this.clientUri = options.clientUri || 'https://github.com/modelcontextprotocol/mcp-cli' - - // If clean flag is set, proactively clean all config files for this server - if (options.clean) { - cleanServerConfig(this.serverUrlHash).catch((err) => { - log('Error cleaning server config:', err) - }) - } } get redirectUrl(): string { @@ -60,7 +53,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { */ async clientInformation(): Promise { // log('Reading client info') - return readJsonFile(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema, this.options.clean) + return readJsonFile(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema) } /** @@ -79,7 +72,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { async tokens(): Promise { // log('Reading tokens') // console.log(new Error().stack) - return readJsonFile(this.serverUrlHash, 'tokens.json', OAuthTokensSchema, this.options.clean) + return readJsonFile(this.serverUrlHash, 'tokens.json', OAuthTokensSchema) } /** @@ -120,6 +113,6 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { */ async codeVerifier(): Promise { // log('Reading code verifier') - return await readTextFile(this.serverUrlHash, 'code_verifier.txt', 'No code verifier saved for session', this.options.clean) + return await readTextFile(this.serverUrlHash, 'code_verifier.txt', 'No code verifier saved for session') } } diff --git a/src/lib/types.ts b/src/lib/types.ts index e719905..188fccb 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -16,8 +16,6 @@ export interface OAuthProviderOptions { clientName?: string /** Client URI to use for OAuth registration */ clientUri?: string - /** Whether to clean stored configuration before reading */ - clean?: boolean } /** diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b53d3b0..07dd579 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -275,18 +275,9 @@ export async function findAvailablePort(preferredPort?: number): Promise * @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, clean flag, and headers + * @returns A promise that resolves to an object with parsed serverUrl, callbackPort and headers */ export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { - // Check for --clean flag - const cleanIndex = args.indexOf('--clean') - const clean = cleanIndex !== -1 - - // Remove the flag from args if it exists - if (clean) { - args.splice(cleanIndex, 1) - } - // Process headers const headers: Record = {} args.forEach((arg, i) => { @@ -327,10 +318,6 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, log(`Using automatically selected callback port: ${callbackPort}`) } - if (clean) { - log('Clean mode enabled: config files will be reset before reading') - } - if (Object.keys(headers).length > 0) { log(`Using custom headers: ${JSON.stringify(headers)}`) } @@ -350,7 +337,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, }) } - return { serverUrl, callbackPort, clean, headers } + return { serverUrl, callbackPort, headers } } /** diff --git a/src/proxy.ts b/src/proxy.ts index f9a415f..9fd87d1 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -4,10 +4,7 @@ * MCP Proxy with OAuth support * A bidirectional proxy between a local STDIO MCP server and a remote SSE server with OAuth authentication. * - * Run with: npx tsx proxy.ts [--clean] https://example.remote/server [callback-port] - * - * Options: - * --clean: Deletes stored configuration before reading, ensuring a fresh session + * Run with: npx tsx proxy.ts https://example.remote/server [callback-port] * * If callback-port is not specified, an available port will be automatically selected. */ @@ -21,7 +18,7 @@ import { coordinateAuth } from './lib/coordination' /** * Main function to run the proxy */ -async function runProxy(serverUrl: string, callbackPort: number, headers: Record, clean: boolean = false) { +async function runProxy(serverUrl: string, callbackPort: number, headers: Record) { // Set up event emitter for auth flow const events = new EventEmitter() @@ -36,7 +33,6 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record serverUrl, callbackPort, clientName: 'MCP CLI Proxy', - clean, }) // If auth was completed by another instance, just log that we'll use the auth from disk @@ -103,9 +99,9 @@ to the CA certificate file. If using claude_desktop_config.json, this might look } // Parse command-line arguments and run the proxy -parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [--clean] [callback-port]') - .then(({ serverUrl, callbackPort, clean, headers }) => { - return runProxy(serverUrl, callbackPort, headers, clean) +parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [callback-port]') + .then(({ serverUrl, callbackPort, headers }) => { + return runProxy(serverUrl, callbackPort, headers) }) .catch((error) => { log('Fatal error:', error) From 89177a5ac5595ba7ee3881c68e81ee1b095a89ae Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Fri, 11 Apr 2025 13:46:42 +1000 Subject: [PATCH 061/104] 0.0.19 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f586b76..e10cf38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.18", + "version": "0.0.19", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 9258e2f6c4244223fad87b0a89881a488402d339 Mon Sep 17 00:00:00 2001 From: dorshany Date: Sun, 13 Apr 2025 14:12:12 -0700 Subject: [PATCH 062/104] Fix: Add Authorization header from authProvider tokens in custom fetch for SSE --- package-lock.json | 3495 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/utils.ts | 22 +- 2 files changed, 3508 insertions(+), 9 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..129df05 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3495 @@ +{ + "name": "mcp-remote", + "version": "0.0.19", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-remote", + "version": "0.0.19", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.7.0", + "express": "^4.21.2", + "open": "^10.1.0" + }, + "bin": { + "mcp-remote": "dist/proxy.js", + "mcp-remote-client": "dist/client.js" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^22.13.10", + "@types/react": "^19.0.12", + "prettier": "^3.5.3", + "react": "^19.0.0", + "tsup": "^8.4.0", + "tsx": "^4.19.3", + "typescript": "^5.8.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", + "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.3", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", + "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.1.tgz", + "integrity": "sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", + "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", + "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.4.0.tgz", + "integrity": "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.25.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 07dd579..51c085f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -85,16 +85,20 @@ export async function connectToRemoteServer( // Create transport with eventSourceInit to pass Authorization header if present const eventSourceInit = { - fetch: (url: string | URL, init: RequestInit | undefined) => { - return fetch(url, { - ...init, - headers: { - ...init?.headers, - ...headers, - }, - }) + fetch: (url: string | URL, init?: RequestInit) => { + return Promise.resolve(authProvider?.tokens?.()).then((tokens) => + fetch(url, { + ...init, + headers: { + ...(init?.headers as Record | undefined), + ...headers, + ...(tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {}), + Accept: "text/event-stream", + } as Record, + }) + ); }, - } + }; const transport = new SSEClientTransport(url, { authProvider, From 3a4d81936c575ab8ac784006cae39d09145f0484 Mon Sep 17 00:00:00 2001 From: dorshany <86413006+dorshany@users.noreply.github.com> Date: Sun, 13 Apr 2025 18:51:44 -0700 Subject: [PATCH 063/104] Remove package-lock.json --- package-lock.json | 3495 --------------------------------------------- 1 file changed, 3495 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 129df05..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3495 +0,0 @@ -{ - "name": "mcp-remote", - "version": "0.0.19", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "mcp-remote", - "version": "0.0.19", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", - "express": "^4.21.2", - "open": "^10.1.0" - }, - "bin": { - "mcp-remote": "dist/proxy.js", - "mcp-remote-client": "dist/client.js" - }, - "devDependencies": { - "@types/express": "^5.0.0", - "@types/node": "^22.13.10", - "@types/react": "^19.0.12", - "prettier": "^3.5.3", - "react": "^19.0.0", - "tsup": "^8.4.0", - "tsx": "^4.19.3", - "typescript": "^5.8.2" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", - "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", - "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.1.tgz", - "integrity": "sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bundle-require": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", - "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", - "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" - } - }, - "node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/rollup": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", - "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/router/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tsup": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.4.0.tgz", - "integrity": "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^5.1.0", - "cac": "^6.7.14", - "chokidar": "^4.0.3", - "consola": "^3.4.0", - "debug": "^4.4.0", - "esbuild": "^0.25.0", - "joycon": "^3.1.1", - "picocolors": "^1.1.1", - "postcss-load-config": "^6.0.1", - "resolve-from": "^5.0.0", - "rollup": "^4.34.8", - "source-map": "0.8.0-beta.0", - "sucrase": "^3.35.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.11", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@microsoft/api-extractor": "^7.36.0", - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.5.0" - }, - "peerDependenciesMeta": { - "@microsoft/api-extractor": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/tsup/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/tsup/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tsx": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", - "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - } - } -} From 2913b96e53d3a88761830f14ad5523cc12929bb8 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 14 Apr 2025 15:38:28 +1000 Subject: [PATCH 064/104] 0.0.20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e10cf38..7899f80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.19", + "version": "0.0.20", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 7a1bd958440a9c2f3d813e06e1434d91451a934e Mon Sep 17 00:00:00 2001 From: dp-rufus <73200607+dp-rufus@users.noreply.github.com> Date: Mon, 14 Apr 2025 22:07:40 +0100 Subject: [PATCH 065/104] Add software_id/software_version --- src/lib/node-oauth-client-provider.ts | 8 +++++++- src/lib/types.ts | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index d6f2955..1e58b7e 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -9,7 +9,7 @@ import { } from '@modelcontextprotocol/sdk/shared/auth.js' import type { OAuthProviderOptions } from './types' import { readJsonFile, writeJsonFile, readTextFile, writeTextFile } from './mcp-auth-config' -import { getServerUrlHash, log } from './utils' +import { getServerUrlHash, log, MCP_REMOTE_VERSION } from './utils' /** * Implements the OAuthClientProvider interface for Node.js environments. @@ -20,6 +20,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { private callbackPath: string private clientName: string private clientUri: string + private softwareId: string + private softwareVersion: string /** * Creates a new NodeOAuthClientProvider @@ -30,6 +32,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { this.callbackPath = options.callbackPath || '/oauth/callback' this.clientName = options.clientName || 'MCP CLI Client' this.clientUri = options.clientUri || 'https://github.com/modelcontextprotocol/mcp-cli' + this.softwareId = options.softwareId || '2e6dc280-f3c3-4e01-99a7-8181dbd1d23d' + this.softwareVersion = options.softwareVersion || MCP_REMOTE_VERSION } get redirectUrl(): string { @@ -44,6 +48,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { response_types: ['code'], client_name: this.clientName, client_uri: this.clientUri, + software_id: this.softwareId, + software_version: this.softwareVersion, } } diff --git a/src/lib/types.ts b/src/lib/types.ts index 188fccb..723b93f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -16,6 +16,10 @@ export interface OAuthProviderOptions { clientName?: string /** Client URI to use for OAuth registration */ clientUri?: string + /** Software ID to use for OAuth registration */ + softwareId?: string + /** Software version to use for OAuth registration */ + softwareVersion?: string } /** From 05961229620e7dbebdb2822837f995741cd9916d Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 16 Apr 2025 05:34:41 +0100 Subject: [PATCH 066/104] Update README.md with cursor workaround --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index c9cdfc7..6204bf3 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,23 @@ To bypass authentication, or to emit custom headers on all requests to your remo } ``` +**Note:** Cursor has a bug where spaces inside `args` aren't escaped when it invokes `npx`, which ends up mangling these values. You can work around it using: + +```jsonc +{ + // rest of config... + "args": [ + "mcp-remote", + "https://remote.mcp.server/sse", + "--header", + "Authorization:${AUTH_HEADER}" // note no spaces around ':' + ] +}, +"env": { + "AUTH_HEADER": "Bearer " // spaces OK in env vars +} +``` + ### Flags * If `npx` is producing errors, consider adding `-y` as the first argument to auto-accept the installation of the `mcp-remote` package. From a2064502b9eba380483c666adf2e142abd6ec368 Mon Sep 17 00:00:00 2001 From: kissrobber Date: Wed, 16 Apr 2025 13:34:48 +0900 Subject: [PATCH 067/104] add --allow-http flag --- README.md | 10 ++++++++++ src/lib/utils.ts | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6204bf3..29a1fbc 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,16 @@ To bypass authentication, or to emit custom headers on all requests to your remo ] ``` +* To allow HTTP connections in trusted private networks, add the `--allow-http` flag. Note: This should only be used in secure private networks where traffic cannot be intercepted. + +```json + "args": [ + "mcp-remote", + "http://internal-service.vpc/sse", + "--allow-http" + ] +``` + ### Claude Desktop [Official Docs](https://modelcontextprotocol.io/quickstart/user) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 51c085f..40c744d 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -299,6 +299,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const serverUrl = args[0] const specifiedPort = args[1] ? parseInt(args[1]) : undefined + const allowHttp = args.includes('--allow-http') if (!serverUrl) { log(usage) @@ -308,7 +309,8 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const url = new URL(serverUrl) const isLocalhost = (url.hostname === 'localhost' || url.hostname === '127.0.0.1') && url.protocol === 'http:' - if (!(url.protocol == 'https:' || isLocalhost)) { + if (!(url.protocol == 'https:' || isLocalhost || allowHttp)) { + log('Error: Non-HTTPS URLs are only allowed for localhost or when --allow-http flag is provided') log(usage) process.exit(1) } From 189ecc9fcec02e6bfa8ec2b66443ca478ecd494d Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 16 Apr 2025 15:01:56 +1000 Subject: [PATCH 068/104] 0.0.21 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7899f80..4117827 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.20", + "version": "0.0.21", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From bb4c03f069a7e7427b93b8b519446cf1e3e75cfa Mon Sep 17 00:00:00 2001 From: Rune Botten Date: Tue, 15 Apr 2025 22:51:14 -0700 Subject: [PATCH 069/104] chore: update @modelcontextprotocol/sdk to 1.9.0 to include fix for missing redirect_uri in token exchange --- package.json | 2 +- pnpm-lock.yaml | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 4117827..0102f38 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "check": "prettier --check . && tsc" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", + "@modelcontextprotocol/sdk": "^1.9.0", "express": "^4.21.2", "open": "^10.1.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74a123e..e550c3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@modelcontextprotocol/sdk': - specifier: ^1.7.0 - version: 1.7.0 + specifier: ^1.9.0 + version: 1.9.0 express: specifier: ^4.21.2 version: 4.21.2 @@ -217,8 +217,8 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@modelcontextprotocol/sdk@1.7.0': - resolution: {integrity: sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==} + '@modelcontextprotocol/sdk@1.9.0': + resolution: {integrity: sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==} engines: {node: '>=18'} '@pkgjs/parseargs@0.11.0': @@ -860,8 +860,8 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkce-challenge@4.1.0: - resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} + pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} engines: {node: '>=16.20.0'} postcss-load-config@6.0.1: @@ -1238,14 +1238,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@modelcontextprotocol/sdk@1.7.0': + '@modelcontextprotocol/sdk@1.9.0': dependencies: content-type: 1.0.5 cors: 2.8.5 + cross-spawn: 7.0.6 eventsource: 3.0.5 express: 5.0.1 express-rate-limit: 7.5.0(express@5.0.1) - pkce-challenge: 4.1.0 + pkce-challenge: 5.0.0 raw-body: 3.0.0 zod: 3.24.2 zod-to-json-schema: 3.24.5(zod@3.24.2) @@ -1885,7 +1886,7 @@ snapshots: pirates@4.0.6: {} - pkce-challenge@4.1.0: {} + pkce-challenge@5.0.0: {} postcss-load-config@6.0.1(tsx@4.19.3): dependencies: From b69fdc8ebec50c8cf7d9ec47980094329e3fc612 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 16 Apr 2025 15:59:57 +1000 Subject: [PATCH 070/104] 0.0.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0102f38..47cfa99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.21", + "version": "0.0.22", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 504aa2676140cb71605b514dce6584c4cebc0153 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 16 Apr 2025 06:00:46 -0700 Subject: [PATCH 071/104] Update link to latest auth spec in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29a1fbc..c7f9e73 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ So far, the majority of MCP servers in the wild are installed locally, using the But there's a reason most software that _could_ be moved to the web _did_ get moved to the web: it's so much easier to find and fix bugs & iterate on new features when you can push updates to all your users with a single deploy. -With the MCP [Authorization specification](https://spec.modelcontextprotocol.io/specification/draft/basic/authorization/) nearing completion, we now have a secure way of sharing our MCP servers with the world _without_ running code on user's laptops. Or at least, you would, if all the popular MCP _clients_ supported it yet. Most are stdio-only, and those that _do_ support HTTP+SSE don't yet support the OAuth flows required. +With the latest MCP [Authorization specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization), we now have a secure way of sharing our MCP servers with the world _without_ running code on user's laptops. Or at least, you would, if all the popular MCP _clients_ supported it yet. Most are stdio-only, and those that _do_ support HTTP+SSE don't yet support the OAuth flows required. That's where `mcp-remote` comes in. As soon as your chosen MCP client supports remote, authorized servers, you can remove it. Until that time, drop in this one liner and dress for the MCP clients you want! From 04e3d255b1605e08eaedf9a5d3900592c4232c2b Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 16 Apr 2025 16:59:36 +1000 Subject: [PATCH 072/104] Added Streamable HTTP support This adds a new CLI argument, --transport, with the following values: http-first (the default), http-only, sse-first, and sse-only. Any of the -first tags attempts to connect to the URL as either an HTTP or SSE server and falls back to the other. --- package.json | 4 +- pnpm-lock.yaml | 158 +++++++++++++++-------------------- src/client.ts | 178 ++++++++++++++++++++-------------------- src/lib/coordination.ts | 34 ++++++++ src/lib/utils.ts | 154 ++++++++++++++++++++++++++++------ src/proxy.ts | 76 ++++++++++++----- 6 files changed, 373 insertions(+), 231 deletions(-) diff --git a/package.json b/package.json index 47cfa99..317eca6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.22", + "version": "0.1.0-2", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", @@ -28,11 +28,11 @@ "check": "prettier --check . && tsc" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.9.0", "express": "^4.21.2", "open": "^10.1.0" }, "devDependencies": { + "@modelcontextprotocol/sdk": "^1.10.2", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "@types/react": "^19.0.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e550c3e..48d8491 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@modelcontextprotocol/sdk': - specifier: ^1.9.0 - version: 1.9.0 express: specifier: ^4.21.2 version: 4.21.2 @@ -18,6 +15,9 @@ importers: specifier: ^10.1.0 version: 10.1.0 devDependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.10.2 + version: 1.10.2 '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -217,8 +217,8 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@modelcontextprotocol/sdk@1.9.0': - resolution: {integrity: sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==} + '@modelcontextprotocol/sdk@1.10.2': + resolution: {integrity: sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==} engines: {node: '>=18'} '@pkgjs/parseargs@0.11.0': @@ -396,8 +396,8 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.1.0: - resolution: {integrity: sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} brace-expansion@2.0.1: @@ -490,15 +490,6 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -576,12 +567,12 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - eventsource-parser@3.0.0: - resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} + eventsource-parser@3.0.1: + resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==} engines: {node: '>=18.0.0'} - eventsource@3.0.5: - resolution: {integrity: sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==} + eventsource@3.0.6: + resolution: {integrity: sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==} engines: {node: '>=18.0.0'} express-rate-limit@7.5.0: @@ -594,8 +585,8 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} - express@5.0.1: - resolution: {integrity: sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==} + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} fdir@6.4.3: @@ -673,10 +664,6 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - iconv-lite@0.5.2: - resolution: {integrity: sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==} - engines: {node: '>=0.10.0'} - iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -771,8 +758,8 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime-types@3.0.0: - resolution: {integrity: sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} engines: {node: '>= 0.6'} mime@1.6.0: @@ -791,9 +778,6 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -935,8 +919,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - router@2.1.0: - resolution: {integrity: sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} run-applescript@7.0.0: @@ -953,16 +937,16 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - send@1.1.0: - resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - serve-static@2.1.0: - resolution: {integrity: sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==} + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} setprototypeof@1.2.0: @@ -1081,8 +1065,8 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.0: - resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} typescript@5.8.2: @@ -1132,8 +1116,8 @@ packages: peerDependencies: zod: ^3.24.1 - zod@3.24.2: - resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zod@3.24.3: + resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} snapshots: @@ -1238,18 +1222,18 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@modelcontextprotocol/sdk@1.9.0': + '@modelcontextprotocol/sdk@1.10.2': dependencies: content-type: 1.0.5 cors: 2.8.5 cross-spawn: 7.0.6 - eventsource: 3.0.5 - express: 5.0.1 - express-rate-limit: 7.5.0(express@5.0.1) + eventsource: 3.0.6 + express: 5.1.0 + express-rate-limit: 7.5.0(express@5.1.0) pkce-challenge: 5.0.0 raw-body: 3.0.0 - zod: 3.24.2 - zod-to-json-schema: 3.24.5(zod@3.24.2) + zod: 3.24.3 + zod-to-json-schema: 3.24.5(zod@3.24.3) transitivePeerDependencies: - supports-color @@ -1372,7 +1356,7 @@ snapshots: accepts@2.0.0: dependencies: - mime-types: 3.0.0 + mime-types: 3.0.1 negotiator: 1.0.0 ansi-regex@5.0.1: {} @@ -1408,17 +1392,17 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.1.0: + body-parser@2.2.0: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 4.4.0 http-errors: 2.0.0 - iconv-lite: 0.5.2 + iconv-lite: 0.6.3 on-finished: 2.4.1 qs: 6.14.0 raw-body: 3.0.0 - type-is: 2.0.0 + type-is: 2.0.1 transitivePeerDependencies: - supports-color @@ -1496,10 +1480,6 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.3.6: - dependencies: - ms: 2.1.2 - debug@4.4.0: dependencies: ms: 2.1.3 @@ -1575,15 +1555,15 @@ snapshots: etag@1.8.1: {} - eventsource-parser@3.0.0: {} + eventsource-parser@3.0.1: {} - eventsource@3.0.5: + eventsource@3.0.6: dependencies: - eventsource-parser: 3.0.0 + eventsource-parser: 3.0.1 - express-rate-limit@7.5.0(express@5.0.1): + express-rate-limit@7.5.0(express@5.1.0): dependencies: - express: 5.0.1 + express: 5.1.0 express@4.21.2: dependencies: @@ -1621,16 +1601,15 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.0.1: + express@5.1.0: dependencies: accepts: 2.0.0 - body-parser: 2.1.0 + body-parser: 2.2.0 content-disposition: 1.0.0 content-type: 1.0.5 cookie: 0.7.1 cookie-signature: 1.2.2 - debug: 4.3.6 - depd: 2.0.0 + debug: 4.4.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -1638,22 +1617,18 @@ snapshots: fresh: 2.0.0 http-errors: 2.0.0 merge-descriptors: 2.0.0 - methods: 1.1.2 - mime-types: 3.0.0 + mime-types: 3.0.1 on-finished: 2.4.1 once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.13.0 + qs: 6.14.0 range-parser: 1.2.1 - router: 2.1.0 - safe-buffer: 5.2.1 - send: 1.1.0 - serve-static: 2.1.0 - setprototypeof: 1.2.0 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 statuses: 2.0.1 - type-is: 2.0.0 - utils-merge: 1.0.1 + type-is: 2.0.1 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -1752,10 +1727,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.5.2: - dependencies: - safer-buffer: 2.1.2 - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -1818,7 +1789,7 @@ snapshots: dependencies: mime-db: 1.52.0 - mime-types@3.0.0: + mime-types@3.0.1: dependencies: mime-db: 1.54.0 @@ -1832,8 +1803,6 @@ snapshots: ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} mz@2.7.0: @@ -1960,11 +1929,15 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.35.0 fsevents: 2.3.3 - router@2.1.0: + router@2.2.0: dependencies: + debug: 4.4.0 + depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color run-applescript@7.0.0: {} @@ -1990,16 +1963,15 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.1.0: + send@1.2.0: dependencies: debug: 4.4.0 - destroy: 1.2.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - fresh: 0.5.2 + fresh: 2.0.0 http-errors: 2.0.0 - mime-types: 2.1.35 + mime-types: 3.0.1 ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 @@ -2016,12 +1988,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.1.0: + serve-static@2.2.0: dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.1.0 + send: 1.2.0 transitivePeerDependencies: - supports-color @@ -2162,11 +2134,11 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.0: + type-is@2.0.1: dependencies: content-type: 1.0.5 media-typer: 1.1.0 - mime-types: 3.0.0 + mime-types: 3.0.1 typescript@5.8.2: {} @@ -2204,8 +2176,8 @@ snapshots: wrappy@1.0.2: {} - zod-to-json-schema@3.24.5(zod@3.24.2): + zod-to-json-schema@3.24.5(zod@3.24.3): dependencies: - zod: 3.24.2 + zod: 3.24.3 - zod@3.24.2: {} + zod@3.24.3: {} diff --git a/src/client.ts b/src/client.ts index d620884..4e0c14c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -11,25 +11,36 @@ import { EventEmitter } from 'events' import { Client } from '@modelcontextprotocol/sdk/client/index.js' -import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' -import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' -import { parseCommandLineArgs, setupSignalHandlers, log, MCP_REMOTE_VERSION, getServerUrlHash } from './lib/utils' -import { coordinateAuth } from './lib/coordination' +import { + parseCommandLineArgs, + setupSignalHandlers, + log, + MCP_REMOTE_VERSION, + getServerUrlHash, + connectToRemoteServer, + TransportStrategy, +} from './lib/utils' +import { createLazyAuthCoordinator } from './lib/coordination' /** * Main function to run the client */ -async function runClient(serverUrl: string, callbackPort: number, headers: Record) { +async function runClient( + serverUrl: string, + callbackPort: number, + headers: Record, + transportStrategy: TransportStrategy = 'http-first', +) { // Set up event emitter for auth flow const events = new EventEmitter() // Get the server URL hash for lockfile operations const serverUrlHash = getServerUrlHash(serverUrl) - // Coordinate authentication with other instances - const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events) + // Create a lazy auth coordinator + const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPort, events) // Create the OAuth client provider const authProvider = new NodeOAuthClientProvider({ @@ -38,14 +49,6 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor clientName: 'MCP CLI Client', }) - // If auth was completed by another instance, just log that we'll use the auth from disk - if (skipBrowserAuth) { - log('Authentication was completed by another instance - will use tokens from disk...') - // TODO: remove, the callback is happening before the tokens are exchanged - // so we're slightly too early - await new Promise((res) => setTimeout(res, 1_000)) - } - // Create the client const client = new Client( { @@ -57,10 +60,33 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor }, ) - // Create the transport factory - const url = new URL(serverUrl) - function initTransport() { - const transport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) + // Keep track of the server instance for cleanup + let server: any = null + + // Define an auth initializer function + const authInitializer = async () => { + const authState = await authCoordinator.initializeAuth() + + // Store server in outer scope for cleanup + server = authState.server + + // If auth was completed by another instance, just log that we'll use the auth from disk + if (authState.skipBrowserAuth) { + log('Authentication was completed by another instance - will use tokens from disk...') + // TODO: remove, the callback is happening before the tokens are exchanged + // so we're slightly too early + await new Promise((res) => setTimeout(res, 1_000)) + } + + return { + waitForAuthCode: authState.waitForAuthCode, + skipBrowserAuth: authState.skipBrowserAuth, + } + } + + try { + // Connect to remote server with lazy authentication + const transport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy) // Set up message and error handlers transport.onmessage = (message) => { @@ -75,89 +101,59 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor log('Connection closed.') process.exit(0) } - return transport - } - const transport = initTransport() - - // Set up cleanup handler - const cleanup = async () => { - log('\nClosing connection...') - await client.close() - server.close() - } - setupSignalHandlers(cleanup) - - // Try to connect - try { - log('Connecting to server...') - await client.connect(transport) - log('Connected successfully!') - } catch (error) { - if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { - log('Authentication required. Waiting for authorization...') - - // Wait for the authorization code from the callback or another instance - const code = await waitForAuthCode() - - try { - log('Completing authorization...') - await transport.finishAuth(code) - - // Reconnect after authorization with a new transport - log('Connecting after authorization...') - await client.connect(initTransport()) - - log('Connected successfully!') - - // Request tools list after auth - log('Requesting tools list...') - const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) - log('Tools:', JSON.stringify(tools, null, 2)) - - // Request resources list after auth - log('Requesting resource list...') - const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) - log('Resources:', JSON.stringify(resources, null, 2)) - - log('Listening for messages. Press Ctrl+C to exit.') - } catch (authError) { - log('Authorization error:', authError) + // Set up cleanup handler + const cleanup = async () => { + log('\nClosing connection...') + await client.close() + // If auth was initialized and server was created, close it + if (server) { server.close() - process.exit(1) } - } else { - log('Connection error:', error) - server.close() - process.exit(1) } - } + setupSignalHandlers(cleanup) - try { - // Request tools list - log('Requesting tools list...') - const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) - log('Tools:', JSON.stringify(tools, null, 2)) - } catch (e) { - log('Error requesting tools list:', e) - } + log('Connected successfully!') - try { - // Request resources list - log('Requesting resource list...') - const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) - log('Resources:', JSON.stringify(resources, null, 2)) - } catch (e) { - log('Error requesting resources list:', e) - } + try { + // Request tools list + log('Requesting tools list...') + const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) + log('Tools:', JSON.stringify(tools, null, 2)) + } catch (e) { + log('Error requesting tools list:', e) + } - log('Listening for messages. Press Ctrl+C to exit.') + try { + // Request resources list + log('Requesting resource list...') + const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) + log('Resources:', JSON.stringify(resources, null, 2)) + } catch (e) { + log('Error requesting resources list:', e) + } + + // log('Listening for messages. Press Ctrl+C to exit.') + log('Exiting OK...') + // Only close the server if it was initialized + if (server) { + server.close() + } + process.exit(0) + } catch (error) { + log('Fatal error:', error) + // Only close the server if it was initialized + if (server) { + server.close() + } + process.exit(1) + } } // Parse command-line arguments and run the client parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [callback-port]') - .then(({ serverUrl, callbackPort, headers }) => { - return runClient(serverUrl, callbackPort, headers) + .then(({ serverUrl, callbackPort, headers, transportStrategy }) => { + return runClient(serverUrl, callbackPort, headers, transportStrategy) }) .catch((error) => { console.error('Fatal error:', error) diff --git a/src/lib/coordination.ts b/src/lib/coordination.ts index ad1d2f6..ffe0c5b 100644 --- a/src/lib/coordination.ts +++ b/src/lib/coordination.ts @@ -5,6 +5,10 @@ import express from 'express' import { AddressInfo } from 'net' import { log, setupOAuthCallbackServerWithLongPoll } from './utils' +export type AuthCoordinator = { + initializeAuth: () => Promise<{ server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean }> +} + /** * Checks if a process with the given PID is running * @param pid The process ID to check @@ -88,6 +92,36 @@ export async function waitForAuthentication(port: number): Promise { } } +/** + * Creates a lazy auth coordinator that will only initiate auth when needed + * @param serverUrlHash The hash of the server URL + * @param callbackPort The port to use for the callback server + * @param events The event emitter to use for signaling + * @returns An AuthCoordinator object with an initializeAuth method + */ +export function createLazyAuthCoordinator( + serverUrlHash: string, + callbackPort: number, + events: EventEmitter +): AuthCoordinator { + let authState: { server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean } | null = null + + return { + initializeAuth: async () => { + // If auth has already been initialized, return the existing state + if (authState) { + return authState + } + + log('Initializing auth coordination on-demand') + + // Initialize auth using the existing coordinateAuth logic + authState = await coordinateAuth(serverUrlHash, callbackPort, events) + return authState + } + } +} + /** * Coordinates authentication between multiple instances of the client/proxy * @param serverUrlHash The hash of the server URL diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 40c744d..1b68bef 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,15 @@ import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' +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' + +// Connection constants +export const REASON_AUTH_NEEDED = 'authentication-needed' +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 express from 'express' import net from 'net' @@ -65,21 +74,33 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo } /** - * Creates and connects to a remote SSE server with OAuth authentication + * Type for the auth initialization function + */ +export type AuthInitializer = () => Promise<{ + waitForAuthCode: () => Promise + skipBrowserAuth: boolean +}> + +/** + * Creates and connects to a remote server with OAuth authentication + * @param client The client to connect with * @param serverUrl The URL of the remote server * @param authProvider The OAuth client provider * @param headers Additional headers to send with the request - * @param waitForAuthCode Function to wait for the auth code - * @param skipBrowserAuth Whether to skip browser auth and use shared auth - * @returns The connected SSE client transport + * @param authInitializer Function to initialize authentication when needed + * @param transportStrategy Strategy for selecting transport type ('sse-only', 'http-only', 'sse-first', 'http-first') + * @param recursionReasons Set of reasons for recursive calls (internal use) + * @returns The connected transport */ export async function connectToRemoteServer( + client: Client | null, serverUrl: string, authProvider: OAuthClientProvider, headers: Record, - waitForAuthCode: () => Promise, - skipBrowserAuth: boolean = false, -): Promise { + authInitializer: AuthInitializer, + transportStrategy: TransportStrategy = 'http-first', + recursionReasons: Set = new Set(), +): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) @@ -93,25 +114,88 @@ export async function connectToRemoteServer( ...(init?.headers as Record | undefined), ...headers, ...(tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {}), - Accept: "text/event-stream", + Accept: 'text/event-stream', } as Record, - }) - ); + }), + ) }, - }; + } - const transport = new SSEClientTransport(url, { - authProvider, - requestInit: { headers }, - eventSourceInit, - }) + log(`Using transport strategy: ${transportStrategy}`) + // Determine if we should attempt to fallback on error + // Choose transport based on user strategy and recursion history + const shouldAttemptFallback = transportStrategy === 'http-first' || transportStrategy === 'sse-first' + + // Create transport instance based on the strategy + const sseTransport = transportStrategy === 'sse-only' || transportStrategy === 'sse-first' + const transport = sseTransport + ? new SSEClientTransport(url, { + authProvider, + requestInit: { headers }, + eventSourceInit, + }) + : new StreamableHTTPClientTransport(url, { + authProvider, + requestInit: { headers }, + }) try { - await transport.start() - log('Connected to remote server') + if (client) { + await client.connect(transport) + } else { + await transport.start() + if (!sseTransport) { + // Extremely hacky, but we didn't actually send a request when calling transport.start() above, so we don't + // know if we're even talking to an HTTP server. But if we forced that now we'd get an error later saying that + // the client is already connected. So let's just create a one-off client to make a single request and figure + // out if we're actually talking to an HTTP server or not. + const testTransport = new StreamableHTTPClientTransport(url, { authProvider, requestInit: { headers } }) + const testClient = new Client({ name: 'mcp-remote-fallback-test', version: '0.0.0' }, { capabilities: {} }) + await testClient.connect(testTransport) + } + } + log(`Connected to remote server using ${transport.constructor.name}`) + return transport } catch (error) { - if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { + // Check if it's a protocol error and we should attempt fallback + if ( + error instanceof Error && + shouldAttemptFallback && + (sseTransport + ? error.message.includes('405') || error.message.includes('Method Not Allowed') + : error.message.includes('404') || error.message.includes('Not Found')) + ) { + log(`Received error: ${error.message}`) + + // If we've already tried falling back once, throw an error + if (recursionReasons.has(REASON_TRANSPORT_FALLBACK)) { + const errorMessage = `Already attempted transport fallback. Giving up.` + log(errorMessage) + throw new Error(errorMessage) + } + + log(`Recursively reconnecting for reason: ${REASON_TRANSPORT_FALLBACK}`) + + // Add to recursion reasons set + recursionReasons.add(REASON_TRANSPORT_FALLBACK) + + // Recursively call connectToRemoteServer with the updated recursion tracking + return connectToRemoteServer( + client, + serverUrl, + authProvider, + headers, + authInitializer, + sseTransport ? 'http-only' : 'sse-only', + recursionReasons, + ) + } else if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { + log('Authentication required. Initializing auth...') + + // Initialize authentication on-demand + const { waitForAuthCode, skipBrowserAuth } = await authInitializer() + if (skipBrowserAuth) { log('Authentication required but skipping browser auth - using shared auth') } else { @@ -125,11 +209,18 @@ export async function connectToRemoteServer( log('Completing authorization...') await transport.finishAuth(code) - // Create a new transport after auth - const newTransport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) - await newTransport.start() - log('Connected to remote server after authentication') - return newTransport + if (recursionReasons.has(REASON_AUTH_NEEDED)) { + const errorMessage = `Already attempted reconnection for reason: ${REASON_AUTH_NEEDED}. Giving up.` + log(errorMessage) + throw new Error(errorMessage) + } + + // Track this reason for recursion + recursionReasons.add(REASON_AUTH_NEEDED) + log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`) + + // Recursively call connectToRemoteServer with the updated recursion tracking + return connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy, recursionReasons) } catch (authError) { log('Authorization error:', authError) throw authError @@ -301,6 +392,19 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const specifiedPort = args[1] ? parseInt(args[1]) : undefined const allowHttp = args.includes('--allow-http') + // Parse transport strategy + let transportStrategy: TransportStrategy = 'http-first' // Default + const transportIndex = args.indexOf('--transport') + if (transportIndex !== -1 && transportIndex < args.length - 1) { + const strategy = args[transportIndex + 1] + if (strategy === 'sse-only' || strategy === 'http-only' || strategy === 'sse-first' || strategy === 'http-first') { + transportStrategy = strategy as TransportStrategy + log(`Using transport strategy: ${transportStrategy}`) + } else { + log(`Warning: Ignoring invalid transport strategy: ${strategy}. Valid values are: sse-only, http-only, sse-first, http-first`) + } + } + if (!serverUrl) { log(usage) process.exit(1) @@ -343,7 +447,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, }) } - return { serverUrl, callbackPort, headers } + return { serverUrl, callbackPort, headers, transportStrategy } } /** diff --git a/src/proxy.ts b/src/proxy.ts index 9fd87d1..7263a95 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -11,22 +11,36 @@ import { EventEmitter } from 'events' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupSignalHandlers, getServerUrlHash } from './lib/utils' +import { + connectToRemoteServer, + log, + mcpProxy, + parseCommandLineArgs, + setupSignalHandlers, + getServerUrlHash, + MCP_REMOTE_VERSION, + TransportStrategy, +} from './lib/utils' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' -import { coordinateAuth } from './lib/coordination' +import { createLazyAuthCoordinator } from './lib/coordination' /** * Main function to run the proxy */ -async function runProxy(serverUrl: string, callbackPort: number, headers: Record) { +async function runProxy( + serverUrl: string, + callbackPort: number, + headers: Record, + transportStrategy: TransportStrategy = 'http-first', +) { // Set up event emitter for auth flow const events = new EventEmitter() // Get the server URL hash for lockfile operations const serverUrlHash = getServerUrlHash(serverUrl) - // Coordinate authentication with other instances - const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events) + // Create a lazy auth coordinator + const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPort, events) // Create the OAuth client provider const authProvider = new NodeOAuthClientProvider({ @@ -35,20 +49,36 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record clientName: 'MCP CLI Proxy', }) - // If auth was completed by another instance, just log that we'll use the auth from disk - if (skipBrowserAuth) { - log('Authentication was completed by another instance - will use tokens from disk') - // TODO: remove, the callback is happening before the tokens are exchanged - // so we're slightly too early - await new Promise((res) => setTimeout(res, 1_000)) - } - // Create the STDIO transport for local connections const localTransport = new StdioServerTransport() + // Keep track of the server instance for cleanup + let server: any = null + + // Define an auth initializer function + const authInitializer = async () => { + const authState = await authCoordinator.initializeAuth() + + // Store server in outer scope for cleanup + server = authState.server + + // If auth was completed by another instance, just log that we'll use the auth from disk + if (authState.skipBrowserAuth) { + log('Authentication was completed by another instance - will use tokens from disk') + // TODO: remove, the callback is happening before the tokens are exchanged + // so we're slightly too early + await new Promise((res) => setTimeout(res, 1_000)) + } + + return { + waitForAuthCode: authState.waitForAuthCode, + skipBrowserAuth: authState.skipBrowserAuth, + } + } + try { - // Connect to remote server with authentication - const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth) + // Connect to remote server with lazy authentication + const remoteTransport = await connectToRemoteServer(null, serverUrl, authProvider, headers, authInitializer, transportStrategy) // Set up bidirectional proxy between local and remote transports mcpProxy({ @@ -59,14 +89,17 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record // Start the local STDIO server await localTransport.start() log('Local STDIO server running') - log('Proxy established successfully between local STDIO and remote SSE') + log(`Proxy established successfully between local STDIO and remote ${remoteTransport.constructor.name}`) log('Press Ctrl+C to exit') // Setup cleanup handler const cleanup = async () => { await remoteTransport.close() await localTransport.close() - server.close() + // Only close the server if it was initialized + if (server) { + server.close() + } } setupSignalHandlers(cleanup) } catch (error) { @@ -93,15 +126,18 @@ to the CA certificate file. If using claude_desktop_config.json, this might look } `) } - server.close() + // Only close the server if it was initialized + if (server) { + server.close() + } process.exit(1) } } // Parse command-line arguments and run the proxy parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [callback-port]') - .then(({ serverUrl, callbackPort, headers }) => { - return runProxy(serverUrl, callbackPort, headers) + .then(({ serverUrl, callbackPort, headers, transportStrategy }) => { + return runProxy(serverUrl, callbackPort, headers, transportStrategy) }) .catch((error) => { log('Fatal error:', error) From 5a38b58f63c44825fa6c02c2410e5a4f77f3c484 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 30 Apr 2025 21:57:17 +1000 Subject: [PATCH 073/104] 0.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 317eca6..eeb95b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.1.0-2", + "version": "0.1.0", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 2b2b12decd409ff627f199cdfaaa6cd3920ba2b9 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Fri, 2 May 2025 11:19:22 +1000 Subject: [PATCH 074/104] Treat 404s and 405s as the same regardless of starting with SSE or HTTPs transport Fixes #47 #48 --- src/lib/utils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 1b68bef..9e3e243 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -162,9 +162,10 @@ export async function connectToRemoteServer( if ( error instanceof Error && shouldAttemptFallback && - (sseTransport - ? error.message.includes('405') || error.message.includes('Method Not Allowed') - : error.message.includes('404') || error.message.includes('Not Found')) + (error.message.includes('405') || + error.message.includes('Method Not Allowed') || + error.message.includes('404') || + error.message.includes('Not Found')) ) { log(`Received error: ${error.message}`) From 15f9c944f6a9c4305a697387d03dbc372c146d6e Mon Sep 17 00:00:00 2001 From: shaun smith <1936278+evalstate@users.noreply.github.com> Date: Thu, 1 May 2025 07:15:09 +0100 Subject: [PATCH 075/104] Update README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index c7f9e73..c36424c 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,23 @@ To bypass authentication, or to emit custom headers on all requests to your remo ] ``` +### Transport Strategies + +MCP Remote supports different transport strategies when connecting to an MCP server. This allows you to control whether it uses Server-Sent Events (SSE) or HTTP transport, and in what order it tries them. + +Specify the transport strategy with the `--transport` flag: + +```bash +npx mcp-remote https://example.remote/server --transport sse-only +``` + +**Available Strategies:** + +- `http-first` (default): Tries HTTP transport first, falls back to SSE if HTTP fails with a 404 error +- `sse-first`: Tries SSE transport first, falls back to HTTP if SSE fails with a 405 error +- `http-only`: Only uses HTTP transport, fails if the server doesn't support it +- `sse-only`: Only uses SSE transport, fails if the server doesn't support it + ### Claude Desktop [Official Docs](https://modelcontextprotocol.io/quickstart/user) From da1330d2aac094753a6a9b1a2fbb0119bccfc96d Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Fri, 2 May 2025 11:38:28 +1000 Subject: [PATCH 076/104] 0.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eeb95b5..bb010a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.1.0", + "version": "0.1.1", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 026caedd3c28905d3079a414825df0880928cc18 Mon Sep 17 00:00:00 2001 From: Fadojutimi Temitayo Olusegun Date: Mon, 5 May 2025 02:37:41 +0100 Subject: [PATCH 077/104] fix: changed the header argument processing from a forEach loop to a while loop to handle array modifications correctly, preventing index errors. --- src/lib/utils.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9e3e243..c6238b2 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -130,14 +130,14 @@ export async function connectToRemoteServer( const sseTransport = transportStrategy === 'sse-only' || transportStrategy === 'sse-first' const transport = sseTransport ? new SSEClientTransport(url, { - authProvider, - requestInit: { headers }, - eventSourceInit, - }) + authProvider, + requestInit: { headers }, + eventSourceInit, + }) : new StreamableHTTPClientTransport(url, { - authProvider, - requestInit: { headers }, - }) + authProvider, + requestInit: { headers }, + }) try { if (client) { @@ -376,8 +376,9 @@ export async function findAvailablePort(preferredPort?: number): Promise export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { // Process headers const headers: Record = {} - args.forEach((arg, i) => { - if (arg === '--header' && i < args.length - 1) { + let i = 0; + while (i < args.length) { + if (args[i] === '--header' && i < args.length - 1) { const value = args[i + 1] const match = value.match(/^([A-Za-z0-9_-]+):(.*)$/) if (match) { @@ -386,8 +387,11 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, log(`Warning: ignoring invalid header argument: ${value}`) } args.splice(i, 2) + // Do not increment i, as the array has shifted + continue } - }) + i++ + } const serverUrl = args[0] const specifiedPort = args[1] ? parseInt(args[1]) : undefined From c4a2d4a2425d85c9bc36b0efbc23e10cacff70f9 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 5 May 2025 12:40:27 +1000 Subject: [PATCH 078/104] 0.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb010a7..341afe2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.1.1", + "version": "0.1.2", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 67bd63192f41552fe57fe4947d811565fde2c3af Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 5 May 2025 15:11:18 +1000 Subject: [PATCH 079/104] Publishing all commits to pkg.pr.new --- .github/workflows/publish.yml | 30 ++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 31 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..dd32058 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,30 @@ +name: Publish Any Commit +on: + pull_request: + push: + branches: + - "**" + tags: + - "!**" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Build + run: pnpm build + + - run: pnpm dlx pkg-pr-new publish --compact --bin diff --git a/package.json b/package.json index 341afe2..5ee38e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "mcp-remote", "version": "0.1.2", + "packageManager": "pnpm@8.15.1", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From c9e082d9e28098c0d6b9c5a6c48fbe0ca9473c69 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 5 May 2025 16:49:20 +1000 Subject: [PATCH 080/104] Removing traces of react --- package.json | 6 +----- pnpm-lock.yaml | 24 ------------------------ tsconfig.json | 2 +- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 5ee38e5..2d65086 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,7 @@ "@modelcontextprotocol/sdk": "^1.10.2", "@types/express": "^5.0.0", "@types/node": "^22.13.10", - "@types/react": "^19.0.12", "prettier": "^3.5.3", - "react": "^19.0.0", "tsup": "^8.4.0", "tsx": "^4.19.3", "typescript": "^5.8.2" @@ -54,8 +52,6 @@ "dts": true, "clean": true, "outDir": "dist", - "external": [ - "react" - ] + "external": [] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48d8491..a987cf4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,15 +24,9 @@ importers: '@types/node': specifier: ^22.13.10 version: 22.13.10 - '@types/react': - specifier: ^19.0.12 - version: 19.0.12 prettier: specifier: ^3.5.3 version: 3.5.3 - react: - specifier: ^19.0.0 - version: 19.0.0 tsup: specifier: ^8.4.0 version: 8.4.0(tsx@4.19.3)(typescript@5.8.2) @@ -350,9 +344,6 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react@19.0.12': - resolution: {integrity: sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==} - '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} @@ -479,9 +470,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -899,10 +887,6 @@ packages: resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} engines: {node: '>= 0.8'} - react@19.0.0: - resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} - engines: {node: '>=0.10.0'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -1334,10 +1318,6 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/react@19.0.12': - dependencies: - csstype: 3.1.3 - '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 @@ -1474,8 +1454,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - csstype@3.1.3: {} - debug@2.6.9: dependencies: ms: 2.0.0 @@ -1896,8 +1874,6 @@ snapshots: iconv-lite: 0.6.3 unpipe: 1.0.0 - react@19.0.0: {} - readdirp@4.1.2: {} resolve-from@5.0.0: {} diff --git a/tsconfig.json b/tsconfig.json index cd9cfa1..9bfece1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "esModuleInterop": true, "noEmit": true, "lib": ["ES2022", "DOM"], - "types": ["node", "react"], + "types": ["node"], "forceConsistentCasingInFileNames": true, "resolveJsonModule": true } From 114ee3c4b6983fe6b425a0ab32b2c01a3cba4301 Mon Sep 17 00:00:00 2001 From: shaun smith <1936278+evalstate@users.noreply.github.com> Date: Mon, 5 May 2025 11:53:53 +0100 Subject: [PATCH 081/104] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c36424c..bc70b15 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ To bypass authentication, or to emit custom headers on all requests to your remo } ``` -**Note:** Cursor has a bug where spaces inside `args` aren't escaped when it invokes `npx`, which ends up mangling these values. You can work around it using: +**Note:** Cursor and Claude Desktop (Windows) have a bug where spaces inside `args` aren't escaped when it invokes `npx`, which ends up mangling these values. You can work around it using: ```jsonc { From b9105958c1f8c40c10361d8542c647d875b204af Mon Sep 17 00:00:00 2001 From: dp-rufus <73200607+dp-rufus@users.noreply.github.com> Date: Fri, 18 Apr 2025 03:30:43 +0100 Subject: [PATCH 082/104] Attempt auto close --- src/lib/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c6238b2..8843c3f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -300,7 +300,8 @@ export function setupOAuthCallbackServerWithLongPoll(options: OAuthCallbackServe log('Auth code received, resolving promise') authCompletedResolve(code) - res.send('Authorization successful! You may close this window and return to the CLI.') + res.send('Authorization successful! You may close this window and return to the CLI.' + + '') // Notify main flow that auth code is available options.events.emit('auth-code-received', code) From 5c71b268694635f7687ab35a38144e6ce4e9db6a Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 5 May 2025 15:25:49 +1000 Subject: [PATCH 083/104] Added a 2 second delay before closing the browser --- src/lib/utils.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 8843c3f..c88a08b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -130,14 +130,14 @@ export async function connectToRemoteServer( const sseTransport = transportStrategy === 'sse-only' || transportStrategy === 'sse-first' const transport = sseTransport ? new SSEClientTransport(url, { - authProvider, - requestInit: { headers }, - eventSourceInit, - }) + authProvider, + requestInit: { headers }, + eventSourceInit, + }) : new StreamableHTTPClientTransport(url, { - authProvider, - requestInit: { headers }, - }) + authProvider, + requestInit: { headers }, + }) try { if (client) { @@ -300,8 +300,16 @@ export function setupOAuthCallbackServerWithLongPoll(options: OAuthCallbackServe log('Auth code received, resolving promise') authCompletedResolve(code) - res.send('Authorization successful! You may close this window and return to the CLI.' + - '') + res.send(` + Authorization successful! + You may close this window and return to the CLI. + + `) // Notify main flow that auth code is available options.events.emit('auth-code-received', code) @@ -377,7 +385,7 @@ export async function findAvailablePort(preferredPort?: number): Promise export async function parseCommandLineArgs(args: string[], defaultPort: number, usage: string) { // Process headers const headers: Record = {} - let i = 0; + let i = 0 while (i < args.length) { if (args[i] === '--header' && i < args.length - 1) { const value = args[i + 1] From 45c1739b4c849575458565c8f5feb746c917fc64 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 6 May 2025 11:13:50 +1000 Subject: [PATCH 084/104] Adding (via mcp-remote ) to clientInfo.name on initialize --- .github/workflows/publish.yml | 11 ++++------- package.json | 1 - src/lib/utils.ts | 15 +++++++++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dd32058..c58f197 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,14 +15,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - run: corepack enable - - uses: actions/setup-node@v4 + - name: Setup pnpm & install + uses: wyvox/action-setup-pnpm@v3 with: - node-version: 20 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install + node-version: 22 + pnpm-version: 10 - name: Build run: pnpm build diff --git a/package.json b/package.json index 2d65086..ba21bf7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "mcp-remote", "version": "0.1.2", - "packageManager": "pnpm@8.15.1", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c88a08b..e86aaac 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -32,14 +32,21 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo let transportToClientClosed = false let transportToServerClosed = false - transportToClient.onmessage = (message) => { - // @ts-expect-error TODO + transportToClient.onmessage = (_message) => { + // TODO: fix types + const message = _message as any log('[Local→Remote]', message.method || message.id) + if (message.method === 'initialize') { + const { clientInfo } = message.params + if (clientInfo) clientInfo.name = `${clientInfo.name} (via mcp-remote ${MCP_REMOTE_VERSION})` + log(JSON.stringify(message, null, 2)) + } transportToServer.send(message).catch(onServerError) } - transportToServer.onmessage = (message) => { - // @ts-expect-error TODO: fix this type + transportToServer.onmessage = (_message) => { + // TODO: fix types + const message = _message as any log('[Remote→Local]', message.method || message.id) transportToClient.send(message).catch(onClientError) } From 63e02eef1c11aa4f218ab94906f3ac1e05591198 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 6 May 2025 09:00:58 +1000 Subject: [PATCH 085/104] Use 127.0.0.1 everywhere _except_ as a redirect_uri for the client registration --- src/lib/node-oauth-client-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/node-oauth-client-provider.ts b/src/lib/node-oauth-client-provider.ts index 1e58b7e..806f3af 100644 --- a/src/lib/node-oauth-client-provider.ts +++ b/src/lib/node-oauth-client-provider.ts @@ -37,7 +37,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider { } get redirectUrl(): string { - return `http://127.0.0.1:${this.options.callbackPort}${this.callbackPath}` + return `http://localhost:${this.options.callbackPort}${this.callbackPath}` } get clientMetadata() { From 46e33334164cb75be0b6490f205a7c87e0ae775c Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 12 May 2025 15:27:57 +1000 Subject: [PATCH 086/104] 0.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba21bf7..77a21a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.1.2", + "version": "0.1.3", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 767549412f79bd92d85f07a3c9dfe4f8a2f0cf7b Mon Sep 17 00:00:00 2001 From: Tomer Zait Date: Tue, 6 May 2025 19:00:33 +0300 Subject: [PATCH 087/104] fix issue #64 --- src/lib/utils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e86aaac..572550c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -484,6 +484,11 @@ export function setupSignalHandlers(cleanup: () => Promise) { // Keep the process alive process.stdin.resume() + process.stdin.on('end', async () => { + log('\nShutting down...') + await cleanup() + process.exit(0) + }) } /** From bd75a1cdf026bed04e03ce097293124028c329f1 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 12 May 2025 15:37:49 +1000 Subject: [PATCH 088/104] 0.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 77a21a4..4c7a40d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.1.3", + "version": "0.1.4", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", 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 089/104] 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 090/104] 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) } /** From e5cdf08bc88616d4b80b04fdd7f553d6169d07fc Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 14 May 2025 20:53:40 +1000 Subject: [PATCH 091/104] Updated SDK version --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 4c7a40d..1f87b70 100644 --- a/package.json +++ b/package.json @@ -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 From 6f2399bbfb149d80ba41542cbeebf8d05ef45745 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 14 May 2025 21:10:35 +1000 Subject: [PATCH 092/104] remove client info on conflict --- src/lib/utils.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 1f60c5e..f5d4df0 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -12,10 +12,11 @@ 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 { getConfigFilePath, readJsonFile } from './mcp-auth-config' import express from 'express' import net from 'net' import crypto from 'crypto' +import fs from 'fs/promises' // Package version from package.json export const MCP_REMOTE_VERSION = require('../../package.json').version @@ -459,14 +460,23 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, // 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 + 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(getServerUrlHash(serverUrl), '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) { From b1dfa9fe5b17f5e9642ef73da2ce7259a43e1acc Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 14 May 2025 21:21:38 +1000 Subject: [PATCH 093/104] Picking a default port based on the server hash --- src/client.ts | 2 +- src/lib/utils.ts | 31 +++++++++++++++++++------------ src/proxy.ts | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) 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 f5d4df0..a0a60dc 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,6 +4,12 @@ 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' +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' @@ -11,12 +17,6 @@ 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 { getConfigFilePath, readJsonFile } from './mcp-auth-config' -import express from 'express' -import net from 'net' -import crypto from 'crypto' -import fs from 'fs/promises' // Package version from package.json export const MCP_REMOTE_VERSION = require('../../package.json').version @@ -355,8 +355,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 @@ -370,6 +369,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 @@ -457,9 +462,11 @@ 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 [existingClientPort, availablePort] = await Promise.all([findExistingClientPort(serverUrlHash), findAvailablePort(defaultPort)]) let callbackPort: number if (specifiedPort) { @@ -467,7 +474,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, 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(getServerUrlHash(serverUrl), 'client_info.json')) + await fs.rm(getConfigFilePath(serverUrlHash, 'client_info.json')) } log(`Using specified callback port: ${specifiedPort}`) callbackPort = specifiedPort diff --git a/src/proxy.ts b/src/proxy.ts index 7263a95..535bfe2 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -135,7 +135,7 @@ to the CA certificate file. If using claude_desktop_config.json, this might look } // Parse command-line arguments and run the proxy -parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [callback-port]') +parseCommandLineArgs(process.argv.slice(2), 'Usage: npx tsx proxy.ts [callback-port]') .then(({ serverUrl, callbackPort, headers, transportStrategy }) => { return runProxy(serverUrl, callbackPort, headers, transportStrategy) }) From 5199279ea7b427237d74848b784b0f43cf434a8b Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 14 May 2025 21:24:02 +1000 Subject: [PATCH 094/104] 0.1.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f87b70..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", From 7eecc9ca3f388a24c0deb0c526fee3aedc74edb6 Mon Sep 17 00:00:00 2001 From: Will <76718650+the-wc@users.noreply.github.com> Date: Wed, 14 May 2025 11:49:34 -0700 Subject: [PATCH 095/104] Update README.md Move `env` into mcpServer configuration. The examples have it placed outside. If you don't pay attention, you'll end up wondering why you have empty `env` being passed through. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bc70b15..bc0a4a7 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,11 @@ To bypass authentication, or to emit custom headers on all requests to your remo "https://remote.mcp.server/sse", "--header", "Authorization: Bearer ${AUTH_TOKEN}" - ] + ], + "env": { + "AUTH_TOKEN": "..." + } }, - "env": { - "AUTH_TOKEN": "..." - } } } ``` @@ -65,11 +65,11 @@ To bypass authentication, or to emit custom headers on all requests to your remo "https://remote.mcp.server/sse", "--header", "Authorization:${AUTH_HEADER}" // note no spaces around ':' - ] + ], + "env": { + "AUTH_HEADER": "Bearer " // spaces OK in env vars + } }, -"env": { - "AUTH_HEADER": "Bearer " // spaces OK in env vars -} ``` ### Flags From 8f83b189665a8e194c9e2bedae2dffe94f97adb3 Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:00:50 +0200 Subject: [PATCH 096/104] adjust ci --- .github/workflows/publish.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c58f197..0b903a1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,11 +1,12 @@ name: Publish Any Commit on: + workflow_dispatch: pull_request: push: branches: - "**" tags: - - "!**" + - "v*" jobs: build: From 675dc6a76008de59a2ec97a030dc813ce2be8577 Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:01:58 +0200 Subject: [PATCH 097/104] fix tool path --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0b903a1..38e9871 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Setup pnpm & install - uses: wyvox/action-setup-pnpm@v3 + uses: https://github.com/wyvox/action-setup-pnpm@v3 with: node-version: 22 pnpm-version: 10 From 4f6de14fbc3f9deaf0a891dda6ed9440bb6bba3e Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:10:14 +0200 Subject: [PATCH 098/104] add packageManager --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b7f3f7f..32c140e 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "express": "^4.21.2", "open": "^10.1.0" }, + "packageManager": "pnpm@7.1.7", "devDependencies": { "@modelcontextprotocol/sdk": "^1.11.2", "@types/express": "^5.0.0", From 0213c20d3d2eb252a6cd03872568f04f12755670 Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:11:17 +0200 Subject: [PATCH 099/104] update pnpm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 32c140e..111afce 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "express": "^4.21.2", "open": "^10.1.0" }, - "packageManager": "pnpm@7.1.7", + "packageManager": "pnpm@10", "devDependencies": { "@modelcontextprotocol/sdk": "^1.11.2", "@types/express": "^5.0.0", From 27907a4624bc2786fd5f8020ef675affc5a9e57f Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:12:39 +0200 Subject: [PATCH 100/104] wip --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 111afce..bb4062c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "express": "^4.21.2", "open": "^10.1.0" }, - "packageManager": "pnpm@10", + "packageManager": "pnpm@10.11.0", "devDependencies": { "@modelcontextprotocol/sdk": "^1.11.2", "@types/express": "^5.0.0", From a7a76d3f1777ebb05dc5bc2ac88d7f07364d1ef0 Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:18:21 +0200 Subject: [PATCH 101/104] wip --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bb4062c..3820e75 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,10 @@ "express": "^4.21.2", "open": "^10.1.0" }, - "packageManager": "pnpm@10.11.0", + "engines": { + "node": ">=10", + "pnpm": ">=10" + }, "devDependencies": { "@modelcontextprotocol/sdk": "^1.11.2", "@types/express": "^5.0.0", From d8ce2745068160bf86b79e66ed6cb12c0111908a Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:23:07 +0200 Subject: [PATCH 102/104] wip --- .github/workflows/publish.yml | 1 - package.json | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 38e9871..6527995 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,7 +20,6 @@ jobs: uses: https://github.com/wyvox/action-setup-pnpm@v3 with: node-version: 22 - pnpm-version: 10 - name: Build run: pnpm build diff --git a/package.json b/package.json index 3820e75..bb4062c 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,7 @@ "express": "^4.21.2", "open": "^10.1.0" }, - "engines": { - "node": ">=10", - "pnpm": ">=10" - }, + "packageManager": "pnpm@10.11.0", "devDependencies": { "@modelcontextprotocol/sdk": "^1.11.2", "@types/express": "^5.0.0", From a63b93aa5cad1cfa65ce69e9de9cc420e9789734 Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 17:32:42 +0200 Subject: [PATCH 103/104] wip --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6527995..1008364 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,4 +24,4 @@ jobs: - name: Build run: pnpm build - - run: pnpm dlx pkg-pr-new publish --compact --bin + - run: pnpm dlx publish --compact --bin From d1cb48f770c4228c8639358ff608544656bbd57c Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Sun, 18 May 2025 18:01:29 +0200 Subject: [PATCH 104/104] a --- .github/workflows/publish.yml | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1008364..005fbc3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,6 +15,12 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + + - name: Add git.kvant.cloud scope + run: npm config set @kvant:registry=https://git.kvant.cloud/api/packages/${{ github.repository_owner }}/npm/ + + - name: Login to git.kvant.cloud npm + run: npm config set -- '//git.kvant.cloud/api/packages/${{ github.repository_owner }}/npm/:_authToken' "${{ secrets.PHOENIX_PACKAGE_WRITER_TOKEN }}" - name: Setup pnpm & install uses: https://github.com/wyvox/action-setup-pnpm@v3 diff --git a/package.json b/package.json index bb4062c..bf4892f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "mcp-remote", + "name": "@kvant/mcp-remote", "version": "0.1.5", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [