@@ -15,6 +15,7 @@ import {
15
15
} from "@modelcontextprotocol/sdk/shared/auth.js" ;
16
16
import {
17
17
OAuthClientProvider ,
18
+ discoverOAuthMetadata ,
18
19
discoverOAuthProtectedResourceMetadata ,
19
20
extractResourceMetadataUrl ,
20
21
} from "@modelcontextprotocol/sdk/client/auth.js" ;
@@ -386,6 +387,9 @@ export class AutomatedOAuthClient extends Server {
386
387
throw new Error ( "OAuth provider not initialized" ) ;
387
388
}
388
389
390
+ // Perform client credentials flow directly for machine-to-machine authentication
391
+ await this . performClientCredentialsFlow ( ) ;
392
+
389
393
logger . debug ( "Creating transport with automated OAuth provider..." ) ;
390
394
const baseUrl = new URL ( this . config . serverUrl ) ;
391
395
const transport = new StreamableHTTPClientTransport ( baseUrl , {
@@ -396,16 +400,85 @@ export class AutomatedOAuthClient extends Server {
396
400
await this . client . connect ( transport ) ;
397
401
logger . debug ( "Connected successfully with automated OAuth" ) ;
398
402
}
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
+ }
399
471
}
400
472
401
473
/**
402
474
* OAuth client provider for automated (client credentials) OAuth flows.
403
475
* 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.
404
478
*/
405
479
class AutomatedOAuthClientProvider implements OAuthClientProvider {
406
480
private _clientInformation : OAuthClientInformationFull ;
407
481
private _tokens ?: OAuthTokens ;
408
- private _codeVerifier ?: string ;
409
482
410
483
constructor (
411
484
private readonly _clientMetadata : OAuthClientMetadata ,
@@ -452,13 +525,16 @@ class AutomatedOAuthClientProvider implements OAuthClientProvider {
452
525
}
453
526
454
527
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
+ ) ;
456
532
}
457
533
458
534
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
+ ) ;
463
539
}
464
540
}
0 commit comments