Go to list of users who liked
Share on X(Twitter)
Share on Facebook
MCP 基礎知識 & MCP 公式の MCP サーバ自作チュートリアル (C#) やってみた
この記事は 100% 人間(私)が書きました。
(というか もともと私の記事は全て「自分の勉強メモ」の意味が強いので、(特別な記載のない限り)全記事 人間 100 です。)
MCP 公式ドキュメントの
MCP サーバ自作チュートリアルに
C# があるのを発見しました!(Python しか無いと思ってた)
チュートリアル
コード
ちょっとやってみるか〜
作るもの
シンプルなお天気予報 MCP サーバ。
2 つの ツール を持ちます:
- get_alerts
- get_forecast
(これもともと Python だったので、ネーミングが Python っぽい(スネークケース)ですね)
事前知識
事前知識①: MCP の機能 3 兄弟
MCP の 3 つのプリミティブな機能:
| # | 機能名 | 説明 | 原文 |
|---|---|---|---|
| 1 | リソース | 読み取り専用のコンテンツ | File-like data that can be read by clients (like API responses or file contents) |
| 2 | ツール | LLM から (ユーザの承認によって) 呼ばれる (call) 機能 (functions)。この三兄弟で一番使うと思う | Functions that can be called by the LLM (with user approval) |
| 3 | プロンプト | プロンプトテンプレートを提供。(良質なプロンプトは長くなりがちだけど、このテンプレート機能により クソ長呪文プロンプト をユーザが使いやすくなる) | Pre-written templates that help users accomplish specific tasks |
このチュートリアルでは、↑の MCP 機能三兄弟の ②ツール に焦点を当てていきます。
事前知識②: MCP の通信(トランスポート)
通信規格
MCP サーバと MCP クライアントの間で情報のやりとりする仕組み(トランスポート)については、
- 標準入出力 (stdio)
- Streamable HTTP (すとりーまぶる HTTP) (旧: HTTP + SSE)
の 2 つが定義されています。
| # | 通信方法 | いつ使う |
|---|---|---|
| 1 | 標準入出力 (stdio) | ローカル MCP サーバ(MCP ホストと同一コンピュータ上に MCP サーバが起動している) |
| 2 | Streamable HTTP /HTTP + SSE | リモート MCP サーバ (MCP ホストと MCP サーバの通信を HTTP を介してやりとり) |
送受信されるデータの形式: JSON-RPC 2.0
送受信される全てのデータの形式はJSON-RPC 2.0 という形式にしたがっています。
呼び出したいメソッド名とその引数を JSON オブジェクトとして送ります。
JSON-RPC は、 RPC (Remote Procedure Call) を実現するプロトコルのひとつです。
Remote Procedure Call (遠隔 手続き 呼び出し) とは、
他のコンピュータのプログラムを呼び出して実行させるための技術、またはそのためのプロトコルです。
JSON-RPC 2.0 の仕様については、
皆様は 実際の中身を見た方が早いと思うので
公式ドキュメントから リクエスト/レスポンス例を引用します
{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1}これは「subtract」という関数に、引数 42, 23 を渡しているということです
(subtract とは、英語で「引き算」を意味します。)
{"jsonrpc":"2.0","result":19,"id":1}subtract(42, 23) の結果 (result) の19 が返ってきています。
ちなみに"jsonrpc": "2.0" ですが、
1.0 では このフィールドは存在しなかったのですが
2.0 では (互換性問題を解消するため) jsonrpc フィールドが追加されました。(「2.2 Differences between 1.0 and 2.0」より)
事前注意:標準入出力ベースのサーバを作るときは標準出力を使わない!
標準入出力ベースのサーバ (STDIO-based servers) を作るときは
標準出力 (Standard output) を使わないようにしましょう。
標準出力を書くと JSON-RPC messages に干渉し、サーバを壊してしまうおそれがあります。
(HTTP-based servers 実装時は気にしなくて良いです。HTTP レスポンスに何も干渉しないので)
チュートリアル開始
.NET 環境の確認
このチュートリアルでは .NET 8 以上が求められているので、環境を確認しましょう。
dotnet--version空の C# プロジェクトを new する
# 作業ディレクトリを作るmkdirweathercdweather# C# プロジェクトの初期化dotnet new console無事に C# コンソールプロジェクトの new が完了したら、
そのプロジェクトを Visual Studio や VS Code などで開くと、
こんな感じになります↓(画面は VS Code のもの)
諸々のパッケージを入れる
MCP SDK と .NET Hosting の NuGet パッケージを入れます
(もし git 管理する人は、この辺で.gitignore を追加しておきましょう)
# MCP SDK の NuGet package を入れるdotnet add package ModelContextProtocol--prerelease# .NET Hosting の NuGet package を入れるdotnet add package Microsoft.Extensions.Hostingサーバの構築
Program.cs を開き、中身をこれに書き換えます
usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Hosting;usingModelContextProtocol;usingSystem.Net.Http.Headers;varbuilder=Host.CreateEmptyApplicationBuilder(settings:null);builder.Services.AddMcpServer().WithStdioServerTransport().WithToolsFromAssembly();builder.Services.AddSingleton(_=>{varclient=newHttpClient(){BaseAddress=newUri("https://api.weather.gov")};client.DefaultRequestHeaders.UserAgent.Add(newProductInfoHeaderValue("weather-tool","1.0"));returnclient;});varapp=builder.Build();awaitapp.RunAsync();このコードは、Model Context Protocol SDK を使用して
標準入出力トランスポートを備えた MCP サーバを作成しています。
標準入トランスポートのサーバ (servers using STDIO transport) を作る際は、ApplicationHostBuilder を作るときCreateDefaultBuilder ではなくCreateEmptyApplicationBuilder を使うようにしましょう。
これにより、サーバがコンソールに余計なメッセージを書き込まなくなります。
天気 API ヘルパー関数
JSON リクエストの処理を簡素化する、
HttpClient の拡張クラスを作ります。
Tools というフォルダを作って、その下にHttpClientExt.cs を作ります。
usingSystem.Text.Json;internalstaticclassHttpClientExt{publicstaticasyncTask<JsonDocument>ReadJsonDocumentAsync(thisHttpClientclient,stringrequestUri){usingvarresponse=awaitclient.GetAsync(requestUri);response.EnsureSuccessStatusCode();returnawaitJsonDocument.ParseAsync(awaitresponse.Content.ReadAsStreamAsync());}}次に、
National Weather Service API からの応答をクエリして変換するハンドラーを持つクラスを定義します。
Tools/WeatherTools.cs を作成します
usingModelContextProtocol.Server;usingSystem.ComponentModel;usingSystem.Globalization;usingSystem.Text.Json;namespaceQuickstartWeatherServer.Tools;[McpServerToolType]publicstaticclassWeatherTools{[McpServerTool,Description("Get weather alerts for a US state.")]publicstaticasyncTask<string>GetAlerts(HttpClientclient,[Description("The US state to get alerts for.")]stringstate){usingvarjsonDocument=awaitclient.ReadJsonDocumentAsync($"/alerts/active/area/{state}");varjsonElement=jsonDocument.RootElement;varalerts=jsonElement.GetProperty("features").EnumerateArray();if(!alerts.Any()){return"No active alerts for this state.";}returnstring.Join("\n--\n",alerts.Select(alert=>{JsonElementproperties=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.")]publicstaticasyncTask<string>GetForecast(HttpClientclient,[Description("Latitude of the location.")]doublelatitude,[Description("Longitude of the location.")]doublelongitude){varpointUrl=string.Create(CultureInfo.InvariantCulture,$"/points/{latitude},{longitude}");usingvarjsonDocument=awaitclient.ReadJsonDocumentAsync(pointUrl);varforecastUrl=jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()??thrownewException($"No forecast URL provided by{client.BaseAddress}points/{latitude},{longitude}");usingvarforecastDocument=awaitclient.ReadJsonDocumentAsync(forecastUrl);varperiods=forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();returnstring.Join("\n---\n",periods.Select(period=>$"""{period.GetProperty("name").GetString()}Temperature:{period.GetProperty("temperature").GetInt32()}°FWind:{period.GetProperty("windSpeed").GetString()}{period.GetProperty("windDirection").GetString()}Forecast:{period.GetProperty("detailedForecast").GetString()}"""));}}サーバの実行
最後に、次のコマンドを使用してサーバを実行します。
dotnet runこれにより、サーバーが起動し、標準入出力でのリクエストをリッスンします。
自作サーバのテスト:MCP インスペクターを使う
原文では初手 Claude Desktop でテストしていますが、
まずは MCP インスペクターで疎通確認をしたほうが良いと思いましたので、追記します。
MCP インスペクターとは、MCP サーバのテスト・デバッグするための開発者ツールです。
実行するプロジェクトと同じディレクトリでこれを叩きます
npx @modelcontextprotocol/inspectorするとローカルで web サーバが起動してブラウザが立ち上がります。
接続設定を以下のように入れます:
| 項目 | 値 |
|---|---|
| Transport Type | STDIO |
| Command | dotnet |
| Arguments | run |
そして(MCP サーバが dot net run された状態で)「Connect」を押すと
接続されます。
右の「List Tools」を押すとエンドポイントの一覧が表示されます。(ここでは「get_forecast」と「get_alearts」の2つ)
ちゃんと動いているようですね!
Claude Desktop で、自作サーバのテストをする
今回は、MCP クライアントとして Claude Desktop を使います。
Claude Desktop の設定ファイルclaude_desktop_config.json を編集します。
VS Code で編集する場合のコマンドはこれです:
code ~/Library/Application\Support/Claude/claude_desktop_config.jsoncode$env:AppData\Claude\claude_desktop_config.json{"mcpServers":{"weather":{"command":"dotnet","args":["run","--project","/プロジェクトへの絶対PATH","--no-build"]}}}トラブルシューティング
Claude Desktop から MCP サーバに繋がらない
原文ではコマンド記載についてdotnet だけですが、
私の環境だとこれでは動かず、dotnet をフルパス指定 (/usr/local/share/dotnet/dotnet) (mac) したら動きました。
詳細は別記事に書きました
Claude Desktop との接続を確認
テキストボックスの左下の "Search and tools" アイコンを押します。
そこで「weather」と自作 MCP サーバが出てきたら OK です。
自作 MCP サーバを叩いてみる
ツール設定アイコンが表示された場合は、Claude Desktop で次のコマンドを実行してサーバーをテストできます。
- サクラメントの天気はどうですか?
- テキサス州で発令中の気象警報は何ですか?
するとこんなダイアログが表示されます
「"weather" という MCP サーバの "get_forecast" というツールを使用してもいいですか?」
「許可する」を押すと、MCP サーバが叩かれて、
こんな感じになります
やったぜ!
(ちなみに、この MCP サーバが叩いているお天気 API は、米国だけしか対応していないので、テストできるのは米国のエリアのみです)
何が起きているのか
- 人間 (あなた) が Claude Desktop で質問を入力する
- クライアント (Claude Desktop) があなたの質問を Claude (LLM) に送る
- Claude (LLM) は利用可能なツールを分析し、どのツールを使用するかを決定
- クライアント (Claude Desktop) は MCP サーバーを通じて選択されたツールを実行する
- 結果は Claude (LLM) に送り返される
- Claude (LLM) は自然言語で応答する
- クライアント (Claude Desktop) に応答が表示される
まとめ
楽しい
追記
有識者(mattn 先生)から MCP のツールの命名についてのアドバイスをいただきました!
要約:MCP のツールの名前はグローバルに相当するから、他のツールとの名前のコンフリクトを避けるために、長くなってもいいからもっと詳細な名前をつけたほうがいい
以下引用
おそらく今後、他の MCP を自作された際に気付くかもですが、MCP のツールの名前はグローバルに相当するので、複数の MCP が混在するとバッティングする可能性や、コンテキストが異なる事で get_alerts は選ばれなくなる可能性が高いです。
若干ウザい名前になってもいいので get_weather_forecast や get_weather_alerts という名前にした方が良いかも。
こうすることで get_earthquake_alerts 等と区別できる様になって、地震や天気の会話でそれぞれが選ばれやすくなります。
https://x.com/mattn_jp/status/1955872341273272753
なるほどオブザイヤー!!!!!
ありがとうございます!!!
追記2:トレンド2位ありがとうございます!
この記事がトレンド入りしていました!!
たくさんの方に読んでいただけて大変嬉しいです!ありがとうございます!
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme











