add --skip-auth flag

This commit is contained in:
kissrobber 2025-04-18 09:45:13 +09:00
parent 504aa26761
commit 2f32badedf
4 changed files with 48 additions and 8 deletions

View file

@ -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 ### Claude Desktop
[Official Docs](https://modelcontextprotocol.io/quickstart/user) [Official Docs](https://modelcontextprotocol.io/quickstart/user)

View file

@ -156,7 +156,7 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor
// Parse command-line arguments and run the client // Parse command-line arguments and run the client
parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts <https://server-url> [callback-port]') parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts <https://server-url> [callback-port]')
.then(({ serverUrl, callbackPort, headers }) => { .then(({ serverUrl, callbackPort, headers, skipAuth }) => {
return runClient(serverUrl, callbackPort, headers) return runClient(serverUrl, callbackPort, headers)
}) })
.catch((error) => { .catch((error) => {

View file

@ -71,6 +71,7 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo
* @param headers Additional headers to send with the request * @param headers Additional headers to send with the request
* @param waitForAuthCode Function to wait for the auth code * @param waitForAuthCode Function to wait for the auth code
* @param skipBrowserAuth Whether to skip browser auth and use shared auth * @param skipBrowserAuth Whether to skip browser auth and use shared auth
* @param skipAuth Whether to skip authentication
* @returns The connected SSE client transport * @returns The connected SSE client transport
*/ */
export async function connectToRemoteServer( export async function connectToRemoteServer(
@ -79,6 +80,7 @@ export async function connectToRemoteServer(
headers: Record<string, string>, headers: Record<string, string>,
waitForAuthCode: () => Promise<string>, waitForAuthCode: () => Promise<string>,
skipBrowserAuth: boolean = false, skipBrowserAuth: boolean = false,
skipAuth: boolean = false,
): Promise<SSEClientTransport> { ): Promise<SSEClientTransport> {
log(`[${pid}] Connecting to remote server: ${serverUrl}`) log(`[${pid}] Connecting to remote server: ${serverUrl}`)
const url = new URL(serverUrl) const url = new URL(serverUrl)
@ -86,6 +88,18 @@ export async function connectToRemoteServer(
// Create transport with eventSourceInit to pass Authorization header if present // Create transport with eventSourceInit to pass Authorization header if present
const eventSourceInit = { const eventSourceInit = {
fetch: (url: string | URL, init?: RequestInit) => { 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<string, string> | undefined),
...headers,
Accept: "text/event-stream",
} as Record<string, string>,
});
}
return Promise.resolve(authProvider?.tokens?.()).then((tokens) => return Promise.resolve(authProvider?.tokens?.()).then((tokens) =>
fetch(url, { fetch(url, {
...init, ...init,
@ -111,6 +125,12 @@ export async function connectToRemoteServer(
log('Connected to remote server') log('Connected to remote server')
return transport return transport
} catch (error) { } 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 (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) {
if (skipBrowserAuth) { if (skipBrowserAuth) {
log('Authentication required but skipping browser auth - using shared auth') 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 serverUrl = args[0]
const specifiedPort = args[1] ? parseInt(args[1]) : undefined const specifiedPort = args[1] ? parseInt(args[1]) : undefined
const allowHttp = args.includes('--allow-http') const allowHttp = args.includes('--allow-http')
const skipAuth = args.includes('--skip-auth')
if (!serverUrl) { if (!serverUrl) {
log(usage) log(usage)
@ -343,7 +364,13 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number,
}) })
} }
return { serverUrl, callbackPort, headers } return {
serverUrl,
callbackPort,
headers,
allowHttp,
skipAuth,
}
} }
/** /**

View file

@ -14,19 +14,22 @@ 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 } from './lib/utils'
import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider'
import { coordinateAuth } from './lib/coordination' import { coordinateAuth } from './lib/coordination'
import express from 'express'
/** /**
* Main function to run the proxy * Main function to run the proxy
*/ */
async function runProxy(serverUrl: string, callbackPort: number, headers: Record<string, string>) { async function runProxy(serverUrl: string, callbackPort: number, headers: Record<string, string>, skipAuth: boolean = false) {
// Set up event emitter for auth flow // Set up event emitter for auth flow
const events = new EventEmitter() const events = new EventEmitter()
// Get the server URL hash for lockfile operations // Get the server URL hash for lockfile operations
const serverUrlHash = getServerUrlHash(serverUrl) const serverUrlHash = getServerUrlHash(serverUrl)
// Coordinate authentication with other instances // Skip authentication related processes when skip auth is enabled
const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events) const { server, waitForAuthCode, skipBrowserAuth } = skipAuth
? { server: express().listen(0), waitForAuthCode: () => Promise.resolve(''), skipBrowserAuth: true }
: await coordinateAuth(serverUrlHash, callbackPort, events)
// Create the OAuth client provider // Create the OAuth client provider
const authProvider = new NodeOAuthClientProvider({ const authProvider = new NodeOAuthClientProvider({
@ -48,7 +51,7 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record
try { try {
// Connect to remote server with authentication // 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 // Set up bidirectional proxy between local and remote transports
mcpProxy({ 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 // Parse command-line arguments and run the proxy
parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts <https://server-url> [callback-port]') parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts <https://server-url> [callback-port]')
.then(({ serverUrl, callbackPort, headers }) => { .then(({ serverUrl, callbackPort, headers, skipAuth }) => {
return runProxy(serverUrl, callbackPort, headers) return runProxy(serverUrl, callbackPort, headers, skipAuth)
}) })
.catch((error) => { .catch((error) => {
log('Fatal error:', error) log('Fatal error:', error)