Skip to content

Commit adb897f

Browse files
authored
Merge branch 'main' into fix/resolve-ref-pre-validation
2 parents c1f18a8 + c91960c commit adb897f

File tree

5 files changed

+65
-21
lines changed

5 files changed

+65
-21
lines changed

client/src/lib/auth.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,40 @@ export const clearClientInformationFromSessionStorage = ({
102102
sessionStorage.removeItem(key);
103103
};
104104

105+
export const getScopeFromSessionStorage = (
106+
serverUrl: string,
107+
): string | undefined => {
108+
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
109+
const value = sessionStorage.getItem(key);
110+
return value || undefined;
111+
};
112+
113+
export const saveScopeToSessionStorage = (
114+
serverUrl: string,
115+
scope: string | undefined,
116+
) => {
117+
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
118+
if (scope) {
119+
sessionStorage.setItem(key, scope);
120+
} else {
121+
sessionStorage.removeItem(key);
122+
}
123+
};
124+
125+
export const clearScopeFromSessionStorage = (serverUrl: string) => {
126+
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
127+
sessionStorage.removeItem(key);
128+
};
129+
105130
export class InspectorOAuthClientProvider implements OAuthClientProvider {
106-
constructor(
107-
protected serverUrl: string,
108-
scope?: string,
109-
) {
110-
this.scope = scope;
131+
constructor(protected serverUrl: string) {
111132
// Save the server URL to session storage
112133
sessionStorage.setItem(SESSION_KEYS.SERVER_URL, serverUrl);
113134
}
114-
scope: string | undefined;
135+
136+
get scope(): string | undefined {
137+
return getScopeFromSessionStorage(this.serverUrl);
138+
}
115139

116140
get redirectUrl() {
117141
return window.location.origin + "/oauth/callback";

client/src/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const SESSION_KEYS = {
1717
PREREGISTERED_CLIENT_INFORMATION: "mcp_preregistered_client_information",
1818
SERVER_METADATA: "mcp_server_metadata",
1919
AUTH_DEBUGGER_STATE: "mcp_auth_debugger_state",
20+
SCOPE: "mcp_scope",
2021
} as const;
2122

2223
// Generate server-specific session storage keys

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ jest.mock("../../auth", () => ({
117117
})),
118118
clearClientInformationFromSessionStorage: jest.fn(),
119119
saveClientInformationToSessionStorage: jest.fn(),
120+
saveScopeToSessionStorage: jest.fn(),
121+
clearScopeFromSessionStorage: jest.fn(),
120122
discoverScopes: jest.fn(),
121123
}));
122124

client/src/lib/hooks/useConnection.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import {
4545
clearClientInformationFromSessionStorage,
4646
InspectorOAuthClientProvider,
4747
saveClientInformationToSessionStorage,
48+
saveScopeToSessionStorage,
49+
clearScopeFromSessionStorage,
4850
discoverScopes,
4951
} from "../auth";
5052
import {
@@ -143,6 +145,15 @@ export function useConnection({
143145
});
144146
}, [oauthClientId, oauthClientSecret, sseUrl]);
145147

148+
useEffect(() => {
149+
if (!oauthScope) {
150+
clearScopeFromSessionStorage(sseUrl);
151+
return;
152+
}
153+
154+
saveScopeToSessionStorage(sseUrl, oauthScope);
155+
}, [oauthScope, sseUrl]);
156+
146157
const pushHistory = (request: object, response?: object) => {
147158
setRequestHistory((prev) => [
148159
...prev,
@@ -347,10 +358,9 @@ export function useConnection({
347358
}
348359
scope = await discoverScopes(sseUrl, resourceMetadata);
349360
}
350-
const serverAuthProvider = new InspectorOAuthClientProvider(
351-
sseUrl,
352-
scope,
353-
);
361+
362+
saveScopeToSessionStorage(sseUrl, scope);
363+
const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
354364

355365
const result = await auth(serverAuthProvider, {
356366
serverUrl: sseUrl,

client/src/lib/oauth-state-machine.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,16 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
8080
const metadata = context.state.oauthMetadata!;
8181
const clientMetadata = context.provider.clientMetadata;
8282

83-
// Prefer scopes from resource metadata if available
84-
const scopesSupported =
85-
context.state.resourceMetadata?.scopes_supported ||
86-
metadata.scopes_supported;
87-
// Add all supported scopes to client registration
88-
if (scopesSupported) {
89-
clientMetadata.scope = scopesSupported.join(" ");
83+
// Priority: user-provided scope > discovered scopes
84+
if (!context.provider.scope || context.provider.scope.trim() === "") {
85+
// Prefer scopes from resource metadata if available
86+
const scopesSupported =
87+
context.state.resourceMetadata?.scopes_supported ||
88+
metadata.scopes_supported;
89+
// Add all supported scopes to client registration
90+
if (scopesSupported) {
91+
clientMetadata.scope = scopesSupported.join(" ");
92+
}
9093
}
9194

9295
// Try Static client first, with DCR as fallback
@@ -113,10 +116,14 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
113116
const metadata = context.state.oauthMetadata!;
114117
const clientInformation = context.state.oauthClientInfo!;
115118

116-
const scope = await discoverScopes(
117-
context.serverUrl,
118-
context.state.resourceMetadata ?? undefined,
119-
);
119+
// Priority: user-provided scope > discovered scopes
120+
let scope = context.provider.scope;
121+
if (!scope || scope.trim() === "") {
122+
scope = await discoverScopes(
123+
context.serverUrl,
124+
context.state.resourceMetadata ?? undefined,
125+
);
126+
}
120127

121128
const { authorizationUrl, codeVerifier } = await startAuthorization(
122129
context.serverUrl,

0 commit comments

Comments
 (0)