@@ -18,7 +18,7 @@ export async function createAutomatedOAuthTransport(
1818 clientId : string ,
1919 clientSecret : string
2020) : Promise < StreamableHTTPClientTransport > {
21- const scope = await discoverScope ( serverUrl ) ;
21+ const scope = await retryWithBackoff ( ( ) => discoverScope ( serverUrl ) , 5 , 2000 ) ;
2222
2323 const clientMetadata : OAuthClientMetadata = {
2424 client_name : "MCP Client" ,
@@ -30,58 +30,95 @@ export async function createAutomatedOAuthTransport(
3030 } ;
3131
3232 const oauthProvider = new AutomatedOAuthClientProvider ( clientMetadata , clientId , clientSecret ) ;
33- await performClientCredentialsFlow ( serverUrl , oauthProvider ) ;
33+ await retryWithBackoff ( ( ) => performClientCredentialsFlow ( serverUrl , oauthProvider ) , 5 , 2000 ) ;
3434
3535 return new StreamableHTTPClientTransport ( new URL ( serverUrl ) , {
3636 authProvider : oauthProvider ,
3737 } ) ;
3838}
3939
40+ async function retryWithBackoff < T > (
41+ fn : ( ) => Promise < T > ,
42+ maxRetries : number ,
43+ initialDelay : number
44+ ) : Promise < T > {
45+ let lastError : Error | undefined ;
46+ for ( let i = 0 ; i < maxRetries ; i ++ ) {
47+ try {
48+ return await fn ( ) ;
49+ } catch ( error ) {
50+ lastError = error as Error ;
51+ if ( i < maxRetries - 1 ) {
52+ const delay = initialDelay * Math . pow ( 2 , i ) ;
53+ await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
54+ }
55+ }
56+ }
57+ throw lastError ;
58+ }
59+
4060async function discoverScope ( serverUrl : string ) : Promise < string > {
41- const response = await fetch ( serverUrl , {
42- method : "POST" ,
43- headers : { "Content-Type" : "application/json" } ,
44- body : JSON . stringify ( { jsonrpc : "2.0" , method : "ping" , id : 1 } ) ,
45- } ) ;
46- const resourceMetadataUrl = extractResourceMetadataUrl ( response ) ;
47- const resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
48- return resourceMetadata . scopes_supported ?. join ( " " ) || "" ;
61+ const controller = new AbortController ( ) ;
62+ const timeout = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ;
63+
64+ try {
65+ const response = await fetch ( serverUrl , {
66+ method : "POST" ,
67+ headers : { "Content-Type" : "application/json" } ,
68+ body : JSON . stringify ( { jsonrpc : "2.0" , method : "ping" , id : 1 } ) ,
69+ signal : controller . signal ,
70+ } ) ;
71+ const resourceMetadataUrl = extractResourceMetadataUrl ( response ) ;
72+ const resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
73+ return resourceMetadata . scopes_supported ?. join ( " " ) || "" ;
74+ } finally {
75+ clearTimeout ( timeout ) ;
76+ }
4977}
5078
5179async function performClientCredentialsFlow ( serverUrl : string , oauthProvider : AutomatedOAuthClientProvider ) : Promise < void > {
5280 if ( oauthProvider . tokens ( ) ?. access_token ) return ;
5381
54- const response = await fetch ( serverUrl , {
55- method : "POST" ,
56- headers : { "Content-Type" : "application/json" } ,
57- body : JSON . stringify ( { jsonrpc : "2.0" , method : "ping" , id : 1 } ) ,
58- } ) ;
59- const resourceMetadataUrl = extractResourceMetadataUrl ( response ) ;
60- const resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
61- const authServerUrl = resourceMetadata . authorization_servers ?. [ 0 ] ;
62- if ( ! authServerUrl ) throw new Error ( "No authorization server found" ) ;
63-
64- const metadata = await discoverAuthorizationServerMetadata ( authServerUrl ) ;
65- if ( ! metadata ?. token_endpoint ) throw new Error ( "No token endpoint found" ) ;
66-
67- const clientInfo = oauthProvider . clientInformation ( ) ;
68- if ( ! clientInfo ) throw new Error ( "No client information available" ) ;
69-
70- const params = new URLSearchParams ( {
71- grant_type : "client_credentials" ,
72- client_id : clientInfo . client_id ,
73- client_secret : clientInfo . client_secret ! ,
74- scope : oauthProvider . clientMetadata . scope || "" ,
75- } ) ;
76-
77- const tokenResponse = await fetch ( metadata . token_endpoint , {
78- method : "POST" ,
79- headers : { "Content-Type" : "application/x-www-form-urlencoded" } ,
80- body : params ,
81- } ) ;
82-
83- if ( ! tokenResponse . ok ) throw new Error ( `Token request failed: ${ tokenResponse . status } ` ) ;
84- oauthProvider . saveTokens ( await tokenResponse . json ( ) ) ;
82+ const controller = new AbortController ( ) ;
83+ const timeout = setTimeout ( ( ) => controller . abort ( ) , 10000 ) ;
84+
85+ try {
86+ const response = await fetch ( serverUrl , {
87+ method : "POST" ,
88+ headers : { "Content-Type" : "application/json" } ,
89+ body : JSON . stringify ( { jsonrpc : "2.0" , method : "ping" , id : 1 } ) ,
90+ signal : controller . signal ,
91+ } ) ;
92+ const resourceMetadataUrl = extractResourceMetadataUrl ( response ) ;
93+ const resourceMetadata = await discoverOAuthProtectedResourceMetadata ( serverUrl , { resourceMetadataUrl } ) ;
94+ const authServerUrl = resourceMetadata . authorization_servers ?. [ 0 ] ;
95+ if ( ! authServerUrl ) throw new Error ( "No authorization server found" ) ;
96+
97+ const metadata = await discoverAuthorizationServerMetadata ( authServerUrl ) ;
98+ if ( ! metadata ?. token_endpoint ) throw new Error ( "No token endpoint found" ) ;
99+
100+ const clientInfo = oauthProvider . clientInformation ( ) ;
101+ if ( ! clientInfo ) throw new Error ( "No client information available" ) ;
102+
103+ const params = new URLSearchParams ( {
104+ grant_type : "client_credentials" ,
105+ client_id : clientInfo . client_id ,
106+ client_secret : clientInfo . client_secret ! ,
107+ scope : oauthProvider . clientMetadata . scope || "" ,
108+ } ) ;
109+
110+ const tokenResponse = await fetch ( metadata . token_endpoint , {
111+ method : "POST" ,
112+ headers : { "Content-Type" : "application/x-www-form-urlencoded" } ,
113+ body : params ,
114+ signal : controller . signal ,
115+ } ) ;
116+
117+ if ( ! tokenResponse . ok ) throw new Error ( `Token request failed: ${ tokenResponse . status } ` ) ;
118+ oauthProvider . saveTokens ( await tokenResponse . json ( ) ) ;
119+ } finally {
120+ clearTimeout ( timeout ) ;
121+ }
85122}
86123
87124class AutomatedOAuthClientProvider implements OAuthClientProvider {
0 commit comments