C# quickstarts (#216)
* Server quickstart docs for C# * C# client quickstart
This commit is contained in:
parent
fb63fef707
commit
c768dcee44
2 changed files with 413 additions and 0 deletions
|
|
@ -1365,6 +1365,184 @@ If you see:
|
|||
|
||||
</Tab>
|
||||
|
||||
<Tab title="C#">
|
||||
[You can find the complete code for this tutorial here.](https://github.io/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
|
||||
|
||||
## System Requirements
|
||||
Before starting, ensure your system meets these requirements:
|
||||
- .NET 8.0 or higher
|
||||
- Anthropic API key (Claude)
|
||||
- Windows, Linux, or MacOS
|
||||
|
||||
## Setting up your environment
|
||||
|
||||
First, create a new .NET project:
|
||||
```bash
|
||||
dotnet new console -n QuickstartClient
|
||||
cd QuickstartClient
|
||||
```
|
||||
|
||||
Then, add the required dependencies to your project:
|
||||
```bash
|
||||
dotnet add package ModelContextProtocol --preview
|
||||
dotnet add package Anthropic.SDK
|
||||
dotnet add package Microsoft.Extensions.Hosting
|
||||
```
|
||||
|
||||
## Setting up your API key
|
||||
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
|
||||
|
||||
```bash
|
||||
dotnet user-secrets init
|
||||
dotnet user-secrets set "ANTHROPIC_API_KEY" "<your key here>"
|
||||
```
|
||||
|
||||
## Creating the Client
|
||||
### Basic Client Structure
|
||||
First, let's setup the basic client class:
|
||||
```csharp
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
|
||||
|
||||
builder.Configuration
|
||||
.AddUserSecrets<Program>();
|
||||
```
|
||||
|
||||
This creates the beginnings of a .NET console application that can read the API key from user secrets.
|
||||
|
||||
Next, we'll setup the MCP Client:
|
||||
|
||||
```csharp
|
||||
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"),
|
||||
_ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
|
||||
};
|
||||
|
||||
var mcpClient = await McpClientFactory.CreateAsync(new()
|
||||
{
|
||||
Id = "demo-client",
|
||||
Name = "Demo Client",
|
||||
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}");
|
||||
}
|
||||
```
|
||||
<Note>
|
||||
Be sure to add the `using` statements for the namespaces:
|
||||
```csharp
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
```
|
||||
</Note>
|
||||
|
||||
This configures a MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
|
||||
|
||||
### Query processing logic
|
||||
Now let's add the core functionality for processing queries and handling tool calls:
|
||||
|
||||
```csharp
|
||||
IChatClient 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.Cast<AITool>()]
|
||||
};
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("MCP Client Started!");
|
||||
Console.WriteLine("Type your queries or 'quit' to exit.");
|
||||
|
||||
string? query = Console.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (string.Equals(query, "quit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var response = await anthropicClient.GetResponseAsync(query, options);
|
||||
|
||||
foreach (var message in response.Messages)
|
||||
{
|
||||
Console.WriteLine(message.Text);
|
||||
}
|
||||
}
|
||||
|
||||
anthropicClient.Dispose();
|
||||
await mcpClient.DisposeAsync();
|
||||
```
|
||||
|
||||
## Key Components Explained
|
||||
|
||||
### 1. Client Initialization
|
||||
* The client is initialized using `McpClientFactory.CreateAsync()`, which sets up the transport type and command to run the server.
|
||||
|
||||
### 2. Server Connection
|
||||
* Supports Python, Node.js, and .NET servers.
|
||||
* The server is started using the command specified in the arguments.
|
||||
* Configures to use stdio for communication with the server.
|
||||
* Initializes the session and available tools.
|
||||
|
||||
### 3. Query Processing
|
||||
* Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
|
||||
* Configures the `IChatClient` to use automatic tool (function) invocation.
|
||||
* The client reads user input and sends it to the server.
|
||||
* The server processes the query and returns a response.
|
||||
* The response is displayed to the user.
|
||||
|
||||
### Running the Client
|
||||
To run your client with any MCP server:
|
||||
```bash
|
||||
dotnet run -- path/to/server.csproj # dotnet server
|
||||
dotnet run -- path/to/server.py # python server
|
||||
dotnet run -- path/to/server.js # node server
|
||||
```
|
||||
<Note>
|
||||
If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
|
||||
</Note>
|
||||
|
||||
The client will:
|
||||
|
||||
1. Connect to the specified server
|
||||
2. List available tools
|
||||
3. Start an interactive chat session where you can:
|
||||
- Enter queries
|
||||
- See tool executions
|
||||
- Get responses from Claude
|
||||
4. Exit the session when done
|
||||
|
||||
Here's an example of what it should look like it connected to a weather server quickstart:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/quickstart-dotnet-client.png" />
|
||||
</Frame>
|
||||
|
||||
</Tab>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Next steps
|
||||
|
|
|
|||
|
|
@ -1406,6 +1406,241 @@ This tells Claude for Desktop:
|
|||
|
||||
Save the file, and restart **Claude for Desktop**.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title='C#'>
|
||||
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
|
||||
|
||||
### Prerequisite knowledge
|
||||
This quickstart assumes you have familiarity with:
|
||||
- C#
|
||||
- LLMs like Claude
|
||||
- .NET 8 or higher
|
||||
|
||||
### System requirements
|
||||
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
|
||||
|
||||
### Set up your environment
|
||||
First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
|
||||
```bash
|
||||
dotnet --version
|
||||
```
|
||||
Now, let's create and set up your project:
|
||||
<CodeGroup>
|
||||
```bash MacOS/Linux
|
||||
# Create a new directory for our project
|
||||
mkdir weather
|
||||
cd weather
|
||||
# Initialize a new C# project
|
||||
dotnet new console
|
||||
```
|
||||
|
||||
```powershell Windows
|
||||
# Create a new directory for our project
|
||||
mkdir weather
|
||||
cd weather
|
||||
# Initialize a new C# project
|
||||
dotnet new console
|
||||
```
|
||||
</CodeGroup>
|
||||
After running `dotnet new console`, you will be presented with a new C# project.
|
||||
You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
|
||||
Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
|
||||
After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
|
||||
|
||||
```bash
|
||||
# Add the Model Context Protocol SDK NuGet package
|
||||
dotnet add package ModelContextProtocol --preview
|
||||
# Add the .NET Hosting NuGet package
|
||||
dotnet add package Microsoft.Extensions.Hosting
|
||||
```
|
||||
Now let’s dive into building your server.
|
||||
|
||||
## Building your server
|
||||
|
||||
Open the `Program.cs` file in your project and replace its contents with the following code:
|
||||
|
||||
```csharp
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using ModelContextProtocol;
|
||||
|
||||
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
|
||||
builder.Services.AddMcpServer()
|
||||
.WithStdioServerTransport()
|
||||
.WithToolsFromAssembly();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
await app.RunAsync();
|
||||
```
|
||||
<Note>
|
||||
When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only neccessary for servers using STDIO transport.
|
||||
</Note>
|
||||
|
||||
This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
|
||||
|
||||
### Weather API helper functions
|
||||
Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
|
||||
|
||||
```csharp
|
||||
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<string> GetAlerts(
|
||||
[Description("The US state to get alerts for.")] string state)
|
||||
{
|
||||
using HttpClient client = GetWeatherClient();
|
||||
|
||||
var response = await client.GetAsync($"/alerts/active/area/{state}");
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var jsonElement = JsonSerializer.Deserialize<JsonElement>(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<string>();
|
||||
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<string> 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<JsonElement>(json);
|
||||
var periods = jsonElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
|
||||
// Process the forecast and return a formatted string
|
||||
var forecastMessages = new List<string>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Running the server
|
||||
Finally, run the server using the following command:
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
```
|
||||
This will start the server and listen for incoming requests on standard input/output.
|
||||
|
||||
## Testing your server with Claude for Desktop
|
||||
<Note>
|
||||
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/quickstart/client) tutorial to build an MCP client that connects to the server we just built.
|
||||
</Note>
|
||||
|
||||
First, make sure you have Claude for Desktop installed. [You can install the latest version
|
||||
here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
|
||||
We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
|
||||
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
|
||||
<Tabs>
|
||||
<Tab title="MacOS/Linux">
|
||||
```bash
|
||||
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
```powershell
|
||||
code $env:AppData\Claude\claude_desktop_config.json
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
|
||||
In this case, we'll add our single weather server like so:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="MacOS/Linux">
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"run",
|
||||
"--project",
|
||||
"/ABSOLUTE/PATH/TO/PROJECT",
|
||||
"--no-build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Windows">
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"run",
|
||||
"--project",
|
||||
"C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
|
||||
"--no-build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
This tells Claude for Desktop:
|
||||
1. There's an MCP server named "weather"
|
||||
2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
|
||||
Save the file, and restart **Claude for Desktop**.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue