csharp-sdk/samples/ProtectedMcpClient/Program.cs
2025-10-03 10:00:51 -07:00

158 lines
No EOL
4.9 KiB
C#

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 HttpClientTransport(new()
{
Endpoint = new Uri(serverUrl),
Name = "Secure Weather Client",
OAuth = new()
{
RedirectUri = new Uri("http://localhost:1179/callback"),
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
DynamicClientRegistration = new()
{
ClientName = "ProtectedMcpClient",
},
}
}, httpClient, consoleLoggerFactory);
var client = await McpClient.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)
{
// 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}");
}
}