AIエージェント・MCP

MCP C# SDKの使い方|v2.2でサーバー・クライアントを作る手順と1.xからの移行

MCP C# SDKの使い方|v2.2でサーバー・クライアントを作る手順と1.xからの移行

MCP C# SDKは、Model Context Protocol(MCP)の公式C#実装で、NuGetのModelContextProtocol系パッケージとして配布されています。2026年10月時点の最新は2.2.0(2026-08-13)で、2.0.0(2026-07-28)からMCP仕様2026-07-28に対応しました。この記事では、.NET SDK 10.0.401の手元環境で2.2.0を実際にビルドして動かした結果をもとに、サーバーとクライアントの作り方、ASP.NET CoreでHTTP公開するときの設定、1.xから上げるときに引っかかる箇所を説明します。MCPそのものの仕組みはMCP(Model Context Protocol)とは?AIと外部ツールをつなぐ標準規格の仕組みにまとめています。

まとめ:MCP C# SDK 2.2で押さえる要点

  • 最新は2.2.0(2026-08-13)。1.0.0は2026-02-25の公開で、1.x系は1.4.1(2026-07-09)が最後です。1.xには重大な不具合の修正しか入りません。
  • パッケージはstdioのサーバーとクライアントならModelContextProtocol、HTTPサーバーならModelContextProtocol.AspNetCore。AspNetCoreだけは.NET 8以上が必要です。
  • ツールはクラスに[McpServerToolType]、メソッドに[McpServerTool]を付けるだけで公開されます。メソッド名GetTotalはツール名get_totalに変換されました。
  • HTTPサーバーは2.0から既定がステートレス。v1の感覚でStateless = falseにすると、2.xのクライアントでも旧仕様2025-11-25に下がって接続されます(実測)。新旧を同じURLで受けるならStatefulForInitializeClientsを使います。
  • ツール実行中のユーザー確認は、ステートレスではElicitAsyncが例外になり、InputRequiredExceptionを投げるMRTR方式に置き換える必要があります。サンプリング・Roots・ロギングのAPIは警告MCP9005付きの非推奨です。

MCP C# SDKの現行版と2.0で変わったこと

MCP C# SDKはMCPの公式GitHub組織にあるリポジトリmodelcontextprotocol/csharp-sdkで開発され、READMEには「Maintained in collaboration with Microsoft」とあります。主要パッケージModelContextProtocolのNuGet累計ダウンロードは2026年10月時点で3,000万件を超えています。

版 公開日 対応する仕様 位置づけ
1.0.0 2026-02-25 2025-11-25 最初の安定版
1.4.1 2026-07-09 2025-11-25 1.x系の最終版
2.0.0 2026-07-28 2026-07-28 ステートレス既定・MRTR
2.1.0 2026-08-05 2026-07-28 subscriptions/listen追加
2.2.0 2026-08-13 2026-07-28 新旧混在のHTTPモード追加

2.0.0のリリースノートは冒頭で「MCP 2026-07-28仕様に安定対応した」と書き、破壊的変更を10項目挙げています。中身は大きく3つで、HTTPサーバーの既定がステートレスになったこと、サーバーからクライアントへ問い合わせる仕組みがMRTR(Multi Round-Trip Requests)に替わったこと、実験機能だったTasksが別パッケージへ移ったことです。Microsoftの発表ブログ(2026-07-28)は「v2.0 is backward compatible」と書き、安定版で非推奨でない1.xのAPIはそのままコンパイルでき、2.xのクライアントは古いサーバーに対して旧来のinitializeハンドシェイクへ自動で切り替えます。仕様側の変更点はStreamable HTTPとは?MCPのトランスポートとSSEの違い、2026-07-28仕様の変更点で扱っています。

ライセンスはApache License 2.0と表示されていますが、2.2.0タグのLICENSEファイルは冒頭で「MITからApache-2.0へ移行中」と明記し、再ライセンスの同意が取れていない過去の貢献はMITのまま残るとしています。社内の法務確認では、この前文までを読んでもらうのが確実です。

NuGetパッケージ5種の選び方と対応フレームワーク

2.2.0のパッケージは5つで、上の3つが本体、下の2つが2.0で追加された拡張です。迷ったらModelContextProtocolから始め、HTTPで公開する段階でModelContextProtocol.AspNetCoreを足すのが公式のGetting Startedの推奨です。

