Remove 'Sse' from AspNetCoreSseServer sample name (#665)
This commit is contained in:
parent
da86d52241
commit
5e5b1af29b
3 changed files with 22 additions and 23 deletions
148
samples/ProtectedMcpClient/Program.cs
Normal file
148
samples/ProtectedMcpClient/Program.cs
Normal file
|
|
@ -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
samples/ProtectedMcpClient/ProtectedMcpClient.csproj
Normal file
18
samples/ProtectedMcpClient/ProtectedMcpClient.csproj
Normal file
|
|
@ -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
samples/ProtectedMcpClient/README.md
Normal file
93
samples/ProtectedMcpClient/README.md
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue