From f233687e17463e00d9712e4a6369e552aa7463c9 Mon Sep 17 00:00:00 2001 From: Uka Osim Date: Sun, 11 Oct 2026 21:45:49 +0100 Subject: [PATCH 1/2] Adapt OAuth provider for legacy MCP SDK Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- api/core/agent/tools/mcpTransport.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/api/core/agent/tools/mcpTransport.ts b/api/core/agent/tools/mcpTransport.ts index 051c8ac..dc4f5c4 100644 --- a/api/core/agent/tools/mcpTransport.ts +++ b/api/core/agent/tools/mcpTransport.ts @@ -5,10 +5,27 @@ import { type JSONRPCMessage, } from "@modelcontextprotocol/sdk/types.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk-legacy/client/sse.js"; +import type { OAuthClientProvider as LegacyOAuthClientProvider } from "@modelcontextprotocol/sdk-legacy/client/auth.js"; import { JSONRPCMessageSchema as LegacyJSONRPCMessageSchema } from "@modelcontextprotocol/sdk-legacy/types.js"; import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js"; import type { McpServerConfig } from "../../library/mcpSettings"; +const adaptOAuthProviderForLegacySdk = ( + provider: OAuthClientProvider, +): LegacyOAuthClientProvider => { + const legacyProvider: LegacyOAuthClientProvider = { + ...provider, + get redirectUrl() { + const redirectUrl = provider.redirectUrl; + if (!redirectUrl) { + throw new Error("The OAuth provider does not have a redirect URL."); + } + return redirectUrl; + }, + }; + return legacyProvider; +}; + export class LegacySseTransportAdapter implements Transport { onclose?: () => void; onerror?: (error: Error) => void; @@ -57,7 +74,9 @@ export const createMcpRemoteTransport = ( if (server.transport === "sse") { const legacyTransport = new SSEClientTransport(url, { - ...(authProvider ? { authProvider } : {}), + ...(authProvider + ? { authProvider: adaptOAuthProviderForLegacySdk(authProvider) } + : {}), requestInit: { headers }, }); return new LegacySseTransportAdapter(legacyTransport); From fcde8ff5fc0d21b4ef05141837f07a540e8613be Mon Sep 17 00:00:00 2001 From: Uka Osim Date: Sun, 11 Oct 2026 22:30:21 +0100 Subject: [PATCH 2/2] Delegate legacy OAuth provider access safely Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- api/core/agent/tools/mcpTransport.ts | 24 +++++++----- tests/mcpTransport.test.ts | 58 ++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/api/core/agent/tools/mcpTransport.ts b/api/core/agent/tools/mcpTransport.ts index dc4f5c4..4a9b05f 100644 --- a/api/core/agent/tools/mcpTransport.ts +++ b/api/core/agent/tools/mcpTransport.ts @@ -13,17 +13,23 @@ import type { McpServerConfig } from "../../library/mcpSettings"; const adaptOAuthProviderForLegacySdk = ( provider: OAuthClientProvider, ): LegacyOAuthClientProvider => { - const legacyProvider: LegacyOAuthClientProvider = { - ...provider, - get redirectUrl() { - const redirectUrl = provider.redirectUrl; - if (!redirectUrl) { - throw new Error("The OAuth provider does not have a redirect URL."); + return new Proxy(provider, { + get(target, property) { + if (property === "redirectUrl") { + const redirectUrl = Reflect.get(target, property, target) as + | string + | URL + | undefined; + if (!redirectUrl) { + throw new Error("The OAuth provider does not have a redirect URL."); + } + return redirectUrl; } - return redirectUrl; + + const value = Reflect.get(target, property, target); + return typeof value === "function" ? value.bind(target) : value; }, - }; - return legacyProvider; + }) as LegacyOAuthClientProvider; }; export class LegacySseTransportAdapter implements Transport { diff --git a/tests/mcpTransport.test.ts b/tests/mcpTransport.test.ts index 471ae50..1a721f1 100644 --- a/tests/mcpTransport.test.ts +++ b/tests/mcpTransport.test.ts @@ -125,22 +125,45 @@ describe("MCP remote transport selection", () => { tools: [{ name: "hello", inputSchema: { type: "object" } }], })); - let accessToken = "initial-token"; - const authProvider: OAuthClientProvider = { - redirectUrl: "http://127.0.0.1/callback", - clientMetadata: { redirect_uris: ["http://127.0.0.1/callback"] }, - clientInformation: () => ({ client_id: "legacy-oauth-test" }), - tokens: () => ({ - access_token: accessToken, - token_type: "Bearer", - }), - saveTokens: (tokens) => { - accessToken = tokens.access_token; - }, - redirectToAuthorization: () => undefined, - saveCodeVerifier: () => undefined, - codeVerifier: () => "test-verifier", - }; + class StatefulOAuthProvider implements OAuthClientProvider { + private accessToken = "initial-token"; + clientMetadataReads = 0; + redirectUrl = "http://127.0.0.1/callback"; + clientId = "legacy-oauth-test"; + + get clientMetadata() { + this.clientMetadataReads += 1; + return { redirect_uris: [this.redirectUrl] }; + } + + clientInformation() { + return { client_id: this.clientId }; + } + + tokens() { + return { + access_token: this.accessToken, + token_type: "Bearer", + }; + } + + saveTokens(tokens: Awaited>) { + if (!tokens) throw new Error("Expected OAuth tokens"); + this.accessToken = tokens.access_token; + } + + redirectToAuthorization() {} + saveCodeVerifier() {} + codeVerifier() { + return "test-verifier"; + } + + rotateAccessToken(accessToken: string) { + this.accessToken = accessToken; + } + } + + const authProvider = new StatefulOAuthProvider(); const transports = new Map(); const authorizationHeaders: string[] = []; let httpServer: HttpServer | undefined; @@ -196,10 +219,11 @@ describe("MCP remote transport selection", () => { }, authProvider, ); + expect(authProvider.clientMetadataReads).toBe(0); client = new Client({ name: "legacy-oauth-test", version: "1.0.0" }); await client.connect(transport); - accessToken = "rotated-token"; + authProvider.rotateAccessToken("rotated-token"); await expect(client.listTools()).resolves.toMatchObject({ tools: [{ name: "hello" }], });