パッケージ 使う場面 対象フレームワーク・前提
ModelContextProtocol.Core クライアントだけ・依存を最小に net8.0〜10.0 / netstandard2.0
ModelContextProtocol stdioサーバー・クライアント(DI・ホスティング込み) net8.0〜10.0 / netstandard2.0
ModelContextProtocol.AspNetCore HTTPサーバー net8.0〜10.0
ModelContextProtocol.Extensions.Tasks 長時間のツール実行をタスク化 2026-07-28仕様が前提
ModelContextProtocol.Extensions.Apps クライアント内に対話型UIを出す 実験的(MCPEXP003)

netstandard2.0版があるので、クライアントは.NET Frameworkのアプリからも使えます。2.0.0では「.NET Framework UI MCP clients」を有効にする修正も入りました。一方でHTTPサーバーはASP.NET Coreが前提なので、.NET Framework上のIISアプリにそのまま載せることはできません。Tasksの仕様はMCP Tasks拡張とは?tasks/getポーリングとサーバ主導の非同期設計を解説、AppsはMCP Appsとは?サンドボックスiframeで動く対話型UI拡張の仕組みと採用判断を参照してください。

stdioで動くMCPサーバーを作る手順

Claude DesktopやVS Codeから子プロセスとして起動してもらう、ローカル用のサーバーです。コンソールアプリにパッケージを2つ足します。

dotnet new console -n InvoiceMcp
cd InvoiceMcp
dotnet add package ModelContextProtocol --version 2.2.0
dotnet add package Microsoft.Extensions.Hosting

Program.csを次の内容に置き換えます。税抜金額から税込金額を返すツールを1つ公開する例です。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using System.ComponentModel;

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();
await builder.Build().RunAsync();

[McpServerToolType]
public static class InvoiceTools
{
    [McpServerTool, Description("税抜金額から税込金額を計算して返す。")]
    public static decimal GetTotal(
        [Description("税抜金額(円)")] decimal amount,
        [Description("税率。0.1なら10%")] decimal rate = 0.1m)
        => Math.Round(amount * (1 + rate), 0, MidpointRounding.AwayFromZero);
}

WithToolsFromAssembly()がアセンブリ内の[McpServerToolType]を探し、付いているメソッドをツールとして登録します。ホストの組み立ては.NET Generic Host(汎用ホスト)とは?CreateApplicationBuilderでDI・設定・ログ・常駐化と同じ仕組みです。stdioでは標準出力がプロトコルの通り道になるので、公式サンプルはログをLogToStandardErrorThresholdで標準エラーへ回しています。

次章のクライアントから接続したところ、次のように公開されました。

  • ツール名はメソッド名GetTotalからget_totalに変わる。クライアントから呼ぶときはこの名前を使う
  • [Description]は入力スキーマのdescriptionになる。既定値のあるrateはrequiredから外れ、"default":0.1が付く
  • amount=1980で呼ぶと2178が返り、stdio接続の仕様版は2026-07-28で決まった

dotnet new mcpserverテンプレートで始める場合

MicrosoftのテンプレートMicrosoft.McpServer.ProjectTemplates(1.2.1)を入れると、dotnet new mcpserverで雛形を作れます。-t remoteでHTTP版、--aotでネイティブAOT、-f net8.0で対象フレームワークを選べます。NuGetでツールとして配布する設定(PackAsToolとPackageType McpServer)と、MCPレジストリ用の.mcp/server.jsonも生成されます。

注意点は、2026年10月8日に生成した雛形の参照が2.1.0だったことです(stdio版はModelContextProtocol 2.1.0、HTTP版はModelContextProtocol.AspNetCore 2.1.0)。2.2.0で入ったHttpServerSessionMode.StatefulForInitializeClientsはAspNetCoreパッケージ側の機能なので、HTTP版の雛形では生成後にdotnet add package ModelContextProtocol.AspNetCore --version 2.2.0で上げてください。

C#でMCPクライアントを作る:接続・ツール一覧・呼び出し

クライアントもModelContextProtocolパッケージ1つで書けます。上のサーバーをReleaseビルドしておき、隣のフォルダーにクライアント用のコンソールアプリを作ります。

cd InvoiceMcp
dotnet build -c Release
cd ..
dotnet new console -n InvoiceClient
cd InvoiceClient
dotnet add package ModelContextProtocol --version 2.2.0

Program.csを次の内容にして、InvoiceClientフォルダーでdotnet runを実行します。サーバーを子プロセスとして起動し、ツールを呼ぶ例です。

