Skip to content

Authorization Support (Using ASP.NET Core Native AuthN/AuthZ Integration) #377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 206 commits into from
Jul 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
206 commits
Select commit Hold shift + click to select a range
0bd80e6
Experimental changes in a different direction
localden May 2, 2025
ac14bb2
Fix build issues
localden May 2, 2025
e8d9a23
Cleanup
localden May 2, 2025
4b3f9f7
Cleanup
localden May 2, 2025
8c790db
Tweaks to logic
localden May 2, 2025
b9ba2b9
Update Program.cs
localden May 2, 2025
47f1937
Cleanup
localden May 2, 2025
638bd35
Update HttpMcpServerBuilderExtensions.cs
localden May 2, 2025
d1f30f8
Tweaks to config
localden May 2, 2025
3713da6
Server configuration
localden May 2, 2025
9582111
Update based on feedback
localden May 2, 2025
8b89c2d
Pop the browser open - implement that in the SDK
localden May 2, 2025
c67ef6c
Tweaks to handles
localden May 2, 2025
a439840
Mock handler
localden May 2, 2025
932e678
Make sure I am not introducing unexpected changes
localden May 2, 2025
2151d00
Rename projects
localden May 2, 2025
3bfc258
Update sample project name and location
localden May 2, 2025
70146dd
Make sure definitions are not changed
localden May 2, 2025
ec25187
Functionality consolidation
localden May 2, 2025
a3926ee
Fix name
localden May 2, 2025
405db57
Organize things better
localden May 2, 2025
a0486d3
Cleanup
localden May 2, 2025
cc2f570
Update samples/ProtectedMCPServer/Program.cs
localden May 2, 2025
4e0b508
Update samples/ProtectedMCPServer/Program.cs
localden May 2, 2025
32f7b16
Update src/ModelContextProtocol.AspNetCore/Auth/McpAuthorizationExten…
localden May 2, 2025
b5728d0
Update samples/AspNetCoreSseServer/Program.cs
localden May 2, 2025
487bbd7
Update src/ModelContextProtocol.AspNetCore/Auth/McpAuthorizationExten…
localden May 2, 2025
22b7bcc
Update src/ModelContextProtocol.AspNetCore/Auth/McpAuthorizationExten…
localden May 2, 2025
a433f5a
Contextual rename
localden May 2, 2025
31f8ce9
Added overload for custom scheme
localden May 2, 2025
48bff9c
Introduce proper constants
localden May 2, 2025
991b29b
Simplify the handling of the WWW-Authenticate implementaiton
localden May 2, 2025
5c28b62
Update Program.cs
localden May 2, 2025
1a1e48c
Remove no longer used entity
localden May 2, 2025
008f521
Move to its own file.
localden May 2, 2025
6e23f4a
Placeholder setup
localden May 2, 2025
ff8a2b7
Fix server devx
localden May 2, 2025
03438d2
Defaults are applied here - no need to redeclare
localden May 2, 2025
a4f2495
Proper authorization configuration
localden May 2, 2025
2cdee6e
Simplify defaults
localden May 2, 2025
e79dcb8
Update McpEndpointRouteBuilderExtensions.cs
localden May 2, 2025
7903c75
Update Program.cs
localden May 2, 2025
fbb1817
Simplify tool configuration
localden May 2, 2025
6aef68b
Update config
localden May 2, 2025
1fd164c
Proper setup
localden May 3, 2025
0badaf8
Update McpAuthenticationHandler.cs
localden May 3, 2025
9209b61
Update samples
localden May 3, 2025
a90d01e
Consolidate some of the PKCE logic.
localden May 3, 2025
f3a3715
Consolidate some OAuth logic
localden May 3, 2025
e044475
Update McpAuthenticationHandler.cs
localden May 3, 2025
8790a35
Clean up the helper
localden May 3, 2025
4cff084
Minor changes to PRM doc logic
localden May 3, 2025
6b768d8
Cleanup
localden May 3, 2025
e3c5c21
Remove unused namespaces
localden May 3, 2025
d77368d
Placeholder for events
localden May 3, 2025
32f2265
Simplify policy setup
localden May 3, 2025
67342cd
Policy setup
localden May 3, 2025
b37a7b9
Escaping
localden May 3, 2025
583de65
Update server configuration
localden May 3, 2025
5f15bce
Validation.
localden May 3, 2025
7c33562
Delegate Base64 encoding to a more performant implementation
localden May 3, 2025
8d4c0c4
Make sure we're consistent
localden May 3, 2025
55bb471
Implement refresh token logic
localden May 3, 2025
debdb67
Stripping out the client implementation - this needs to be re-thought
localden May 4, 2025
003f5a0
Standardize namespacing
localden May 4, 2025
ceee919
Updating the approach for client auth
localden May 4, 2025
51bb38b
Cleanup for client logic
localden May 4, 2025
afd05af
Cleanup
localden May 4, 2025
a0fbec6
Simplify client logic
localden May 4, 2025
4073efd
Complete PRM doc properties. Update for consistency
localden May 4, 2025
2e06f59
Update for consistency
localden May 4, 2025
2d7da73
Work on the basic OAuth implementation in sample
localden May 4, 2025
10cf1f7
Token acquisition logic
localden May 4, 2025
2f84981
Update token logic
localden May 4, 2025
6768089
Significantly more basic provider implementation
localden May 4, 2025
cbb5c36
Working client-server interaction
localden May 4, 2025
0aeec19
Cleanup
localden May 4, 2025
531370a
Simplification
localden May 4, 2025
d511312
Cleanup for consistency
localden May 4, 2025
1496b41
Update with better DevEx
localden May 5, 2025
6b0b7ef
Cleanup
localden May 5, 2025
a9acba8
Delete AuthorizationServerUtils.cs
localden May 5, 2025
f8650f9
Cleanup
localden May 5, 2025
d4cc3ad
Multiple scheme support
localden May 5, 2025
31f611a
Multi-scheme support
localden May 5, 2025
3852be9
Cleaner implementation
localden May 5, 2025
8f926ba
Redundant call
localden May 5, 2025
b501d23
Merge branch 'main' into localden/experimental
localden May 6, 2025
c92343c
Update src/ModelContextProtocol/Authentication/AuthorizationDelegatin…
localden May 7, 2025
c57b85c
Update src/ModelContextProtocol/Authentication/AuthorizationDelegatin…
localden May 7, 2025
bb25dbd
Update src/ModelContextProtocol/Authentication/AuthorizationDelegatin…
localden May 7, 2025
291c7ec
Update src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenti…
localden May 7, 2025
d72221d
Update src/ModelContextProtocol.AspNetCore/Authentication/McpAuthoriz…
localden May 7, 2025
2945083
Clean up scheme checks
localden May 7, 2025
26f44de
Use ConfigureAwait
localden May 7, 2025
ca0cdf3
Fix LINQ issue. Simplify best scheme match check
localden May 7, 2025
45c4b30
Proper array caching
localden May 7, 2025
627e1e4
Update 401 handling logic. Remove pragma warning silencing.
localden May 7, 2025
8903c42
Update based on feedback
localden May 7, 2025
a0275ea
Update the HttpClient configuration
localden May 7, 2025
05e3550
Update for cleanup
localden May 7, 2025
e8f7012
Update McpAuthenticationHandler.cs
localden May 7, 2025
94c39bc
No direct inclusion of HTTP client factory
localden May 8, 2025
0504aee
Remove legacy packages
localden May 8, 2025
7ac5c4a
Update signature and make test explicit
localden May 8, 2025
7b6c9fd
URL check
localden May 8, 2025
c406230
Changes based on feedback
localden May 8, 2025
b8954b3
Update to use a HttpClientFactory
localden May 8, 2025
0b030c2
Update McpAuthenticationHandler.cs
localden May 8, 2025
7c6e406
Update based on feedback
localden May 8, 2025
9420013
Update McpAuthenticationOptions.cs
localden May 8, 2025
4fbf2e9
Update McpAuthenticationOptions.cs
localden May 8, 2025
22962cf
Minor optimizations to the delegating handler
localden May 8, 2025
5c94158
Update AuthorizationDelegatingHandler.cs
localden May 8, 2025
0abea22
Update AuthorizationHelpers.cs
localden May 8, 2025
58a7a3d
Update AuthorizationHelpers.cs
localden May 8, 2025
b3b8168
Update AuthorizationHelpers.cs
localden May 8, 2025
aad7ad5
Update AuthorizationHelpers.cs
localden May 8, 2025
43eb6ff
Add record support for unauthorized result
localden May 8, 2025
9a4cea0
Update BasicOAuthAuthorizationProvider.cs
localden May 8, 2025
b3766aa
Update ProtectedResourceMetadata.cs
localden May 8, 2025
bc1566e
Proper check for resource metadata
localden May 8, 2025
0857b94
Update AuthorizationHelpers.cs
localden May 8, 2025
a40325d
.NET Standard 2.0 compatibility fix
localden May 8, 2025
a0cd4ad
Naming consistency
localden May 8, 2025
fe0d8ba
Update for consistency
localden May 8, 2025
0ff4ac4
Update src/ModelContextProtocol/Authentication/AuthorizationDelegatin…
localden May 10, 2025
3b46c8d
Update src/ModelContextProtocol/Protocol/Transport/SseClientTransport…
localden May 10, 2025
8c6e1dc
Merge branch 'main' into localden/experimental
localden May 10, 2025
3602c66
Merge branch 'main' into localden/experimental
localden May 12, 2025
5980c9a
Update src/ModelContextProtocol/Authentication/AuthorizationDelegatin…
localden May 15, 2025
34794dd
Update samples/ProtectedMCPServer/Program.cs
localden Jun 2, 2025
614f8b3
Update McpAuthenticationOptions.cs
localden Jun 2, 2025
6ed573d
Delete McpAuthorizationPolicyExtensions.cs
localden Jun 2, 2025
bd542e2
Update AuthorizationHelpers.cs
localden Jun 2, 2025
325647b
Update AuthorizationHelpers.cs
localden Jun 2, 2025
5435861
Merge branch 'main' of https://github.com/modelcontextprotocol/csharp…
localden Jun 2, 2025
e1ff4ca
Cleanup
localden Jun 2, 2025
f806625
Update with configuration for generic OAuth provider
localden Jun 2, 2025
8fc0af1
Update GenericOAuthProvider.cs
localden Jun 3, 2025
0af8a58
Merge branch 'main' of https://github.com/modelcontextprotocol/csharp…
localden Jun 3, 2025
c6b1e3a
Move auth stuff to Core.
localden Jun 3, 2025
d3591d6
Update AuthorizationHelpers.cs
localden Jun 3, 2025
4d0a126
Update Program.cs
localden Jun 3, 2025
cbb504f
Shift listener implementation out of the SDK
localden Jun 3, 2025
b6ac759
Remove unused stuff
localden Jun 3, 2025
d81d30c
Update Directory.Packages.props
localden Jun 3, 2025
6ccc1d1
Update Directory.Packages.props
localden Jun 3, 2025
3df91d1
Merge branch 'main' into localden/experimental
localden Jun 4, 2025
9f22fd3
Update src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenti…
localden Jun 4, 2025
e0c507c
Update Directory.Packages.props
localden Jun 6, 2025
253f5d5
Update src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenti…
localden Jun 6, 2025
2c88d7a
Update src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenti…
localden Jun 6, 2025
6b3ec23
Update src/ModelContextProtocol.Core/Authentication/GenericOAuthProvi…
localden Jun 6, 2025
39f0588
Update src/ModelContextProtocol.Core/Authentication/GenericOAuthProvi…
localden Jun 6, 2025
2b4fb27
Update ModelContextProtocol.slnx
localden Jun 7, 2025
ecf63e3
Merge branch 'localden/experimental' of https://github.com/modelconte…
localden Jun 7, 2025
5d32edd
Merge branch 'main' into localden/experimental
localden Jun 8, 2025
01f0e89
Minor tweaks to update logic for PRM
localden Jun 11, 2025
c028c7b
Update McpAuthenticationHandler.cs
localden Jun 11, 2025
6601b79
Update GenericOAuthProvider.cs
localden Jun 11, 2025
f2557ee
Update GenericOAuthProvider.cs
localden Jun 11, 2025
70a1414
Update GenericOAuthProvider.cs
localden Jun 11, 2025
1078ad0
Update AuthorizationHelpers.cs
localden Jun 11, 2025
be83c92
Update AuthorizationHelpers.cs
localden Jun 11, 2025
da9fb9b
Update McpAuthenticationOptions.cs
localden Jun 11, 2025
58ca77c
Small updates
localden Jun 11, 2025
f2c779d
File name changes
localden Jun 11, 2025
84a7253
Merge branch 'main' into localden/experimental
dend Jun 13, 2025
d5c833d
Merge branch 'main' into localden/experimental
localden Jun 13, 2025
c2a77b0
Merge branch 'main' into localden/experimental
localden Jun 17, 2025
cbfe8c7
Don't hard code tenant and client IDs
halter73 Jun 17, 2025
a253cd0
Use top-level statements in ProtectedMCPClient Program.cs
halter73 Jun 17, 2025
1720664
Use scope_supported in GenericOAuthProvider
halter73 Jun 17, 2025
ec926c3
WIP whitespace
halter73 Jun 17, 2025
0c10b09
Merge branch 'main' into localden/experimental
localden Jun 18, 2025
cb52e85
Merge remote-tracking branch 'origin/main' into localden/experimental
halter73 Jun 19, 2025
1de26c7
Manually wrap HttpClient rather than use DelegatingHandler for client…
halter73 Jun 19, 2025
1a3d2c7
Simplify HandleUnauthorizedResponseAsync signature by using exceptions
halter73 Jun 20, 2025
886d388
Add console logger to ProtectedMCPClient sample
halter73 Jun 20, 2025
2b9eae4
Merge remote-tracking branch 'origin/main' into localden/experimental
halter73 Jun 24, 2025
dfa29e1
Add TestOAuthServer
halter73 Jun 26, 2025
3cc5c7f
Allow scopes_supported to be overridden like TS SDK
halter73 Jun 30, 2025
03d0fda
Add ClientOAuthOptions
halter73 Jun 30, 2025
a3e8601
Merge remote-tracking branch 'origin/main' into localden/experimental…
halter73 Jun 30, 2025
5b20710
Add RFC 8707 to the client and TestOAuthServer
halter73 Jun 30, 2025
d747e72
Add Dynamic Client Registration support following RFC 7591
halter73 Jun 30, 2025
910a979
Disable cert validation in tests because the dev cert may not be trusted
halter73 Jun 30, 2025
ed0fd6a
Support client_secret_post instead of client_secret_basic
halter73 Jul 1, 2025
06652f5
Merge remote-tracking branch 'origin/main' into localden/experimental
halter73 Jul 1, 2025
363754b
Make StreamableHttpClient_SendsMcpProtocolVersionHeader_AfterInitiali…
halter73 Jul 1, 2025
21f0efa
Add resource parameter to /token and /refresh requests
halter73 Jul 1, 2025
8b36076
Use McpAuthenticationEvents to customize ProtectedResourceMetadata pe…
halter73 Jul 1, 2025
ee2f76b
Add CloneResourceMetadataClonesAllProperties test
halter73 Jul 2, 2025
87956d4
Add CanAuthenticate_WithTokenRefresh test
halter73 Jul 2, 2025
dd2c075
Add READMEs to ProtectedMCPServer and ProtectedMcpClient
halter73 Jul 2, 2025
f3e62e6
Merge remote-tracking branch 'origin/main' into localden/experimental
halter73 Jul 2, 2025
cb8b7df
Tweak to use the right metadata construct
localden Jul 3, 2025
4f50202
Update OAuthServerMetadata.cs
localden Jul 3, 2025
855466b
Support /.well-known/oauth-authorization-server and /.well-known/open…
halter73 Jul 3, 2025
70e7578
Delete OAuthUtils.cs from TestOAuthServer
halter73 Jul 3, 2025
f759db3
Add ClientOAuthOptions.AdditionalAuthorizationParameters
halter73 Jul 3, 2025
72a36be
Use logging source generator in ClientOAuthProvider
halter73 Jul 3, 2025
43eb8dc
Remove unused field from McpAuthenticationHandler
halter73 Jul 3, 2025
60f5631
React to snake_case default tool naming and simplify samples
halter73 Jul 3, 2025
249eed1
Make ClientRegistrationResponse compatible with the typescript-sdk si…
halter73 Jul 3, 2025
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
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@

