From 87078c456d83ba4cee2a76a0431ddaa20b78ee5d Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Fri, 7 Aug 2026 17:04:33 -0400 Subject: [PATCH 01/11] Started work on a page for an MCP server/client quickstart --- .../docs/identityserver/quickstarts/8-mcp.mdx | 349 ++++++++++++++++++ .../identityserver/samples/mcp-server.mdx | 22 ++ 2 files changed, 371 insertions(+) create mode 100644 astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx create mode 100644 astro/src/content/docs/identityserver/samples/mcp-server.mdx diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx new file mode 100644 index 000000000..9411e0af3 --- /dev/null +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -0,0 +1,349 @@ +--- +title: "Connecting an MCP Server and Client with DCR" +description: "Learn how to build a secure MCP server that protects requests using Duende IdentityServer and allows any client to dynamically connect." +date: 2026-08-07T01:00:00+00:00 +sidebar: + order: 16 +--- + +import { Code } from "@astrojs/starlight/components"; +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Duende IdentityServer contains the [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr) feature which allows an application to add clients at runtime instead of requiring you to statically create them on startup with code, or loading them from persistent storage. This + +:::warning +Each client that dynamically that connects to IdentityServer using DCR counts towards the Client Ids allowed in your license. +::: + +## Setting Up The Projects + +We will create 3 projects. An IdentityServer project, an MCP API, and a client which will make requests to the MCP. + +```console +mkdir mcp-quickstart +cd mcp-quickstart +``` + +### Create the IdentityServer Project + +The IdentityServer project needs to be configured to use DCR to allow clients at runtime. The below steps disable the static clients and adds DCR. Add a new Duende IdentityServer InMemory project to its own directory inside the `mcp-quickstart` directory you created above. + +```console +dotnet new duende-is-inmem --name "McpQuickStart.IdentityServer" +``` + +#### Add NuGet Packages + +Add the latest versions of the `Duende.IdentityServer` and `Duende.IdentityServer.Configuration` NuGet packages to the project. The `.csproj` file should look like the below. + +```xml + + + net10.0 + enable + enable + + + + + + + + +``` + +#### Configure API Resources and Scopes + +No clients are statically added to IdentityServer on startup. However IdentityServer still needs to know what API Scopes and Resources will be accessing it at runtime. To enable this, edit the `Config.cs` file to look like this: + +```csharp +public static class Config +{ + public static IEnumerable IdentityResources => + [ + new IdentityResources.OpenId(), + new IdentityResources.Profile() + ]; + + public static IEnumerable ApiResources => + [ + new("https://localhost:7141", "MCP Server") + { + Scopes = { "mcp:tools" } + } + ]; + + public static IEnumerable ApiScopes => + [ + new("mcp:tools") + ]; +} +``` + +#### Configure IdentityServer Services + +When configuring services for IdentityServer, + +1. The DiscoveryDocument registration endpoint must be set to `RegistrationEndpointMode.Inferred`. +1. Add the API Scopes from `Config.cs`. +1. Add the API Resources from `Config.cs`. +1. Store the dynamically added clients. For this quickstart, keep them in memory. + +When you are done, the code would look like this: + +```csharp +using Duende.IdentityServer.Configuration; +using Duende.IdentityServer.Configuration.Validation.DynamicClientRegistration; +... + +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; + }) + .AddTestUsers(TestUsers.Users) + .AddLicenseSummary(); + +// in-memory, code config +isBuilder.AddInMemoryIdentityResources(Config.IdentityResources); +isBuilder.AddInMemoryApiScopes(Config.ApiScopes); +// since this will use DCR, we do not need any pre-configured clients +isBuilder.AddInMemoryClients([]); +isBuilder.AddInMemoryApiResources(Config.ApiResources); + +builder.Services.AddIdentityServerConfiguration(_ => { }) + // in memory is being used here to keep the quickstart simple. in a real scenario, a persistent storage + // mechanism is needed for client registrations to persist across application restarts + .AddInMemoryClientConfigurationStore(); +``` + +#### Hard Code Application Url + +For this quickstart, we are using known URLs for each application. Force the project to self-host at `https://localhost:5001`. The `/Properties/launchSettings.json` file should look like: + +```json +{ + "profiles": { + "https": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001" + } + } +} +``` + +### Create the MCP Server Project + +The MCP Server project we create will be an ASP.NET Web API that returns weather data. Start by adding a new Web API project project to its own directory inside the `mcp-quickstart` directory you created above. + +```console +dotnet new webapi --name "McpQuickStart.McpServer" +``` + +#### Add NuGet Packages + +Add the latest versions of the `Microsoft.AspNetCore.Authentication.JwtBearer` and `ModelContextProtocol.AspNetCore` NuGet packages to the project. The `.csproj` file should look like the below. + +```xml + + + net10.0 + enable + enable + + + + + + + + +``` + +#### Configure MCP Tools + +Create a new directory called `McpTools` and add a `WeatherTools` C# file. This will be the API called by our clients. The code for the file is below. + +```csharp +using System.ComponentModel; +using System.Globalization; +using System.Text.Json; +using ModelContextProtocol; +using ModelContextProtocol.Server; + +namespace McpQuickStart.McpServer.McpTools; + +[McpServerToolType] +public sealed class WeatherTools +{ + private readonly IHttpClientFactory _httpClientFactory; + + public WeatherTools(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } + + [McpServerTool, Description("Get weather alerts for a US state.")] + public async Task GetAlerts( + [Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state) + { + var client = _httpClientFactory.CreateClient("WeatherApi"); + using var jsonDocument = await client.GetFromJsonAsync($"/alerts/active/area/{state}") + ?? throw new McpException("No JSON returned from alerts endpoint"); + + var alerts = jsonDocument.RootElement.GetProperty("features").EnumerateArray(); + + if (!alerts.Any()) + { + return "No active alerts for this state."; + } + + return string.Join("\n--\n", alerts.Select(alert => + { + JsonElement properties = alert.GetProperty("properties"); + return $""" + Event: {properties.GetProperty("event").GetString()} + Area: {properties.GetProperty("areaDesc").GetString()} + Severity: {properties.GetProperty("severity").GetString()} + Description: {properties.GetProperty("description").GetString()} + Instruction: {properties.GetProperty("instruction").GetString()} + """; + })); + } + + [McpServerTool, Description("Get weather forecast for a location.")] + public async Task GetForecast( + [Description("Latitude of the location.")] double latitude, + [Description("Longitude of the location.")] double longitude) + { + var client = _httpClientFactory.CreateClient("WeatherApi"); + var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}"); + + using var locationDocument = await client.GetFromJsonAsync(pointUrl); + var forecastUrl = locationDocument?.RootElement.GetProperty("properties").GetProperty("forecast").GetString() + ?? throw new McpException($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}"); + + using var forecastDocument = await client.GetFromJsonAsync(forecastUrl); + var periods = forecastDocument?.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray() + ?? throw new McpException("No JSON returned from forecast endpoint"); + + return string.Join("\n---\n", periods.Select(period => $""" + {period.GetProperty("name").GetString()} + Temperature: {period.GetProperty("temperature").GetInt32()}°F + Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()} + Forecast: {period.GetProperty("detailedForecast").GetString()} + """)); + } +} +``` + +#### Configure MCP Services + +Inside the `Program.cs` file, configure the application to host an MCP Server. This is done by: + +1. Calling `.AddMcp()`, then configuring the resource metadata clients will be able to access. +1. Added the endpoint for the `WeatherTools` type to the MCP Server by calling `.AddMcpServer().WithTools()`. + +The full code for `Program.cs` looks like: + +```csharp +using System.Net.Http.Headers; +using McpQuickStart.McpServer.McpTools; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using ModelContextProtocol.AspNetCore.Authentication; + +var builder = WebApplication.CreateBuilder(args); + +var mcpServerUrl = "https://localhost:7141"; +var inMemoryOAuthServerUrl = "https://localhost:5001"; + +builder.Services.AddAuthentication(options => + { + options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme; + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.Authority = inMemoryOAuthServerUrl; + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + ValidAudience = mcpServerUrl, + ValidIssuer = inMemoryOAuthServerUrl, + NameClaimType = "name", + RoleClaimType = "role" + }; + }) + .AddMcp(options => + { + options.ResourceMetadata = new() + { + Resource = mcpServerUrl, + ResourceDocumentation = "https://docs.example/api/weather", + AuthorizationServers = { inMemoryOAuthServerUrl }, + ScopesSupported = ["mcp:tools"] + }; + }); + +builder.Services.AddAuthorization(); + +builder.Services.AddHttpContextAccessor(); +builder.Services.AddMcpServer() + .WithTools() + .WithHttpTransport(); + +builder.Services.AddHttpClient("WeatherApi", client => +{ + client.BaseAddress = new Uri("https://api.weather.gov"); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0")); +}); + +var app = builder.Build(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapMcp().RequireAuthorization(); + +app.Run(); +``` + +#### Hard Code Application Url + +For this quickstart, we are using known URLs for each application. Force the project to self-host at `https://localhost:7141`. The `/Properties/launchSettings.json` file should look like: + +```json +{ + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7141", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} +``` + +### Create the Client Console Application + +The MCP Server can be accessed by any client through HTTP requests. For this quickstart create a simple Console application that will register itself with the MCP server and make a request. Add the new project to its own directory inside the `mcp-quickstart` directory you created above. + +```console +dotnet new console --name "McpQuickStart.Client" +``` + + + +## Source Code + +The finished source code is available in the Samples repository, and a reference implementation of this quickstart is available [here](/identityserver/samples/mcp-server). diff --git a/astro/src/content/docs/identityserver/samples/mcp-server.mdx b/astro/src/content/docs/identityserver/samples/mcp-server.mdx new file mode 100644 index 000000000..c663917f4 --- /dev/null +++ b/astro/src/content/docs/identityserver/samples/mcp-server.mdx @@ -0,0 +1,22 @@ +--- +title: "MCP Server and Client" +description: "Sample demonstrating a complete implementation of a console client making requests to an MCP Server using DCR to authenticate with IdentityServer." +date: 2026-08-07 +sidebar: + order: 70 +--- + +import {LinkCard} from "@astrojs/starlight/components"; + +This section contains a sample demonstrating the Duende IdentityServer [Dynamic Client Registration](/identityserver/usermanagement/configuration/dcr.mdx) feature to allow an [MCP](https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro) server to dynamically connect clients. + +### Demo Sample + +This sample contains the finished source code for the [Getting Started with User Management tutorial](/identityserver/usermanagement/getting-started.mdx). + + From 7b43d3efda343790633f0f3c016d498a1921e2a3 Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Mon, 10 Aug 2026 16:19:26 -0400 Subject: [PATCH 02/11] Completed MCP Server quickstart doc --- .../docs/identityserver/quickstarts/8-mcp.mdx | 301 ++++++++++++++++-- .../identityserver/samples/mcp-server.mdx | 6 +- 2 files changed, 277 insertions(+), 30 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index 9411e0af3..bd5f2908b 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -9,15 +9,15 @@ sidebar: import { Code } from "@astrojs/starlight/components"; import { Tabs, TabItem } from "@astrojs/starlight/components"; -Duende IdentityServer contains the [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr) feature which allows an application to add clients at runtime instead of requiring you to statically create them on startup with code, or loading them from persistent storage. This - -:::warning +Duende IdentityServer contains the [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr) feature which allows an application to add clients at runtime instead of requiring you to statically create them on startup with code, or loading them from persistent storage. This quickstart will show you how to create an MCP server that dynamically adds clients at runtime to connect to Duende IdentityServer. + +:::caution Each client that dynamically that connects to IdentityServer using DCR counts towards the Client Ids allowed in your license. ::: ## Setting Up The Projects -We will create 3 projects. An IdentityServer project, an MCP API, and a client which will make requests to the MCP. +We will create 3 projects. An IdentityServer project, an MCP Server, and a console client which will make requests to the MCP. We'll start by creating a directory to store the 3 projects. ```console mkdir mcp-quickstart @@ -82,39 +82,96 @@ public static class Config #### Configure IdentityServer Services -When configuring services for IdentityServer, +When configuring services for IdentityServer: 1. The DiscoveryDocument registration endpoint must be set to `RegistrationEndpointMode.Inferred`. 1. Add the API Scopes from `Config.cs`. 1. Add the API Resources from `Config.cs`. 1. Store the dynamically added clients. For this quickstart, keep them in memory. -When you are done, the code would look like this: +When you are done, the code for the `ConfigureServices()` method inside `HostingExtensions.cs` will look like this: ```csharp -using Duende.IdentityServer.Configuration; -using Duende.IdentityServer.Configuration.Validation.DynamicClientRegistration; -... +public static WebApplication ConfigureServices(this WebApplicationBuilder builder) +{ + builder.Services.AddRazorPages(); -var isBuilder = builder.Services.AddIdentityServer(options => + 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; }) - .AddTestUsers(TestUsers.Users) - .AddLicenseSummary(); - -// in-memory, code config -isBuilder.AddInMemoryIdentityResources(Config.IdentityResources); -isBuilder.AddInMemoryApiScopes(Config.ApiScopes); -// since this will use DCR, we do not need any pre-configured clients -isBuilder.AddInMemoryClients([]); -isBuilder.AddInMemoryApiResources(Config.ApiResources); - -builder.Services.AddIdentityServerConfiguration(_ => { }) - // in memory is being used here to keep the quickstart simple. in a real scenario, a persistent storage - // mechanism is needed for client registrations to persist across application restarts - .AddInMemoryClientConfigurationStore(); + .AddTestUsers(TestUsers.Users) + .AddLicenseSummary(); + + // in-memory, code config + isBuilder.AddInMemoryIdentityResources(Config.IdentityResources); + isBuilder.AddInMemoryApiScopes(Config.ApiScopes); + // since this will use DCR, we do not need any pre-configured clients + isBuilder.AddInMemoryClients([]); + isBuilder.AddInMemoryApiResources(Config.ApiResources); + + builder.Services.AddIdentityServerConfiguration(_ => { }) + // in memory is being used here to keep the demo simple. in a real scenario, a persistent storage + // mechanism is needed for client registrations to persist across application restarts + .AddInMemoryClientConfigurationStore(); + + builder.Services.AddAuthentication() + .AddOpenIdConnect("oidc", "Sign-in with demo.duendesoftware.com", options => + { + options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; + options.SignOutScheme = IdentityServerConstants.SignoutScheme; + options.SaveTokens = true; + + options.Authority = "https://demo.duendesoftware.com"; + options.ClientId = "interactive.confidential"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + + // Add `.PersistKeysTo…()` and `.ProtectKeysWith…()` calls + // See more at https://docs.duendesoftware.com/general/data-protection + builder.Services.AddDataProtection() + .SetApplicationName("IdentityServer"); + + return builder.Build(); +} +``` +#### Configure IdentityServer Pipeline + +The IdentityServer middleware pipeline needs to be configured to add clients dynamically. This is done with a call to `app.MapDynamicClientRegistration();` inside the `ConfigurePipeline()` method inside `HostingExtensions.cs`. + +Your `ConfigurePipeline()` method should look like this: + +```csharp +public static WebApplication ConfigurePipeline(this WebApplication app) +{ + _ = app.UseSerilogRequestLogging(); + + if (app.Environment.IsDevelopment()) + { + _ = app.UseDeveloperExceptionPage(); + } + + _ = app.UseStaticFiles(); + _ = app.UseRouting(); + _ = app.UseIdentityServer(); + _ = app.UseAuthorization(); + + _ = app.MapRazorPages() + .RequireAuthorization(); + + //Add this line + _ = app.MapDynamicClientRegistration(); + + return app; +} ``` #### Hard Code Application Url @@ -138,7 +195,7 @@ For this quickstart, we are using known URLs for each application. Force the pro ### Create the MCP Server Project -The MCP Server project we create will be an ASP.NET Web API that returns weather data. Start by adding a new Web API project project to its own directory inside the `mcp-quickstart` directory you created above. +The MCP Server project we create will be an ASP.NET Web API that returns weather data. Start by adding a new Web API project to its own directory inside `mcp-quickstart` you created above. ```console dotnet new webapi --name "McpQuickStart.McpServer" @@ -166,7 +223,7 @@ Add the latest versions of the `Microsoft.AspNetCore.Authentication.JwtBearer` a #### Configure MCP Tools -Create a new directory called `McpTools` and add a `WeatherTools` C# file. This will be the API called by our clients. The code for the file is below. +Create a new directory called `McpTools` and add a `WeatherTools.cs` C# file. This will be the API called by our clients. The code for the file is below. ```csharp using System.ComponentModel; @@ -336,12 +393,202 @@ For this quickstart, we are using known URLs for each application. Force the pro ### Create the Client Console Application -The MCP Server can be accessed by any client through HTTP requests. For this quickstart create a simple Console application that will register itself with the MCP server and make a request. Add the new project to its own directory inside the `mcp-quickstart` directory you created above. +The MCP Server can be accessed by any client through HTTP requests. For this quickstart, we create a simple console application that will register itself with the MCP server and make a request. Add the new project to its own directory inside the `mcp-quickstart` directory you created above. ```console dotnet new console --name "McpQuickStart.Client" ``` +#### Add NuGet Packages + +Add the latest versions of the `Microsoft.AspNetCore.Authentication.JwtBearer` and `ModelContextProtocol.AspNetCore` NuGet packages to the project. The `.csproj` file should look like the below. + +```xml + + + Exe + net10.0 + enable + enable + + + + + + +``` + +#### Implement the Client + +The console client will need to: + +1. Use the `ModelContextProtocol` NuGet package to create an `HttpClientTransport` object to communicate with the MCP Server. +1. Include a `RedirectUri` back to itself after the user signs in. +1. After user sign-in completes, make a call to the MCP Server using the `get_alerts` tool implemented by the MCP Server and output its response. + +The code for the entire console application is: + +```csharp +using System.Diagnostics; +using System.Net; +using System.Text; +using System.Web; + +using ModelContextProtocol.Authentication; +using ModelContextProtocol.Client; +using ModelContextProtocol.Protocol; + +var mcpServerUrl = "https://localhost:7141"; + +Console.WriteLine("Protected MCP Client"); +Console.WriteLine($"Connecting to server at {mcpServerUrl}..."); +Console.WriteLine(); + +var httpClient = new HttpClient(); + +var transport = new HttpClientTransport(new HttpClientTransportOptions +{ + Endpoint = new Uri(mcpServerUrl), + Name = "Weather MCP Client", + OAuth = new ClientOAuthOptions + { + RedirectUri = new Uri("http://localhost:1179/callback"), + AuthorizationCallbackHandler = HandleAuthorizationUrlAsync, + DynamicClientRegistration = new DynamicClientRegistrationOptions + { + ClientName = "ProtectedMcpClient" + }, + Scopes = ["mcp:tools"], + }, +}, httpClient); + +var client = await McpClient.CreateAsync(transport); + +var tools = await client.ListToolsAsync(); +if (tools.Count == 0) +{ + Console.WriteLine("No tools available on the server."); + return; +} + +Console.WriteLine($"Found {tools.Count} tools on the server."); +Console.WriteLine(); + + +if (tools.Any(t => t.Name == "get_alerts")) +{ + Console.WriteLine("Calling get_alerts tool..."); + + var result = await client.CallToolAsync("get_alerts", new Dictionary { ["state"] = "NY" }); + + Console.WriteLine("Result: " + ((TextContentBlock)result.Content[0]).Text); + Console.WriteLine(); +} + +/// Handles the OAuth authorization URL by starting a local HTTP server and opening a browser. +/// This implementation demonstrates how SDK consumers can provide their own authorization flow. +/// +/// The authorization URL to open in the browser. +/// 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(AuthorizationCallbackContext authContext, CancellationToken cancellationToken) +{ + Console.WriteLine("Starting OAuth authorization flow..."); + Console.WriteLine($"Opening browser to: {authContext.AuthorizationUri}"); + + var listenerPrefix = authContext.RedirectUri.GetLeftPart(UriPartial.Authority); + if (!listenerPrefix.EndsWith("/")) listenerPrefix += "/"; + + using var listener = new HttpListener(); + listener.Prefixes.Add(listenerPrefix); + + try + { + listener.Start(); + Console.WriteLine($"Listening for OAuth callback on: {listenerPrefix}"); + + 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.

