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
This commit is contained in:
parent
8be681543d
commit
5171ccc3c3
7 changed files with 223 additions and 1 deletions
|
|
@ -25,7 +25,7 @@
|
||||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||||
|
|
||||||
<!-- Testing dependencies -->
|
<!-- Testing dependencies -->
|
||||||
<PackageVersion Include="Anthropic.SDK" Version="4.7.1" />
|
<PackageVersion Include="Anthropic.SDK" Version="5.0.0" />
|
||||||
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
|
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="$(MicrosoftExtensionsAIVersion)" />
|
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="$(MicrosoftExtensionsAIVersion)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsVersion)" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithTools", "samples\ChatWithTools\ChatWithTools.csproj", "{0C6D0512-D26D-63D3-5019-C5F7A657B28C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatWithTools", "samples\ChatWithTools\ChatWithTools.csproj", "{0C6D0512-D26D-63D3-5019-C5F7A657B28C}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{0C6D0512-D26D-63D3-5019-C5F7A657B28C}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -93,6 +105,8 @@ Global
|
||||||
{B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
{20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40}
|
{20AACB9B-307D-419C-BCC6-1C639C402295} = {1288ADA5-1BF1-4A7F-A33E-9EA29097AA40}
|
||||||
{0C6D0512-D26D-63D3-5019-C5F7A657B28C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89}
|
SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89}
|
||||||
|
|
|
||||||
98
samples/QuickstartClient/Program.cs
Normal file
98
samples/QuickstartClient/Program.cs
Normal file
|
|
@ -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<Program>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
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")
|
||||||
|
};
|
||||||
|
}
|
||||||
20
samples/QuickstartClient/QuickstartClient.csproj
Normal file
20
samples/QuickstartClient/QuickstartClient.csproj
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UserSecretsId>a4e20a70-5009-4b81-b5b6-780b6d43e78e</UserSecretsId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Anthropic.SDK" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
19
samples/QuickstartWeatherServer/Program.cs
Normal file
19
samples/QuickstartWeatherServer/Program.cs
Normal file
|
|
@ -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();
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
53
samples/QuickstartWeatherServer/Tools/WeatherTools.cs
Normal file
53
samples/QuickstartWeatherServer/Tools/WeatherTools.cs
Normal file
|
|
@ -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<string> GetAlerts(
|
||||||
|
HttpClient client,
|
||||||
|
[Description("The US state to get alerts for.")] string state)
|
||||||
|
{
|
||||||
|
var jsonElement = await client.GetFromJsonAsync<JsonElement>($"/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<string> GetForecast(
|
||||||
|
HttpClient client,
|
||||||
|
[Description("Latitude of the location.")] double latitude,
|
||||||
|
[Description("Longitude of the location.")] double longitude)
|
||||||
|
{
|
||||||
|
var jsonElement = await client.GetFromJsonAsync<JsonElement>($"/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()}
|
||||||
|
"""));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue