feat(auth): Add dynamic client registration to connectToRemoteServer for proxy

This commit adds dynamic OAuth client registration capability to the proxy.ts implementation, bringing it in line with the client.ts functionality. The proxy can now successfully connect to MCP servers that require dynamic client registration. The changes include:

- Adding client registration check and flow to connectToRemoteServer
- Safely handling optional saveClientInformation method 
- Proper error handling for auth providers that don't support registration
- Consistent logging throughout the registration process

Previously, the proxy would fail to connect to servers requiring dynamic registration, while the client worked fine. This change ensures both components have the same capabilities for OAuth authentication.
This commit is contained in:
Jon Slominski 2025-04-15 11:28:24 -05:00 committed by GitHub
parent 7a1bd95844
commit 6068ddfeb7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -85,20 +85,16 @@ export async function connectToRemoteServer(
// Create transport with eventSourceInit to pass Authorization header if present
const eventSourceInit = {
fetch: (url: string | URL, init?: RequestInit) => {
return Promise.resolve(authProvider?.tokens?.()).then((tokens) =>
fetch(url, {
...init,
headers: {
...(init?.headers as Record<string, string> | undefined),
...headers,
...(tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {}),
Accept: "text/event-stream",
} as Record<string, string>,
})
);
fetch: (url: string | URL, init: RequestInit | undefined) => {
return fetch(url, {
...init,
headers: {
...init?.headers,
...headers,
},
})
},
};
}
const transport = new SSEClientTransport(url, {
authProvider,
@ -116,6 +112,54 @@ export async function connectToRemoteServer(
log('Authentication required but skipping browser auth - using shared auth')
} else {
log('Authentication required. Waiting for authorization...')
// Check if we need to register the client
const clientInfo = await authProvider.clientInformation()
if (!clientInfo) {
log('No client information found - performing dynamic client registration')
try {
// Derive registration endpoint URL from server URL
// Typically /register or /oauth/register
const registrationUrl = new URL(url.origin)
registrationUrl.pathname = registrationUrl.pathname.replace(/\/*$/, '') + '/oauth/register'
log(`Registering client at ${registrationUrl.toString()}`)
// Get client metadata from the auth provider
const clientMetadata = (authProvider as any).clientMetadata
// Register the client
const response = await fetch(registrationUrl.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...headers
},
body: JSON.stringify(clientMetadata)
})
if (!response.ok) {
const error = await response.text()
throw new Error(`Client registration failed: ${error}`)
}
// Parse and save client information
const clientRegistration = await response.json()
log('Client registration successful')
// Check if saveClientInformation is implemented before calling it
if (typeof authProvider.saveClientInformation === 'function') {
await authProvider.saveClientInformation(clientRegistration)
} else {
log('Warning: saveClientInformation not implemented, cannot save client registration data')
throw new Error('Cannot complete OAuth flow: saveClientInformation not implemented in auth provider')
}
} catch (registrationError) {
log('Client registration error:', registrationError)
throw registrationError
}
}
}
// Wait for the authorization code from the callback