Skip to content

Commit c91960c

Browse files
authored
Merge pull request #871 from stdevi/fix/set-scope-dcr
fix: set user specified scope in DCR
2 parents 9e6c9a2 + cb905a3 commit c91960c

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
@@ -112,6 +112,8 @@ jest.mock("../../auth", () => ({
112112
})),
113113
clearClientInformationFromSessionStorage: jest.fn(),
114114
saveClientInformationToSessionStorage: jest.fn(),
115+
saveScopeToSessionStorage: jest.fn(),
116+
clearScopeFromSessionStorage: jest.fn(),
115117
discoverScopes: jest.fn(),
116118
}));
117119

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 {
@@ -142,6 +144,15 @@ export function useConnection({
142144
});
143145
}, [oauthClientId, oauthClientSecret, sseUrl]);
144146

147+
useEffect(() => {
148+
if (!oauthScope) {
149+
clearScopeFromSessionStorage(sseUrl);
150+
return;
151+
}
152+
153+
saveScopeToSessionStorage(sseUrl, oauthScope);
154+
}, [oauthScope, sseUrl]);
155+
145156
const pushHistory = (request: object, response?: object) => {
146157
setRequestHistory((prev) => [
147158
...prev,
@@ -346,10 +357,9 @@ export function useConnection({
346357
}
347358
scope = await discoverScopes(sseUrl, resourceMetadata);
348359
}
349-
const serverAuthProvider = new InspectorOAuthClientProvider(
350-
sseUrl,
351-
scope,
352-
);
360+
361+
saveScopeToSessionStorage(sseUrl, scope);
362+
const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
353363

354364
const result = await auth(serverAuthProvider, {
355365
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)