---
title: "MCP C# SDKの使い方｜v2.2でサーバー・クライアントを作る手順と1.xからの移行"
url: "https://www.issoh.co.jp/tech/details/11306/"
published: 2026-03-10
updated: 2026-10-08
categories: ["AIエージェント・MCP"]
publisher: "株式会社一創"
---

# 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と外部ツールをつなぐ標準規格の仕組み](/tech/details/5736/)にまとめています。

## まとめ：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のリリースノート](https://github.com/modelcontextprotocol/csharp-sdk/releases/tag/v2.0.0)は冒頭で「MCP 2026-07-28仕様に安定対応した」と書き、破壊的変更を10項目挙げています。中身は大きく3つで、HTTPサーバーの既定がステートレスになったこと、サーバーからクライアントへ問い合わせる仕組みがMRTR（Multi Round-Trip Requests）に替わったこと、実験機能だったTasksが別パッケージへ移ったことです。[Microsoftの発表ブログ](https://devblogs.microsoft.com/dotnet/announcing-v20-of-the-official-mcp-csharp-sdk/)（2026-07-28）は「v2.0 is backward compatible」と書き、安定版で非推奨でない1.xのAPIはそのままコンパイルでき、2.xのクライアントは古いサーバーに対して旧来の`initialize`ハンドシェイクへ自動で切り替えます。仕様側の変更点は[Streamable HTTPとは？MCPのトランスポートとSSEの違い、2026-07-28仕様の変更点](/tech/details/6580/)で扱っています。

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

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

2.2.0のパッケージは5つで、上の3つが本体、下の2つが2.0で追加された拡張です。迷ったら`ModelContextProtocol`から始め、HTTPで公開する段階で`ModelContextProtocol.AspNetCore`を足すのが[公式のGetting Started](https://csharp.sdk.modelcontextprotocol.io/concepts/getting-started.html)の推奨です。

| パッケージ                                 | 使う場面                          | 対象フレームワーク・前提                 |
| ------------------------------------- | ----------------------------- | ---------------------------- |
| 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ポーリングとサーバ主導の非同期設計を解説](/tech/details/16919/)、Appsは[MCP Appsとは？サンドボックスiframeで動く対話型UI拡張の仕組みと採用判断](/tech/details/16397/)を参照してください。

## 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・設定・ログ・常駐化](/tech/details/5649/)と同じ仕組みです。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クライアント登録の仕組みと実装](/tech/details/10011/)で説明しています。

## ツール実行中のユーザー確認：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とは？パッケージ分割とステートレス化の移行要点](/tech/details/15905/)、Pythonは[FastMCP 4とは？sessionless対応と廃止APIから読む3系からの移行判断](/tech/details/15907/)、Goは[MCP Go SDKの使い方｜v1.8.0でサーバーを作る手順とmcp-goとの違い](/tech/details/8272/)で扱っています。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）](/tech/details/9954/)が無難です。

### 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](/tech/details/11675/)を参照してください。

## 関連記事

- [MCP（Model Context Protocol）とは？AIと外部ツールをつなぐ標準規格の仕組みをわかりやすく解説](/tech/details/5736/)
- [Streamable HTTPとは？MCPのトランスポートとSSEの違い、2026-07-28仕様の変更点](/tech/details/6580/)
- [MCP Tasks拡張とは？tasks/getポーリングとサーバ主導の非同期設計を解説](/tech/details/16919/)
- [MCP TypeScript SDK v2とは？パッケージ分割とステートレス化の移行要点](/tech/details/15905/)
- [MCP Go SDKの使い方｜v1.8.0でサーバーを作る手順とmcp-goとの違い](/tech/details/8272/)

---

出典: [MCP C\# SDKの使い方｜v2.2でサーバー・クライアントを作る手順と1.xからの移行](<https://www.issoh.co.jp/tech/details/11306/>)（株式会社一創）