<!-- Product dependencies LTS -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.15" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
</ItemGroup>

<!-- Product dependencies .NET 9 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.9.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(System9Version)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(System9Version)" />
<PackageVersion Include="System.IO.Pipelines" Version="$(System9Version)" />
Expand Down
3 changes: 3 additions & 0 deletions ModelContextProtocol.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<Project Path="samples/AspNetCoreSseServer/AspNetCoreSseServer.csproj" />
<Project Path="samples/ChatWithTools/ChatWithTools.csproj" />
<Project Path="samples/EverythingServer/EverythingServer.csproj" />
<Project Path="samples/ProtectedMCPClient/ProtectedMCPClient.csproj" />
<Project Path="samples/ProtectedMCPServer/ProtectedMCPServer.csproj" />
<Project Path="samples/QuickstartClient/QuickstartClient.csproj" />
<Project Path="samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj" />
<Project Path="samples/TestServerWithHosting/TestServerWithHosting.csproj" />
Expand All @@ -33,6 +35,7 @@
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ModelContextProtocol.AspNetCore.Tests/ModelContextProtocol.AspNetCore.Tests.csproj" />
<Project Path="tests/ModelContextProtocol.TestOAuthServer/ModelContextProtocol.TestOAuthServer.csproj" />
<Project Path="tests/ModelContextProtocol.Tests/ModelContextProtocol.Tests.csproj" />
<Project Path="tests/ModelContextProtocol.TestServer/ModelContextProtocol.TestServer.csproj" />
<Project Path="tests/ModelContextProtocol.TestSseServer/ModelContextProtocol.TestSseServer.csproj" />
Expand Down
148 changes: 148 additions & 0 deletions samples/ProtectedMCPClient/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Web;

