Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -38,9 +38,9 @@
<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