I Built My Own MCP Server in C# and Added It to Claude Code as a Tool
Introduction
AI coding assistants such as Claude Code let you add custom tools through a protocol called MCP (Model Context Protocol).
“Hooking up your own tools” sounds hard, but with the official C# SDK it’s surprisingly simple. This time I used a weather tool as an example and tried building an MCP server from scratch and connecting it to Claude Code.
What Is MCP?
MCP (Model Context Protocol) is an open protocol Anthropic released in 2024. It lets AI talk to external tools and data sources through a standard interface.
Claude Code ←→ MCPプロトコル ←→ 自作MCPサーバー ←→ 外部API・DBなど
By writing your own MCP server, you can give the AI things like “a tool that calls an internal API” or “project-specific operations.”
What You Need
- .NET 9 SDK
- Claude Code (with access to edit its settings file)
Creating the Project
Create it as a console app.
dotnet new console -n WeatherMcp
cd WeatherMcp
Add the NuGet packages.
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting
What is
ModelContextProtocol?
The official C# MCP SDK, maintained by Microsoft. You define tools with attributes, which keeps the implementation simple.
Implementation
Program.cs
Replace the whole of Program.cs with the following.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMcpServer()
.WithStdioServerTransport()
.WithTools<WeatherTools>();
await builder.Build().RunAsync();
WeatherTools.cs
Add a new WeatherTools.cs file to the project.
using ModelContextProtocol.Server;
using System.ComponentModel;
[McpServerToolType]
public class WeatherTools
{
private static readonly HttpClient _http = new();
[McpServerTool]
[Description("指定した都市の現在の天気(天候・気温・湿度)を取得します")]
public static async Task<string> GetWeather(
[Description("都市名(英語)例: Tokyo, Osaka, New York")] string city)
{
var url = $"https://wttr.in/{Uri.EscapeDataString(city)}?format=%C+%t+%h";
var result = await _http.GetStringAsync(url);
return $"{city}の天気: {result.Trim()}";
}
}
Key points:
| Attribute | Role |
|---|---|
[McpServerToolType] | Declares that this class contains tools |
[McpServerTool] | Exposes this method as a tool |
[Description] | The description for the AI. Important for the AI to understand how to use the tool |
The weather data comes from wttr.in, a handy service that needs no API key. What the format string means:
| Symbol | Content |
|---|---|
%C | Weather description (Sunny, Cloudy, etc.) |
%t | Temperature |
%h | Humidity |
Testing It on Its Own
Before connecting it to Claude Code, check that it works by itself.
dotnet run
Once it starts, paste the following into standard input and press Enter.
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }
If you get back information about the GetWeather tool, it works. Press Ctrl+C to exit.
Registering It with Claude Code
Add it to a Claude Code settings file.
- Per project:
.claude/settings.json - Global:
~/.claude/settings.json
{
"mcpServers": {
"weather": {
"command": "dotnet",
"args": ["run", "--project", "C:/path/to/WeatherMcp"]
}
}
}
Replace C:/path/to/WeatherMcp with the actual path.
Making startup faster
dotnet runbuilds every time, so it’s slow to start. I recommend generating an exe withdotnet publishand pointing to it directly.dotnet publish -c Release -r win-x64 --self-contained{ "mcpServers": { "weather": { "command": "C:/path/to/WeatherMcp/bin/Release/net9.0/win-x64/publish/WeatherMcp.exe", "args": [] } } }
Trying It Out
Restart Claude Code after configuring it, and the weather server connects. Then just ask:
> 東京と大阪の今日の天気を比較して
(That asks it to compare today’s weather in Tokyo and Osaka.) Claude Code calls GetWeather twice and sums up the results it got from wttr.in.
[Tool: GetWeather(city="Tokyo")] → Overcast +18°C 72%
[Tool: GetWeather(city="Osaka")] → Partly cloudy +20°C 65%
東京は曇り・18℃・湿度72%、大阪は晴れ時々曇り・20℃・湿度65%です。
大阪の方が2℃高く、湿度は低めです。
The answer says Tokyo is overcast at 18°C with 72% humidity, Osaka is partly cloudy at 20°C with 65% humidity, and Osaka is 2°C warmer and less humid. The fun part is that you can see the tool calls happening.
Summary
Looking back at the steps for building an MCP server in C#:
- Add the
ModelContextProtocolNuGet package - Define tools with
[McpServerToolType]/[McpServerTool] - Start the server with
AddMcpServer().WithStdioServerTransport().WithTools<T>() - Register it in
settings.jsonand call it from Claude Code
Overall there’s very little code, and it was pleasant that just adding attributes is enough for something to be recognized as a tool. Swap the weather API for a tool that calls an internal REST API or runs SQL queries, and it looks perfectly usable for real work. It feels like growing my own personal AI toolkit, and I want to keep experimenting with it.
Related Posts
I Measured dev-orchestra: Time per Request, and Findings by Number of Reviewers
With dev-orchestra 1.2.1, I made one small feature request and measured how long it took (about 6 minutes, not counting my approval) and how many findings were reported and accepted with 1, 2, and 4 reviewers. More reviewers meant more accepted findings, but the extra ones were about tests.
Giving dev-orchestra Standing Instructions per Role
I tried instructions, added in dev-orchestra 1.2.1. Standing instructions for the implementer or reviewers written in the repository go into every prompt. I check, with real output, how they are shown, where they go, and how a change to the instructions is treated.
[Released] dev-orchestra: Parallel Work Units and Per-Role Instructions v1.1.0 - v1.2.1
A roundup of dev-orchestra's changes from v1.1.0 to v1.2.1: running a plan as work units side by side, standing instructions per role, questions before the design, and settings for progress reports. If you capture its output on Windows, note the encoding change.