HandleAuthorizationUrlAsync(AuthorizationCallbackContext authContext, CancellationToken cancellationToken)
{
Console.WriteLine("Starting OAuth authorization flow...");
- Console.WriteLine($"Opening browser to: {authorizationUrl}");
+ Console.WriteLine($"Opening browser to: {authContext.AuthorizationUri}");
- var listenerPrefix = redirectUri.GetLeftPart(UriPartial.Authority);
+ var listenerPrefix = authContext.RedirectUri.GetLeftPart(UriPartial.Authority);
if (!listenerPrefix.EndsWith("/")) listenerPrefix += "/";
using var listener = new HttpListener();
@@ -90,11 +78,13 @@
listener.Start();
Console.WriteLine($"Listening for OAuth callback on: {listenerPrefix}");
- OpenBrowser(authorizationUrl);
+ OpenBrowser(authContext.AuthorizationUri);
var context = await listener.GetContextAsync();
var query = HttpUtility.ParseQueryString(context.Request.Url?.Query ?? string.Empty);
var code = query["code"];
+ var state = query["state"];
+ var iss = query["iss"];
var error = query["error"];
string responseHtml = "Authentication complete
You can close this window now.
";
@@ -117,7 +107,12 @@
}
Console.WriteLine("Authorization code received successfully.");
- return code;
+ return new AuthorizationResult
+ {
+ Code = code,
+ State = state,
+ Iss = iss
+ };
}
catch (Exception ex)
{
diff --git a/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/Config.cs b/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/Config.cs
index 9be841355..4f9cc6e68 100644
--- a/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/Config.cs
+++ b/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/Config.cs
@@ -12,7 +12,7 @@ public static class Config
public static IEnumerable ApiResources =>
[
- new("https://localhost:7141/", "MCP Server")
+ new("https://localhost:7141", "MCP Server")
{
Scopes = { "mcp:tools" }
}
diff --git a/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs b/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs
index 093df1eec..08632f22e 100644
--- a/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs
+++ b/IdentityServer/v7/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs
@@ -1,10 +1,7 @@
-using System.Globalization;
using Duende.IdentityServer;
using Duende.IdentityServer.Configuration;
-using Duende.IdentityServer.Configuration.Validation.DynamicClientRegistration;
-using Duende.IdentityServer.Stores;
+
using Microsoft.AspNetCore.DataProtection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens;
namespace McpDemo.IdentityServer;
@@ -17,10 +14,10 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde
builder.Services.AddRazorPages();
var isBuilder = builder.Services.AddIdentityServer(options =>
- {
- // this will add the default dynamic client registration endpoint to the discovery/metadatada documents
- options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Inferred;
- })
+ {
+ // this will add the default dynamic client registration endpoint to the discovery/metadatada documents
+ options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Inferred;
+ })
.AddTestUsers(TestUsers.Users)
.AddLicenseSummary();
diff --git a/IdentityServer/v7/McpDemo/McpDemo.McpServer/Program.cs b/IdentityServer/v7/McpDemo/McpDemo.McpServer/Program.cs
index 55e045381..00a3a3803 100644
--- a/IdentityServer/v7/McpDemo/McpDemo.McpServer/Program.cs
+++ b/IdentityServer/v7/McpDemo/McpDemo.McpServer/Program.cs
@@ -1,21 +1,24 @@
using System.Net.Http.Headers;
+
using McpDemo.McpServer.McpTools;
+
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
+
using ModelContextProtocol.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
-var serverUrl = "https://localhost:7141";
-var inMemoryOAuthServerUrl = "https://localhost:5001/";
+var mcpServerUrl = "https://localhost:7141";
+var inMemoryOAuthServerUrl = "https://localhost:5001";
builder.Services.AddAuthentication(options =>
- {
- options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
- options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
- })
+{
+ options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+})
.AddJwtBearer(options =>
{
options.Authority = inMemoryOAuthServerUrl;
@@ -23,7 +26,7 @@
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
- ValidAudience = serverUrl,
+ ValidAudience = mcpServerUrl,
ValidIssuer = inMemoryOAuthServerUrl,
NameClaimType = "name",
RoleClaimType = "role"
@@ -33,9 +36,9 @@
{
options.ResourceMetadata = new()
{
- Resource = serverUrl,
+ Resource = mcpServerUrl,
ResourceDocumentation = "https://docs.example/api/weather",
- AuthorizationServers = [inMemoryOAuthServerUrl],
+ AuthorizationServers = { inMemoryOAuthServerUrl },
ScopesSupported = ["mcp:tools"]
};
});
diff --git a/IdentityServer/v8/Directory.Packages.props b/IdentityServer/v8/Directory.Packages.props
index 30cf1fab9..1e3dc435b 100644
--- a/IdentityServer/v8/Directory.Packages.props
+++ b/IdentityServer/v8/Directory.Packages.props
@@ -47,8 +47,8 @@
-
-
+
+
@@ -63,4 +63,4 @@
-
\ No newline at end of file
+
diff --git a/IdentityServer/v8/McpDemo/McpDemo.AppHost/Properties/launchSettings.json b/IdentityServer/v8/McpDemo/McpDemo.AppHost/Properties/launchSettings.json
index 667942c6d..6e041618a 100644
--- a/IdentityServer/v8/McpDemo/McpDemo.AppHost/Properties/launchSettings.json
+++ b/IdentityServer/v8/McpDemo/McpDemo.AppHost/Properties/launchSettings.json
@@ -13,19 +13,6 @@
"ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23176",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22124"
}
- },
- "http": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "applicationUrl": "http://localhost:15291",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "DOTNET_ENVIRONMENT": "Development",
- "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19220",
- "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18149",
- "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20034"
- }
}
}
}
diff --git a/IdentityServer/v8/McpDemo/McpDemo.Client/Program.cs b/IdentityServer/v8/McpDemo/McpDemo.Client/Program.cs
index 7e214c248..b5294c5d2 100644
--- a/IdentityServer/v8/McpDemo/McpDemo.Client/Program.cs
+++ b/IdentityServer/v8/McpDemo/McpDemo.Client/Program.cs
@@ -2,7 +2,7 @@
using System.Net;
using System.Text;
using System.Web;
-using Microsoft.Extensions.Logging;
+
using ModelContextProtocol.Authentication;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
@@ -13,38 +13,25 @@
Console.WriteLine($"Connecting to server at {mcpServerUrl}...");
Console.WriteLine();
-// We can customize a shared HttpClient with a custom handler if desired
-var sharedHandler = new SocketsHttpHandler
-{
- PooledConnectionLifetime = TimeSpan.FromMinutes(2),
- PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1)
-};
-var httpClient = new HttpClient(sharedHandler);
-
-var consoleLoggerFactory = LoggerFactory.Create(builder =>
-{
- builder.AddConsole();
-});
+var httpClient = new HttpClient();
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
- Endpoint = new Uri(mcpServerUrl),
+ Endpoint = new Uri(mcpServerUrl),
Name = "Weather MCP Client",
OAuth = new ClientOAuthOptions
{
RedirectUri = new Uri("http://localhost:1179/callback"),
- AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
+ AuthorizationCallbackHandler = HandleAuthorizationUrlAsync,
DynamicClientRegistration = new DynamicClientRegistrationOptions
{
ClientName = "ProtectedMcpClient"
},
- // Odd that this config is required. I would expect the client to read the supported scopes from the MCP server's
- // protected resource metadata and use the scopes listed there in its dynamic client registration request.
- Scopes = ["mcp:tools"]
+ Scopes = ["mcp:tools"],
},
-}, httpClient, consoleLoggerFactory);
+}, httpClient);
-var client = await McpClient.CreateAsync(transport, loggerFactory: consoleLoggerFactory);
+var client = await McpClient.CreateAsync(transport);
var tools = await client.ListToolsAsync();
if (tools.Count == 0)
@@ -74,12 +61,13 @@
/// The redirect URI where the authorization code will be sent.
/// The cancellation token.
/// The authorization code extracted from the callback, or null if the operation failed.
-static async Task HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
+// static async Task HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
+static async Task HandleAuthorizationUrlAsync(AuthorizationCallbackContext authContext, CancellationToken cancellationToken)
{
Console.WriteLine("Starting OAuth authorization flow...");
- Console.WriteLine($"Opening browser to: {authorizationUrl}");
+ Console.WriteLine($"Opening browser to: {authContext.AuthorizationUri}");
- var listenerPrefix = redirectUri.GetLeftPart(UriPartial.Authority);
+ var listenerPrefix = authContext.RedirectUri.GetLeftPart(UriPartial.Authority);
if (!listenerPrefix.EndsWith("/")) listenerPrefix += "/";
using var listener = new HttpListener();
@@ -90,11 +78,13 @@
listener.Start();
Console.WriteLine($"Listening for OAuth callback on: {listenerPrefix}");
- OpenBrowser(authorizationUrl);
+ OpenBrowser(authContext.AuthorizationUri);
var context = await listener.GetContextAsync();
var query = HttpUtility.ParseQueryString(context.Request.Url?.Query ?? string.Empty);
var code = query["code"];
+ var state = query["state"];
+ var iss = query["iss"];
var error = query["error"];
string responseHtml = "Authentication complete
You can close this window now.
";
@@ -117,7 +107,12 @@
}
Console.WriteLine("Authorization code received successfully.");
- return code;
+ return new AuthorizationResult
+ {
+ Code = code,
+ State = state,
+ Iss = iss
+ };
}
catch (Exception ex)
{
diff --git a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Config.cs b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Config.cs
index 9be841355..4f9cc6e68 100644
--- a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Config.cs
+++ b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Config.cs
@@ -12,7 +12,7 @@ public static class Config
public static IEnumerable ApiResources =>
[
- new("https://localhost:7141/", "MCP Server")
+ new("https://localhost:7141", "MCP Server")
{
Scopes = { "mcp:tools" }
}
diff --git a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs
index 093df1eec..6820ce4eb 100644
--- a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs
+++ b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/HostingExtensions.cs
@@ -1,10 +1,6 @@
-using System.Globalization;
using Duende.IdentityServer;
using Duende.IdentityServer.Configuration;
-using Duende.IdentityServer.Configuration.Validation.DynamicClientRegistration;
-using Duende.IdentityServer.Stores;
using Microsoft.AspNetCore.DataProtection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens;
namespace McpDemo.IdentityServer;
diff --git a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Pages/Index.cshtml b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Pages/Index.cshtml
index 2a5058d5c..7f43dcd13 100644
--- a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Pages/Index.cshtml
+++ b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Pages/Index.cshtml
@@ -33,14 +33,14 @@
- @if (Model.License != null)
+ @if (Model.License != null && Model.License.IsConfigured)
{
License
- Serial Number
- - @Model.License.SerialNumber
+ - @(Model.License.SerialNumber.HasValue ? Model.License.SerialNumber : "???")
- Expiration
- - @Model.License.Expiration!.Value.ToString("F")
+ - @((Model.License.Expiration.HasValue) ? Model.License.Expiration.Value.ToString("F") : "???")
}
diff --git a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Properties/launchSettings.json b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Properties/launchSettings.json
index f40feb3de..50b02f22f 100644
--- a/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Properties/launchSettings.json
+++ b/IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Properties/launchSettings.json
@@ -1,12 +1,14 @@
-{
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
- "SelfHost": {
+ "https": {
"commandName": "Project",
- "launchBrowser": true,
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
- },
- "applicationUrl": "https://localhost:5001"
+ }
}
}
}
diff --git a/IdentityServer/v8/McpDemo/McpDemo.McpServer/Program.cs b/IdentityServer/v8/McpDemo/McpDemo.McpServer/Program.cs
index 472f50d75..52f405b20 100644
--- a/IdentityServer/v8/McpDemo/McpDemo.McpServer/Program.cs
+++ b/IdentityServer/v8/McpDemo/McpDemo.McpServer/Program.cs
@@ -8,8 +8,8 @@
builder.AddServiceDefaults();
-var serverUrl = "https://localhost:7141";
-var inMemoryOAuthServerUrl = "https://localhost:5001/";
+var mcpServerUrl = "https://localhost:7141";
+var inMemoryOAuthServerUrl = "https://localhost:5001";
builder.Services.AddAuthentication(options =>
{
@@ -23,7 +23,7 @@
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
- ValidAudience = serverUrl,
+ ValidAudience = mcpServerUrl,
ValidIssuer = inMemoryOAuthServerUrl,
NameClaimType = "name",
RoleClaimType = "role"
@@ -33,7 +33,7 @@
{
options.ResourceMetadata = new()
{
- Resource = serverUrl,
+ Resource = mcpServerUrl,
ResourceDocumentation = "https://docs.example/api/weather",
AuthorizationServers = { inMemoryOAuthServerUrl },
ScopesSupported = ["mcp:tools"]