var serverUrl = "http://localhost:7071/";

Console.WriteLine("Protected MCP Client");
Console.WriteLine($"Connecting to weather server at {serverUrl}...");
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 transport = new SseClientTransport(new()
{
Endpoint = new Uri(serverUrl),
Name = "Secure Weather Client",
OAuth = new()
{
ClientName = "ProtectedMcpClient",
RedirectUri = new Uri("http://localhost:1179/callback"),
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
}
}, httpClient, consoleLoggerFactory);

var client = await McpClientFactory.CreateAsync(transport, loggerFactory: consoleLoggerFactory);

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<string, object?> { { "state", "WA" } }
);

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.
/// </summary>
/// <param name="authorizationUrl">The authorization URL to open in the browser.</param>
/// <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)
{
Console.WriteLine("Starting OAuth authorization flow...");
Console.WriteLine($"Opening browser to: {authorizationUrl}");

var listenerPrefix = 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(authorizationUrl);

var context = await listener.GetContextAsync();
var query = HttpUtility.ParseQueryString(context.Request.Url?.Query ?? string.Empty);
var code = query["code"];
var error = query["error"];

string responseHtml = "<html><body><h1>Authentication complete</h1><p>You can close this window now.</p></body></html>";
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 code;
}
catch (Exception ex)
{
Console.WriteLine($"Error getting auth code: {ex.Message}");
return null;
}
finally
{
if (listener.IsListening) listener.Stop();
}
}

/// <summary>
/// Opens the specified URL in the default browser.
/// </summary>
/// <param name="url">The URL to open.</param>
static void OpenBrowser(Uri url)
{
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}");
}
}
18 changes: 18 additions & 0 deletions samples/ProtectedMCPClient/ProtectedMCPClient.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ModelContextProtocol.Core\ModelContextProtocol.Core.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>

</Project>
93 changes: 93 additions & 0 deletions samples/ProtectedMCPClient/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Protected MCP Client Sample

This sample demonstrates how to create an MCP client that connects to a protected MCP server using OAuth 2.0 authentication. The client implements a custom OAuth authorization flow with browser-based authentication.

## Overview

The Protected MCP Client sample shows how to:
- Connect to an OAuth-protected MCP server
- Handle OAuth 2.0 authorization code flow
- Use custom authorization redirect handling
- Call protected MCP tools with authentication

## Prerequisites

- .NET 9.0 or later
- A running TestOAuthServer (for OAuth authentication)
- A running ProtectedMCPServer (for MCP services)

## Setup and Running

### Step 1: Start the Test OAuth Server

First, you need to start the TestOAuthServer which provides OAuth authentication:

```bash
cd tests\ModelContextProtocol.TestOAuthServer
dotnet run --framework net9.0
```

The OAuth server will start at `https://localhost:7029`

