@@ -174,7 +174,7 @@ export class McpOAuthClientProvider implements OAuthClientProvider {
174174
175175 // Check if we have a cached client_id from previous registration
176176 const cachedData = await this . _secretStorage . getOAuthData ( this . _serverUrl )
177- if ( cachedData ?. client_id && cachedData . redirect_uri === this . redirectUrl ) {
177+ if ( cachedData ?. client_id ) {
178178 this . _clientInfo = {
179179 client_id : cachedData . client_id ,
180180 redirect_uris : [ this . redirectUrl ] ,
@@ -219,9 +219,17 @@ export class McpOAuthClientProvider implements OAuthClientProvider {
219219 return this . _refreshPromise
220220 }
221221
222- this . _refreshPromise = this . refreshAccessToken ( data . tokens . refresh_token ) . finally ( ( ) => {
223- this . _refreshPromise = null
224- } )
222+ // Use the client_id stored alongside the tokens — it is the one the
223+ // auth server bound the refresh token to. `this._clientInfo.client_id`
224+ // may differ if a fresh DCR was performed (e.g. after stale token
225+ // cleanup removed the cached data).
226+ const clientIdForRefresh = data . client_id ?? this . _clientInfo ?. client_id
227+
228+ this . _refreshPromise = this . refreshAccessToken ( data . tokens . refresh_token , clientIdForRefresh ) . finally (
229+ ( ) => {
230+ this . _refreshPromise = null
231+ } ,
232+ )
225233
226234 try {
227235 return await this . _refreshPromise
@@ -236,13 +244,12 @@ export class McpOAuthClientProvider implements OAuthClientProvider {
236244 return undefined
237245 }
238246
239- async saveTokens ( tokens : OAuthTokens ) : Promise < void > {
247+ async saveTokens ( tokens : OAuthTokens , clientIdOverride ?: string ) : Promise < void > {
240248 const expires_at = tokens . expires_in ? Date . now ( ) + tokens . expires_in * 1000 : Date . now ( ) + 3600 * 1000 // default 1 hour when server omits expires_in
241249 await this . _secretStorage . saveOAuthData ( this . _serverUrl , {
242250 tokens,
243251 expires_at,
244- client_id : this . _clientInfo ?. client_id ,
245- redirect_uri : this . redirectUrl ,
252+ client_id : clientIdOverride ?? this . _clientInfo ?. client_id ,
246253 } )
247254 }
248255
@@ -374,24 +381,30 @@ export class McpOAuthClientProvider implements OAuthClientProvider {
374381
375382 /**
376383 * Refreshes the access token using a refresh token.
384+ *
377385 * @param refreshToken The refresh token to use.
386+ * @param clientIdOverride Optional client_id to use instead of `this._clientInfo.client_id`.
387+ * This is used when the stored tokens were issued to a different client_id than the
388+ * current in-memory registration (e.g. after a port change caused a new DCR).
378389 * @returns The new tokens.
379390 */
380- async refreshAccessToken ( refreshToken : string ) : Promise < OAuthTokens > {
391+ async refreshAccessToken ( refreshToken : string , clientIdOverride ?: string ) : Promise < OAuthTokens > {
381392 if ( ! this . _authServerMeta ?. token_endpoint ) {
382393 throw new Error ( "No token_endpoint in auth server metadata — cannot refresh token" )
383394 }
384- if ( ! this . _clientInfo ) {
395+
396+ const clientId = clientIdOverride ?? this . _clientInfo ?. client_id
397+ if ( ! clientId ) {
385398 throw new Error ( "No client information — registerClientIfNeeded() must be called first" )
386399 }
387400
388401 const params : Record < string , string > = {
389402 grant_type : "refresh_token" ,
390403 refresh_token : refreshToken ,
391- client_id : this . _clientInfo . client_id ,
404+ client_id : clientId ,
392405 }
393406
394- if ( this . _tokenEndpointAuthMethod === "client_secret_post" && this . _clientInfo . client_secret ) {
407+ if ( this . _tokenEndpointAuthMethod === "client_secret_post" && this . _clientInfo ? .client_secret ) {
395408 params . client_secret = this . _clientInfo . client_secret
396409 }
397410
@@ -405,11 +418,12 @@ export class McpOAuthClientProvider implements OAuthClientProvider {
405418 } )
406419
407420 if ( ! response . ok ) {
408- throw new Error ( `Token refresh failed: HTTP ${ response . status } ` )
421+ const errorBody = await response . text ( ) . catch ( ( ) => "" )
422+ throw new Error ( `Token refresh failed: HTTP ${ response . status } ${ errorBody } ` )
409423 }
410424
411425 const tokens = ( await response . json ( ) ) as OAuthTokens
412- await this . saveTokens ( tokens )
426+ await this . saveTokens ( tokens , clientId )
413427 return tokens
414428 }
415429
0 commit comments