UNPKG

mcp-remote

Version:

Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth

204 lines (200 loc) • 6 kB
#!/usr/bin/env node import { Client, NodeOAuthClientProvider, connectToRemoteServer, createFetchWithInit, createLazyAuthCoordinator, debugLog, discoverOAuthServerInfo, fetchWithMcpHeaders, hasUsableTokens, log, parseCommandLineArgs, serverIssuesAuthChallenge, setupSignalHandlers, version } from "./chunk-EL3EX2HH.js"; import "./chunk-MLKGABMK.js"; // src/client.ts import { EventEmitter } from "events"; // src/lib/client-diagnostics.ts function attachClientDiagnostics(client, transport, onClose) { const dispatch = transport.onmessage; transport.onmessage = (message, extra) => { log("Received message:", JSON.stringify(message, null, 2)); dispatch?.(message, extra); }; client.onerror = (error) => { log("Transport error:", error); }; client.onclose = () => { log("Connection closed."); onClose(); }; } // src/client.ts async function runClient(serverUrl, callbackPath, callbackPort, specifiedPort, headers, transportStrategy = "http-first", host, staticOAuthClientMetadata, staticOAuthClientInfo, clientMetadataUrl, useIdToken, useDeviceCode, useClientCredentials, tokenEndpoint, authorizeResource, skipResourceParameter, authorizeParams, authTimeoutMs, serverUrlHash, authSuccessUrl, bindHost) { const events = new EventEmitter(); const strictPort = !!specifiedPort || !!staticOAuthClientInfo || !!clientMetadataUrl; const authCoordinator = createLazyAuthCoordinator( serverUrlHash, callbackPath, callbackPort, events, authTimeoutMs, strictPort, authSuccessUrl, bindHost ); log("Discovering OAuth server configuration..."); const discoveryResult = await discoverOAuthServerInfo(serverUrl, headers, tokenEndpoint); if (discoveryResult.protectedResourceMetadata) { log(`Discovered authorization server: ${discoveryResult.authorizationServerUrl}`); if (discoveryResult.protectedResourceMetadata.scopes_supported) { debugLog("Protected Resource Metadata scopes", { scopes_supported: discoveryResult.protectedResourceMetadata.scopes_supported }); } } else { debugLog("No Protected Resource Metadata found, using server URL as authorization server"); } const authProvider = new NodeOAuthClientProvider({ serverUrl: discoveryResult.authorizationServerUrl, resourceServerUrl: serverUrl, callbackPath, callbackPort, host, clientName: "MCP CLI Client", staticOAuthClientMetadata, staticOAuthClientInfo, clientMetadataUrl, useIdToken, useDeviceCode, useClientCredentials, tokenEndpoint, fetchFn: createFetchWithInit(fetchWithMcpHeaders, { headers }), authorizeResource, skipResourceParameter, authorizeParams, serverUrlHash, authorizationServerMetadata: discoveryResult.authorizationServerMetadata, protectedResourceMetadata: discoveryResult.protectedResourceMetadata, wwwAuthenticateScope: discoveryResult.wwwAuthenticateScope }); const client = new Client( { name: "mcp-remote", version }, { capabilities: {} } ); let server = null; const authInitializer = async (options) => { const authState = await authCoordinator.initializeAuth(options); server = authState.server; if (authState.actualPort !== callbackPort) { log(`Using callback port ${authState.actualPort}`); authProvider.setCallbackPort(authState.actualPort); } return { waitForAuthCode: authState.waitForAuthCode, skipBrowserAuth: authState.skipBrowserAuth }; }; if (!useDeviceCode && !await hasUsableTokens(serverUrlHash) && await serverIssuesAuthChallenge(serverUrl, headers)) { await authInitializer(); } try { const transport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy); attachClientDiagnostics(client, transport, () => process.exit(0)); const cleanup = async () => { log("\nClosing connection..."); await client.close(); if (server) { server.close(); } }; setupSignalHandlers(cleanup); log("Connected successfully!"); try { log("Requesting tools list..."); const tools = await client.request({ method: "tools/list" }); log("Tools:", JSON.stringify(tools, null, 2)); } catch (e) { log("Error requesting tools list:", e); } try { log("Requesting resource list..."); const resources = await client.request({ method: "resources/list" }); log("Resources:", JSON.stringify(resources, null, 2)); } catch (e) { log("Error requesting resources list:", e); } log("Exiting OK..."); if (server) { server.close(); } process.exit(0); } catch (error) { log("Fatal error:", error); if (server) { server.close(); } process.exit(1); } } parseCommandLineArgs(process.argv.slice(2), "Usage: mcp-remote-client <https://server-url> [callback-port] [--debug]").then( ({ serverUrl, callbackPath, callbackPort, specifiedPort, headers, transportStrategy, host, staticOAuthClientMetadata, staticOAuthClientInfo, clientMetadataUrl, useIdToken, useDeviceCode, useClientCredentials, tokenEndpoint, authorizeResource, skipResourceParameter, authorizeParams, authTimeoutMs, serverUrlHash, authSuccessUrl, bindHost }) => { return runClient( serverUrl, callbackPath, callbackPort, specifiedPort, headers, transportStrategy, host, staticOAuthClientMetadata, staticOAuthClientInfo, clientMetadataUrl, useIdToken, useDeviceCode, useClientCredentials, tokenEndpoint, authorizeResource, skipResourceParameter, authorizeParams, authTimeoutMs, serverUrlHash, authSuccessUrl, bindHost ); } ).catch((error) => { console.error("Fatal error:", error); process.exit(1); });