From 2dbcd05174048be8e890b6f3e7a8c3fe9574cb8a Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Thu, 6 Aug 2026 16:06:06 -0300 Subject: [PATCH] fix(oauth): use selected callback port for token exchange --- src/lib/mcp-oauth-provider.ts | 9 ++++++--- tests/unit/mcp-oauth-provider.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/lib/mcp-oauth-provider.ts b/src/lib/mcp-oauth-provider.ts index 678b16c..8e6c1d7 100644 --- a/src/lib/mcp-oauth-provider.ts +++ b/src/lib/mcp-oauth-provider.ts @@ -403,7 +403,7 @@ export class MCPOAuthProvider { const authCode = await this.waitForAuthorizationCode(); // Step 8: Exchange authorization code for access token - const tokens = await this.exchangeCodeForTokens(authCode); + const tokens = await this.exchangeCodeForTokens(authCode, actualRedirectUri); // Step 9: Store tokens await writeTokens(this.serverUrlHash, tokens); @@ -456,7 +456,10 @@ export class MCPOAuthProvider { /** * Exchange authorization code for access tokens */ - private async exchangeCodeForTokens(code: string): Promise { + private async exchangeCodeForTokens( + code: string, + redirectUri = this.config.redirectUri + ): Promise { try { const codeVerifier = await readTextFile( this.serverUrlHash, @@ -467,7 +470,7 @@ export class MCPOAuthProvider { const tokenResponse = await exchangeAuthorizationCode( this.config.tokenEndpoint!, code, - this.config.redirectUri, + redirectUri, this.config.clientId!, codeVerifier, CONFIG.OAUTH_RESOURCE_INDICATOR ? this.config.resource : undefined diff --git a/tests/unit/mcp-oauth-provider.test.ts b/tests/unit/mcp-oauth-provider.test.ts index 80d289a..db2e920 100644 --- a/tests/unit/mcp-oauth-provider.test.ts +++ b/tests/unit/mcp-oauth-provider.test.ts @@ -160,4 +160,26 @@ describe('MCPOAuthProvider client registration', () => { expect(scope.isDone()).toBe(false); expect(provider.getConfig().clientId).toBe('env-client'); }); + + it('uses the selected callback port when exchanging the authorization code', async () => { + const { MCPOAuthProvider, generateServerUrlHash } = await loadModules(); + const { writeTextFile } = await import('../../src/lib/persistent-auth-config.js'); + const provider = new MCPOAuthProvider({ + serverUrl, + clientId: 'dynamic-port-client', + scopes: ['read'], + }); + (provider as any).config.tokenEndpoint = `${origin}/oauth/token`; + + await writeTextFile(generateServerUrlHash(serverUrl), 'pkce_verifier.txt', 'a'.repeat(64)); + + const callback = 'http://127.0.0.1:49152/oauth/callback'; + const exchange = nock(origin) + .post('/oauth/token', body => body.redirect_uri === callback) + .reply(200, { access_token: 'access-token', token_type: 'Bearer', expires_in: 3600 }); + + await (provider as any).exchangeCodeForTokens('authorization-code', callback); + + expect(exchange.isDone()).toBe(true); + }); });