Skip to content

Commit f806625

Browse files
committed
Update with configuration for generic OAuth provider
1 parent e1ff4ca commit f806625

File tree

8 files changed

+139
-86
lines changed

8 files changed

+139
-86
lines changed

samples/ProtectedMCPClient/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using ModelContextProtocol.Authentication;
12
using ModelContextProtocol.Client;
23

34
namespace ProtectedMCPClient;
@@ -22,7 +23,7 @@ static async Task Main(string[] args)
2223

2324
// Create the token provider with our custom HttpClient,
2425
// letting the AuthorizationHelpers be created automatically
25-
var tokenProvider = new BasicOAuthProvider(
26+
var tokenProvider = new GenericOAuthProvider(
2627
new Uri(serverUrl),
2728
httpClient,
2829
null, // AuthorizationHelpers will be created automatically
@@ -47,6 +48,7 @@ static async Task Main(string[] args)
4748
transportOptions,
4849
tokenProvider
4950
);
51+
5052
var client = await McpClientFactory.CreateAsync(transport);
5153

5254
var tools = await client.ListToolsAsync();

samples/ProtectedMCPServer/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
Resource = new Uri("http://localhost"),
6565
BearerMethodsSupported = { "header" },
6666
ResourceDocumentation = new Uri("https://docs.example.com/api/weather"),
67-
AuthorizationServers = { new Uri($"{instance}{tenantId}/oauth2/v2.0") }
67+
AuthorizationServers = { new Uri($"{instance}{tenantId}/v2.0") }
6868
};
6969

7070
metadata.ScopesSupported.AddRange([

src/ModelContextProtocol/Authentication/AuthorizationHelpers.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ private Uri ExtractBaseResourceUri(Uri metadataUri)
149149
/// <returns>The resource metadata if the resource matches the server, otherwise throws an exception.</returns>
150150
/// <exception cref="InvalidOperationException">Thrown when the response is not a 401, lacks a WWW-Authenticate header,
151151
/// lacks a resource_metadata parameter, the metadata can't be fetched, or the resource URI doesn't match the server URL.</exception>
152-
public async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
152+
internal async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
153153
HttpResponseMessage response,
154154
Uri serverUrl,
155155
CancellationToken cancellationToken = default)
@@ -169,7 +169,7 @@ public async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
169169
string? resourceMetadataUrl = null;
170170
foreach (var header in response.Headers.WwwAuthenticate)
171171
{
172-
if (string.Equals(header.Scheme, "Bearer", StringComparison.OrdinalIgnoreCase) &&
172+
if (string.Equals(header.Scheme, "Bearer", StringComparison.OrdinalIgnoreCase) &&
173173
!string.IsNullOrEmpty(header.Parameter))
174174
{
175175
resourceMetadataUrl = ParseWwwAuthenticateParameters(header.Parameter, "resource_metadata");
@@ -186,17 +186,17 @@ public async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
186186
}
187187

188188
Uri metadataUri = new(resourceMetadataUrl);
189-
189+
190190
var metadata = await FetchProtectedResourceMetadataAsync(metadataUri, cancellationToken).ConfigureAwait(false);
191191
if (metadata == null)
192192
{
193193
throw new InvalidOperationException($"Failed to fetch resource metadata from {resourceMetadataUrl}");
194194
}
195-
195+
196196
// Extract the base URI from the metadata URL
197197
Uri urlToValidate = ExtractBaseResourceUri(metadataUri);
198198
_logger.LogDebug($"Validating resource metadata against base URL: {urlToValidate}");
199-
199+
200200
if (!VerifyResourceMatch(metadata, urlToValidate))
201201
{
202202
throw new InvalidOperationException(
@@ -245,5 +245,33 @@ public async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
245245
}
246246

247247
return null;
248+
} /// <summary>
249+
/// Handles a 401 Unauthorized response and returns all available authorization servers.
250+
/// This is the primary method for OAuth discovery - use this when you want full control
251+
/// over authorization server selection.
252+
/// </summary>
253+
/// <param name="response">The 401 HTTP response.</param>
254+
/// <param name="serverUrl">The server URL that returned the 401.</param>
255+
/// <param name="cancellationToken">A token to cancel the operation.</param>
256+
/// <returns>A list of available authorization server URIs.</returns>
257+
/// <exception cref="ArgumentNullException">Thrown when response is null.</exception>
258+
public async Task<IReadOnlyList<Uri>> GetAvailableAuthorizationServersAsync(
259+
HttpResponseMessage response,
260+
Uri serverUrl,
261+
CancellationToken cancellationToken = default)
262+
{
263+
if (response == null) throw new ArgumentNullException(nameof(response));
264+
265+
try
266+
{
267+
// Extract resource metadata behind the scenes
268+
var metadata = await ExtractProtectedResourceMetadata(response, serverUrl, cancellationToken);
269+
return metadata.AuthorizationServers ?? new List<Uri>();
270+
}
271+
catch (Exception ex)
272+
{
273+
_logger.LogError(ex, "Failed to get available authorization servers");
274+
return new List<Uri>();
275+
}
248276
}
249277
}

samples/ProtectedMCPClient/Types/AuthorizationServerMetadata.cs renamed to src/ModelContextProtocol/Authentication/AuthorizationServerMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Text.Json.Serialization;
22

3-
namespace ModelContextProtocol.Types.Authentication;
3+
namespace ModelContextProtocol.Authentication;
44

55
/// <summary>
66
/// Represents the metadata about an OAuth authorization server.
@@ -66,4 +66,4 @@ public class AuthorizationServerMetadata
6666
/// </summary>
6767
[JsonPropertyName("scopes_supported")]
6868
public List<string>? ScopesSupported { get; set; }
69-
}
69+
}

0 commit comments

Comments
 (0)