Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions IdentityServer/v7/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.19.1" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.17.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
<PackageVersion Include="ModelContextProtocol" Version="1.2.0" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="1.2.0" />
<PackageVersion Include="ModelContextProtocol" Version="2.1.0" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="2.1.0" />
<PackageVersion Include="OpenTelemetry" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
Expand Down
47 changes: 21 additions & 26 deletions IdentityServer/v7/McpDemo/McpDemo.Client/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Diagnostics;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Web;
using Microsoft.Extensions.Logging;

using ModelContextProtocol.Authentication;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
Expand All @@ -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)
Expand Down Expand Up @@ -74,12 +61,13 @@
/// <param name="redirectUri">The redirect URI where the authorization code will be sent.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The authorization code extracted from the callback, or null if the operation failed.</returns>
static async Task<string?> HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
// static async Task<string?> HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
static async Task<AuthorizationResult?> 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();
Expand All @@ -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 = "<html><body><h1>Authentication complete</h1><p>You can close this window now.</p></body></html>";
Expand All @@ -117,7 +107,12 @@
}

Console.WriteLine("Authorization code received successfully.");
return code;
return new AuthorizationResult
{
Code = code,
State = state,
Iss = iss
};
}
catch (Exception ex)
{
Expand Down
2 changes: 1 addition & 1 deletion IdentityServer/v7/McpDemo/McpDemo.IdentityServer/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class Config

public static IEnumerable<ApiResource> ApiResources =>
[
new("https://localhost:7141/", "MCP Server")
new("https://localhost:7141", "MCP Server")
{
Scopes = { "mcp:tools" }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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();

Expand Down
21 changes: 12 additions & 9 deletions IdentityServer/v7/McpDemo/McpDemo.McpServer/Program.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
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;

options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidAudience = serverUrl,
ValidAudience = mcpServerUrl,
ValidIssuer = inMemoryOAuthServerUrl,
NameClaimType = "name",
RoleClaimType = "role"
Expand All @@ -33,9 +36,9 @@
{
options.ResourceMetadata = new()
{
Resource = serverUrl,
Resource = mcpServerUrl,
ResourceDocumentation = "https://docs.example/api/weather",
AuthorizationServers = [inMemoryOAuthServerUrl],
AuthorizationServers = { inMemoryOAuthServerUrl },
ScopesSupported = ["mcp:tools"]
};
});
Expand Down
6 changes: 3 additions & 3 deletions IdentityServer/v8/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.17.0" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.17.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
<PackageVersion Include="ModelContextProtocol" Version="1.2.0" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="1.2.0" />
<PackageVersion Include="ModelContextProtocol" Version="2.1.0" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="2.1.0" />
<PackageVersion Include="OpenTelemetry" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.16.0" />
Expand All @@ -63,4 +63,4 @@
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
45 changes: 20 additions & 25 deletions IdentityServer/v8/McpDemo/McpDemo.Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -74,12 +61,13 @@
/// <param name="redirectUri">The redirect URI where the authorization code will be sent.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The authorization code extracted from the callback, or null if the operation failed.</returns>
static async Task<string?> HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
// static async Task<string?> HandleAuthorizationUrlAsync(Uri authorizationUrl, Uri redirectUri, CancellationToken cancellationToken)
static async Task<AuthorizationResult?> 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();
Expand All @@ -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 = "<html><body><h1>Authentication complete</h1><p>You can close this window now.</p></body></html>";
Expand All @@ -117,7 +107,12 @@
}

Console.WriteLine("Authorization code received successfully.");
return code;
return new AuthorizationResult
{
Code = code,
State = state,
Iss = iss
};
}
catch (Exception ex)
{
Expand Down
2 changes: 1 addition & 1 deletion IdentityServer/v8/McpDemo/McpDemo.IdentityServer/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class Config

public static IEnumerable<ApiResource> ApiResources =>
[
new("https://localhost:7141/", "MCP Server")
new("https://localhost:7141", "MCP Server")
{
Scopes = { "mcp:tools" }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
</li>
</ul>

@if (Model.License != null)
@if (Model.License != null && Model.License.IsConfigured)
{
<h2>License</h2>
<dl>
<dt>Serial Number</dt>
<dd>@Model.License.SerialNumber</dd>
<dd>@(Model.License.SerialNumber.HasValue ? Model.License.SerialNumber : "???")</dd>
Comment thread
ProgrammerAL marked this conversation as resolved.
<dt>Expiration</dt>
<dd>@Model.License.Expiration!.Value.ToString("F") </dd>
<dd>@((Model.License.Expiration.HasValue) ? Model.License.Expiration.Value.ToString("F") : "???")</dd>
</dl>
}
</div>
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
Loading
Loading