add --skip-auth flag
This commit is contained in:
parent
504aa26761
commit
2f32badedf
4 changed files with 48 additions and 8 deletions
10
README.md
10
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
|
### Claude Desktop
|
||||||
|
|
||||||
[Official Docs](https://modelcontextprotocol.io/quickstart/user)
|
[Official Docs](https://modelcontextprotocol.io/quickstart/user)
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
15
src/proxy.ts
15
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 { 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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue