Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />

<!-- 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="Microsoft.Extensions.AI.OpenAI" Version="$(MicrosoftExtensionsAIVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsVersion)" />
Expand Down
14 changes: 14 additions & 0 deletions ModelContextProtocol.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down
98 changes: 98 additions & 0 deletions samples/QuickstartClient/Program.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions samples/QuickstartClient/QuickstartClient.csproj
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions samples/QuickstartWeatherServer/Program.cs
Original file line number Diff line number Diff line change
@@ -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();
18 changes: 18 additions & 0 deletions samples/QuickstartWeatherServer/QuickstartWeatherServer.csproj
Original file line number Diff line number Diff line change
@@ -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 changes: 53 additions & 0 deletions samples/QuickstartWeatherServer/Tools/WeatherTools.cs
Original file line number Diff line number Diff line change
@@ -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()}
"""));
}
}