Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion api/core/agent/tools/mcpTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,33 @@ 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 => {
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;
}

const value = Reflect.get(target, property, target);
return typeof value === "function" ? value.bind(target) : value;
},
}) as LegacyOAuthClientProvider;
};

export class LegacySseTransportAdapter implements Transport {
onclose?: () => void;
onerror?: (error: Error) => void;
Expand Down Expand Up @@ -57,7 +80,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);
Expand Down
58 changes: 41 additions & 17 deletions tests/mcpTransport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReturnType<OAuthClientProvider["tokens"]>>) {
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<string, SSEServerTransport>();
const authorizationHeaders: string[] = [];
let httpServer: HttpServer | undefined;
Expand Down Expand Up @@ -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" }],
});
Expand Down
Loading