using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;

var transport = new StdioClientTransport(new StdioClientTransportOptions
{
    Name = "invoice",
    Command = "dotnet",
    Arguments = ["../InvoiceMcp/bin/Release/net10.0/InvoiceMcp.dll"],
});
await using var client = await McpClient.CreateAsync(transport);
Console.WriteLine(client.NegotiatedProtocolVersion);

foreach (var tool in await client.ListToolsAsync())
    Console.WriteLine(tool.Name);

var result = await client.CallToolAsync(
    "get_total",
    new Dictionary<string, object?> { ["amount"] = 1980 });
Console.WriteLine(result.Content.OfType<TextContentBlock>().First().Text);

HTTPのサーバーへつなぐときは、トランスポートをnew HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri(...) })に替えるだけです。2.xのクライアントは最初にserver/discoverで相手の対応版を確かめ、古いサーバーならinitializeハンドシェイクに切り替えます。

ListToolsAsync()が返すMcpClientToolは、Microsoft.Extensions.AIのAIFunctionを継承しています。AIFunction f = tools[0];がそのままコンパイルできるので、IChatClientに渡すChatOptions.Toolsへ並べれば、MCPサーバーのツール定義をLLMに渡せます。ただしToolsに並べるだけでは定義が渡るだけで、モデルが要求したツールを実行して結果を返す処理は別に要ります。Microsoft.Extensions.AIパッケージのUseFunctionInvocation()を付けてchatClientを組むと、この往復を自動で行います。

IChatClient chatClient = new ChatClientBuilder(providerClient)
    .UseFunctionInvocation()
    .Build();

IList<McpClientTool> tools = await client.ListToolsAsync();
var response = await chatClient.GetResponseAsync(
    "1,980円の税込金額は?",
    new ChatOptions { Tools = [.. tools] });

providerClientは、OpenAIやAzure OpenAIなど各LLMプロバイダーのSDKから得たIChatClientです。

ASP.NET CoreでHTTPサーバーにする:SessionModeの3択

リモートから使わせるサーバーはModelContextProtocol.AspNetCoreで作ります。空のWebアプリにパッケージを足します。

dotnet new web -n InvoiceWeb
cd InvoiceWeb
dotnet add package ModelContextProtocol.AspNetCore --version 2.2.0

Program.csを次の内容にします。ツールクラスはstdio版のInvoiceToolsをそのまま使えます。

using ModelContextProtocol.AspNetCore;
using ModelContextProtocol.Server;
using System.ComponentModel;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
    .WithHttpTransport(o =>
    {
        o.SessionMode = HttpServerSessionMode.Stateless;
    })
    .WithToolsFromAssembly();

var app = builder.Build();
app.MapMcp("/mcp");
app.Run("http://localhost:3001");

[McpServerToolType]
public static class InvoiceTools
{
    [McpServerTool, Description("税抜金額から税込金額を計算して返す。")]
    public static decimal GetTotal(decimal amount, decimal rate = 0.1m)
        => Math.Round(amount * (1 + rate), 0, MidpointRounding.AwayFromZero);
}

SessionModeは2.2.0で3値になりました。同じサーバーを3つの設定で起動し、2.2.0のC#クライアントと、旧仕様2025-11-25のinitializeを送るcurlで接続した結果が次の表です。

SessionMode 2.2.0クライアント 旧仕様のinitialize GET /mcp
Stateless(既定) 2026-07-28・セッションなし 200・セッションIDなし 405
Stateful 2025-11-25に下がる・セッションあり セッションID発行 400
StatefulForInitializeClients 2026-07-28・セッションなし セッションID発行 400

要注意は2行目です。v1時代のコードにはStateless = falseと書いたものが多く、2.xでもこの指定はStatefulとして生きています。ステートフルなサーバーは2026-07-28の要求を-32022 UnsupportedProtocolVersionで断る作りなので、クライアントを2.xに上げても接続は旧仕様のままになり、MRTRなど新仕様の機能は使えません。

選び方は次のとおりです。

  • 新規ならStateless。セッションを持たないので、ロードバランサーの背後で複数台に増やしてもスティッキーセッションが要りません。
  • 既存のステートフルなサーバーはStatefulForInitializeClients。古いクライアントにはセッションを渡し、新しいクライアントはステートレスで受けられます。
  • Statefulを残すのは、旧仕様の方式でサーバーから任意のタイミングで通知を送る、resources/subscribeで購読を受ける、クライアントごとに状態を分ける、のいずれかに頼っている場合です。公式ドキュメントのリソース購読の例もStatefulを指定しています。2026-07-28仕様では、クライアントが開くsubscriptions/listenの長時間ストリームで通知を受ける方式が別に用意され、2.1.0でその処理を差し替えるWithSubscriptionsListenHandlerが入りました。

