From 157330a0f5aa0a393fa00cb253b1dbc848736936 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Wed, 26 Mar 2025 12:09:18 +1100 Subject: [PATCH 1/7] Server quickstart for docs --- ModelContextProtocol.sln | 7 ++ samples/QuickstartWeatherServer/Program.cs | 16 ++++ .../QuickstartWeatherServer.csproj | 18 ++++ .../Tools/WeatherTools.cs | 84 +++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 samples/QuickstartWeatherServer/Program.cs create mode 100644 samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj create mode 100644 samples/QuickstartWeatherServer/Tools/WeatherTools.cs diff --git a/ModelContextProtocol.sln b/ModelContextProtocol.sln index 4d2b4b5a7..0909a51de 100644 --- a/ModelContextProtocol.sln +++ b/ModelContextProtocol.sln @@ -46,6 +46,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithTools", "samples\ChatWithTools\ChatWithTools.csproj", "{0C6D0512-D26D-63D3-5019-C5F7A657B28C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickstartWeatherServer", "samples\QuickstartWeatherServer\QuickstartWeatherServer.csproj", "{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -80,6 +82,10 @@ Global {0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Debug|Any CPU.Build.0 = Debug|Any CPU {0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.ActiveCfg = Release|Any CPU {0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.Build.0 = Release|Any CPU + {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -93,6 +99,7 @@ Global {B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40} {0C6D0512-D26D-63D3-5019-C5F7A657B28C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89} diff --git a/samples/QuickstartWeatherServer/Program.cs b/samples/QuickstartWeatherServer/Program.cs new file mode 100644 index 000000000..31f44ff2d --- /dev/null +++ b/samples/QuickstartWeatherServer/Program.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using ModelContextProtocol; + +var builder = Host.CreateEmptyApplicationBuilder(settings: null); + +//builder.Logging.ClearProviders(); +//builder.Logging.AddFilter("Microsoft", LogLevel.Warning); // Adjust the log level as needed + +builder.Services.AddMcpServer() + .WithStdioServerTransport() + .WithToolsFromAssembly(); + +var app = builder.Build(); + +await app.RunAsync(); diff --git a/samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj b/samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj new file mode 100644 index 000000000..2e9154fd2 --- /dev/null +++ b/samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/samples/QuickstartWeatherServer/Tools/WeatherTools.cs b/samples/QuickstartWeatherServer/Tools/WeatherTools.cs new file mode 100644 index 000000000..b39c13052 --- /dev/null +++ b/samples/QuickstartWeatherServer/Tools/WeatherTools.cs @@ -0,0 +1,84 @@ +using ModelContextProtocol.Server; +using System.ComponentModel; +using System.Net.Http.Headers; +using System.Text.Json; + +namespace QuickstartWeatherServer.Tools; + +[McpServerToolType] +public static class WeatherTools +{ + [McpServerTool, Description("Get weather alerts for a US state.")] + public static async Task GetAlerts( + [Description("The US state to get alerts for.")] string state) + { + using HttpClient client = GetWeatherClient(); + + var response = await client.GetAsync($"/alerts/active/area/{state}"); + + if (!response.IsSuccessStatusCode) + { + return "Failed to retrieve alerts."; + } + + var json = await response.Content.ReadAsStringAsync(); + var jsonElement = JsonSerializer.Deserialize(json); + var alerts = jsonElement.GetProperty("features").EnumerateArray(); + + if (!alerts.Any()) + { + return "No active alerts for this state."; + } + + // Process the alerts and return a formatted string + var alertMessages = new List(); + foreach (var alert in alerts) + { + JsonElement properties = alert.GetProperty("properties"); + alertMessages.Add($""" + Event: {properties.GetProperty("event").GetString()} + Area: {properties.GetProperty("areaDesc").GetString()} + Severity: {properties.GetProperty("severity").GetString()} + Description: {properties.GetProperty("description").GetString()} + Instruction: {properties.GetProperty("instruction").GetString()} + """); + } + return string.Join("\n---\n", alertMessages); + } + + [McpServerTool, Description("Get weather forecast for a location.")] + public static async Task GetForecast( + [Description("Latitude of the location.")] double latitude, + [Description("Longitude of the location.")] double longitude) + { + using HttpClient client = GetWeatherClient(); + var response = await client.GetAsync($"/points/{latitude},{longitude}"); + if (!response.IsSuccessStatusCode) + { + return "Failed to retrieve forecast."; + } + + var json = await response.Content.ReadAsStringAsync(); + var jsonElement = JsonSerializer.Deserialize(json); + var periods = jsonElement.GetProperty("properties").GetProperty("periods").EnumerateArray(); + // Process the forecast and return a formatted string + var forecastMessages = new List(); + foreach (var period in periods) + { + forecastMessages.Add($""" + {period.GetProperty("name").GetString()} + Temperature: {period.GetProperty("temperature").GetInt32()}°F + Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()} + Forecast: {period.GetProperty("detailedForecast").GetString()} + """); + } + return string.Join("\n---\n", forecastMessages); + } + + private static HttpClient GetWeatherClient() + { + var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") }; + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0")); + return client; + } +} \ No newline at end of file From 9f407bd9cfd4e32e94e45a4d002228d8a2aa644c Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Wed, 26 Mar 2025 13:40:51 +1100 Subject: [PATCH 2/7] WIP --- ModelContextProtocol.sln | 7 ++ samples/QuickstartClient/Program.cs | 78 +++++++++++++++++++ .../QuickstartClient/QuickstartClient.csproj | 20 +++++ 3 files changed, 105 insertions(+) create mode 100644 samples/QuickstartClient/Program.cs create mode 100644 samples/QuickstartClient/QuickstartClient.csproj diff --git a/ModelContextProtocol.sln b/ModelContextProtocol.sln index 0909a51de..1f7a475b6 100644 --- a/ModelContextProtocol.sln +++ b/ModelContextProtocol.sln @@ -48,6 +48,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithTools", "samples\Ch EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickstartWeatherServer", "samples\QuickstartWeatherServer\QuickstartWeatherServer.csproj", "{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickstartClient", "samples\QuickstartClient\QuickstartClient.csproj", "{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -86,6 +88,10 @@ Global {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Debug|Any CPU.Build.0 = Debug|Any CPU {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Release|Any CPU.Build.0 = Release|Any CPU + {0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -100,6 +106,7 @@ Global {20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40} {0C6D0512-D26D-63D3-5019-C5F7A657B28C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {0D1552DC-E6ED-4AAC-5562-12F8352F46AA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89} diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs new file mode 100644 index 000000000..09bbcf73b --- /dev/null +++ b/samples/QuickstartClient/Program.cs @@ -0,0 +1,78 @@ +using Anthropic.SDK; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using ModelContextProtocol.Client; +using ModelContextProtocol.Protocol.Transport; + +var builder = Host.CreateEmptyApplicationBuilder(settings: null); + +builder.Configuration + .AddEnvironmentVariables() + .AddUserSecrets(); + +var mcpClient = await McpClientFactory.CreateAsync(new() +{ + Id = "weather", + Name = "Weather", + TransportType = TransportTypes.StdIo, + TransportOptions = new() + { + ["command"] = "dotnet", + ["arguments"] = "run --project ../QuickstartWeatherServer", + } +}); + +var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"])) + .Messages + .AsBuilder() + .UseFunctionInvocation() + .Build(); + +var tools = await mcpClient.ListToolsAsync(); +foreach (var tool in tools) +{ + Console.WriteLine($"Tool: {tool.Name}"); +} + +while (true) +{ + Console.WriteLine("MCP Client Started!"); + Console.WriteLine("Enter a command (or 'exit' to quit):"); + + string? command = Console.ReadLine(); + + if (string.IsNullOrWhiteSpace(command)) + { + continue; + } + if (string.Equals(command, "exit", StringComparison.OrdinalIgnoreCase)) + { + break; + } + + var response = await ProcessQueryAsync(command); + + if (string.IsNullOrWhiteSpace(response)) + { + Console.WriteLine("No response received."); + } + else + { + Console.WriteLine($"Response: {response}"); + } +} + +async Task ProcessQueryAsync(string query) +{ + var options = new ChatOptions + { + MaxOutputTokens = 1000, + ModelId = "claude-3-5-sonnet-20241022", + Tools = [.. tools.Cast()] + }; + + var response = await anthropicClient.GetResponseAsync(query, options); + + return ""; +} \ No newline at end of file diff --git a/samples/QuickstartClient/QuickstartClient.csproj b/samples/QuickstartClient/QuickstartClient.csproj new file mode 100644 index 000000000..b820bedc1 --- /dev/null +++ b/samples/QuickstartClient/QuickstartClient.csproj @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + a4e20a70-5009-4b81-b5b6-780b6d43e78e + + + + + + + + + + + + From 459ae4a92c35ae51152b5a487263cf75b461ea25 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Wed, 26 Mar 2025 15:30:04 +1100 Subject: [PATCH 3/7] Client quickstart for docs --- samples/QuickstartClient/Program.cs | 66 ++++++++++++++--------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs index 09bbcf73b..f6b390660 100644 --- a/samples/QuickstartClient/Program.cs +++ b/samples/QuickstartClient/Program.cs @@ -1,4 +1,4 @@ -using Anthropic.SDK; +using Anthropic.SDK; using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -11,68 +11,68 @@ .AddEnvironmentVariables() .AddUserSecrets(); +var (command, arguments) = args switch +{ + [var script] when script.EndsWith(".py") => ("python", script), + [var script] when script.EndsWith(".js") => ("node", script), + [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", $"run --project {script} --no-build"), + _ => ("dotnet", "run --project ../../../../QuickstartWeatherServer --no-build") +}; + var mcpClient = await McpClientFactory.CreateAsync(new() { - Id = "weather", - Name = "Weather", + Id = "demo-client", + Name = "Demo Client", TransportType = TransportTypes.StdIo, TransportOptions = new() { - ["command"] = "dotnet", - ["arguments"] = "run --project ../QuickstartWeatherServer", + ["command"] = command, + ["arguments"] = arguments, } }); +var tools = await mcpClient.ListToolsAsync(); +foreach (var tool in tools) +{ + Console.WriteLine($"Connected to server with tools: {tool.Name}"); +} + var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"])) .Messages .AsBuilder() .UseFunctionInvocation() .Build(); -var tools = await mcpClient.ListToolsAsync(); -foreach (var tool in tools) +var options = new ChatOptions { - Console.WriteLine($"Tool: {tool.Name}"); -} + MaxOutputTokens = 1000, + ModelId = "claude-3-5-sonnet-20241022", + Tools = [.. tools.Cast()] +}; while (true) { Console.WriteLine("MCP Client Started!"); - Console.WriteLine("Enter a command (or 'exit' to quit):"); + Console.WriteLine("Type your queries or 'quit' to exit."); - string? command = Console.ReadLine(); + string? query = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(command)) + if (string.IsNullOrWhiteSpace(query)) { continue; } - if (string.Equals(command, "exit", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(query, "quit", StringComparison.OrdinalIgnoreCase)) { break; } - var response = await ProcessQueryAsync(command); + var response = await anthropicClient.GetResponseAsync(query, options); - if (string.IsNullOrWhiteSpace(response)) + foreach (var message in response.Messages) { - Console.WriteLine("No response received."); - } - else - { - Console.WriteLine($"Response: {response}"); + Console.WriteLine(message.Text); } } -async Task ProcessQueryAsync(string query) -{ - var options = new ChatOptions - { - MaxOutputTokens = 1000, - ModelId = "claude-3-5-sonnet-20241022", - Tools = [.. tools.Cast()] - }; - - var response = await anthropicClient.GetResponseAsync(query, options); - - return ""; -} \ No newline at end of file +anthropicClient.Dispose(); +await mcpClient.DisposeAsync(); From 6311e743a5ae84850b867c44be78f61fb3882169 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Wed, 26 Mar 2025 16:04:51 +1100 Subject: [PATCH 4/7] Code review feedback --- samples/QuickstartClient/Program.cs | 16 +++-- samples/QuickstartWeatherServer/Program.cs | 15 +++-- .../Tools/WeatherTools.cs | 63 +++++-------------- 3 files changed, 33 insertions(+), 61 deletions(-) diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs index f6b390660..76c09814f 100644 --- a/samples/QuickstartClient/Program.cs +++ b/samples/QuickstartClient/Program.cs @@ -19,7 +19,7 @@ [var script] when Directory.Exists(script) || (File.Exists(script) && script.End _ => ("dotnet", "run --project ../../../../QuickstartWeatherServer --no-build") }; -var mcpClient = await McpClientFactory.CreateAsync(new() +await using var mcpClient = await McpClientFactory.CreateAsync(new() { Id = "demo-client", Name = "Demo Client", @@ -37,7 +37,7 @@ [var script] when Directory.Exists(script) || (File.Exists(script) && script.End Console.WriteLine($"Connected to server with tools: {tool.Name}"); } -var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"])) +using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"])) .Messages .AsBuilder() .UseFunctionInvocation() @@ -47,7 +47,7 @@ [var script] when Directory.Exists(script) || (File.Exists(script) && script.End { MaxOutputTokens = 1000, ModelId = "claude-3-5-sonnet-20241022", - Tools = [.. tools.Cast()] + Tools = [.. tools] }; while (true) @@ -66,13 +66,11 @@ [var script] when Directory.Exists(script) || (File.Exists(script) && script.End break; } - var response = await anthropicClient.GetResponseAsync(query, options); + var response = anthropicClient.GetStreamingResponseAsync(query, options); - foreach (var message in response.Messages) + await foreach (var message in response) { - Console.WriteLine(message.Text); + Console.Write(message.Text); } + Console.WriteLine(); } - -anthropicClient.Dispose(); -await mcpClient.DisposeAsync(); diff --git a/samples/QuickstartWeatherServer/Program.cs b/samples/QuickstartWeatherServer/Program.cs index 31f44ff2d..329a0cbf9 100644 --- a/samples/QuickstartWeatherServer/Program.cs +++ b/samples/QuickstartWeatherServer/Program.cs @@ -1,16 +1,21 @@ -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using ModelContextProtocol; +using System.Net.Http.Headers; var builder = Host.CreateEmptyApplicationBuilder(settings: null); -//builder.Logging.ClearProviders(); -//builder.Logging.AddFilter("Microsoft", LogLevel.Warning); // Adjust the log level as needed - builder.Services.AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); +builder.Services.AddSingleton(_ => +{ + var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") }; + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0")); + return client; +}); + var app = builder.Build(); await app.RunAsync(); diff --git a/samples/QuickstartWeatherServer/Tools/WeatherTools.cs b/samples/QuickstartWeatherServer/Tools/WeatherTools.cs index b39c13052..697b80952 100644 --- a/samples/QuickstartWeatherServer/Tools/WeatherTools.cs +++ b/samples/QuickstartWeatherServer/Tools/WeatherTools.cs @@ -1,6 +1,6 @@ -using ModelContextProtocol.Server; +using ModelContextProtocol.Server; using System.ComponentModel; -using System.Net.Http.Headers; +using System.Net.Http.Json; using System.Text.Json; namespace QuickstartWeatherServer.Tools; @@ -10,19 +10,10 @@ public static class WeatherTools { [McpServerTool, Description("Get weather alerts for a US state.")] public static async Task GetAlerts( + HttpClient client, [Description("The US state to get alerts for.")] string state) { - using HttpClient client = GetWeatherClient(); - - var response = await client.GetAsync($"/alerts/active/area/{state}"); - - if (!response.IsSuccessStatusCode) - { - return "Failed to retrieve alerts."; - } - - var json = await response.Content.ReadAsStringAsync(); - var jsonElement = JsonSerializer.Deserialize(json); + var jsonElement = await client.GetFromJsonAsync($"/alerts/active/area/{state}"); var alerts = jsonElement.GetProperty("features").EnumerateArray(); if (!alerts.Any()) @@ -30,55 +21,33 @@ public static async Task GetAlerts( return "No active alerts for this state."; } - // Process the alerts and return a formatted string - var alertMessages = new List(); - foreach (var alert in alerts) + return string.Join("\n--\n", alerts.Select(alert => { JsonElement properties = alert.GetProperty("properties"); - alertMessages.Add($""" + return $""" Event: {properties.GetProperty("event").GetString()} Area: {properties.GetProperty("areaDesc").GetString()} Severity: {properties.GetProperty("severity").GetString()} Description: {properties.GetProperty("description").GetString()} Instruction: {properties.GetProperty("instruction").GetString()} - """); - } - return string.Join("\n---\n", alertMessages); + """; + })); } [McpServerTool, Description("Get weather forecast for a location.")] public static async Task GetForecast( + HttpClient client, [Description("Latitude of the location.")] double latitude, [Description("Longitude of the location.")] double longitude) { - using HttpClient client = GetWeatherClient(); - var response = await client.GetAsync($"/points/{latitude},{longitude}"); - if (!response.IsSuccessStatusCode) - { - return "Failed to retrieve forecast."; - } - - var json = await response.Content.ReadAsStringAsync(); - var jsonElement = JsonSerializer.Deserialize(json); + var jsonElement = await client.GetFromJsonAsync($"/points/{latitude},{longitude}"); var periods = jsonElement.GetProperty("properties").GetProperty("periods").EnumerateArray(); - // Process the forecast and return a formatted string - var forecastMessages = new List(); - foreach (var period in periods) - { - forecastMessages.Add($""" - {period.GetProperty("name").GetString()} - Temperature: {period.GetProperty("temperature").GetInt32()}°F - Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()} - Forecast: {period.GetProperty("detailedForecast").GetString()} - """); - } - return string.Join("\n---\n", forecastMessages); - } - private static HttpClient GetWeatherClient() - { - var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") }; - client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0")); - return client; + return string.Join("\n---\n", periods.Select(period => $""" + {period.GetProperty("name").GetString()} + Temperature: {period.GetProperty("temperature").GetInt32()}°F + Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()} + Forecast: {period.GetProperty("detailedForecast").GetString()} + """)); } } \ No newline at end of file From 4cf87017237b64a81c819b41486e0176f5e12241 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 27 Mar 2025 12:39:18 +1100 Subject: [PATCH 5/7] Fixing anthropic SDK and improving the prompting --- Directory.Packages.props | 2 +- samples/QuickstartClient/Program.cs | 68 +++++++++++++++++++---------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1c15c8941..554361cbe 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,7 +25,7 @@ - + diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs index 76c09814f..477cc8deb 100644 --- a/samples/QuickstartClient/Program.cs +++ b/samples/QuickstartClient/Program.cs @@ -1,4 +1,4 @@ -using Anthropic.SDK; +using Anthropic.SDK; using Microsoft.Extensions.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -11,18 +11,12 @@ .AddEnvironmentVariables() .AddUserSecrets(); -var (command, arguments) = args switch -{ - [var script] when script.EndsWith(".py") => ("python", script), - [var script] when script.EndsWith(".js") => ("node", script), - [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", $"run --project {script} --no-build"), - _ => ("dotnet", "run --project ../../../../QuickstartWeatherServer --no-build") -}; +var (command, arguments) = GetCommandAndArguments(args); await using var mcpClient = await McpClientFactory.CreateAsync(new() { - Id = "demo-client", - Name = "Demo Client", + Id = "demo-server", + Name = "Demo Server", TransportType = TransportTypes.StdIo, TransportOptions = new() { @@ -50,27 +44,55 @@ [var script] when Directory.Exists(script) || (File.Exists(script) && script.End Tools = [.. tools] }; -while (true) -{ - Console.WriteLine("MCP Client Started!"); - Console.WriteLine("Type your queries or 'quit' to exit."); - - string? query = Console.ReadLine(); +Console.ForegroundColor = ConsoleColor.Green; +Console.WriteLine("MCP Client Started!"); +Console.ResetColor(); +PromptForInput(); +while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase)) +{ if (string.IsNullOrWhiteSpace(query)) { + PromptForInput(); continue; } - if (string.Equals(query, "quit", StringComparison.OrdinalIgnoreCase)) - { - break; - } - - var response = anthropicClient.GetStreamingResponseAsync(query, options); - await foreach (var message in response) + await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options)) { Console.Write(message.Text); } Console.WriteLine(); + + PromptForInput(); +} + +static void PromptForInput() +{ + Console.WriteLine("Enter a command (or 'exit' to quit):"); + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Write("> "); + Console.ResetColor(); } + +/// +/// Determines the command (executable) to run and the script/path to pass to it. This allows different +/// languages/runtime environments to be used as the MCP server. +/// +/// +/// This method uses the file extension of the first argument to determine the command, if it's py, it'll run python, +/// if it's js, it'll run node, if it's a directory or a csproj file, it'll run dotnet. +/// +/// If no arguments are provided, it defaults to running the QuickstartWeatherServer project from the current repo. +/// +/// This method would only be required if you're creating a generic client, such as we use for the quickstart. +/// +static (string command, string arguments) GetCommandAndArguments(string[] args) +{ + return args switch + { + [var script] when script.EndsWith(".py") => ("python", script), + [var script] when script.EndsWith(".js") => ("node", script), + [var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", $"run --project {script} --no-build"), + _ => ("dotnet", "run --project ../../../../QuickstartWeatherServer --no-build") + }; +} \ No newline at end of file From b0de1bb2628305fd51c97bd6d3f2c7e4da508c99 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 27 Mar 2025 12:48:00 +1100 Subject: [PATCH 6/7] Little cleanup --- samples/QuickstartClient/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs index 477cc8deb..364c2b870 100644 --- a/samples/QuickstartClient/Program.cs +++ b/samples/QuickstartClient/Program.cs @@ -59,7 +59,7 @@ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options)) { - Console.Write(message.Text); + Console.Write(message); } Console.WriteLine(); From 6ff9c57bc4e182acd4346c146cb4c1d67123fd8f Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 27 Mar 2025 12:49:36 +1100 Subject: [PATCH 7/7] Code review feedback --- samples/QuickstartWeatherServer/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/samples/QuickstartWeatherServer/Program.cs b/samples/QuickstartWeatherServer/Program.cs index 329a0cbf9..fbc2b44bb 100644 --- a/samples/QuickstartWeatherServer/Program.cs +++ b/samples/QuickstartWeatherServer/Program.cs @@ -16,6 +16,4 @@ return client; }); -var app = builder.Build(); - -await app.RunAsync(); +await builder.Build().RunAsync();