Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Glen Maddern
aa91270ddc 0.0.19 2025-04-11 13:46:42 +10:00
Glen Maddern
e5d0ac8fe9 removing --clean flag as it sent things into a reauth loop 2025-04-11 13:45:56 +10:00
8 changed files with 20 additions and 101 deletions

View file

@ -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.

View file

@ -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",

View file

@ -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<string, string>, clean: boolean = false) {
async function runClient(serverUrl: string, callbackPort: number, headers: Record<string, string>) {
// 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] <https://server-url> [callback-port]')
.then(({ serverUrl, callbackPort, clean, headers }) => {
return runClient(serverUrl, callbackPort, headers, clean)
parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts <https://server-url> [callback-port]')
.then(({ serverUrl, callbackPort, headers }) => {
return runClient(serverUrl, callbackPort, headers)
})
.catch((error) => {
console.error('Fatal error:', error)

View file

@ -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<void> {
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<void> {
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<T>(
serverUrlHash: string,
filename: string,
schema: any,
clean: boolean = false,
): Promise<T | undefined> {
export async function readJsonFile<T>(serverUrlHash: string, filename: string, schema: any): Promise<T | undefined> {
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<string> {
export async function readTextFile(serverUrlHash: string, filename: string, errorMessage?: string): Promise<string> {
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) {

View file

@ -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<OAuthClientInformation | undefined> {
// log('Reading client info')
return readJsonFile<OAuthClientInformation>(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema, this.options.clean)
return readJsonFile<OAuthClientInformation>(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema)
}
/**
@ -79,7 +72,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider {
async tokens(): Promise<OAuthTokens | undefined> {
// log('Reading tokens')
// console.log(new Error().stack)
return readJsonFile<OAuthTokens>(this.serverUrlHash, 'tokens.json', OAuthTokensSchema, this.options.clean)
return readJsonFile<OAuthTokens>(this.serverUrlHash, 'tokens.json', OAuthTokensSchema)
}
/**
@ -120,6 +113,6 @@ export class NodeOAuthClientProvider implements OAuthClientProvider {
*/
async codeVerifier(): Promise<string> {
// 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')
}
}

View file

@ -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
}
/**

View file

@ -275,18 +275,9 @@ export async function findAvailablePort(preferredPort?: number): Promise<number>
* @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<string, string> = {}
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 }
}
/**

View file

@ -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<string, string>, clean: boolean = false) {
async function runProxy(serverUrl: string, callbackPort: number, headers: Record<string, string>) {
// 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] <https://server-url> [callback-port]')
.then(({ serverUrl, callbackPort, clean, headers }) => {
return runProxy(serverUrl, callbackPort, headers, clean)
parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts <https://server-url> [callback-port]')
.then(({ serverUrl, callbackPort, headers }) => {
return runProxy(serverUrl, callbackPort, headers)
})
.catch((error) => {
log('Fatal error:', error)