using Microsoft.Extensions.Logging; using ModelContextProtocol.Authentication; 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"), AuthorizationCallbackHandler = 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 { { "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. /// /// The context containing the authorization and redirect URIs. /// The to monitor for cancellation requests. The default is . /// The authorization result extracted from the callback, or null if the operation failed. static async Task HandleAuthorizationUrlAsync( AuthorizationCallbackContext authorizationContext, CancellationToken cancellationToken) { var authorizationUrl = authorizationContext.AuthorizationUri; var redirectUri = authorizationContext.RedirectUri; 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 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}"); } }