Skip to content

Commit df0b526

Browse files
committed
fix: store auth tokens with server-specific keys
Changes client information and access tokens to use server-specific keys in sessionStorage. This fixes issues where changing the server URL would try to use tokens from a different server.
1 parent f7272d8 commit df0b526

File tree

5 files changed

+55
-20
lines changed

5 files changed

+55
-20
lines changed

client/src/components/OAuthCallback.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useRef } from "react";
2-
import { authProvider } from "../lib/auth";
2+
import { InspectorOAuthClientProvider } from "../lib/auth";
33
import { SESSION_KEYS } from "../lib/constants";
44
import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
55

@@ -25,7 +25,10 @@ const OAuthCallback = () => {
2525
}
2626

2727
try {
28-
const result = await auth(authProvider, {
28+
// Create an auth provider with the current server URL
29+
const serverAuthProvider = new InspectorOAuthClientProvider(serverUrl);
30+
31+
const result = await auth(serverAuthProvider, {
2932
serverUrl,
3033
authorizationCode: code,
3134
});

client/src/lib/auth.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import {
55
OAuthTokens,
66
OAuthTokensSchema,
77
} from "@modelcontextprotocol/sdk/shared/auth.js";
8-
import { SESSION_KEYS } from "./constants";
8+
import { SESSION_KEYS, getServerSpecificKey } from "./constants";
9+
10+
export class InspectorOAuthClientProvider implements OAuthClientProvider {
11+
constructor(private serverUrl: string) {
12+
// Save the server URL to session storage
13+
sessionStorage.setItem(SESSION_KEYS.SERVER_URL, serverUrl);
14+
}
915

10-
class InspectorOAuthClientProvider implements OAuthClientProvider {
1116
get redirectUrl() {
1217
return window.location.origin + "/oauth/callback";
1318
}
@@ -24,7 +29,11 @@ class InspectorOAuthClientProvider implements OAuthClientProvider {
2429
}
2530

2631
async clientInformation() {
27-
const value = sessionStorage.getItem(SESSION_KEYS.CLIENT_INFORMATION);
32+
const key = getServerSpecificKey(
33+
SESSION_KEYS.CLIENT_INFORMATION,
34+
this.serverUrl,
35+
);
36+
const value = sessionStorage.getItem(key);
2837
if (!value) {
2938
return undefined;
3039
}
@@ -33,14 +42,16 @@ class InspectorOAuthClientProvider implements OAuthClientProvider {
3342
}
3443

3544
saveClientInformation(clientInformation: OAuthClientInformation) {
36-
sessionStorage.setItem(
45+
const key = getServerSpecificKey(
3746
SESSION_KEYS.CLIENT_INFORMATION,
38-
JSON.stringify(clientInformation),
47+
this.serverUrl,
3948
);
49+
sessionStorage.setItem(key, JSON.stringify(clientInformation));
4050
}
4151

4252
async tokens() {
43-
const tokens = sessionStorage.getItem(SESSION_KEYS.TOKENS);
53+
const key = getServerSpecificKey(SESSION_KEYS.TOKENS, this.serverUrl);
54+
const tokens = sessionStorage.getItem(key);
4455
if (!tokens) {
4556
return undefined;
4657
}
@@ -49,25 +60,32 @@ class InspectorOAuthClientProvider implements OAuthClientProvider {
4960
}
5061

5162
saveTokens(tokens: OAuthTokens) {
52-
sessionStorage.setItem(SESSION_KEYS.TOKENS, JSON.stringify(tokens));
63+
const key = getServerSpecificKey(SESSION_KEYS.TOKENS, this.serverUrl);
64+
sessionStorage.setItem(key, JSON.stringify(tokens));
5365
}
5466

5567
redirectToAuthorization(authorizationUrl: URL) {
5668
window.location.href = authorizationUrl.href;
5769
}
5870

5971
saveCodeVerifier(codeVerifier: string) {
60-
sessionStorage.setItem(SESSION_KEYS.CODE_VERIFIER, codeVerifier);
72+
const key = getServerSpecificKey(
73+
SESSION_KEYS.CODE_VERIFIER,
74+
this.serverUrl,
75+
);
76+
sessionStorage.setItem(key, codeVerifier);
6177
}
6278

6379
codeVerifier() {
64-
const verifier = sessionStorage.getItem(SESSION_KEYS.CODE_VERIFIER);
80+
const key = getServerSpecificKey(
81+
SESSION_KEYS.CODE_VERIFIER,
82+
this.serverUrl,
83+
);
84+
const verifier = sessionStorage.getItem(key);
6585
if (!verifier) {
6686
throw new Error("No code verifier saved for session");
6787
}
6888

6989
return verifier;
7090
}
7191
}
72-
73-
export const authProvider = new InspectorOAuthClientProvider();

client/src/lib/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ export const SESSION_KEYS = {
88
CLIENT_INFORMATION: "mcp_client_information",
99
} as const;
1010

11+
// Generate server-specific session storage keys
12+
export const getServerSpecificKey = (
13+
baseKey: string,
14+
serverUrl?: string,
15+
): string => {
16+
if (!serverUrl) return baseKey;
17+
return `[${serverUrl}] ${baseKey}`;
18+
};
19+
1120
export type ConnectionStatus =
1221
| "disconnected"
1322
| "connected"

client/src/lib/hooks/__tests__/useConnection.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ jest.mock("@/hooks/use-toast", () => ({
4545

4646
// Mock the auth provider
4747
jest.mock("../../auth", () => ({
48-
authProvider: {
48+
InspectorOAuthClientProvider: jest.fn().mockImplementation(() => ({
4949
tokens: jest.fn().mockResolvedValue({ access_token: "mock-token" }),
50-
},
50+
})),
5151
}));
5252

5353
describe("useConnection", () => {

client/src/lib/hooks/useConnection.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
2828
import { useState } from "react";
2929
import { useToast } from "@/hooks/use-toast";
3030
import { z } from "zod";
31-
import { ConnectionStatus, SESSION_KEYS } from "../constants";
31+
import { ConnectionStatus } from "../constants";
3232
import { Notification, StdErrNotificationSchema } from "../notificationTypes";
3333
import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
34-
import { authProvider } from "../auth";
34+
import { InspectorOAuthClientProvider } from "../auth";
3535
import packageJson from "../../../package.json";
3636
import {
3737
getMCPProxyAddress,
@@ -246,9 +246,10 @@ export function useConnection({
246246

247247
const handleAuthError = async (error: unknown) => {
248248
if (error instanceof SseError && error.code === 401) {
249-
sessionStorage.setItem(SESSION_KEYS.SERVER_URL, sseUrl);
249+
// Create a new auth provider with the current server URL
250+
const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
250251

251-
const result = await auth(authProvider, { serverUrl: sseUrl });
252+
const result = await auth(serverAuthProvider, { serverUrl: sseUrl });
252253
return result === "AUTHORIZED";
253254
}
254255

@@ -292,8 +293,12 @@ export function useConnection({
292293
// proxying through the inspector server first.
293294
const headers: HeadersInit = {};
294295

296+
// Create an auth provider with the current server URL
297+
const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
298+
295299
// Use manually provided bearer token if available, otherwise use OAuth tokens
296-
const token = bearerToken || (await authProvider.tokens())?.access_token;
300+
const token =
301+
bearerToken || (await serverAuthProvider.tokens())?.access_token;
297302
if (token) {
298303
const authHeaderName = headerName || "Authorization";
299304
headers[authHeaderName] = `Bearer ${token}`;

0 commit comments

Comments
 (0)