公開時はAllowedHostsを実際のホスト名に絞ってください。公式のGetting Startedは、KestrelがHostヘッダーを既定では検証しないため、ローカルのサーバーでもDNSリバインディング対策としてループバックの名前だけを許可するよう求めています。OAuthで保護する場合、2.0からは認可サーバーのメタデータがcode_challenge_methods_supportedにS256を宣言していないと認可が失敗し、RFC 9207のissuer不一致も拒否されます。クライアント登録の方式はCIMD(Client ID Metadata Documents)とは?MCPでDCRに代わるOAuthクライアント登録の仕組みと実装で説明しています。

ツール実行中のユーザー確認:ElicitAsyncからMRTRへ

「本当に削除しますか」のように、ツールの途中でクライアント側へ入力を求める処理は、2.xで書き方が変わりました。v1までのserver.ElicitAsync(...)は、サーバーからクライアントへ別のリクエストを送るため、セッションが必要です。ステートレスのサーバーで呼ぶと、サーバーログにSystem.InvalidOperationException: Elicitation is not supported in stateless mode.が出て、クライアントにはisErrorがtrueの結果だけが返りました。

2026-07-28仕様では、ツールが「入力が要る」という結果をいったん返し、クライアントが回答を付けて同じツールを呼び直すMRTRに置き換わります。C#ではInputRequiredExceptionを投げます。次のメソッドは、上のInvoiceToolsのような[McpServerToolType]クラスの中に追加します。ファイル先頭にusing ModelContextProtocol.Protocol;とusing System.Text.Json;が必要です。

[McpServerTool, Description("請求書を削除する")]
public static string DeleteInvoice(
    McpServer server,
    RequestContext<CallToolRequestParams> context,
    string id)
{
    if (context.Params!.InputResponses is { } responses)
    {
        var r = responses["confirm"].Deserialize(InputResponse.ElicitResultJsonTypeInfo);
        var ok = r?.Action == "accept"
            && r.Content is { } content
            && content.TryGetValue("ok", out var v)
            && v.ValueKind == JsonValueKind.True;
        return ok ? $"{id} の削除を確認しました" : "中止しました";
    }

    if (!server.IsMrtrSupported)
        return "このクライアントは確認の問い合わせに対応していません";

    throw new InputRequiredException(
        inputRequests: new Dictionary<string, InputRequest>
        {
            ["confirm"] = InputRequest.ForElicitation(new ElicitRequestParams
            {
                Message = $"{id} を削除しますか",
                RequestedSchema = new()
                {
                    Properties = new Dictionary<string, ElicitRequestParams.PrimitiveSchemaDefinition>
                    {
                        ["ok"] = new ElicitRequestParams.BooleanSchema()
                    }
                }
            })
        },
        requestState: $"delete:{id}");
}

クライアント側は、McpClientOptions.Handlers.ElicitationHandlerを登録しておけば、呼び直しはSDKが自動で行います。チェックを付けずに送信された場合も「承認(accept)」で返り、そのときokはfalseなので、Actionだけでなく入力値まで見て判定します。3つのSessionModeすべてでこのツールを呼んだところ、どれも成功し、ハンドラーが呼ばれたのは1回でした。旧仕様で接続したステートフルなセッションでも、SDKが従来のリクエストに変換して動かします。

サンプリング(サーバーがクライアント側のLLMを使う機能)、Roots、ロギングは2026-07-28仕様で非推奨になりました。SampleAsync・AsSamplingChatClient・RequestRootsAsyncを使うコードは警告MCP9005を出してビルドされます。仕様の移行先は、サンプリングがLLMプロバイダーのAPIを直接呼ぶ形、Rootsがツール引数やリソースURIで渡す形、ロギングがstdioでは標準エラー・それ以外はOpenTelemetryです。仕様上は2027-07-28以降の版まで削除されません。

1.xから2.xへ上げるときの確認項目

安定版で非推奨でないAPIだけを使っていれば、パッケージを上げてもビルドは通ります。ただし既定値と検証の厳しさが変わったため、ビルドが通っても挙動が変わる箇所があります。