"; + byte[] buffer = Encoding.UTF8.GetBytes(responseHtml); + context.Response.ContentLength64 = buffer.Length; + context.Response.ContentType = "text/html"; + context.Response.OutputStream.Write(buffer, 0, buffer.Length); + context.Response.Close(); + + if (!string.IsNullOrEmpty(error)) + { + Console.WriteLine($"Auth error: {error}"); + return null; + } + + if (string.IsNullOrEmpty(code)) + { + Console.WriteLine("No authorization code received"); + return null; + } + + Console.WriteLine("Authorization code received successfully."); + return new AuthorizationResult + { + Code = code, + State = state, + Iss = iss + }; + } + catch (Exception ex) + { + Console.WriteLine($"Error getting auth code: {ex.Message}"); + return null; + } + finally + { + if (listener.IsListening) listener.Stop(); + } +} + +/// +/// Opens the specified URL in the default browser. +/// +/// The URL to open. +static void OpenBrowser(Uri url) +{ + // Validate the URI scheme - only allow safe protocols + if (url.Scheme != Uri.UriSchemeHttp && url.Scheme != Uri.UriSchemeHttps) + { + Console.WriteLine($"Error: Only HTTP and HTTPS URLs are allowed."); + return; + } + + try + { + var psi = new ProcessStartInfo + { + FileName = url.ToString(), + UseShellExecute = true + }; + Process.Start(psi); + } + catch (Exception ex) + { + Console.WriteLine($"Error opening browser: {ex.Message}"); + Console.WriteLine($"Please manually open this URL: {url}"); + } +} +``` + +### Run the Samples + +Start the IdentityServer and MCP Server applications, then run the console client. When prompted to sign in for the console client, use username `bob` with password `bob` to sign in. The console will output the response from the MCP Server after it self-registers. ## Source Code diff --git a/astro/src/content/docs/identityserver/samples/mcp-server.mdx b/astro/src/content/docs/identityserver/samples/mcp-server.mdx index c663917f4..c0f83b323 100644 --- a/astro/src/content/docs/identityserver/samples/mcp-server.mdx +++ b/astro/src/content/docs/identityserver/samples/mcp-server.mdx @@ -12,11 +12,11 @@ This section contains a sample demonstrating the Duende IdentityServer [Dynamic ### Demo Sample -This sample contains the finished source code for the [Getting Started with User Management tutorial](/identityserver/usermanagement/getting-started.mdx). +This sample contains the finished source code for the [MCP Server sample](/identityserver/quickstarts/8-mcp.mdx). From bbdb1bc01ab1425a5d0bfabedfa52e859488d7b7 Mon Sep 17 00:00:00 2001 From: Al Rodriguez Date: Tue, 11 Aug 2026 13:47:24 -0400 Subject: [PATCH 03/11] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index bd5f2908b..823305a70 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -12,7 +12,7 @@ import { Tabs, TabItem } from "@astrojs/starlight/components"; Duende IdentityServer contains the [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr) feature which allows an application to add clients at runtime instead of requiring you to statically create them on startup with code, or loading them from persistent storage. This quickstart will show you how to create an MCP server that dynamically adds clients at runtime to connect to Duende IdentityServer. :::caution -Each client that dynamically that connects to IdentityServer using DCR counts towards the Client Ids allowed in your license. +Each client that dynamically connects to IdentityServer using DCR counts towards the Client Ids allowed in your license. ::: ## Setting Up The Projects From 5340bc1ec6022dc35d548a8712034b95d806a979 Mon Sep 17 00:00:00 2001 From: Al Rodriguez Date: Tue, 11 Aug 2026 13:51:43 -0400 Subject: [PATCH 04/11] Apply suggestions from code review Co-authored-by: Maarten Balliauw --- .../docs/identityserver/quickstarts/8-mcp.mdx | 32 ++++++++++++------- .../identityserver/samples/mcp-server.mdx | 6 ++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index 823305a70..04911a303 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -1,6 +1,6 @@ --- -title: "Connecting an MCP Server and Client with DCR" -description: "Learn how to build a secure MCP server that protects requests using Duende IdentityServer and allows any client to dynamically connect." +title: "Securing an MCP Server with IdentityServer" +description: "Learn how to protect an MCP server with Duende IdentityServer so AI clients can securely connect using OAuth and Dynamic Client Registration." date: 2026-08-07T01:00:00+00:00 sidebar: order: 16 @@ -9,15 +9,15 @@ sidebar: import { Code } from "@astrojs/starlight/components"; import { Tabs, TabItem } from "@astrojs/starlight/components"; -Duende IdentityServer contains the [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr) feature which allows an application to add clients at runtime instead of requiring you to statically create them on startup with code, or loading them from persistent storage. This quickstart will show you how to create an MCP server that dynamically adds clients at runtime to connect to Duende IdentityServer. +AI agents and tools increasingly communicate via the Model Context Protocol (MCP). This quickstart shows how to secure an MCP server with Duende IdentityServer, using [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr.mdx) so any compliant client can connect without being pre-configured. -:::caution +:::note Each client that dynamically connects to IdentityServer using DCR counts towards the Client Ids allowed in your license. ::: ## Setting Up The Projects -We will create 3 projects. An IdentityServer project, an MCP Server, and a console client which will make requests to the MCP. We'll start by creating a directory to store the 3 projects. +We will create 3 projects. An IdentityServer project, an MCP Server, and a console client which will make requests to the MCP server. We'll start by creating a directory to store the 3 projects. ```console mkdir mcp-quickstart @@ -54,9 +54,10 @@ Add the latest versions of the `Duende.IdentityServer` and `Duende.IdentityServe #### Configure API Resources and Scopes -No clients are statically added to IdentityServer on startup. However IdentityServer still needs to know what API Scopes and Resources will be accessing it at runtime. To enable this, edit the `Config.cs` file to look like this: +IdentityServer will need to know what API Scopes and Resources will be required at runtime. To enable this, edit the `Config.cs` file to look like this: ```csharp +// Config.cs public static class Config { public static IEnumerable IdentityResources => @@ -80,11 +81,13 @@ public static class Config } ``` +Note the configuration does not include any client registrations. MCP clients will dynamically register with IdentityServer when needed. + #### Configure IdentityServer Services When configuring services for IdentityServer: -1. The DiscoveryDocument registration endpoint must be set to `RegistrationEndpointMode.Inferred`. +1. The `DiscoveryDocument` registration endpoint must be configured so that the registration endpoint is made visible, and the URL for it is inferred. You can do this by setting `options.Discovery.DynamicClientRegistration.RegistrationEndpointMode` to `RegistrationEndpointMode.Inferred`. 1. Add the API Scopes from `Config.cs`. 1. Add the API Resources from `Config.cs`. 1. Store the dynamically added clients. For this quickstart, keep them in memory. @@ -107,7 +110,9 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde // in-memory, code config isBuilder.AddInMemoryIdentityResources(Config.IdentityResources); isBuilder.AddInMemoryApiScopes(Config.ApiScopes); + // since this will use DCR, we do not need any pre-configured clients + // note that DCR can be combined with statically configured clients, so you can mix and match isBuilder.AddInMemoryClients([]); isBuilder.AddInMemoryApiResources(Config.ApiResources); @@ -167,7 +172,7 @@ public static WebApplication ConfigurePipeline(this WebApplication app) _ = app.MapRazorPages() .RequireAuthorization(); - //Add this line + // add this line _ = app.MapDynamicClientRegistration(); return app; @@ -195,7 +200,7 @@ For this quickstart, we are using known URLs for each application. Force the pro ### Create the MCP Server Project -The MCP Server project we create will be an ASP.NET Web API that returns weather data. Start by adding a new Web API project to its own directory inside `mcp-quickstart` you created above. +The MCP Server project we create will return weather data. Start by adding a new ASP.NET Web API project to its own directory inside `mcp-quickstart` you created above. ```console dotnet new webapi --name "McpQuickStart.McpServer" @@ -223,9 +228,10 @@ Add the latest versions of the `Microsoft.AspNetCore.Authentication.JwtBearer` a #### Configure MCP Tools -Create a new directory called `McpTools` and add a `WeatherTools.cs` C# file. This will be the API called by our clients. The code for the file is below. +Create a new directory called `McpTools` and add a `WeatherTools.cs` C# file. This will be the MCP server definition that an AI agent or an MCP client can consume. The code for the file is below. ```csharp +// WeatherTools.cs using System.ComponentModel; using System.Globalization; using System.Text.Json; @@ -303,11 +309,12 @@ public sealed class WeatherTools Inside the `Program.cs` file, configure the application to host an MCP Server. This is done by: 1. Calling `.AddMcp()`, then configuring the resource metadata clients will be able to access. -1. Added the endpoint for the `WeatherTools` type to the MCP Server by calling `.AddMcpServer().WithTools()`. +1. Registering the `WeatherTools` type as MCP tools the server should expose, by calling `.AddMcpServer().WithTools()`. The full code for `Program.cs` looks like: ```csharp +// Program.cs using System.Net.Http.Headers; using McpQuickStart.McpServer.McpTools; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -393,7 +400,7 @@ For this quickstart, we are using known URLs for each application. Force the pro ### Create the Client Console Application -The MCP Server can be accessed by any client through HTTP requests. For this quickstart, we create a simple console application that will register itself with the MCP server and make a request. Add the new project to its own directory inside the `mcp-quickstart` directory you created above. +The MCP server can be accessed by any client through HTTP requests. For this quickstart, we create a simple console application that will register itself with IdentityServer and make an authenticated request to the MCP server. Add the new project to its own directory inside the `mcp-quickstart` directory you created above. ```console dotnet new console --name "McpQuickStart.Client" @@ -429,6 +436,7 @@ The console client will need to: The code for the entire console application is: ```csharp +// Program.cs using System.Diagnostics; using System.Net; using System.Text; diff --git a/astro/src/content/docs/identityserver/samples/mcp-server.mdx b/astro/src/content/docs/identityserver/samples/mcp-server.mdx index c0f83b323..98c839511 100644 --- a/astro/src/content/docs/identityserver/samples/mcp-server.mdx +++ b/astro/src/content/docs/identityserver/samples/mcp-server.mdx @@ -1,6 +1,6 @@ --- -title: "MCP Server and Client" -description: "Sample demonstrating a complete implementation of a console client making requests to an MCP Server using DCR to authenticate with IdentityServer." +title: "AI and MCP Server and Client" +description: "Samples demonstrating how IdentityServer fits in an AI architecture, including as the authorization server for MCP." date: 2026-08-07 sidebar: order: 70 @@ -8,7 +8,7 @@ sidebar: import {LinkCard} from "@astrojs/starlight/components"; -This section contains a sample demonstrating the Duende IdentityServer [Dynamic Client Registration](/identityserver/usermanagement/configuration/dcr.mdx) feature to allow an [MCP](https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro) server to dynamically connect clients. +This section contains samples on how to protect an MCP server with Duende IdentityServer. It uses [Dynamic Client Registration](/identityserver/configuration/dcr.mdx) so any MCP-compatible client can connect without pre-configuration." ### Demo Sample From 6429af1827db778d535fb4c0c4fa3b167b3df089 Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Tue, 11 Aug 2026 15:21:23 -0400 Subject: [PATCH 05/11] Added a mermaid diagram to the MCP Server quickstart page --- .../docs/identityserver/quickstarts/8-mcp.mdx | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index 04911a303..d8648ca86 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -1,6 +1,6 @@ --- title: "Securing an MCP Server with IdentityServer" -description: "Learn how to protect an MCP server with Duende IdentityServer so AI clients can securely connect using OAuth and Dynamic Client Registration." +description: "Learn how to protect an MCP Server with Duende IdentityServer so AI clients can securely connect using OAuth and Dynamic Client Registration." date: 2026-08-07T01:00:00+00:00 sidebar: order: 16 @@ -9,15 +9,27 @@ sidebar: import { Code } from "@astrojs/starlight/components"; import { Tabs, TabItem } from "@astrojs/starlight/components"; -AI agents and tools increasingly communicate via the Model Context Protocol (MCP). This quickstart shows how to secure an MCP server with Duende IdentityServer, using [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr.mdx) so any compliant client can connect without being pre-configured. +AI agents and tools increasingly communicate via the Model Context Protocol (MCP). This quickstart shows how to secure an MCP Server with Duende IdentityServer, using [Dynamic Client Registration (DCR)](/identityserver/configuration/dcr.mdx) so any compliant client can connect without being pre-configured. :::note Each client that dynamically connects to IdentityServer using DCR counts towards the Client Ids allowed in your license. ::: -## Setting Up The Projects +## Quickstart Applications -We will create 3 projects. An IdentityServer project, an MCP Server, and a console client which will make requests to the MCP server. We'll start by creating a directory to store the 3 projects. +We will create 3 projects. An IdentityServer project, an MCP Server, and a console client which will make requests to the MCP Server (but sign in using IdentityServer). + +```mermaid +architecture-beta + service mcpServer(server)[MCP Server] + service client(server)[Client] + service is(server)[IdentityServer] + + is:L -- R:mcpServer + mcpServer:R -- L:client +``` + +We'll start by creating a directory to store the 3 projects. ```console mkdir mcp-quickstart @@ -228,7 +240,7 @@ Add the latest versions of the `Microsoft.AspNetCore.Authentication.JwtBearer` a #### Configure MCP Tools -Create a new directory called `McpTools` and add a `WeatherTools.cs` C# file. This will be the MCP server definition that an AI agent or an MCP client can consume. The code for the file is below. +Create a new directory called `McpTools` and add a `WeatherTools.cs` C# file. This will be the MCP Server definition that an AI agent or an MCP client can consume. The code for the file is below. ```csharp // WeatherTools.cs @@ -400,7 +412,7 @@ For this quickstart, we are using known URLs for each application. Force the pro ### Create the Client Console Application -The MCP server can be accessed by any client through HTTP requests. For this quickstart, we create a simple console application that will register itself with IdentityServer and make an authenticated request to the MCP server. Add the new project to its own directory inside the `mcp-quickstart` directory you created above. +The MCP Server can be accessed by any client through HTTP requests. For this quickstart, we create a simple console application that will register itself with IdentityServer and make an authenticated request to the MCP server. Add the new project to its own directory inside the `mcp-quickstart` directory you created above. ```console dotnet new console --name "McpQuickStart.Client" @@ -564,10 +576,6 @@ static async Task HandleAuthorizationUrlAsync(Authorizatio } } -/// -/// Opens the specified URL in the default browser. -/// -/// The URL to open. static void OpenBrowser(Uri url) { // Validate the URI scheme - only allow safe protocols From 5280102ecdec4fe0f3d9da18ea822b5c9db41cea Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Tue, 11 Aug 2026 15:33:27 -0400 Subject: [PATCH 06/11] Trimmed comments a bit --- .../docs/identityserver/quickstarts/8-mcp.mdx | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index d8648ca86..f3b3b7b94 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -69,7 +69,7 @@ Add the latest versions of the `Duende.IdentityServer` and `Duende.IdentityServe IdentityServer will need to know what API Scopes and Resources will be required at runtime. To enable this, edit the `Config.cs` file to look like this: ```csharp -// Config.cs +// ~/mcp-quickstart/McpQuickStart.IdentityServer/Config.cs public static class Config { public static IEnumerable IdentityResources => @@ -107,13 +107,14 @@ When configuring services for IdentityServer: When you are done, the code for the `ConfigureServices()` method inside `HostingExtensions.cs` will look like this: ```csharp +// ~/mcp-quickstart/McpQuickStart.IdentityServer/HostingExtensions.cs public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { builder.Services.AddRazorPages(); var isBuilder = builder.Services.AddIdentityServer(options => { - // this will add the default dynamic client registration endpoint to the discovery/metadatada documents + // add the default dynamic client registration endpoint to the discovery/metadatada documents options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Inferred; }) .AddTestUsers(TestUsers.Users) @@ -123,14 +124,12 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde isBuilder.AddInMemoryIdentityResources(Config.IdentityResources); isBuilder.AddInMemoryApiScopes(Config.ApiScopes); - // since this will use DCR, we do not need any pre-configured clients - // note that DCR can be combined with statically configured clients, so you can mix and match + // note we're not adding static clients, they will be added dynamically at runtime through DCR isBuilder.AddInMemoryClients([]); isBuilder.AddInMemoryApiResources(Config.ApiResources); builder.Services.AddIdentityServerConfiguration(_ => { }) - // in memory is being used here to keep the demo simple. in a real scenario, a persistent storage - // mechanism is needed for client registrations to persist across application restarts + // in memory client store only used for sample .AddInMemoryClientConfigurationStore(); builder.Services.AddAuthentication() @@ -167,6 +166,7 @@ The IdentityServer middleware pipeline needs to be configured to add clients dyn Your `ConfigurePipeline()` method should look like this: ```csharp +// ~/mcp-quickstart/McpQuickStart.IdentityServer/HostingExtensions.cs public static WebApplication ConfigurePipeline(this WebApplication app) { _ = app.UseSerilogRequestLogging(); @@ -243,7 +243,7 @@ Add the latest versions of the `Microsoft.AspNetCore.Authentication.JwtBearer` a Create a new directory called `McpTools` and add a `WeatherTools.cs` C# file. This will be the MCP Server definition that an AI agent or an MCP client can consume. The code for the file is below. ```csharp -// WeatherTools.cs +// ~/mcp-quickstart/McpQuickStart.McpServer/McpTools/WeatherTools.cs using System.ComponentModel; using System.Globalization; using System.Text.Json; @@ -326,7 +326,7 @@ Inside the `Program.cs` file, configure the application to host an MCP Server. T The full code for `Program.cs` looks like: ```csharp -// Program.cs +// ~/mcp-quickstart/McpQuickStart.McpServer/Program.cs using System.Net.Http.Headers; using McpQuickStart.McpServer.McpTools; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -448,7 +448,7 @@ The console client will need to: The code for the entire console application is: ```csharp -// Program.cs +// ~/mcp-quickstart/McpQuickStart.Client/Program.cs using System.Diagnostics; using System.Net; using System.Text; @@ -505,14 +505,6 @@ if (tools.Any(t => t.Name == "get_alerts")) Console.WriteLine(); } -/// Handles the OAuth authorization URL by starting a local HTTP server and opening a browser. -/// This implementation demonstrates how SDK consumers can provide their own authorization flow. -/// -/// The authorization URL to open in the browser. -/// 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(AuthorizationCallbackContext authContext, CancellationToken cancellationToken) { Console.WriteLine("Starting OAuth authorization flow..."); From 85854fcf5eaab5a8a34f6016d3dfed1e97fc753e Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Tue, 11 Aug 2026 15:47:38 -0400 Subject: [PATCH 07/11] Clarified enabling DCR in the IdentityServer pipeline --- .../src/content/docs/identityserver/quickstarts/8-mcp.mdx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index f3b3b7b94..0e0b9be5b 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -159,11 +159,9 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde return builder.Build(); } ``` -#### Configure IdentityServer Pipeline +#### Map DCR in the IdentityServer Pipeline -The IdentityServer middleware pipeline needs to be configured to add clients dynamically. This is done with a call to `app.MapDynamicClientRegistration();` inside the `ConfigurePipeline()` method inside `HostingExtensions.cs`. - -Your `ConfigurePipeline()` method should look like this: +The IdentityServer middleware pipeline needs to be able to add clients dynamically. This is done with the `MapDynamicClientRegistration()` method from the `Duende.IdentityServer.Configuration` NuGet package. Add the line `app.MapDynamicClientRegistration();` inside the `ConfigurePipeline()` method. Afterwards, your `ConfigurePipeline()` method should look like: ```csharp // ~/mcp-quickstart/McpQuickStart.IdentityServer/HostingExtensions.cs @@ -184,7 +182,7 @@ public static WebApplication ConfigurePipeline(this WebApplication app) _ = app.MapRazorPages() .RequireAuthorization(); - // add this line + // added this line _ = app.MapDynamicClientRegistration(); return app; From 6a47ca9afd8412202321e0704d32ba80afdc579b Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Tue, 11 Aug 2026 15:57:07 -0400 Subject: [PATCH 08/11] Highlited sample code instead of a comment callout --- astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index 0e0b9be5b..b9c11fd7c 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -163,7 +163,7 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde The IdentityServer middleware pipeline needs to be able to add clients dynamically. This is done with the `MapDynamicClientRegistration()` method from the `Duende.IdentityServer.Configuration` NuGet package. Add the line `app.MapDynamicClientRegistration();` inside the `ConfigurePipeline()` method. Afterwards, your `ConfigurePipeline()` method should look like: -```csharp +```csharp {19} // ~/mcp-quickstart/McpQuickStart.IdentityServer/HostingExtensions.cs public static WebApplication ConfigurePipeline(this WebApplication app) { @@ -182,7 +182,6 @@ public static WebApplication ConfigurePipeline(this WebApplication app) _ = app.MapRazorPages() .RequireAuthorization(); - // added this line _ = app.MapDynamicClientRegistration(); return app; From d96473890d6665f843caec2bb01420b833e619a6 Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Tue, 11 Aug 2026 16:01:59 -0400 Subject: [PATCH 09/11] Updated how json files are displayed --- .../src/content/docs/identityserver/quickstarts/8-mcp.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index b9c11fd7c..e22d39d8f 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -188,11 +188,11 @@ public static WebApplication ConfigurePipeline(this WebApplication app) } ``` -#### Hard Code Application Url +#### Update Launch Url For Local Development For this quickstart, we are using known URLs for each application. Force the project to self-host at `https://localhost:5001`. The `/Properties/launchSettings.json` file should look like: -```json +```json {9} title="~/mcp-quickstart/McpQuickStart.IdentityServer/Properties/launchSettings.json" { "profiles": { "https": { @@ -387,11 +387,11 @@ app.MapMcp().RequireAuthorization(); app.Run(); ``` -#### Hard Code Application Url +#### Update Launch Url For Local Development For this quickstart, we are using known URLs for each application. Force the project to self-host at `https://localhost:7141`. The `/Properties/launchSettings.json` file should look like: -```json +```json {7} title="~/mcp-quickstart/McpQuickStart.McpServer/Properties/launchSettings.json" { "profiles": { "https": { From 7f9c3b7452cad87841fca4b7ee906c33b2514816 Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Wed, 12 Aug 2026 12:45:38 -0400 Subject: [PATCH 10/11] Elaborated on scopes, added sample output --- .../docs/identityserver/quickstarts/8-mcp.mdx | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index e22d39d8f..48bbfcce6 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -66,7 +66,7 @@ Add the latest versions of the `Duende.IdentityServer` and `Duende.IdentityServe #### Configure API Resources and Scopes -IdentityServer will need to know what API Scopes and Resources will be required at runtime. To enable this, edit the `Config.cs` file to look like this: +IdentityServer will need to know what API Scopes and Resources will be required at runtime. We will only configure a single scope, `mcp:tools`, which will be used to allow the client to access the tool hosted by the MCP Server connected application. To enable this, edit the `Config.cs` file to look like this: ```csharp // ~/mcp-quickstart/McpQuickStart.IdentityServer/Config.cs @@ -317,7 +317,7 @@ public sealed class WeatherTools Inside the `Program.cs` file, configure the application to host an MCP Server. This is done by: -1. Calling `.AddMcp()`, then configuring the resource metadata clients will be able to access. +1. Calling `.AddMcp()`, then configuring the resource metadata clients will be able to access. The `ScopesSupported = ["mcp:tools"]` property states the client will only be able to request the `mcp:tools` scope from IdentityServer when the user signs in. 1. Registering the `WeatherTools` type as MCP tools the server should expose, by calling `.AddMcpServer().WithTools()`. The full code for `Program.cs` looks like: @@ -442,6 +442,8 @@ The console client will need to: 1. Include a `RedirectUri` back to itself after the user signs in. 1. After user sign-in completes, make a call to the MCP Server using the `get_alerts` tool implemented by the MCP Server and output its response. +When creating the `HttpClientTransport` object, the `new ClientOAuthOptions()` initializer needs to set the `RedirectUri` and `Scopes` properties. The `RedirectUri` is the local endpoint this console client expects a redirect to after the user signs in. Once the OAuth flow completes, IdentityServer needs to know where to redirect the user back to, and the `RedirectUri` property tells it to redirect back to this client application. The `Scopes` property uses the single scope we have configured for the clients. It was configured in IdentityServer and the MCP Server was configured to use that scope. When this client signs in, it will request and receive a token with this scope. + The code for the entire console application is: ```csharp @@ -596,6 +598,59 @@ static void OpenBrowser(Uri url) Start the IdentityServer and MCP Server applications, then run the console client. When prompted to sign in for the console client, use username `bob` with password `bob` to sign in. The console will output the response from the MCP Server after it self-registers. +#### Samples Output + +```text +Protected MCP Client +Connecting to server at https://localhost:7141... + +Starting OAuth authorization flow... +Opening browser to: https://localhost:5001/connect/authorize?client_id=AZwWIaA8ApNcB5jptVQrSaNO4hF-nbRtBz19QnKEGOI&redirect_uri=http%3a%2f%2flocalhost%3a1279%2fcallback&response_type=code&code_challenge=mfZCS1wY7EHZwUkIb50SD6dmzReczXjyDMC_GFKEHvM&code_challenge_method=S256&state=EAx8LsLDnK0gVNY8Oxw8wF0Jv6TPCu9-YlyHL7XfFHE&resource=https%3a%2f%2flocalhost%3a7141&scope=mcp%3atools+offline_access +Listening for OAuth callback on: http://localhost:1279/ +Authorization code received successfully. +Found 2 tools on the server. + +Calling get_alerts tool... +Result: Event: Coastal Flood Statement +Area: Southern Queens; Southern Nassau +Severity: Minor +Description: * WHAT...Up to one half foot of inundation above ground level +expected in vulnerable areas near the waterfront and shoreline. + +* WHERE...Southern Queens and Southern Nassau Counties. + +* WHEN...This evening. + +* IMPACTS...Brief minor flooding of the most vulnerable locations near the +waterfront and shoreline. + +* ADDITIONAL DETAILS...Additional rounds of localized minor +flooding are likely with the Wednesday Night and Thursday Night +high tides. Minor coastal flooding could be a bit more +widespread with the Wednesday night high tide. +Instruction: Do not drive through flooded roadways. +-- +Event: Coastal Flood Statement +Area: Southern Fairfield; Southern Westchester +Severity: Minor +Description: * WHAT...Up to one half foot of inundation above ground level +expected in vulnerable areas near the waterfront and shoreline. + +* WHERE...In Connecticut, Southern Fairfield County. In New +York, Southern Westchester County. + +* WHEN...This evening. + +* IMPACTS...Brief minor flooding of the most vulnerable locations near the +waterfront and shoreline + +* ADDITIONAL DETAILS...Additional rounds of localized minor +flooding are likely with the Wednesday Night and Thursday Night +high tides. Minor coastal flooding could be a bit more +widespread with the Wednesday night high tide. +Instruction: Do not drive through flooded roadways. +``` + ## Source Code The finished source code is available in the Samples repository, and a reference implementation of this quickstart is available [here](/identityserver/samples/mcp-server). From f0d0319bc3e95d7c8f5ce5136448fdc9d25d3121 Mon Sep 17 00:00:00 2001 From: AL Rodriguez Date: Wed, 12 Aug 2026 13:04:44 -0400 Subject: [PATCH 11/11] Some text cleanup --- .../src/content/docs/identityserver/quickstarts/8-mcp.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx index 48bbfcce6..3f7246c07 100644 --- a/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx +++ b/astro/src/content/docs/identityserver/quickstarts/8-mcp.mdx @@ -38,7 +38,7 @@ cd mcp-quickstart ### Create the IdentityServer Project -The IdentityServer project needs to be configured to use DCR to allow clients at runtime. The below steps disable the static clients and adds DCR. Add a new Duende IdentityServer InMemory project to its own directory inside the `mcp-quickstart` directory you created above. +The IdentityServer project needs to be configured to use DCR to allow clients at runtime. The below steps disable the static clients and adds DCR. Create a new Duende IdentityServer InMemory project to its own directory inside the `mcp-quickstart` directory you created above. ```console dotnet new duende-is-inmem --name "McpQuickStart.IdentityServer" @@ -66,7 +66,7 @@ Add the latest versions of the `Duende.IdentityServer` and `Duende.IdentityServe #### Configure API Resources and Scopes -IdentityServer will need to know what API Scopes and Resources will be required at runtime. We will only configure a single scope, `mcp:tools`, which will be used to allow the client to access the tool hosted by the MCP Server connected application. To enable this, edit the `Config.cs` file to look like this: +IdentityServer will need to know what API Scopes and Resources will be required at runtime. We will only configure a single scope, `mcp:tools`, which will be used to allow the client to access the tool hosted by the MCP Server connected application. The scope is tied to an `ApiResource` object specific to the MCP Server, meaning the client will receive a token that says the `mcp:tools` scope can only be used by the MCP Server. For more information, see [Resource Isolation](/identityserver/fundamentals/resources/isolation). To enable this, edit the `Config.cs` file to look like this: ```csharp // ~/mcp-quickstart/McpQuickStart.IdentityServer/Config.cs @@ -322,7 +322,7 @@ Inside the `Program.cs` file, configure the application to host an MCP Server. T The full code for `Program.cs` looks like: -```csharp +```csharp {31-40} // ~/mcp-quickstart/McpQuickStart.McpServer/Program.cs using System.Net.Http.Headers; using McpQuickStart.McpServer.McpTools; @@ -598,7 +598,7 @@ static void OpenBrowser(Uri url) Start the IdentityServer and MCP Server applications, then run the console client. When prompted to sign in for the console client, use username `bob` with password `bob` to sign in. The console will output the response from the MCP Server after it self-registers. -#### Samples Output +#### Sample Client Output ```text Protected MCP Client