### Step 2: Start the Protected MCP Server

Next, start the ProtectedMCPServer which provides the weather tools:

```bash
cd samples\ProtectedMCPServer
dotnet run
```

The protected server will start at `http://localhost:7071`

### Step 3: Run the Protected MCP Client

Finally, run this client:

```bash
cd samples\ProtectedMCPClient
dotnet run
```

## What Happens

1. The client attempts to connect to the protected MCP server at `http://localhost:7071`
2. The server responds with OAuth metadata indicating authentication is required
3. The client initiates OAuth 2.0 authorization code flow:
- Opens a browser to the authorization URL at the OAuth server
- Starts a local HTTP listener on `http://localhost:1179/callback` to receive the authorization code
- Exchanges the authorization code for an access token
4. The client uses the access token to authenticate with the MCP server
5. The client lists available tools and calls the `GetAlerts` tool for Washington state

## OAuth Configuration

The client is configured with:
- **Client ID**: `demo-client`
- **Client Secret**: `demo-secret`
- **Redirect URI**: `http://localhost:1179/callback`
- **OAuth Server**: `https://localhost:7029`
- **Protected Resource**: `http://localhost:7071`

## Available Tools

Once authenticated, the client can access weather tools including:
- **GetAlerts**: Get weather alerts for a US state
- **GetForecast**: Get weather forecast for a location (latitude/longitude)

