Skip to content

Commit 25ab4df

Browse files
committed
rename: CrossApplicationAccess* -> IdentityAssertionGrant* (fix CS8603 + final rename per review)
1 parent 1ce785c commit 25ab4df

11 files changed

Lines changed: 99 additions & 98 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ For more information about MCP:
3434
## Cross-Application Access (Identity Assertion Authorization Grant flow)
3535

3636
The SDK provides support for the [Identity Assertion Authorization Grant flow](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/enterprise-managed-authorization.mdx)
37-
via `CrossApplicationAccessProvider`. See the [Cross-Application Access](docs/concepts/transports/transports.md#cross-application-access) section in the transport docs for full usage details.
37+
via `IdentityAssertionGrantProvider`. See the [Cross-Application Access](docs/concepts/transports/transports.md#cross-application-access) section in the transport docs for full usage details.
3838

3939
## License
4040

docs/concepts/transports/transports.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ Like [stdio](#stdio-transport), the in-memory transport is inherently single-ses
381381

382382
## Cross-Application Access
383383

384-
The SDK provides built-in support for the [Identity Assertion Authorization Grant (IDAG) flow](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/enterprise-managed-authorization.mdx) via `CrossApplicationAccessProvider`. This enables non-interactive enterprise SSO scenarios where users authenticate once via their enterprise Identity Provider (IdP) and access MCP servers without per-server authorization prompts.
384+
The SDK provides built-in support for the [Identity Assertion Authorization Grant (IDAG) flow](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/enterprise-managed-authorization.mdx) via `IdentityAssertionGrantProvider`. This enables non-interactive enterprise SSO scenarios where users authenticate once via their enterprise Identity Provider (IdP) and access MCP servers without per-server authorization prompts.
385385

386386
The flow consists of two steps:
387387
1. **RFC 8693 Token Exchange** at the enterprise IdP: OIDC ID token → JWT Authorization Grant (JAG)
@@ -395,8 +395,8 @@ using ModelContextProtocol.Authentication;
395395
// The caller owns the HttpClient lifetime.
396396
var httpClient = new HttpClient();
397397

398-
var provider = new CrossApplicationAccessProvider(
399-
new CrossApplicationAccessProviderOptions
398+
var provider = new IdentityAssertionGrantProvider(
399+
new IdentityAssertionGrantProviderOptions
400400
{
401401
ClientId = "mcp-client-id",
402402
IdpTokenEndpoint = "https://company.okta.com/oauth2/token",

src/ModelContextProtocol.Core/Authentication/CrossApplicationAccess.cs renamed to src/ModelContextProtocol.Core/Authentication/IdentityAssertionGrant.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace ModelContextProtocol.Authentication;
1010
/// Implements the Enterprise Managed Authorization flow as specified at
1111
/// <see href="https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/enterprise-managed-authorization.mdx"/>.
1212
/// </remarks>
13-
internal static class CrossApplicationAccess
13+
internal static class IdentityAssertionGrant
1414
{
1515
#region Constants
1616

@@ -103,7 +103,7 @@ public static async Task<string> RequestJwtAuthorizationGrantAsync(
103103
// Could not parse error response
104104
}
105105

106-
throw new CrossApplicationAccessException(
106+
throw new IdentityAssertionGrantException(
107107
$"Token exchange failed with status {(int)httpResponse.StatusCode}.",
108108
errorResponse?.Error,
109109
errorResponse?.ErrorDescription,
@@ -114,25 +114,25 @@ public static async Task<string> RequestJwtAuthorizationGrantAsync(
114114

115115
if (response is null)
116116
{
117-
var ex = new CrossApplicationAccessException("Failed to parse token exchange response.");
117+
var ex = new IdentityAssertionGrantException("Failed to parse token exchange response.");
118118
ex.Data["ResponseBody"] = responseBody;
119119
throw ex;
120120
}
121121

122122
if (string.IsNullOrEmpty(response.AccessToken))
123123
{
124-
throw new CrossApplicationAccessException("Token exchange response missing required field: access_token");
124+
throw new IdentityAssertionGrantException("Token exchange response missing required field: access_token");
125125
}
126126

127127
if (!string.Equals(response.IssuedTokenType, TokenTypeIdJag, StringComparison.Ordinal))
128128
{
129-
throw new CrossApplicationAccessException(
129+
throw new IdentityAssertionGrantException(
130130
$"Token exchange response issued_token_type must be '{TokenTypeIdJag}', got '{response.IssuedTokenType}'.");
131131
}
132132

133133
if (!string.Equals(response.TokenType, TokenTypeNotApplicable, StringComparison.Ordinal))
134134
{
135-
throw new CrossApplicationAccessException(
135+
throw new IdentityAssertionGrantException(
136136
$"Token exchange response token_type must be '{TokenTypeNotApplicable}' per RFC 8693 §2.2.1, got '{response.TokenType}'.");
137137
}
138138

@@ -197,7 +197,7 @@ public static async Task<TokenContainer> ExchangeJwtBearerGrantAsync(
197197
// Could not parse error response
198198
}
199199

200-
throw new CrossApplicationAccessException(
200+
throw new IdentityAssertionGrantException(
201201
$"JWT bearer grant failed with status {(int)httpResponse.StatusCode}.",
202202
errorResponse?.Error,
203203
errorResponse?.ErrorDescription,
@@ -208,24 +208,24 @@ public static async Task<TokenContainer> ExchangeJwtBearerGrantAsync(
208208

209209
if (response is null)
210210
{
211-
var ex = new CrossApplicationAccessException("Failed to parse JWT bearer grant response.");
211+
var ex = new IdentityAssertionGrantException("Failed to parse JWT bearer grant response.");
212212
ex.Data["ResponseBody"] = responseBody;
213213
throw ex;
214214
}
215215

216216
if (string.IsNullOrEmpty(response.AccessToken))
217217
{
218-
throw new CrossApplicationAccessException("JWT bearer grant response missing required field: access_token");
218+
throw new IdentityAssertionGrantException("JWT bearer grant response missing required field: access_token");
219219
}
220220

221221
if (string.IsNullOrEmpty(response.TokenType))
222222
{
223-
throw new CrossApplicationAccessException("JWT bearer grant response missing required field: token_type");
223+
throw new IdentityAssertionGrantException("JWT bearer grant response missing required field: token_type");
224224
}
225225

226226
if (!string.Equals(response.TokenType, "bearer", StringComparison.OrdinalIgnoreCase))
227227
{
228-
throw new CrossApplicationAccessException(
228+
throw new IdentityAssertionGrantException(
229229
$"JWT bearer grant response token_type must be 'bearer' per RFC 7523, got '{response.TokenType}'.");
230230
}
231231

@@ -289,7 +289,7 @@ internal static async Task<AuthorizationServerMetadata> DiscoverAuthServerMetada
289289
}
290290
}
291291

292-
throw new CrossApplicationAccessException($"Failed to discover authorization server metadata for: {issuerUrl}");
292+
throw new IdentityAssertionGrantException($"Failed to discover authorization server metadata for: {issuerUrl}");
293293
}
294294

295295
#endregion

src/ModelContextProtocol.Core/Authentication/CrossApplicationAccessContext.cs renamed to src/ModelContextProtocol.Core/Authentication/IdentityAssertionGrantContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
namespace ModelContextProtocol.Authentication;
22

33
/// <summary>
4-
/// Context provided to the <see cref="CrossApplicationAccessIdTokenCallback"/> for a Cross-Application Access
4+
/// Context provided to the <see cref="IdentityAssertionGrantIdTokenCallback"/> for a Cross-Application Access
55
/// authorization flow. Contains the URLs discovered during the OAuth flow needed for the token exchange step.
66
/// </summary>
7-
public sealed class CrossApplicationAccessContext
7+
public sealed class IdentityAssertionGrantContext
88
{
99
/// <summary>
1010
/// Gets the MCP resource server URL (i.e., the <c>resource</c> parameter for token exchange).

src/ModelContextProtocol.Core/Authentication/CrossApplicationAccessException.cs renamed to src/ModelContextProtocol.Core/Authentication/IdentityAssertionGrantException.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace ModelContextProtocol.Authentication;
44
/// Represents an error that occurred during a Cross-Application Access authorization operation
55
/// (token exchange per RFC 8693, and JWT bearer grant per RFC 7523).
66
/// </summary>
7-
public sealed class CrossApplicationAccessException : Exception
7+
public sealed class IdentityAssertionGrantException : Exception
88
{
99
/// <summary>
1010
/// Gets the OAuth error code, if available (e.g., "invalid_request", "invalid_grant").
@@ -22,13 +22,13 @@ public sealed class CrossApplicationAccessException : Exception
2222
public string? ErrorUri { get; }
2323

2424
/// <summary>
25-
/// Initializes a new instance of the <see cref="CrossApplicationAccessException"/> class.
25+
/// Initializes a new instance of the <see cref="IdentityAssertionGrantException"/> class.
2626
/// </summary>
2727
/// <param name="message">The error message.</param>
2828
/// <param name="errorCode">The OAuth error code.</param>
2929
/// <param name="errorDescription">The human-readable error description.</param>
3030
/// <param name="errorUri">The error URI.</param>
31-
public CrossApplicationAccessException(string message, string? errorCode = null, string? errorDescription = null, string? errorUri = null)
31+
public IdentityAssertionGrantException(string message, string? errorCode = null, string? errorDescription = null, string? errorUri = null)
3232
: base(FormatMessage(message, errorCode, errorDescription))
3333
{
3434
ErrorCode = errorCode;

src/ModelContextProtocol.Core/Authentication/CrossApplicationAccessIdTokenCallback.cs renamed to src/ModelContextProtocol.Core/Authentication/IdentityAssertionGrantIdTokenCallback.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ namespace ModelContextProtocol.Authentication;
1212
/// obtained from the enterprise Identity Provider (e.g., via SSO login). The provider will then use this
1313
/// ID token to perform the RFC 8693 token exchange to obtain a JWT Authorization Grant.
1414
/// </returns>
15-
public delegate Task<string> CrossApplicationAccessIdTokenCallback(
16-
CrossApplicationAccessContext context,
15+
public delegate Task<string> IdentityAssertionGrantIdTokenCallback(
16+
IdentityAssertionGrantContext context,
1717
CancellationToken cancellationToken);

src/ModelContextProtocol.Core/Authentication/CrossApplicationAccessProvider.cs renamed to src/ModelContextProtocol.Core/Authentication/IdentityAssertionGrantProvider.cs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ namespace ModelContextProtocol.Authentication;
1414
/// </para>
1515
/// <list type="number">
1616
/// <item><description>
17-
/// The <see cref="CrossApplicationAccessProviderOptions.IdTokenCallback"/> is called to obtain an OIDC ID token.
18-
/// It receives a <see cref="CrossApplicationAccessContext"/> with the discovered resource and authorization
17+
/// The <see cref="IdentityAssertionGrantProviderOptions.IdTokenCallback"/> is called to obtain an OIDC ID token.
18+
/// It receives a <see cref="IdentityAssertionGrantContext"/> with the discovered resource and authorization
1919
/// server URLs.
2020
/// </description></item>
2121
/// <item><description>
@@ -31,8 +31,8 @@ namespace ModelContextProtocol.Authentication;
3131
/// </remarks>
3232
/// <example>
3333
/// <code>
34-
/// var provider = new CrossApplicationAccessProvider(
35-
/// new CrossApplicationAccessProviderOptions
34+
/// var provider = new IdentityAssertionGrantProvider(
35+
/// new IdentityAssertionGrantProviderOptions
3636
/// {
3737
/// ClientId = "mcp-client-id",
3838
/// IdpTokenEndpoint = "https://company.okta.com/oauth2/token",
@@ -48,16 +48,16 @@ namespace ModelContextProtocol.Authentication;
4848
/// cancellationToken: ct);
4949
/// </code>
5050
/// </example>
51-
public sealed class CrossApplicationAccessProvider
51+
public sealed class IdentityAssertionGrantProvider
5252
{
53-
private readonly CrossApplicationAccessProviderOptions _options;
53+
private readonly IdentityAssertionGrantProviderOptions _options;
5454
private readonly HttpClient _httpClient;
5555
private readonly ILogger _logger;
5656

5757
private TokenContainer? _cachedTokens;
5858

5959
/// <summary>
60-
/// Initializes a new instance of the <see cref="CrossApplicationAccessProvider"/> class.
60+
/// Initializes a new instance of the <see cref="IdentityAssertionGrantProvider"/> class.
6161
/// </summary>
6262
/// <param name="options">Configuration for the Cross-Application Access provider.</param>
6363
/// <param name="httpClient">
@@ -66,8 +66,8 @@ public sealed class CrossApplicationAccessProvider
6666
/// <param name="loggerFactory">Optional logger factory.</param>
6767
/// <exception cref="ArgumentNullException"><paramref name="options"/> or <paramref name="httpClient"/> is null.</exception>
6868
/// <exception cref="ArgumentException">Required option values are missing.</exception>
69-
public CrossApplicationAccessProvider(
70-
CrossApplicationAccessProviderOptions options,
69+
public IdentityAssertionGrantProvider(
70+
IdentityAssertionGrantProviderOptions options,
7171
HttpClient httpClient,
7272
ILoggerFactory? loggerFactory = null)
7373
{
@@ -89,7 +89,7 @@ public CrossApplicationAccessProvider(
8989

9090
_options = options;
9191
_httpClient = httpClient;
92-
_logger = (ILogger?)loggerFactory?.CreateLogger<CrossApplicationAccessProvider>() ?? NullLogger.Instance;
92+
_logger = (ILogger?)loggerFactory?.CreateLogger<IdentityAssertionGrantProvider>() ?? NullLogger.Instance;
9393
}
9494

9595
/// <summary>
@@ -99,7 +99,7 @@ public CrossApplicationAccessProvider(
9999
/// <param name="authorizationServerUrl">The MCP authorization server URL.</param>
100100
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
101101
/// <returns>A <see cref="TokenContainer"/> containing the access token.</returns>
102-
/// <exception cref="CrossApplicationAccessException">Thrown when any step of the flow fails.</exception>
102+
/// <exception cref="IdentityAssertionGrantException">Thrown when any step of the flow fails.</exception>
103103
public async Task<TokenContainer> GetAccessTokenAsync(
104104
Uri resourceUrl,
105105
Uri authorizationServerUrl,
@@ -114,15 +114,15 @@ public async Task<TokenContainer> GetAccessTokenAsync(
114114
_logger.LogDebug("Starting Cross-Application Access flow for resource {ResourceUrl}", resourceUrl);
115115

116116
// Step 1: Discover MCP authorization server metadata to find the token endpoint
117-
var mcpAuthMetadata = await CrossApplicationAccess.DiscoverAuthServerMetadataAsync(
117+
var mcpAuthMetadata = await IdentityAssertionGrant.DiscoverAuthServerMetadataAsync(
118118
authorizationServerUrl, _httpClient, cancellationToken).ConfigureAwait(false);
119119

120120
var mcpTokenEndpoint = mcpAuthMetadata.TokenEndpoint?.ToString()
121-
?? throw new CrossApplicationAccessException(
121+
?? throw new IdentityAssertionGrantException(
122122
$"MCP authorization server metadata at {authorizationServerUrl} missing token_endpoint.");
123123

124124
// Step 2: Call the ID token callback to get the caller's OIDC ID token
125-
var context = new CrossApplicationAccessContext
125+
var context = new IdentityAssertionGrantContext
126126
{
127127
ResourceUrl = resourceUrl,
128128
AuthorizationServerUrl = authorizationServerUrl,
@@ -133,14 +133,14 @@ public async Task<TokenContainer> GetAccessTokenAsync(
133133

134134
if (string.IsNullOrEmpty(idToken))
135135
{
136-
throw new CrossApplicationAccessException("ID token callback returned a null or empty token.");
136+
throw new IdentityAssertionGrantException("ID token callback returned a null or empty token.");
137137
}
138138

139139
// Step 3: RFC 8693 token exchange — ID token → JWT Authorization Grant (JAG) at the enterprise IdP
140140
_logger.LogDebug("Performing RFC 8693 token exchange at IdP");
141141
var idpTokenEndpoint = await ResolveIdpTokenEndpointAsync(cancellationToken).ConfigureAwait(false);
142142

143-
var jag = await CrossApplicationAccess.RequestJwtAuthorizationGrantAsync(
143+
var jag = await IdentityAssertionGrant.RequestJwtAuthorizationGrantAsync(
144144
new RequestJwtAuthGrantOptions
145145
{
146146
TokenEndpoint = idpTokenEndpoint,
@@ -154,7 +154,7 @@ public async Task<TokenContainer> GetAccessTokenAsync(
154154

155155
// Step 4: RFC 7523 JWT bearer grant — JAG → access token at the MCP authorization server
156156
_logger.LogDebug("Exchanging JAG for access token at {McpTokenEndpoint}", mcpTokenEndpoint);
157-
var tokens = await CrossApplicationAccess.ExchangeJwtBearerGrantAsync(
157+
var tokens = await IdentityAssertionGrant.ExchangeJwtBearerGrantAsync(
158158
new ExchangeJwtBearerGrantOptions
159159
{
160160
TokenEndpoint = mcpTokenEndpoint,
@@ -194,13 +194,14 @@ private async Task<string> ResolveIdpTokenEndpointAsync(CancellationToken cancel
194194
}
195195

196196
// Discover from IdpUrl
197-
var idpMetadata = await CrossApplicationAccess.DiscoverAuthServerMetadataAsync(
197+
var idpMetadata = await IdentityAssertionGrant.DiscoverAuthServerMetadataAsync(
198198
new Uri(_options.IdpUrl!), _httpClient, cancellationToken).ConfigureAwait(false);
199199

200-
_resolvedIdpTokenEndpoint = idpMetadata.TokenEndpoint?.ToString()
201-
?? throw new CrossApplicationAccessException(
200+
var resolved = idpMetadata.TokenEndpoint?.ToString()
201+
?? throw new IdentityAssertionGrantException(
202202
$"IdP metadata discovery for {_options.IdpUrl} did not return a token_endpoint.");
203203

204-
return _resolvedIdpTokenEndpoint;
204+
_resolvedIdpTokenEndpoint = resolved;
205+
return resolved;
205206
}
206207
}

src/ModelContextProtocol.Core/Authentication/CrossApplicationAccessProviderOptions.cs renamed to src/ModelContextProtocol.Core/Authentication/IdentityAssertionGrantProviderOptions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
namespace ModelContextProtocol.Authentication;
22

33
/// <summary>
4-
/// Configuration options for the <see cref="CrossApplicationAccessProvider"/>.
4+
/// Configuration options for the <see cref="IdentityAssertionGrantProvider"/>.
55
/// </summary>
6-
public sealed class CrossApplicationAccessProviderOptions
6+
public sealed class IdentityAssertionGrantProviderOptions
77
{
88
/// <summary>
99
/// Gets or sets the MCP client ID used for the JWT Bearer grant (RFC 7523) at the MCP authorization server.
@@ -55,7 +55,7 @@ public sealed class CrossApplicationAccessProviderOptions
5555
/// <remarks>
5656
/// <para>
5757
/// This callback is invoked after the MCP resource and authorization server URLs have been discovered.
58-
/// It receives a <see cref="CrossApplicationAccessContext"/> with these URLs and should return the
58+
/// It receives a <see cref="IdentityAssertionGrantContext"/> with these URLs and should return the
5959
/// OIDC ID token string obtained from the enterprise Identity Provider (e.g., from an SSO login session).
6060
/// </para>
6161
/// <para>
@@ -64,5 +64,5 @@ public sealed class CrossApplicationAccessProviderOptions
6464
/// the MCP authorization server via RFC 7523.
6565
/// </para>
6666
/// </remarks>
67-
public required CrossApplicationAccessIdTokenCallback IdTokenCallback { get; set; }
67+
public required IdentityAssertionGrantIdTokenCallback IdTokenCallback { get; set; }
6868
}

src/ModelContextProtocol.Core/Authentication/JagTokenExchangeResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal sealed class JagTokenExchangeResponse
1515

1616
/// <summary>
1717
/// Gets or sets the type of the security token issued.
18-
/// This MUST be <see cref="CrossApplicationAccess.TokenTypeIdJag"/>.
18+
/// This MUST be <see cref="IdentityAssertionGrant.TokenTypeIdJag"/>.
1919
/// </summary>
2020
[System.Text.Json.Serialization.JsonPropertyName("issued_token_type")]
2121
public string IssuedTokenType { get; set; } = null!;

0 commit comments

Comments
 (0)