From eca378b903961d8d3c3b0d6a5aa11ca71928b8a4 Mon Sep 17 00:00:00 2001 From: Enrico Sabbadin Date: Fri, 30 May 2025 12:58:45 +0200 Subject: [PATCH 1/5] WIP --- samples/AspNetCoreSseServer/Program.cs | 6 +- .../AspNetCoreSseServer/Tools/WeatherTools.cs | 82 +++++++++++++++++++ samples/QuickstartClient/Program.cs | 40 ++++++--- .../Properties/launchSettings.json | 8 ++ 4 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 samples/AspNetCoreSseServer/Tools/WeatherTools.cs create mode 100644 samples/QuickstartClient/Properties/launchSettings.json diff --git a/samples/AspNetCoreSseServer/Program.cs b/samples/AspNetCoreSseServer/Program.cs index f24b6a17..91bd2418 100644 --- a/samples/AspNetCoreSseServer/Program.cs +++ b/samples/AspNetCoreSseServer/Program.cs @@ -1,3 +1,4 @@ +using AspNetCoreSseServer.Tools; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -7,7 +8,8 @@ builder.Services.AddMcpServer() .WithHttpTransport() .WithTools() - .WithTools(); + .WithTools() + .WithTools(); builder.Services.AddOpenTelemetry() .WithTracing(b => b.AddSource("*") @@ -18,7 +20,7 @@ .AddHttpClientInstrumentation()) .WithLogging() .UseOtlpExporter(); - +builder.Services.AddHttpClient(); var app = builder.Build(); app.MapMcp(); diff --git a/samples/AspNetCoreSseServer/Tools/WeatherTools.cs b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs new file mode 100644 index 00000000..25c03488 --- /dev/null +++ b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using ModelContextProtocol.Server; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Threading; + + +namespace AspNetCoreSseServer.Tools; + +[McpServerToolType] +public sealed class WeatherTools +{ + private readonly IHttpClientFactory _httpClientFactory; + + public WeatherTools(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } + [McpServerTool(Name = "get_current_weather"), Description("returns the current weather given a town or region name")] + public async Task Get_Weather(IMcpServer mcpServer, [Description("The location (town or region) name")] string location) + { + try + { + var client = _httpClientFactory.CreateClient(); + var request = new HttpRequestMessage(HttpMethod.Get, $"https://nominatim.openstreetmap.org/search?format=json&q={location}"); + request.Headers.Add("User-Agent", "Test-MCP-Server"); + var ret = await client.SendAsync(request); + if (!ret.IsSuccessStatusCode) + { + return $"error getting coordinates from location StatusCode: {ret.StatusCode} message: {await ret.Content.ReadAsStringAsync()}"; + } + var response = ret.Content.ReadAsStreamAsync(); + var locationInfo = JsonNode.Parse(await response) as JsonArray ?? new JsonArray(); + if (locationInfo == null || locationInfo.Count == 0) + { + return $"could not parse no result {response} into an json array or no results were found for location {location}"; + } + request = new HttpRequestMessage(HttpMethod.Get, $"https://api.open-meteo.com/v1/forecast?latitude={locationInfo.First()?["lat"]}&longitude={locationInfo.First()?["lon"]}9¤t_weather=true"); + request.Headers.Add("User-Agent", "Test-MCP-Server"); + ret = await client.SendAsync(request); + if (!ret.IsSuccessStatusCode) + { + return $"error getting coordinates from location StatusCode: {ret.StatusCode} message: {await ret.Content.ReadAsStringAsync()}"; + } + return await ret.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + return $"general error: {ex.ToString()}"; + } + } +} + + + + +public record PlaceInformation +{ + public int place_id { get; init; } + public string licence { get; init; } = ""; + public string osm_type { get; init; } = ""; + public int osm_id { get; init; } + public string lat { get; init; } = ""; + public string lon { get; init; } = ""; + + public string type { get; init; } = ""; + public int place_rank { get; init; } + public double importance { get; init; } + public string addresstype { get; init; } = ""; + public string name { get; init; } = ""; + public string display_name { get; init; } = ""; + public List boundingbox { get; init; } = new List(); +} + + diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs index e3ee4fe8..8e633e3d 100644 --- a/samples/QuickstartClient/Program.cs +++ b/samples/QuickstartClient/Program.cs @@ -3,6 +3,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using ModelContextProtocol.Client; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Intrinsics.X86; +using System.Text; var builder = Host.CreateApplicationBuilder(args); @@ -11,15 +14,22 @@ .AddUserSecrets(); var (command, arguments) = GetCommandAndArguments(args); - -var clientTransport = new StdioClientTransport(new() +IClientTransport? clientTransport = null; +if (command == "http") { - Name = "Demo Server", - Command = command, - Arguments = arguments, -}); - -await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport); + // make sure AspNetCoreSseServer is running + clientTransport = new SseClientTransport(new SseClientTransportOptions { Endpoint = new Uri("https://localhost:7133"), UseStreamableHttp = true }); +} +else +{ + clientTransport = new StdioClientTransport(new() + { + Name = "Demo Server", + Command = command, + Arguments = arguments, + }); +} +await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport!); var tools = await mcpClient.ListToolsAsync(); foreach (var tool in tools) @@ -43,7 +53,7 @@ Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("MCP Client Started!"); Console.ResetColor(); - +var messages = new List(); PromptForInput(); while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase)) { @@ -52,14 +62,18 @@ PromptForInput(); continue; } - - await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options)) + messages.Add(new ChatMessage(ChatRole.User, query)); + var sb = new StringBuilder(); + await foreach (var message in anthropicClient.GetStreamingResponseAsync(messages, options)) { Console.Write(message); + sb.AppendLine(message.ToString()); } + messages.Add(new ChatMessage(ChatRole.Assistant, sb.ToString())); Console.WriteLine(); PromptForInput(); + } static void PromptForInput() @@ -89,6 +103,8 @@ static void PromptForInput() [var script] when script.EndsWith(".py") => ("python", args), [var script] when script.EndsWith(".js") => ("node", args), [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]), + [var script] when script.Equals("http", StringComparison.OrdinalIgnoreCase) => ("http", args), _ => ("dotnet", ["run", "--project", "../../../../QuickstartWeatherServer", "--no-build"]) }; -} \ No newline at end of file +} + diff --git a/samples/QuickstartClient/Properties/launchSettings.json b/samples/QuickstartClient/Properties/launchSettings.json new file mode 100644 index 00000000..83f36695 --- /dev/null +++ b/samples/QuickstartClient/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "QuickstartClient": { + "commandName": "Project", + "commandLineArgs": "http" + } + } +} \ No newline at end of file From 9ce76cac4fdac0d491147e8d63ca22e50f768e18 Mon Sep 17 00:00:00 2001 From: Enrico Sabbadin Date: Fri, 30 May 2025 14:09:08 +0200 Subject: [PATCH 2/5] correct namepspace of WeatherTools --- samples/AspNetCoreSseServer/Program.cs | 1 - samples/AspNetCoreSseServer/Tools/WeatherTools.cs | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/samples/AspNetCoreSseServer/Program.cs b/samples/AspNetCoreSseServer/Program.cs index 91bd2418..e7a83323 100644 --- a/samples/AspNetCoreSseServer/Program.cs +++ b/samples/AspNetCoreSseServer/Program.cs @@ -1,4 +1,3 @@ -using AspNetCoreSseServer.Tools; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; diff --git a/samples/AspNetCoreSseServer/Tools/WeatherTools.cs b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs index 25c03488..93529f52 100644 --- a/samples/AspNetCoreSseServer/Tools/WeatherTools.cs +++ b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs @@ -6,13 +6,9 @@ using System.Collections.Generic; using System.ComponentModel; using System.Net.Http; -using System.Text.Json; using System.Text.Json.Nodes; -using System.Text.Json.Serialization; -using System.Threading; - -namespace AspNetCoreSseServer.Tools; +namespace TestServerWithHosting.Tools; [McpServerToolType] public sealed class WeatherTools From 5250903b9813a41fc981977484607593e0d866a3 Mon Sep 17 00:00:00 2001 From: Enrico Sabbadin Date: Fri, 30 May 2025 14:12:25 +0200 Subject: [PATCH 3/5] change error message --- samples/AspNetCoreSseServer/Tools/WeatherTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/AspNetCoreSseServer/Tools/WeatherTools.cs b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs index 93529f52..adbf3968 100644 --- a/samples/AspNetCoreSseServer/Tools/WeatherTools.cs +++ b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs @@ -30,7 +30,7 @@ public async Task Get_Weather(IMcpServer mcpServer, [Description("The lo var ret = await client.SendAsync(request); if (!ret.IsSuccessStatusCode) { - return $"error getting coordinates from location StatusCode: {ret.StatusCode} message: {await ret.Content.ReadAsStringAsync()}"; + return $"there was an error getting coordinates from location StatusCode: {ret.StatusCode} message: {await ret.Content.ReadAsStringAsync()}"; } var response = ret.Content.ReadAsStreamAsync(); var locationInfo = JsonNode.Parse(await response) as JsonArray ?? new JsonArray(); From 395f88a02876d83a8773dd5603a87fdf083c99cd Mon Sep 17 00:00:00 2001 From: Enrico Sabbadin Date: Fri, 30 May 2025 14:17:29 +0200 Subject: [PATCH 4/5] remove not required class PlaceInformation --- .../AspNetCoreSseServer/Tools/WeatherTools.cs | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/samples/AspNetCoreSseServer/Tools/WeatherTools.cs b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs index adbf3968..6f2201e1 100644 --- a/samples/AspNetCoreSseServer/Tools/WeatherTools.cs +++ b/samples/AspNetCoreSseServer/Tools/WeatherTools.cs @@ -52,27 +52,4 @@ public async Task Get_Weather(IMcpServer mcpServer, [Description("The lo return $"general error: {ex.ToString()}"; } } -} - - - - -public record PlaceInformation -{ - public int place_id { get; init; } - public string licence { get; init; } = ""; - public string osm_type { get; init; } = ""; - public int osm_id { get; init; } - public string lat { get; init; } = ""; - public string lon { get; init; } = ""; - - public string type { get; init; } = ""; - public int place_rank { get; init; } - public double importance { get; init; } - public string addresstype { get; init; } = ""; - public string name { get; init; } = ""; - public string display_name { get; init; } = ""; - public List boundingbox { get; init; } = new List(); -} - - +} \ No newline at end of file From 2a4ea8c064e3da54b234d06edc5facaabe09b15f Mon Sep 17 00:00:00 2001 From: Enrico Sabbadin Date: Thu, 3 Jul 2025 17:00:33 +0200 Subject: [PATCH 5/5] adjust code after merge from main --- samples/AspNetCoreSseServer/Program.cs | 2 +- samples/QuickstartClient/Program.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/AspNetCoreSseServer/Program.cs b/samples/AspNetCoreSseServer/Program.cs index 5fed7e91..4d86f2cf 100644 --- a/samples/AspNetCoreSseServer/Program.cs +++ b/samples/AspNetCoreSseServer/Program.cs @@ -9,7 +9,7 @@ .WithHttpTransport() .WithTools() .WithTools() - .WithResources(); + .WithResources() .WithTools(); builder.Services.AddOpenTelemetry() diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs index f3a41b74..84f7bd01 100644 --- a/samples/QuickstartClient/Program.cs +++ b/samples/QuickstartClient/Program.cs @@ -5,6 +5,7 @@ using ModelContextProtocol.Client; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Text; var builder = Host.CreateApplicationBuilder(args); @@ -17,7 +18,7 @@ if (command == "http") { // make sure AspNetCoreSseServer is running - clientTransport = new SseClientTransport(new SseClientTransportOptions { Endpoint = new Uri("https://localhost:7133"), UseStreamableHttp = true }); + clientTransport = new SseClientTransport(new SseClientTransportOptions { Endpoint = new Uri("https://localhost:7133"), TransportMode = HttpTransportMode.StreamableHttp}); } else {