Skip to content

Commit 5171ccc

Browse files
authored
Samples for quickstart docs (#104)
* Server quickstart for docs * WIP * Client quickstart for docs * Code review feedback * Fixing anthropic SDK and improving the prompting * Little cleanup * Code review feedback
1 parent 8be6815 commit 5171ccc

File tree

7 files changed

+223
-1
lines changed

7 files changed

+223
-1
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
2626

2727
<!-- Testing dependencies -->
28-
<PackageVersion Include="Anthropic.SDK" Version="4.7.1" />
28+
<PackageVersion Include="Anthropic.SDK" Version="5.0.0" />
2929
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
3030
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="$(MicrosoftExtensionsAIVersion)" />
3131
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsVersion)" />

ModelContextProtocol.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
4646
EndProject
4747
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithTools", "samples\ChatWithTools\ChatWithTools.csproj", "{0C6D0512-D26D-63D3-5019-C5F7A657B28C}"
4848
EndProject
49+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickstartWeatherServer", "samples\QuickstartWeatherServer\QuickstartWeatherServer.csproj", "{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}"
50+
EndProject
51+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickstartClient", "samples\QuickstartClient\QuickstartClient.csproj", "{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}"
52+
EndProject
4953
Global
5054
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5155
Debug|Any CPU = Debug|Any CPU
@@ -80,6 +84,14 @@ Global
8084
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Debug|Any CPU.Build.0 = Debug|Any CPU
8185
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.ActiveCfg = Release|Any CPU
8286
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.Build.0 = Release|Any CPU
87+
{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
88+
{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
89+
{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
90+
{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF}.Release|Any CPU.Build.0 = Release|Any CPU
91+
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
92+
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
93+
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
94+
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA}.Release|Any CPU.Build.0 = Release|Any CPU
8395
EndGlobalSection
8496
GlobalSection(SolutionProperties) = preSolution
8597
HideSolutionNode = FALSE
@@ -93,6 +105,8 @@ Global
93105
{B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
94106
{20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40}
95107
{0C6D0512-D26D-63D3-5019-C5F7A657B28C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
108+
{4653EB0C-8FC0-98F4-E9C8-220EDA7A69DF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
109+
{0D1552DC-E6ED-4AAC-5562-12F8352F46AA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
96110
EndGlobalSection
97111
GlobalSection(ExtensibilityGlobals) = postSolution
98112
SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89}

samples/QuickstartClient/Program.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Anthropic.SDK;
2+
using Microsoft.Extensions.AI;
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.Hosting;
5+
using ModelContextProtocol.Client;
6+
using ModelContextProtocol.Protocol.Transport;
7+
8+
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
9+
10+
builder.Configuration
11+
.AddEnvironmentVariables()
12+
.AddUserSecrets<Program>();
13+
14+
var (command, arguments) = GetCommandAndArguments(args);
15+
16+
await using var mcpClient = await McpClientFactory.CreateAsync(new()
17+
{
18+
Id = "demo-server",
19+
Name = "Demo Server",
20+
TransportType = TransportTypes.StdIo,
21+
TransportOptions = new()
22+
{
23+
["command"] = command,
24+
["arguments"] = arguments,
25+
}
26+
});
27+
28+
var tools = await mcpClient.ListToolsAsync();
29+
foreach (var tool in tools)
30+
{
31+
Console.WriteLine($"Connected to server with tools: {tool.Name}");
32+
}
33+
34+
using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
35+
.Messages
36+
.AsBuilder()
37+
.UseFunctionInvocation()
38+
.Build();
39+
40+
var options = new ChatOptions
41+
{
42+
MaxOutputTokens = 1000,
43+
ModelId = "claude-3-5-sonnet-20241022",
44+
Tools = [.. tools]
45+
};
46+
47+
Console.ForegroundColor = ConsoleColor.Green;
48+
Console.WriteLine("MCP Client Started!");
49+
Console.ResetColor();
50+
51+
PromptForInput();
52+
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
53+
{
54+
if (string.IsNullOrWhiteSpace(query))
55+
{
56+
PromptForInput();
57+
continue;
58+
}
59+
60+
await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
61+
{
62+
Console.Write(message);
63+
}
64+
Console.WriteLine();
65+
66+
PromptForInput();
67+
}
68+
69+
static void PromptForInput()
70+
{
71+
Console.WriteLine("Enter a command (or 'exit' to quit):");
72+
Console.ForegroundColor = ConsoleColor.Cyan;
73+
Console.Write("> ");
74+
Console.ResetColor();
75+
}
76+
77+
/// <summary>
78+
/// Determines the command (executable) to run and the script/path to pass to it. This allows different
79+
/// languages/runtime environments to be used as the MCP server.
80+
/// </summary>
81+
/// <remarks>
82+
/// This method uses the file extension of the first argument to determine the command, if it's py, it'll run python,
83+
/// if it's js, it'll run node, if it's a directory or a csproj file, it'll run dotnet.
84+
///
85+
/// If no arguments are provided, it defaults to running the QuickstartWeatherServer project from the current repo.
86+
///
87+
/// This method would only be required if you're creating a generic client, such as we use for the quickstart.
88+
/// </remarks>
89+
static (string command, string arguments) GetCommandAndArguments(string[] args)
90+
{
91+
return args switch
92+
{
93+
[var script] when script.EndsWith(".py") => ("python", script),
94+
[var script] when script.EndsWith(".js") => ("node", script),
95+
[var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", $"run --project {script} --no-build"),
96+
_ => ("dotnet", "run --project ../../../../QuickstartWeatherServer --no-build")
97+
};
98+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<UserSecretsId>a4e20a70-5009-4b81-b5b6-780b6d43e78e</UserSecretsId>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="Anthropic.SDK" />
17+
<PackageReference Include="Microsoft.Extensions.Hosting" />
18+
</ItemGroup>
19+
20+
</Project>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Hosting;
3+
using ModelContextProtocol;
4+
using System.Net.Http.Headers;
5+
6+
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
7+
8+
builder.Services.AddMcpServer()
9+
.WithStdioServerTransport()
10+
.WithToolsFromAssembly();
11+
12+
builder.Services.AddSingleton(_ =>
13+
{
14+
var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
15+
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
16+
return client;
17+
});
18+
19+
await builder.Build().RunAsync();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.Hosting" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using ModelContextProtocol.Server;
2+
using System.ComponentModel;
3+
using System.Net.Http.Json;
4+
using System.Text.Json;
5+
6+
namespace QuickstartWeatherServer.Tools;
7+
8+
[McpServerToolType]
9+
public static class WeatherTools
10+
{
11+
[McpServerTool, Description("Get weather alerts for a US state.")]
12+
public static async Task<string> GetAlerts(
13+
HttpClient client,
14+
[Description("The US state to get alerts for.")] string state)
15+
{
16+
var jsonElement = await client.GetFromJsonAsync<JsonElement>($"/alerts/active/area/{state}");
17+
var alerts = jsonElement.GetProperty("features").EnumerateArray();
18+
19+
if (!alerts.Any())
20+
{
21+
return "No active alerts for this state.";
22+
}
23+
24+
return string.Join("\n--\n", alerts.Select(alert =>
25+
{
26+
JsonElement properties = alert.GetProperty("properties");
27+
return $"""
28+
Event: {properties.GetProperty("event").GetString()}
29+
Area: {properties.GetProperty("areaDesc").GetString()}
30+
Severity: {properties.GetProperty("severity").GetString()}
31+
Description: {properties.GetProperty("description").GetString()}
32+
Instruction: {properties.GetProperty("instruction").GetString()}
33+
""";
34+
}));
35+
}
36+
37+
[McpServerTool, Description("Get weather forecast for a location.")]
38+
public static async Task<string> GetForecast(
39+
HttpClient client,
40+
[Description("Latitude of the location.")] double latitude,
41+
[Description("Longitude of the location.")] double longitude)
42+
{
43+
var jsonElement = await client.GetFromJsonAsync<JsonElement>($"/points/{latitude},{longitude}");
44+
var periods = jsonElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
45+
46+
return string.Join("\n---\n", periods.Select(period => $"""
47+
{period.GetProperty("name").GetString()}
48+
Temperature: {period.GetProperty("temperature").GetInt32()}°F
49+
Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
50+
Forecast: {period.GetProperty("detailedForecast").GetString()}
51+
"""));
52+
}
53+
}

0 commit comments

Comments
 (0)