@@ -15,6 +15,7 @@ import {
1515} from "@modelcontextprotocol/sdk/shared/auth.js" ;
1616import {
1717 OAuthClientProvider ,
18+ discoverOAuthMetadata ,
1819 discoverOAuthProtectedResourceMetadata ,
1920 extractResourceMetadataUrl ,
2021} from "@modelcontextprotocol/sdk/client/auth.js" ;
@@ -386,6 +387,9 @@ export class AutomatedOAuthClient extends Server {
386387 throw new Error ( "OAuth provider not initialized" ) ;
387388 }
388389
390+ // Perform client credentials flow directly for machine-to-machine authentication
391+ await this . performClientCredentialsFlow ( ) ;
392+
389393 logger . debug ( "Creating transport with automated OAuth provider..." ) ;
390394 const baseUrl = new URL ( this . config . serverUrl ) ;
391395 const transport = new StreamableHTTPClientTransport ( baseUrl , {
@@ -396,16 +400,85 @@ export class AutomatedOAuthClient extends Server {
396400 await this . client . connect ( transport ) ;
397401 logger . debug ( "Connected successfully with automated OAuth" ) ;
398402 }
403+
404+ /**
405+ * Performs the client credentials OAuth flow to obtain access tokens
406+ */
407+ private async performClientCredentialsFlow ( ) : Promise < void > {
408+ if ( ! this . oauthProvider ) {
409+ throw new Error ( "OAuth provider not initialized" ) ;
410+ }
411+
412+ try {
413+ // Check if we already have a tokens
414+ if ( this . oauthProvider . tokens ( ) ?. access_token ) {
415+ logger . debug ( "Using existing access token" ) ;
416+ return ;
417+ }
418+
419+ logger . debug ( "Performing client credentials flow..." ) ;
420+
421+ // Discover OAuth metadata
422+ const metadata = await discoverOAuthMetadata ( this . config . serverUrl ) ;
423+
424+ if ( ! metadata ?. token_endpoint ) {
425+ throw new Error ( "No token endpoint found in OAuth metadata" ) ;
426+ }
427+
428+ const clientInfo = this . oauthProvider . clientInformation ( ) ;
429+ if ( ! clientInfo ) {
430+ throw new Error ( "No client information available" ) ;
431+ }
432+
433+ // Perform client credentials token request
434+ const tokenUrl = new URL ( metadata . token_endpoint ) ;
435+ const params = new URLSearchParams ( {
436+ grant_type : "client_credentials" ,
437+ client_id : clientInfo . client_id ,
438+ client_secret : clientInfo . client_secret ! ,
439+ scope : this . oauthProvider . clientMetadata . scope || "" ,
440+ } ) ;
441+
442+ logger . debug ( `Making token request to: ${ tokenUrl } ` ) ;
443+ const response = await fetch ( tokenUrl , {
444+ method : "POST" ,
445+ headers : {
446+ "Content-Type" : "application/x-www-form-urlencoded" ,
447+ } ,
448+ body : params ,
449+ } ) ;
450+
451+ if ( ! response . ok ) {
452+ const errorText = await response . text ( ) ;
453+ throw new Error (
454+ `Token request failed: HTTP ${ response . status } - ${ errorText } `
455+ ) ;
456+ }
457+
458+ const tokenResponse = await response . json ( ) ;
459+
460+ logger . debug (
461+ "Successfully obtained access token via client credentials flow"
462+ ) ;
463+
464+ // Save the tokens (saveTokens will automatically track the issued time)
465+ this . oauthProvider . saveTokens ( tokenResponse ) ;
466+ } catch ( error ) {
467+ logger . error ( "Client credentials flow failed:" , error ) ;
468+ throw error ;
469+ }
470+ }
399471}
400472
401473/**
402474 * OAuth client provider for automated (client credentials) OAuth flows.
403475 * This provider handles machine-to-machine authentication without user interaction.
476+ * For the purposes of the integration tests, this implementation does not
477+ * refresh tokens and assumes that the intial token will not expire before the test completes.
404478 */
405479class AutomatedOAuthClientProvider implements OAuthClientProvider {
406480 private _clientInformation : OAuthClientInformationFull ;
407481 private _tokens ?: OAuthTokens ;
408- private _codeVerifier ?: string ;
409482
410483 constructor (
411484 private readonly _clientMetadata : OAuthClientMetadata ,
@@ -452,13 +525,16 @@ class AutomatedOAuthClientProvider implements OAuthClientProvider {
452525 }
453526
454527 saveCodeVerifier ( codeVerifier : string ) : void {
455- this . _codeVerifier = codeVerifier ;
528+ // Not used in client credentials flow
529+ throw new Error (
530+ "saveCodeVerifier should not be called in automated OAuth flow"
531+ ) ;
456532 }
457533
458534 codeVerifier ( ) : string {
459- if ( ! this . _codeVerifier ) {
460- throw new Error ( "No code verifier saved" ) ;
461- }
462- return this . _codeVerifier ;
535+ // Not used in client credentials flow
536+ throw new Error (
537+ "codeVerifier should not be called in automated OAuth flow"
538+ ) ;
463539 }
464540}
0 commit comments