From f0e1121cc3d7b4eb1773f2ae898fa3ca4677ab47 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Fri, 24 Apr 2026 09:56:29 -0400 Subject: [PATCH] feat(McpOAuthClientProvider): adding callback to cancel callback server --- src/services/mcp/McpOAuthClientProvider.ts | 23 +++++++++++++++---- .../__tests__/McpOAuthClientProvider.spec.ts | 3 ++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/services/mcp/McpOAuthClientProvider.ts b/src/services/mcp/McpOAuthClientProvider.ts index fe070ffd3c..9aa8bad8b1 100644 --- a/src/services/mcp/McpOAuthClientProvider.ts +++ b/src/services/mcp/McpOAuthClientProvider.ts @@ -78,6 +78,7 @@ export class McpOAuthClientProvider implements OAuthClientProvider { private _server: http.Server | null, private _port: number, private _authCodePromise: Promise | null, + private _cancelCallbackServer: (() => void) | null, private readonly _tokenEndpointAuthMethod: string, private readonly _grantTypes: string[], private readonly _scopes: string[], @@ -122,9 +123,9 @@ export class McpOAuthClientProvider implements OAuthClientProvider { } // Extract auth-method preferences. - // Prefer "none" → first supported → "client_secret_post" + // Only pick methods we actually implement: "none" or "client_secret_post". const authMethods: string[] = authServerMeta?.token_endpoint_auth_methods_supported ?? [] - const tokenEndpointAuthMethod = authMethods.includes("none") ? "none" : (authMethods[0] ?? "client_secret_post") + const tokenEndpointAuthMethod = authMethods.includes("none") ? "none" : "client_secret_post" const grantTypes: string[] = authServerMeta?.grant_types_supported ?? ["authorization_code", "refresh_token"] const scopes: string[] = authServerMeta?.scopes_supported ?? [] @@ -142,6 +143,7 @@ export class McpOAuthClientProvider implements OAuthClientProvider { null, 0, null, + null, tokenEndpointAuthMethod, grantTypes, scopes, @@ -178,9 +180,10 @@ export class McpOAuthClientProvider implements OAuthClientProvider { private async _doStartCallbackServer(): Promise { this._closed = false - const { server, port, result } = await startCallbackServer(this._port, this._state) + const { server, port, result, cancel } = await startCallbackServer(this._port, this._state) this._server = server this._port = port + this._cancelCallbackServer = cancel this._authCodePromise = result.then((r) => { if (r.error) throw new Error(`OAuth authorization failed: ${r.error}`) if (!r.code) throw new Error("No authorization code received in callback") @@ -442,6 +445,12 @@ export class McpOAuthClientProvider implements OAuthClientProvider { code_verifier: codeVerifier, } + // RFC 8707: include resource indicator so servers that bind token requests + // to a specific resource can validate the exchange. + if (this._resourceIndicator) { + params.resource = this._resourceIndicator + } + // Include client_secret in the body when the auth method is client_secret_post. if (this._tokenEndpointAuthMethod === "client_secret_post" && this._clientInfo.client_secret) { params.client_secret = this._clientInfo.client_secret @@ -489,6 +498,11 @@ export class McpOAuthClientProvider implements OAuthClientProvider { client_id: clientId, } + // RFC 8707: include resource indicator in refresh requests too. + if (this._resourceIndicator) { + params.resource = this._resourceIndicator + } + if (this._tokenEndpointAuthMethod === "client_secret_post" && this._clientInfo?.client_secret) { params.client_secret = this._clientInfo.client_secret } @@ -521,8 +535,9 @@ export class McpOAuthClientProvider implements OAuthClientProvider { } if (!this._closed && this._server) { this._closed = true - await stopCallbackServer(this._server).catch(() => {}) + await stopCallbackServer(this._server, this._cancelCallbackServer ?? (() => {})).catch(() => {}) this._server = null + this._cancelCallbackServer = null this._authCodePromise = null } } diff --git a/src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts b/src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts index 8464970213..3232eb10e2 100644 --- a/src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts +++ b/src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts @@ -75,6 +75,7 @@ function setupCallbackServerMock(code = "test-auth-code", state?: string) { server: mockServer, port: 12345, result: resultPromise, + cancel: vi.fn(), }) return { mockServer, resultPromise } } @@ -675,7 +676,7 @@ describe("McpOAuthClientProvider", () => { await provider.close() - expect(stopCallbackServer).toHaveBeenCalledWith(mockServer) + expect(stopCallbackServer).toHaveBeenCalledWith(mockServer, expect.any(Function)) }) it("should be idempotent", async () => {