## Troubleshooting

- Ensure the ASP.NET Core dev certificate is trusted.
```
dotnet dev-certs https --clean
dotnet dev-certs https --trust
```
- Ensure all three services are running in the correct order
- Check that ports 7029, 7071, and 1179 are available
- If the browser doesn't open automatically, copy the authorization URL from the console and open it manually
- Make sure to allow the OAuth server's self-signed certificate in your browser

## Key Files

- `Program.cs`: Main client application with OAuth flow implementation
- `ProtectedMCPClient.csproj`: Project file with dependencies
93 changes: 93 additions & 0 deletions samples/ProtectedMCPServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using ModelContextProtocol.AspNetCore.Authentication;
using ProtectedMCPServer.Tools;
using System.Net.Http.Headers;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

var serverUrl = "http://localhost:7071/";
var inMemoryOAuthServerUrl = "https://localhost:7029";

builder.Services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure to validate tokens from our in-memory OAuth server
options.Authority = inMemoryOAuthServerUrl;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = serverUrl, // Validate that the audience matches the resource metadata as suggested in RFC 8707
ValidIssuer = inMemoryOAuthServerUrl,
NameClaimType = "name",
RoleClaimType = "roles"
};

options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var name = context.Principal?.Identity?.Name ?? "unknown";
var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
Console.WriteLine($"Token validated for: {name} ({email})");
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
Console.WriteLine($"Authentication failed: {context.Exception.Message}");
return Task.CompletedTask;
},
OnChallenge = context =>
{
Console.WriteLine($"Challenging client to authenticate with Entra ID");
return Task.CompletedTask;
}
};
})
.AddMcp(options =>
{
options.ResourceMetadata = new()
{
Resource = new Uri(serverUrl),
ResourceDocumentation = new Uri("https://docs.example.com/api/weather"),
AuthorizationServers = { new Uri(inMemoryOAuthServerUrl) },
ScopesSupported = ["mcp:tools"],
};
});

builder.Services.AddAuthorization();

builder.Services.AddHttpContextAccessor();
builder.Services.AddMcpServer()
.WithTools<WeatherTools>()
.WithHttpTransport();

// Configure HttpClientFactory for weather.gov API
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();

// Use the default MCP policy name that we've configured
app.MapMcp().RequireAuthorization();

Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
Console.WriteLine($"Using in-memory OAuth server at {inMemoryOAuthServerUrl}");
Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
Console.WriteLine("Press Ctrl+C to stop the server");

app.Run(serverUrl);
Loading