Merge 0508a005a2
into bd75a1cdf0
This commit is contained in:
commit
43e1148725
6 changed files with 85 additions and 10 deletions
29
README.md
29
README.md
|
@ -131,6 +131,33 @@ npx mcp-remote https://example.remote/server --transport sse-only
|
|||
- `http-only`: Only uses HTTP transport, fails if the server doesn't support it
|
||||
- `sse-only`: Only uses SSE transport, fails if the server doesn't support it
|
||||
|
||||
### Static OAuth Client Metadata
|
||||
|
||||
MCP Remote supports providing static OAuth client metadata instead of using the mcp-remote defaults.
|
||||
This is useful when connecting to OAuth servers that expect specific client/software IDs or scopes.
|
||||
|
||||
Provide the client metadata as JSON with the `--static-oauth-client-metadata` flag:
|
||||
|
||||
```bash
|
||||
npx mcp-remote https://example.remote/server --static-oauth-client-metadata '{ "scope": "space separated scopes" }'
|
||||
```
|
||||
|
||||
### Static OAuth Client Information
|
||||
|
||||
Per the [spec](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#2-4-dynamic-client-registration),
|
||||
servers are encouraged but not required to support [OAuth dynamic client registration](https://datatracker.ietf.org/doc/html/rfc7591).
|
||||
|
||||
For these servers, MCP Remote supports providing static OAuth client information instead.
|
||||
This is useful when connecting to OAuth servers that require pre-registered clients.
|
||||
|
||||
Provide the client information as JSON with the `--static-oauth-client-info` flag:
|
||||
|
||||
```bash
|
||||
export MCP_REMOTE_CLIENT_ID=xxx
|
||||
export MCP_REMOTE_CLIENT_SECRET=yyy
|
||||
npx mcp-remote https://example.remote/server --static-oauth-client-info "{ \"client_id\": \"$MCP_REMOTE_CLIENT_ID\", \"client_secret\": \"$MCP_REMOTE_CLIENT_SECRET\" }"
|
||||
```
|
||||
|
||||
### Claude Desktop
|
||||
|
||||
[Official Docs](https://modelcontextprotocol.io/quickstart/user)
|
||||
|
@ -187,7 +214,7 @@ Then restarting your MCP client.
|
|||
|
||||
### Check your Node version
|
||||
|
||||
Make sure that the version of Node you have installed is [18 or
|
||||
Make sure that the version of Node you have installed is [18 or
|
||||
higher](https://modelcontextprotocol.io/quickstart/server). Claude
|
||||
Desktop will use your system version of Node, even if you have a newer
|
||||
version installed elsewhere.
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
connectToRemoteServer,
|
||||
TransportStrategy,
|
||||
} from './lib/utils'
|
||||
import { StaticOAuthClientInformationFull, StaticOAuthClientMetadata } from './lib/types'
|
||||
import { createLazyAuthCoordinator } from './lib/coordination'
|
||||
|
||||
/**
|
||||
|
@ -32,6 +33,8 @@ async function runClient(
|
|||
callbackPort: number,
|
||||
headers: Record<string, string>,
|
||||
transportStrategy: TransportStrategy = 'http-first',
|
||||
staticOAuthClientMetadata: StaticOAuthClientMetadata,
|
||||
staticOAuthClientInfo: StaticOAuthClientInformationFull,
|
||||
) {
|
||||
// Set up event emitter for auth flow
|
||||
const events = new EventEmitter()
|
||||
|
@ -47,6 +50,8 @@ async function runClient(
|
|||
serverUrl,
|
||||
callbackPort,
|
||||
clientName: 'MCP CLI Client',
|
||||
staticOAuthClientMetadata,
|
||||
staticOAuthClientInfo,
|
||||
})
|
||||
|
||||
// Create the client
|
||||
|
@ -152,8 +157,8 @@ async function runClient(
|
|||
|
||||
// Parse command-line arguments and run the client
|
||||
parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts <https://server-url> [callback-port]')
|
||||
.then(({ serverUrl, callbackPort, headers, transportStrategy }) => {
|
||||
return runClient(serverUrl, callbackPort, headers, transportStrategy)
|
||||
.then(({ serverUrl, callbackPort, headers, transportStrategy, staticOAuthClientMetadata, staticOAuthClientInfo }) => {
|
||||
return runClient(serverUrl, callbackPort, headers, transportStrategy, staticOAuthClientMetadata, staticOAuthClientInfo)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Fatal error:', error)
|
||||
|
|
|
@ -7,9 +7,10 @@ import {
|
|||
OAuthTokens,
|
||||
OAuthTokensSchema,
|
||||
} from '@modelcontextprotocol/sdk/shared/auth.js'
|
||||
import type { OAuthProviderOptions } from './types'
|
||||
import type { OAuthProviderOptions, StaticOAuthClientMetadata } from './types'
|
||||
import { readJsonFile, writeJsonFile, readTextFile, writeTextFile } from './mcp-auth-config'
|
||||
import { getServerUrlHash, log, MCP_REMOTE_VERSION } from './utils'
|
||||
import { StaticOAuthClientInformationFull } from './types'
|
||||
|
||||
/**
|
||||
* Implements the OAuthClientProvider interface for Node.js environments.
|
||||
|
@ -22,6 +23,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider {
|
|||
private clientUri: string
|
||||
private softwareId: string
|
||||
private softwareVersion: string
|
||||
private staticOAuthClientMetadata: StaticOAuthClientMetadata
|
||||
private staticOAuthClientInfo: StaticOAuthClientInformationFull
|
||||
|
||||
/**
|
||||
* Creates a new NodeOAuthClientProvider
|
||||
|
@ -34,6 +37,8 @@ export class NodeOAuthClientProvider implements OAuthClientProvider {
|
|||
this.clientUri = options.clientUri || 'https://github.com/modelcontextprotocol/mcp-cli'
|
||||
this.softwareId = options.softwareId || '2e6dc280-f3c3-4e01-99a7-8181dbd1d23d'
|
||||
this.softwareVersion = options.softwareVersion || MCP_REMOTE_VERSION
|
||||
this.staticOAuthClientMetadata = options.staticOAuthClientMetadata
|
||||
this.staticOAuthClientInfo = options.staticOAuthClientInfo
|
||||
}
|
||||
|
||||
get redirectUrl(): string {
|
||||
|
@ -50,6 +55,7 @@ export class NodeOAuthClientProvider implements OAuthClientProvider {
|
|||
client_uri: this.clientUri,
|
||||
software_id: this.softwareId,
|
||||
software_version: this.softwareVersion,
|
||||
...this.staticOAuthClientMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +64,9 @@ export class NodeOAuthClientProvider implements OAuthClientProvider {
|
|||
* @returns The client information or undefined
|
||||
*/
|
||||
async clientInformation(): Promise<OAuthClientInformation | undefined> {
|
||||
if (this.staticOAuthClientInfo) {
|
||||
return this.staticOAuthClientInfo
|
||||
}
|
||||
// log('Reading client info')
|
||||
return readJsonFile<OAuthClientInformation>(this.serverUrlHash, 'client_info.json', OAuthClientInformationSchema)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { EventEmitter } from 'events'
|
||||
import { OAuthClientInformationFull, OAuthClientMetadata } from '@modelcontextprotocol/sdk/shared/auth.js'
|
||||
|
||||
/**
|
||||
* Options for creating an OAuth client provider
|
||||
|
@ -20,6 +21,10 @@ export interface OAuthProviderOptions {
|
|||
softwareId?: string
|
||||
/** Software version to use for OAuth registration */
|
||||
softwareVersion?: string
|
||||
/** Static OAuth client metadata to override default OAuth client metadata */
|
||||
staticOAuthClientMetadata?: StaticOAuthClientMetadata
|
||||
/** Static OAuth client information to use instead of OAuth registration */
|
||||
staticOAuthClientInfo?: StaticOAuthClientInformationFull
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,3 +38,7 @@ export interface OAuthCallbackServerOptions {
|
|||
/** Event emitter to signal when auth code is received */
|
||||
events: EventEmitter
|
||||
}
|
||||
|
||||
// optional tatic OAuth client information
|
||||
export type StaticOAuthClientMetadata = OAuthClientMetadata | null | undefined
|
||||
export type StaticOAuthClientInformationFull = OAuthClientInformationFull | null | undefined
|
||||
|
|
|
@ -10,7 +10,7 @@ export const REASON_TRANSPORT_FALLBACK = 'falling-back-to-alternate-transport'
|
|||
|
||||
// Transport strategy types
|
||||
export type TransportStrategy = 'sse-only' | 'http-only' | 'sse-first' | 'http-first'
|
||||
import { OAuthCallbackServerOptions } from './types'
|
||||
import { OAuthCallbackServerOptions, StaticOAuthClientInformationFull, StaticOAuthClientMetadata } from './types'
|
||||
import express from 'express'
|
||||
import net from 'net'
|
||||
import crypto from 'crypto'
|
||||
|
@ -311,8 +311,8 @@ export function setupOAuthCallbackServerWithLongPoll(options: OAuthCallbackServe
|
|||
Authorization successful!
|
||||
You may close this window and return to the CLI.
|
||||
<script>
|
||||
// If this is a non-interactive session (no manual approval step was required) then
|
||||
// this should automatically close the window. If not, this will have no effect and
|
||||
// If this is a non-interactive session (no manual approval step was required) then
|
||||
// this should automatically close the window. If not, this will have no effect and
|
||||
// the user will see the message above.
|
||||
window.close();
|
||||
</script>
|
||||
|
@ -426,6 +426,26 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number,
|
|||
}
|
||||
}
|
||||
|
||||
let staticOAuthClientMetadata: StaticOAuthClientMetadata = null
|
||||
const staticOAuthClientMetadataIndex = args.indexOf('--static-oauth-client-metadata')
|
||||
if (staticOAuthClientMetadataIndex !== -1 && staticOAuthClientMetadataIndex < args.length - 1) {
|
||||
staticOAuthClientMetadata = JSON.parse(args[staticOAuthClientMetadataIndex + 1])
|
||||
if (staticOAuthClientMetadata) {
|
||||
log(`Using static OAuth client metadata`)
|
||||
}
|
||||
}
|
||||
|
||||
// parse static OAuth client information, if provided
|
||||
// defaults to OAuth dynamic client registration
|
||||
let staticOAuthClientInfo: StaticOAuthClientInformationFull = null
|
||||
const staticOAuthClientInfoIndex = args.indexOf('--static-oauth-client-info')
|
||||
if (staticOAuthClientInfoIndex !== -1 && staticOAuthClientInfoIndex < args.length - 1) {
|
||||
staticOAuthClientInfo = JSON.parse(args[staticOAuthClientInfoIndex + 1])
|
||||
if (staticOAuthClientInfo) {
|
||||
log(`Using static OAuth client information`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!serverUrl) {
|
||||
log(usage)
|
||||
process.exit(1)
|
||||
|
@ -468,7 +488,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number,
|
|||
})
|
||||
}
|
||||
|
||||
return { serverUrl, callbackPort, headers, transportStrategy }
|
||||
return { serverUrl, callbackPort, headers, transportStrategy, staticOAuthClientMetadata, staticOAuthClientInfo }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
MCP_REMOTE_VERSION,
|
||||
TransportStrategy,
|
||||
} from './lib/utils'
|
||||
import { StaticOAuthClientInformationFull, StaticOAuthClientMetadata } from './lib/types'
|
||||
import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider'
|
||||
import { createLazyAuthCoordinator } from './lib/coordination'
|
||||
|
||||
|
@ -32,6 +33,8 @@ async function runProxy(
|
|||
callbackPort: number,
|
||||
headers: Record<string, string>,
|
||||
transportStrategy: TransportStrategy = 'http-first',
|
||||
staticOAuthClientMetadata: StaticOAuthClientMetadata,
|
||||
staticOAuthClientInfo: StaticOAuthClientInformationFull,
|
||||
) {
|
||||
// Set up event emitter for auth flow
|
||||
const events = new EventEmitter()
|
||||
|
@ -47,6 +50,8 @@ async function runProxy(
|
|||
serverUrl,
|
||||
callbackPort,
|
||||
clientName: 'MCP CLI Proxy',
|
||||
staticOAuthClientMetadata,
|
||||
staticOAuthClientInfo,
|
||||
})
|
||||
|
||||
// Create the STDIO transport for local connections
|
||||
|
@ -136,8 +141,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 <https://server-url> [callback-port]')
|
||||
.then(({ serverUrl, callbackPort, headers, transportStrategy }) => {
|
||||
return runProxy(serverUrl, callbackPort, headers, transportStrategy)
|
||||
.then(({ serverUrl, callbackPort, headers, transportStrategy, staticOAuthClientMetadata, staticOAuthClientInfo }) => {
|
||||
return runProxy(serverUrl, callbackPort, headers, transportStrategy, staticOAuthClientMetadata, staticOAuthClientInfo)
|
||||
})
|
||||
.catch((error) => {
|
||||
log('Fatal error:', error)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue