Skip to content

Commit 1720664

Browse files
committed
Use scope_supported in GenericOAuthProvider
1 parent a253cd0 commit 1720664

File tree

5 files changed

+25
-61
lines changed

5 files changed

+25
-61
lines changed

samples/ProtectedMCPClient/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
clientId: clientId,
3030
clientSecret: "", // No secret needed for this client
3131
redirectUri: new Uri("http://localhost:1179/callback"),
32-
scopes: [$"api://{clientId}/weather.read"],
32+
scopes: null, // Scopes listed in scopes_supported will be requested automatically
3333
logger: null,
3434
authorizationUrlHandler: HandleAuthorizationUrlAsync
3535
);

src/ModelContextProtocol.Core/Authentication/AuthorizationHelpers.cs

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -203,33 +203,4 @@ internal async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
203203

204204
return null;
205205
}
206-
207-
/// <summary>
208-
/// Handles a 401 Unauthorized response and returns all available authorization servers.
209-
/// This is the primary method for OAuth discovery - use this when you want full control
210-
/// over authorization server selection.
211-
/// </summary>
212-
/// <param name="response">The 401 HTTP response.</param>
213-
/// <param name="serverUrl">The server URL that returned the 401.</param>
214-
/// <param name="cancellationToken">A token to cancel the operation.</param>
215-
/// <returns>A list of available authorization server URIs.</returns>
216-
/// <exception cref="ArgumentNullException">Thrown when response is null.</exception>
217-
public async Task<IReadOnlyList<Uri>> GetAvailableAuthorizationServersAsync(
218-
HttpResponseMessage response,
219-
Uri serverUrl,
220-
CancellationToken cancellationToken = default)
221-
{
222-
if (response == null) throw new ArgumentNullException(nameof(response));
223-
224-
try
225-
{
226-
var metadata = await ExtractProtectedResourceMetadata(response, serverUrl, cancellationToken);
227-
return metadata.AuthorizationServers ?? [];
228-
}
229-
catch (Exception ex)
230-
{
231-
_logger.LogError(ex, "Failed to get available authorization servers");
232-
return [];
233-
}
234-
}
235206
}

src/ModelContextProtocol.Core/Authentication/GenericOAuthProvider.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,8 @@ private async Task<McpUnauthorizedResponseResult> PerformOAuthAuthorizationAsync
232232
CancellationToken cancellationToken)
233233
{
234234
// Get available authorization servers from the 401 response
235-
var availableAuthorizationServers = await _authorizationHelpers.GetAvailableAuthorizationServersAsync(
236-
response,
237-
_serverUrl,
238-
cancellationToken);
235+
var protectedResourceMetadata = await _authorizationHelpers.ExtractProtectedResourceMetadata(response, _serverUrl, cancellationToken);
236+
var availableAuthorizationServers = protectedResourceMetadata.AuthorizationServers ?? [];
239237

240238
if (!availableAuthorizationServers.Any())
241239
{
@@ -269,7 +267,7 @@ private async Task<McpUnauthorizedResponseResult> PerformOAuthAuthorizationAsync
269267
_authServerMetadata = authServerMetadata;
270268

271269
// Perform the OAuth flow
272-
var token = await InitiateAuthorizationCodeFlowAsync(authServerMetadata, cancellationToken);
270+
var token = await InitiateAuthorizationCodeFlowAsync(protectedResourceMetadata, authServerMetadata, cancellationToken);
273271
if (token != null)
274272
{
275273
_token = token;
@@ -342,7 +340,7 @@ private async Task<McpUnauthorizedResponseResult> PerformOAuthAuthorizationAsync
342340
if (response.IsSuccessStatusCode)
343341
{
344342
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
345-
var metadata = await JsonSerializer.DeserializeAsync<AuthorizationServerMetadata>(stream, McpJsonUtilities.JsonContext.Default.AuthorizationServerMetadata, cancellationToken);
343+
var metadata = await JsonSerializer.DeserializeAsync(stream, McpJsonUtilities.JsonContext.Default.AuthorizationServerMetadata, cancellationToken);
346344

347345
if (metadata != null)
348346
{
@@ -413,21 +411,25 @@ private async Task<McpUnauthorizedResponseResult> PerformOAuthAuthorizationAsync
413411
}
414412

415413
private async Task<TokenContainer?> InitiateAuthorizationCodeFlowAsync(
414+
ProtectedResourceMetadata protectedResourceMetadata,
416415
AuthorizationServerMetadata authServerMetadata,
417416
CancellationToken cancellationToken)
418417
{
419418
var codeVerifier = GenerateCodeVerifier();
420419
var codeChallenge = GenerateCodeChallenge(codeVerifier);
421420

422-
var authUrl = BuildAuthorizationUrl(authServerMetadata, codeChallenge);
421+
var authUrl = BuildAuthorizationUrl(protectedResourceMetadata, authServerMetadata, codeChallenge);
423422
var authCode = await GetAuthorizationCodeAsync(authUrl, cancellationToken);
424423
if (string.IsNullOrEmpty(authCode))
425424
return null;
426425

427426
return await ExchangeCodeForTokenAsync(authServerMetadata, authCode!, codeVerifier, cancellationToken);
428427
}
429428

430-
private Uri BuildAuthorizationUrl(AuthorizationServerMetadata authServerMetadata, string codeChallenge)
429+
private Uri BuildAuthorizationUrl(
430+
ProtectedResourceMetadata protectedResourceMetadata,
431+
AuthorizationServerMetadata authServerMetadata,
432+
string codeChallenge)
431433
{
432434
if (authServerMetadata.AuthorizationEndpoint.Scheme != Uri.UriSchemeHttp &&
433435
authServerMetadata.AuthorizationEndpoint.Scheme != Uri.UriSchemeHttps)
@@ -442,9 +444,10 @@ private Uri BuildAuthorizationUrl(AuthorizationServerMetadata authServerMetadata
442444
queryParams["code_challenge"] = codeChallenge;
443445
queryParams["code_challenge_method"] = "S256";
444446

445-
if (_scopes.Any())
447+
var scopesSupported = protectedResourceMetadata.ScopesSupported ?? [];
448+
if (_scopes.Count > 0 || scopesSupported.Count > 0)
446449
{
447-
queryParams["scope"] = string.Join(" ", _scopes);
450+
queryParams["scope"] = string.Join(" ", [.._scopes, ..scopesSupported]);
448451
}
449452

450453
var uriBuilder = new UriBuilder(authServerMetadata.AuthorizationEndpoint)

src/ModelContextProtocol.Core/Authentication/ProtectedResourceMetadata.cs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,10 @@ namespace ModelContextProtocol.Authentication;
44

55
/// <summary>
66
/// Represents the resource metadata for OAuth authorization as defined in RFC 9396.
7-
/// Defined by <see href="https://datatracker.ietf.org/doc/rfc9728/">RFC 9728</see>.
7+
/// Defined by <see href="https://datatracker.ietf.org/doc/rfc9728/">RFC 9728</see>.
88
/// </summary>
99
public class ProtectedResourceMetadata
1010
{
11-
/// <summary>
12-
/// Initializes a new instance of the <see cref="ProtectedResourceMetadata"/> class.
13-
/// </summary>
14-
public ProtectedResourceMetadata()
15-
{
16-
AuthorizationServers = [];
17-
BearerMethodsSupported = [];
18-
ScopesSupported = [];
19-
}
20-
2111
/// <summary>
2212
/// The resource URI.
2313
/// </summary>
@@ -26,7 +16,7 @@ public ProtectedResourceMetadata()
2616
/// </remarks>
2717
[JsonPropertyName("resource")]
2818
public required Uri Resource { get; init; }
29-
19+
3020
/// <summary>
3121
/// The list of authorization server URIs.
3222
/// </summary>
@@ -35,8 +25,8 @@ public ProtectedResourceMetadata()
3525
/// for authorization servers that can be used with this protected resource.
3626
/// </remarks>
3727
[JsonPropertyName("authorization_servers")]
38-
public List<Uri> AuthorizationServers { get; set; }
39-
28+
public List<Uri> AuthorizationServers { get; set; } = [];
29+
4030
/// <summary>
4131
/// The supported bearer token methods.
4232
/// </summary>
@@ -45,8 +35,8 @@ public ProtectedResourceMetadata()
4535
/// to the protected resource. Defined values are ["header", "body", "query"].
4636
/// </remarks>
4737
[JsonPropertyName("bearer_methods_supported")]
48-
public List<string> BearerMethodsSupported { get; set; }
49-
38+
public List<string> BearerMethodsSupported { get; set; } = [];
39+
5040
/// <summary>
5141
/// The supported scopes.
5242
/// </summary>
@@ -55,8 +45,8 @@ public ProtectedResourceMetadata()
5545
/// requests to request access to this protected resource.
5646
/// </remarks>
5747
[JsonPropertyName("scopes_supported")]
58-
public List<string> ScopesSupported { get; set; }
59-
48+
public List<string> ScopesSupported { get; set; } = [];
49+
6050
/// <summary>
6151
/// URL of the protected resource's JSON Web Key (JWK) Set document.
6252
/// </summary>

src/ModelContextProtocol.Core/Client/SseClientTransport.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ public SseClientTransport(SseClientTransportOptions transportOptions, HttpClient
5858
/// <param name="transportOptions">Configuration options for the transport.</param>
5959
/// <param name="credentialProvider">The authorization provider to use for authentication.</param>
6060
/// <param name="loggerFactory">Logger factory for creating loggers used for diagnostic output during transport operations.</param>
61-
/// <param name="baseMessageHandler">Optional. The base message handler to use under the authorization handler.
62-
/// If null, a new <see cref="HttpClientHandler"/> will be used. This allows for custom HTTP client pipelines (e.g., from HttpClientFactory)
61+
/// <param name="baseMessageHandler">Optional. The base message handler to use under the authorization handler.
62+
/// If null, a new <see cref="HttpClientHandler"/> will be used. This allows for custom HTTP client pipelines (e.g., from HttpClientFactory)
6363
/// to be used in conjunction with the token-based authentication provided by <paramref name="credentialProvider"/>.</param>
6464
public SseClientTransport(SseClientTransportOptions transportOptions, IMcpCredentialProvider credentialProvider, ILoggerFactory? loggerFactory = null, HttpMessageHandler? baseMessageHandler = null)
6565
{
@@ -74,7 +74,7 @@ public SseClientTransport(SseClientTransportOptions transportOptions, IMcpCreden
7474
{
7575
InnerHandler = baseMessageHandler ?? new HttpClientHandler()
7676
};
77-
77+
7878
_httpClient = new HttpClient(authHandler);
7979
_ownsHttpClient = true;
8080
}

0 commit comments

Comments
 (0)