diff --git a/README.md b/README.md index c7f9e73..f76dbe3 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,16 @@ To bypass authentication, or to emit custom headers on all requests to your remo ] ``` +* To completely skip authentication for servers that don't require it, add the `--skip-auth` flag. While the primary purpose of mcp-remote is to provide OAuth authentication to clients connecting to MCP servers, this flag enables the tool to be used as a simple SSE connection proxy in trusted environments (e.g., development environments, internal networks) where authentication is not required. + +```json + "args": [ + "mcp-remote", + "https://remote.mcp.server/sse", + "--skip-auth" + ] +``` + ### Claude Desktop [Official Docs](https://modelcontextprotocol.io/quickstart/user) diff --git a/src/client.ts b/src/client.ts index d620884..f3cf67b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -156,7 +156,7 @@ 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 [callback-port]') - .then(({ serverUrl, callbackPort, headers }) => { + .then(({ serverUrl, callbackPort, headers, skipAuth }) => { return runClient(serverUrl, callbackPort, headers) }) .catch((error) => { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 40c744d..18a29a6 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -71,6 +71,7 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo * @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 + * @param skipAuth Whether to skip authentication * @returns The connected SSE client transport */ export async function connectToRemoteServer( @@ -79,6 +80,7 @@ export async function connectToRemoteServer( headers: Record, waitForAuthCode: () => Promise, skipBrowserAuth: boolean = false, + skipAuth: boolean = false, ): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) @@ -86,6 +88,18 @@ export async function connectToRemoteServer( // Create transport with eventSourceInit to pass Authorization header if present const eventSourceInit = { fetch: (url: string | URL, init?: RequestInit) => { + // Skip adding authorization header when skip auth is enabled + if (skipAuth) { + return fetch(url, { + ...init, + headers: { + ...(init?.headers as Record | undefined), + ...headers, + Accept: "text/event-stream", + } as Record, + }); + } + return Promise.resolve(authProvider?.tokens?.()).then((tokens) => fetch(url, { ...init, @@ -111,6 +125,12 @@ export async function connectToRemoteServer( log('Connected to remote server') return transport } catch (error) { + // Ignore authentication errors when skip auth is enabled + if (skipAuth) { + log('Skipping authentication as requested') + return transport + } + if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { if (skipBrowserAuth) { log('Authentication required but skipping browser auth - using shared auth') @@ -300,6 +320,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') + const skipAuth = args.includes('--skip-auth') if (!serverUrl) { log(usage) @@ -343,7 +364,13 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, }) } - return { serverUrl, callbackPort, headers } + return { + serverUrl, + callbackPort, + headers, + allowHttp, + skipAuth, + } } /** diff --git a/src/proxy.ts b/src/proxy.ts index 9fd87d1..fee581c 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -14,19 +14,22 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupSignalHandlers, getServerUrlHash } from './lib/utils' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' import { coordinateAuth } from './lib/coordination' +import express from 'express' /** * Main function to run the proxy */ -async function runProxy(serverUrl: string, callbackPort: number, headers: Record) { +async function runProxy(serverUrl: string, callbackPort: number, headers: Record, skipAuth: boolean = false) { // 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) + // Skip authentication related processes when skip auth is enabled + const { server, waitForAuthCode, skipBrowserAuth } = skipAuth + ? { server: express().listen(0), waitForAuthCode: () => Promise.resolve(''), skipBrowserAuth: true } + : await coordinateAuth(serverUrlHash, callbackPort, events) // Create the OAuth client provider const authProvider = new NodeOAuthClientProvider({ @@ -48,7 +51,7 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record try { // Connect to remote server with authentication - const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth) + const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, skipAuth) // Set up bidirectional proxy between local and remote transports mcpProxy({ @@ -100,8 +103,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 [callback-port]') - .then(({ serverUrl, callbackPort, headers }) => { - return runProxy(serverUrl, callbackPort, headers) + .then(({ serverUrl, callbackPort, headers, skipAuth }) => { + return runProxy(serverUrl, callbackPort, headers, skipAuth) }) .catch((error) => { log('Fatal error:', error)