ビルドは通るが挙動が変わる設定

  • HTTPの既定がステートレスに:Statelessを書いていなかったサーバーは、GETのSSEストリームとサーバー発の通知を失います。ステートフル専用のオプションには警告MCP9006が出ます。
  • 構造化出力が値そのままに:UseStructuredContent = trueで数値などオブジェクト以外を返すツールは、{ "result": 72 }ではなく72を返すようになりました。resultプロパティを読んでいるクライアントは壊れます。
  • ToolのJSONにinputSchemaが必須に:自作のプロキシやテスト用の固定JSONで省略しているとJsonExceptionになります。空の{}で足ります。
  • OAuthの検証が厳格に:PKCEのS256宣言、issuerの一致、DCRでapplication_typeが送られるようになった点を認可サーバー側で確認します。

急いで2.xに上げない方がよい2つのケース

1つ目は、1.4.x の実験機能Tasksを使っているサーバーです。2.xのTasksはModelContextProtocol.Extensions.Tasksへ移り、APIも通信形式も互換がありません。発表ブログも、後方互換の唯一の例外としてTasksを挙げています。2.xのクライアントから1.xのタスク対応サーバーを呼んでも普通のツール結果として返るだけなので、クライアントとサーバーの両方を同時に移行する計画が要ります。

2つ目は、サーバーからのサンプリングに処理の中心を置いているサーバーです。2.xでも動きますが、ステートレスでは例外になり、仕様からもいずれ消える機能です。上げる作業より先に、LLMを直接呼ぶ設計への作り替えを決める必要があります。どちらにも当たらないなら、1.xに残る理由はほとんどありません。1.xのサポートは、保守者が重大と判断した不具合を1.xの最新マイナー版にパッチとして出すだけです。

他の言語のSDKも同じ仕様改定に合わせて大きく変わりました。TypeScriptはMCP TypeScript SDK v2とは?パッケージ分割とステートレス化の移行要点、PythonはFastMCP 4とは?sessionless対応と廃止APIから読む3系からの移行判断、GoはMCP Go SDKの使い方|v1.8.0でサーバーを作る手順とmcp-goとの違いで扱っています。GoのSDKも既定のステートフルのままだとHTTPで2025-11-25に下がり、C#と同じ落とし穴があります。

よくある質問

MCP C# SDKは公式のSDKですか?

はい。MCPの公式GitHub組織にあるmodelcontextprotocol/csharp-sdkで、Microsoftと協力して保守されています。NuGetのパッケージIDはModelContextProtocolです。

MCP C# SDKに必要な.NETのバージョンは?

2.2.0は.NET 8・9・10とnetstandard2.0を対象にしています。HTTPサーバー用のModelContextProtocol.AspNetCoreだけは.NET 8以上が必要です。新しく作るなら長期サポート版の.NET 10(LTS)が無難です。

C#でFastMCPのような書き方はできますか?

できます。PythonのFastMCPがデコレーターで関数をツールにするのと同じく、C# SDKはメソッドに[McpServerTool]属性を付けるだけでツールになり、引数の型と[Description]から入力スキーマが自動で作られます。別のライブラリを入れる必要はありません。

v1のコードは2.xでもそのまま動きますか?

安定版で非推奨でないAPIはそのままコンパイルでき、実行もできます。ただしHTTPの既定がステートレスに変わり、ステートレスではElicitAsyncとSampleAsyncが例外になります。セッションに頼るサーバーはSessionModeを明示してから上げてください。

Semantic KernelやMicrosoft Agent FrameworkからMCPのツールを使えますか?

使えます。C# SDKのクライアントが返すMcpClientToolはMicrosoft.Extensions.AIのAIFunctionを継承しているので、IChatClient系の仕組みにそのまま渡せます。エージェント側の構成はMicrosoft Agent Framework 1.0とは|Semantic KernelとAutoGenを統合した.NET/Python共通エージェントSDKを参照してください。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  3. 2026.10.24 テックブログ カプコン不正アクセス:CAPCOM ID 1,177万件漏えいと196件の乗っ取りへの対処
  4. 2026.10.24 テックブログ ブラストメール不正アクセス:配信先288件の漏えいと利用企業の報告・通知の手順
  5. 2026.10.14 テックブログ ローソンID不正アクセス:215万件漏えいの対象確認と本物メールの見分け方

RELATED POSTS 関連記事

目次