wip, client now using the util with fallback

This commit is contained in:
Glen Maddern 2025-04-17 12:26:47 +10:00
parent f80c6c4850
commit 0bf84d5d22

View file

@ -11,17 +11,28 @@
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'
import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider'
import { parseCommandLineArgs, setupSignalHandlers, log, MCP_REMOTE_VERSION, getServerUrlHash } from './lib/utils' import {
parseCommandLineArgs,
setupSignalHandlers,
log,
MCP_REMOTE_VERSION,
getServerUrlHash,
connectToRemoteServer,
TransportStrategy,
} from './lib/utils'
import { coordinateAuth } from './lib/coordination' import { coordinateAuth } from './lib/coordination'
/** /**
* Main function to run the client * Main function to run the client
*/ */
async function runClient(serverUrl: string, callbackPort: number, headers: Record<string, string>) { async function runClient(
serverUrl: string,
callbackPort: number,
headers: Record<string, string>,
transportStrategy: TransportStrategy = 'http-first',
) {
// Set up event emitter for auth flow // Set up event emitter for auth flow
const events = new EventEmitter() const events = new EventEmitter()
@ -57,10 +68,9 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor
}, },
) )
// Create the transport factory try {
const url = new URL(serverUrl) // Connect to remote server with authentication
function initTransport() { const transport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, transportStrategy)
const transport = new SSEClientTransport(url, { authProvider, requestInit: { headers } })
// Set up message and error handlers // Set up message and error handlers
transport.onmessage = (message) => { transport.onmessage = (message) => {
@ -75,89 +85,50 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor
log('Connection closed.') log('Connection closed.')
process.exit(0) process.exit(0)
} }
return transport
}
const transport = initTransport() // Set up cleanup handler
const cleanup = async () => {
log('\nClosing connection...')
await client.close()
server.close()
}
setupSignalHandlers(cleanup)
// Set up cleanup handler // Connect the client
const cleanup = async () => { log('Connecting client...')
log('\nClosing connection...')
await client.close()
server.close()
}
setupSignalHandlers(cleanup)
// Try to connect
try {
log('Connecting to server...')
await client.connect(transport) await client.connect(transport)
log('Connected successfully!') log('Connected successfully!')
} catch (error) {
if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) {
log('Authentication required. Waiting for authorization...')
// Wait for the authorization code from the callback or another instance try {
const code = await waitForAuthCode() // Request tools list
log('Requesting tools list...')
try { const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema)
log('Completing authorization...') log('Tools:', JSON.stringify(tools, null, 2))
await transport.finishAuth(code) } catch (e) {
log('Error requesting tools list:', e)
// Reconnect after authorization with a new transport
log('Connecting after authorization...')
await client.connect(initTransport())
log('Connected successfully!')
// Request tools list after auth
log('Requesting tools list...')
const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema)
log('Tools:', JSON.stringify(tools, null, 2))
// Request resources list after auth
log('Requesting resource list...')
const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema)
log('Resources:', JSON.stringify(resources, null, 2))
log('Listening for messages. Press Ctrl+C to exit.')
} catch (authError) {
log('Authorization error:', authError)
server.close()
process.exit(1)
}
} else {
log('Connection error:', error)
server.close()
process.exit(1)
} }
}
try { try {
// Request tools list // Request resources list
log('Requesting tools list...') log('Requesting resource list...')
const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema)
log('Tools:', JSON.stringify(tools, null, 2)) log('Resources:', JSON.stringify(resources, null, 2))
} catch (e) { } catch (e) {
log('Error requesting tools list:', e) log('Error requesting resources list:', e)
} }
try { log('Listening for messages. Press Ctrl+C to exit.')
// Request resources list } catch (error) {
log('Requesting resource list...') log('Fatal error:', error)
const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) server.close()
log('Resources:', JSON.stringify(resources, null, 2)) process.exit(1)
} catch (e) {
log('Error requesting resources list:', e)
} }
log('Listening for messages. Press Ctrl+C to exit.')
} }
// 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, transportStrategy }) => {
return runClient(serverUrl, callbackPort, headers) return runClient(serverUrl, callbackPort, headers, transportStrategy)
}) })
.catch((error) => { .catch((error) => {
console.error('Fatal error:', error) console.error('Fatal error:', error)