@@ -221,6 +221,158 @@ index 3617e787f0ba70447c99501aee7aa67584d89758..4a96d6a0328fa348b96f3869ab7e0bb7
221221 this._cleanupTimeout(messageId);
222222 reject(error);
223223 });
224+ diff --git a/dist/cjs/client/auth.d.ts b/dist/cjs/client/auth.d.ts
225+ index f4363ce7c94fbddf0e1d5943b1b26682bdbaa40e..e7dd57096e4f056bcd735d5081433beea1b32f04 100644
226+ --- a/dist/cjs/client/auth.d.ts
227+ +++ b/dist/cjs/client/auth.d.ts
228+ @@ -205,6 +205,15 @@ export declare function parseErrorResponse(input: Response | string): Promise<OA
229+ * @returns A Promise that resolves to an OAuthError instance
230+ */
231+ export declare function parseErrorResponse(input: Response | string): Promise<OAuthError>;
232+ + /**
233+ + * Selects scopes per the MCP spec and augments them for refresh token support.
234+ + */
235+ + export declare function determineScope(options: {
236+ + requestedScope?: string;
237+ + resourceMetadata?: OAuthProtectedResourceMetadata;
238+ + authServerMetadata?: AuthorizationServerMetadata;
239+ + clientMetadata: OAuthClientMetadata;
240+ + }): string | undefined;
241+ /**
242+ * Orchestrates the full auth flow with a server.
243+ *
244+ diff --git a/dist/cjs/client/auth.js b/dist/cjs/client/auth.js
245+ index c2e4fa91d26f5336889f6afa416147db75fc4872..178d7cfd96412d53bc14bbc13a8f76c11f727ee7 100644
246+ --- a/dist/cjs/client/auth.js
247+ +++ b/dist/cjs/client/auth.js
248+ @@ -7,6 +7,7 @@ exports.UnauthorizedError = void 0;
249+ exports.selectClientAuthMethod = selectClientAuthMethod;
250+ exports.parseErrorResponse = parseErrorResponse;
251+ exports.auth = auth;
252+ + exports.determineScope = determineScope;
253+ exports.isHttpsUrl = isHttpsUrl;
254+ exports.selectResourceURL = selectResourceURL;
255+ exports.extractWWWAuthenticateParams = extractWWWAuthenticateParams;
256+ @@ -186,6 +187,19 @@ async function auth(provider, options) {
257+ throw error;
258+ }
259+ }
260+ + /**
261+ + * Selects scopes per the MCP spec and augments them for refresh token support.
262+ + */
263+ + function determineScope({ requestedScope, resourceMetadata, authServerMetadata, clientMetadata }) {
264+ + let effectiveScope = requestedScope || resourceMetadata?.scopes_supported?.join(' ') || clientMetadata.scope;
265+ + if (effectiveScope &&
266+ + authServerMetadata?.scopes_supported?.includes('offline_access') &&
267+ + !effectiveScope.split(' ').includes('offline_access') &&
268+ + clientMetadata.grant_types?.includes('refresh_token')) {
269+ + effectiveScope = `${effectiveScope} offline_access`;
270+ + }
271+ + return effectiveScope;
272+ + }
273+ async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
274+ // Check if the provider has cached discovery state to skip discovery
275+ const cachedState = await provider.discoveryState?.();
276+ @@ -241,12 +255,12 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
277+ });
278+ }
279+ const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
280+ - // Apply scope selection strategy (SEP-835):
281+ - // 1. WWW-Authenticate scope (passed via `scope` param)
282+ - // 2. PRM scopes_supported
283+ - // 3. Client metadata scope (user-configured fallback)
284+ - // The resolved scope is used consistently for both DCR and the authorization request.
285+ - const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(' ') || provider.clientMetadata.scope;
286+ + const resolvedScope = determineScope({
287+ + requestedScope: scope,
288+ + resourceMetadata,
289+ + authServerMetadata: metadata,
290+ + clientMetadata: provider.clientMetadata
291+ + });
292+ // Handle client registration if needed
293+ let clientInformation = await Promise.resolve(provider.clientInformation());
294+ if (!clientInformation) {
295+ @@ -741,7 +755,7 @@ async function startAuthorization(authorizationServerUrl, { metadata, clientInfo
296+ if (scope) {
297+ authorizationUrl.searchParams.set('scope', scope);
298+ }
299+ - if (scope?.includes('offline_access')) {
300+ + if (scope?.split(' ').includes('offline_access')) {
301+ // if the request includes the OIDC-only "offline_access" scope,
302+ // we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
303+ // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
304+ diff --git a/dist/esm/client/auth.d.ts b/dist/esm/client/auth.d.ts
305+ index f4363ce7c94fbddf0e1d5943b1b26682bdbaa40e..e7dd57096e4f056bcd735d5081433beea1b32f04 100644
306+ --- a/dist/esm/client/auth.d.ts
307+ +++ b/dist/esm/client/auth.d.ts
308+ @@ -205,6 +205,15 @@ export declare function parseErrorResponse(input: Response | string): Promise<OA
309+ * @returns A Promise that resolves to an OAuthError instance
310+ */
311+ export declare function parseErrorResponse(input: Response | string): Promise<OAuthError>;
312+ + /**
313+ + * Selects scopes per the MCP spec and augments them for refresh token support.
314+ + */
315+ + export declare function determineScope(options: {
316+ + requestedScope?: string;
317+ + resourceMetadata?: OAuthProtectedResourceMetadata;
318+ + authServerMetadata?: AuthorizationServerMetadata;
319+ + clientMetadata: OAuthClientMetadata;
320+ + }): string | undefined;
321+ /**
322+ * Orchestrates the full auth flow with a server.
323+ *
324+ diff --git a/dist/esm/client/auth.js b/dist/esm/client/auth.js
325+ index e183040fc2bba22ca1ccc784984f3310854403b7..d367661e580ee61a96654f7af78b2af61dcad98b 100644
326+ --- a/dist/esm/client/auth.js
327+ +++ b/dist/esm/client/auth.js
328+ @@ -161,6 +161,19 @@ export async function auth(provider, options) {
329+ throw error;
330+ }
331+ }
332+ + /**
333+ + * Selects scopes per the MCP spec and augments them for refresh token support.
334+ + */
335+ + export function determineScope({ requestedScope, resourceMetadata, authServerMetadata, clientMetadata }) {
336+ + let effectiveScope = requestedScope || resourceMetadata?.scopes_supported?.join(' ') || clientMetadata.scope;
337+ + if (effectiveScope &&
338+ + authServerMetadata?.scopes_supported?.includes('offline_access') &&
339+ + !effectiveScope.split(' ').includes('offline_access') &&
340+ + clientMetadata.grant_types?.includes('refresh_token')) {
341+ + effectiveScope = `${effectiveScope} offline_access`;
342+ + }
343+ + return effectiveScope;
344+ + }
345+ async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
346+ // Check if the provider has cached discovery state to skip discovery
347+ const cachedState = await provider.discoveryState?.();
348+ @@ -216,12 +229,12 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
349+ });
350+ }
351+ const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
352+ - // Apply scope selection strategy (SEP-835):
353+ - // 1. WWW-Authenticate scope (passed via `scope` param)
354+ - // 2. PRM scopes_supported
355+ - // 3. Client metadata scope (user-configured fallback)
356+ - // The resolved scope is used consistently for both DCR and the authorization request.
357+ - const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(' ') || provider.clientMetadata.scope;
358+ + const resolvedScope = determineScope({
359+ + requestedScope: scope,
360+ + resourceMetadata,
361+ + authServerMetadata: metadata,
362+ + clientMetadata: provider.clientMetadata
363+ + });
364+ // Handle client registration if needed
365+ let clientInformation = await Promise.resolve(provider.clientInformation());
366+ if (!clientInformation) {
367+ @@ -716,7 +729,7 @@ export async function startAuthorization(authorizationServerUrl, { metadata, cli
368+ if (scope) {
369+ authorizationUrl.searchParams.set('scope', scope);
370+ }
371+ - if (scope?.includes('offline_access')) {
372+ + if (scope?.split(' ').includes('offline_access')) {
373+ // if the request includes the OIDC-only "offline_access" scope,
374+ // we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
375+ // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
224376diff --git a/dist/esm/client/index.js b/dist/esm/client/index.js
225377index 49b12c6cd918c457420fef7ad5528a9443d1a191..2afe2e22e960f26c9d516ef135d89f8eb9e4caff 100644
226378--- a/dist/esm/client/index.js
0 commit comments