Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Append offline_access to authorization scope when AS advertises it (SEP-2207) - #1479

Merged
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope
May 19, 2026
Merged

Append offline_access to authorization scope when AS advertises it (SEP-2207)#1479
halter73 merged 3 commits into
mainfrom
copilot/add-offline-access-scope

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

SEP-2207 specifies that MCP clients MAY add offline_access to the authorization request scope when the Authorization Server's scopes_supported metadata includes it, signaling to OIDC-flavored servers that a refresh token is desired. The SDK was not doing this.

Changes

  • ClientOAuthProvider: Added AugmentScopeWithOfflineAccess helper called from BuildAuthorizationUrl after GetScopeParameter. Appends offline_access only when the AS advertises it in scopes_supported and it isn't already present in the scope string.

  • TestOAuthServer: Added IncludeOfflineAccessInMetadata property (default false) to opt the test AS into advertising offline_access in scopes_supported.

  • AuthTests: Three new integration tests covering: augmentation when AS advertises offline_access, no augmentation when it doesn't, and no duplication when it's already in the scope.

// AS metadata: scopes_supported = ["openid", "mcp:tools", "offline_access"]// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools offline_access"// AS metadata: scopes_supported = ["openid", "mcp:tools"] (no offline_access)// PRM scope: "mcp:tools"// → authorization request scope: "mcp:tools" (unchanged)
Original prompt

Context

SEP-2207 (now merged) provides OIDC-flavored refresh token guidance for MCP OAuth clients. The C# SDK already satisfies all MUST/SHOULD requirements, but there is one MAY-level enhancement that the spec explicitly calls for reference implementations in official SDKs to adopt.

Problem

The SEP states that MCP Clients MAY add offline_access to the scope parameter in the authorization request when the Authorization Server metadata (scopes_supported) contains "offline_access". This signals to OIDC-flavored authorization servers that the client desires a refresh token.

Currently, ClientOAuthProvider.BuildAuthorizationUrl() and related methods pass through scopes from the protected resource metadata or configured scopes, but never augment them with offline_access by checking AuthorizationServerMetadata.ScopesSupported.

Desired Change

In ClientOAuthProvider, after determining the scope string to use (via GetScopeParameter), check if authServerMetadata.ScopesSupported contains "offline_access". If it does, and offline_access is not already present in the scope string, append it before sending the authorization request.

Key locations to change:

  1. ClientOAuthProvider.cs — Modify scope construction in BuildAuthorizationUrl (and optionally ExchangeCodeForTokenAsync) to augment the scope with offline_access when:

    • authServerMetadata.ScopesSupported contains "offline_access"
    • The current scope string does not already include offline_access
  2. Tests — Add test coverage verifying that:

    • offline_access is appended to the authorization URL scope when the AS metadata advertises it in scopes_supported
    • offline_access is NOT added when the AS metadata does not include it
    • offline_access is NOT duplicated if it's already present in the configured scopes

Important notes:

  • The AuthorizationServerMetadata class already has ScopesSupported (scopes_supported) deserialized, so no model changes are needed.
  • The BuildAuthorizationUrl method already receives authServerMetadata as a parameter, so it has access to the needed information.
  • This is a client-side only change; no server-side changes are needed.

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: Is there any work needed in the C# SDK to adapt to this PR?

Assistant:

Let me examine the referenced PR and the current state of the C# SDK to understand what changes might be needed.

Assistant: Now let me look at the C# SDK's OAuth/authorization client code to see how it currently handles scopes, grant types, and refresh tokens:

Assistant: Now let me look at the key areas more closely — the GetScopeParameter method and BuildAuthorizationUrl to understand how scopes are currently assembled for the authorization request, and the AuthorizationServerMetadata to check for scopes_supported:

Assistant: Based on my thorough analysis of the PR and the C# SDK codebase, here's my assessment:

SEP-2207 Impact on the C# SDK

The C# SDK already partially complies with the SEP-2207 guidance, but there is one concrete client-side improvement that could be made as a reference implementation, and everything else is already in good shape.

✅ What's already correct

  1. Dynamic Client Registration already includes refresh_token in grant_types — In ClientOAuthProvider.cs line 651, the DCR request already sends GrantTypes = ["authorization_code", "refresh_token"], which satisfies the SHOULD requirement that clients include refresh_token in their grant_types client metadata.

  2. Refresh token handling is already implemented — The SDK already stores, retrieves, and uses refresh tokens via RefreshTokensAsync(), GetAccessTokenSilentAsync(), and the token cache infrastructure.

  3. No server-side offline_access violation — The SDK's server-side code does not include offline_access in WWW-Authenticate scope or Protected Resource Metadata scopes_supported, which aligns with the SHOULD NOT guidance.

🔧 What could be added (optional, MAY-level)

The SEP says MCP Clients MAY add offline_access to the scope parameter when the Authorization Server metadata contains it in scopes_supported. The C# SDK currently does not do this — the BuildAuthorizationUrl and [GetScopeParameter](https://github.com/modelcontextprotocol/csharp-sdk/blob/7662ce6b7092c1b026f7613ed3e79a658192fa83/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs...

This pull request was created from Copilot chat.

@stephentoubstephentoub changed the title [WIP] Add offline_access to authorization request scopes[WIP] Add offline_access to authorization request scopes for SEP-2207Mar 30, 2026
CopilotAI changed the title [WIP] Add offline_access to authorization request scopes for SEP-2207Append offline_access to authorization scope when AS advertises it (SEP-2207)Mar 30, 2026
CopilotAI requested a review from stephentoubMarch 30, 2026 18:00
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
Comment threadsrc/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs Outdated
@stephentoub

Copy link
Copy Markdown
Contributor

@copilot please address the feedback and review/fix any other issues

@stephentoub
stephentoub marked this pull request as ready for review March 31, 2026 12:52
@halter73
halter73 merged commit 74788af into mainMay 19, 2026
3 checks passed
@halter73
halter73 deleted the copilot/add-offline-access-scope branch May 19, 2026 21:47
@jeffhandleyjeffhandley mentioned this pull request Jul 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@stephentoub@halter73@jeffhandley