Merge pull request #871 from stdevi/fix/set-scope-dcr

fix: set user specified scope in DCR
This commit is contained in:
Cliff Hall 2025-11-08 12:58:55 -05:00 • committed by GitHub
commit c91960c5e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 65 additions and 21 deletions

View file

@ -102,16 +102,40 @@ export const clearClientInformationFromSessionStorage = ({
sessionStorage.removeItem(key); sessionStorage.removeItem(key);
}; };
export const getScopeFromSessionStorage = (
serverUrl: string,
): string | undefined => {
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
const value = sessionStorage.getItem(key);
return value || undefined;
};
export const saveScopeToSessionStorage = (
serverUrl: string,
scope: string | undefined,
) => {
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
if (scope) {
sessionStorage.setItem(key, scope);
} else {
sessionStorage.removeItem(key);
}
};
export const clearScopeFromSessionStorage = (serverUrl: string) => {
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
sessionStorage.removeItem(key);
};
export class InspectorOAuthClientProvider implements OAuthClientProvider { export class InspectorOAuthClientProvider implements OAuthClientProvider {
constructor( constructor(protected serverUrl: string) {
protected serverUrl: string,
scope?: string,
) {
this.scope = scope;
// Save the server URL to session storage // Save the server URL to session storage
sessionStorage.setItem(SESSION_KEYS.SERVER_URL, serverUrl); sessionStorage.setItem(SESSION_KEYS.SERVER_URL, serverUrl);
} }
scope: string | undefined;
get scope(): string | undefined {
return getScopeFromSessionStorage(this.serverUrl);
}
get redirectUrl() { get redirectUrl() {
return window.location.origin + "/oauth/callback"; return window.location.origin + "/oauth/callback";

View file

@ -17,6 +17,7 @@ export const SESSION_KEYS = {
PREREGISTERED_CLIENT_INFORMATION: "mcp_preregistered_client_information", PREREGISTERED_CLIENT_INFORMATION: "mcp_preregistered_client_information",
SERVER_METADATA: "mcp_server_metadata", SERVER_METADATA: "mcp_server_metadata",
AUTH_DEBUGGER_STATE: "mcp_auth_debugger_state", AUTH_DEBUGGER_STATE: "mcp_auth_debugger_state",
SCOPE: "mcp_scope",
} as const; } as const;
// Generate server-specific session storage keys // Generate server-specific session storage keys

View file

@ -112,6 +112,8 @@ jest.mock("../../auth", () => ({
})), })),
clearClientInformationFromSessionStorage: jest.fn(), clearClientInformationFromSessionStorage: jest.fn(),
saveClientInformationToSessionStorage: jest.fn(), saveClientInformationToSessionStorage: jest.fn(),
saveScopeToSessionStorage: jest.fn(),
clearScopeFromSessionStorage: jest.fn(),
discoverScopes: jest.fn(), discoverScopes: jest.fn(),
})); }));

View file

@ -45,6 +45,8 @@ import {
clearClientInformationFromSessionStorage, clearClientInformationFromSessionStorage,
InspectorOAuthClientProvider, InspectorOAuthClientProvider,
saveClientInformationToSessionStorage, saveClientInformationToSessionStorage,
saveScopeToSessionStorage,
clearScopeFromSessionStorage,
discoverScopes, discoverScopes,
} from "../auth"; } from "../auth";
import { import {
@ -142,6 +144,15 @@ export function useConnection({
}); });
}, [oauthClientId, oauthClientSecret, sseUrl]); }, [oauthClientId, oauthClientSecret, sseUrl]);
useEffect(() => {
if (!oauthScope) {
clearScopeFromSessionStorage(sseUrl);
return;
}
saveScopeToSessionStorage(sseUrl, oauthScope);
}, [oauthScope, sseUrl]);
const pushHistory = (request: object, response?: object) => { const pushHistory = (request: object, response?: object) => {
setRequestHistory((prev) => [ setRequestHistory((prev) => [
...prev, ...prev,
@ -346,10 +357,9 @@ export function useConnection({
} }
scope = await discoverScopes(sseUrl, resourceMetadata); scope = await discoverScopes(sseUrl, resourceMetadata);
} }
const serverAuthProvider = new InspectorOAuthClientProvider(
sseUrl, saveScopeToSessionStorage(sseUrl, scope);
scope, const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
);
const result = await auth(serverAuthProvider, { const result = await auth(serverAuthProvider, {
serverUrl: sseUrl, serverUrl: sseUrl,

View file

@ -80,13 +80,16 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
const metadata = context.state.oauthMetadata!; const metadata = context.state.oauthMetadata!;
const clientMetadata = context.provider.clientMetadata; const clientMetadata = context.provider.clientMetadata;
// Prefer scopes from resource metadata if available // Priority: user-provided scope > discovered scopes
const scopesSupported = if (!context.provider.scope || context.provider.scope.trim() === "") {
context.state.resourceMetadata?.scopes_supported || // Prefer scopes from resource metadata if available
metadata.scopes_supported; const scopesSupported =
// Add all supported scopes to client registration context.state.resourceMetadata?.scopes_supported ||
if (scopesSupported) { metadata.scopes_supported;
clientMetadata.scope = scopesSupported.join(" "); // Add all supported scopes to client registration
if (scopesSupported) {
clientMetadata.scope = scopesSupported.join(" ");
}
} }
// Try Static client first, with DCR as fallback // Try Static client first, with DCR as fallback
@ -113,10 +116,14 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
const metadata = context.state.oauthMetadata!; const metadata = context.state.oauthMetadata!;
const clientInformation = context.state.oauthClientInfo!; const clientInformation = context.state.oauthClientInfo!;
const scope = await discoverScopes( // Priority: user-provided scope > discovered scopes
context.serverUrl, let scope = context.provider.scope;
context.state.resourceMetadata ?? undefined, if (!scope || scope.trim() === "") {
); scope = await discoverScopes(
context.serverUrl,
context.state.resourceMetadata ?? undefined,
);
}
const { authorizationUrl, codeVerifier } = await startAuthorization( const { authorizationUrl, codeVerifier } = await startAuthorization(
context.serverUrl, context.serverUrl,