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/ModelContextProtocol.sln b/ModelContextProtocol.sln
index 4d2b4b5a7..1f7a475b6 100644
--- a/ModelContextProtocol.sln
+++ b/ModelContextProtocol.sln
@@ -46,6 +46,10 @@ 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
+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
@@ -80,6 +84,14 @@ 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
+ {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
@@ -93,6 +105,8 @@ 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}
+ {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..364c2b870
--- /dev/null
+++ b/samples/QuickstartClient/Program.cs
@@ -0,0 +1,98 @@
+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 (command, arguments) = GetCommandAndArguments(args);
+
+await using var mcpClient = await McpClientFactory.CreateAsync(new()
+{
+ Id = "demo-server",
+ Name = "Demo Server",
+ TransportType = TransportTypes.StdIo,
+ TransportOptions = new()
+ {
+ ["command"] = command,
+ ["arguments"] = arguments,
+ }
+});
+
+var tools = await mcpClient.ListToolsAsync();
+foreach (var tool in tools)
+{
+ Console.WriteLine($"Connected to server with tools: {tool.Name}");
+}
+
+using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
+ .Messages
+ .AsBuilder()
+ .UseFunctionInvocation()
+ .Build();
+
+var options = new ChatOptions
+{
+ MaxOutputTokens = 1000,
+ ModelId = "claude-3-5-sonnet-20241022",
+ Tools = [.. tools]
+};
+
+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;
+ }
+
+ await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
+ {
+ Console.Write(message);
+ }
+ 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
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
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/QuickstartWeatherServer/Program.cs b/samples/QuickstartWeatherServer/Program.cs
new file mode 100644
index 000000000..fbc2b44bb
--- /dev/null
+++ b/samples/QuickstartWeatherServer/Program.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using ModelContextProtocol;
+using System.Net.Http.Headers;
+
+var builder = Host.CreateEmptyApplicationBuilder(settings: null);
+
+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;
+});
+
+await builder.Build().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..697b80952
--- /dev/null
+++ b/samples/QuickstartWeatherServer/Tools/WeatherTools.cs
@@ -0,0 +1,53 @@
+using ModelContextProtocol.Server;
+using System.ComponentModel;
+using System.Net.Http.Json;
+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(
+ HttpClient client,
+ [Description("The US state to get alerts for.")] string state)
+ {
+ var jsonElement = await client.GetFromJsonAsync($"/alerts/active/area/{state}");
+ var alerts = jsonElement.GetProperty("features").EnumerateArray();
+
+ if (!alerts.Any())
+ {
+ return "No active alerts for this state.";
+ }
+
+ return string.Join("\n--\n", alerts.Select(alert =>
+ {
+ JsonElement properties = alert.GetProperty("properties");
+ 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()}
+ """;
+ }));
+ }
+
+ [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)
+ {
+ var jsonElement = await client.GetFromJsonAsync($"/points/{latitude},{longitude}");
+ var periods = jsonElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
+
+ 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