MCP C# SDK v2.0へ更新するとき、最も重要な変更はHTTPサーバートランスポートがステートレス既定になったことです。通常のツール呼び出しを提供するMCPサーバーで、セッション固有の状態や従来のSSE接続に依存していなければ、パッケージを2.0.0へ更新し、WithHttpTransport()をそのまま使うだけでステートレスHTTPへ移行できます。
一方、同じコードがコンパイルできても、v1ではステートフル、v2ではステートレスとして動く可能性があります。initializeやMcp-Session-Id、ロードバランサーのスティッキーセッション、プロセス内のセッションデータに依存している場合は、更新前に設計を見直す必要があります。MCP C# SDK v2.0は旧クライアント・旧サーバーとの互換性を維持していますが、「通信方式の既定値まで同じ」という意味ではありません。(Microsoft for Developers)
MCP C# SDK v2.0移行ガイドで最初に押さえる変更点
MCP C# SDK v2.0は、2026年7月28日版のMCP仕様に対応した正式リリースです。新仕様では、HTTP通信をセッション中心の仕組みから、各リクエストが必要な情報を持つステートレスな仕組みへ変更しています。(GitHub)
主な違いは次のとおりです。
| 確認項目 | v1の一般的な動作 | v2の新しい動作 |
|---|---|---|
| HTTPトランスポートの既定値 | ステートフル | ステートレス |
| 接続開始 | initializeとinitializedを実行 | server/discoverで能力を確認 |
| セッション識別 | Mcp-Session-Idを後続リクエストに付与 | 新仕様のステートレス通信では使用しない |
| クライアント情報・能力 | 初期化時に交換 | リクエストごとの_metaに含める |
| ロードバランサー | セッションアフィニティが必要になりやすい | 任意のインスタンスへ振り分け可能 |
| 旧仕様との互換性 | 対象外 | 旧方式へ自動フォールバック可能 |
v2では、HttpServerTransportOptions.Statelessの既定値がtrueです。したがって、次のコードはv1とv2で見た目が同じでも、HTTPサーバーの動作が変わります。(MCP C# SDK)
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
v1では既定のステートフル動作、v2では既定のステートレス動作になります。この「コンパイルは通るが実行時の前提が変わる」という点が、移行時に最も注意すべきポイントです。
既存のMCPサーバーは壊れるのか
結論として、一般的なツール、リソース、プロンプトを提供しているだけのサーバーは、そのまま動く可能性が高いです。安定版かつ非推奨ではないv1 APIは、v2でも引き続きコンパイルして実行できる方針が示されています。また、v2クライアントは旧サーバーに対して従来のinitialize方式へ切り替え、v2サーバーも旧クライアントからの初期化要求を受け付けます。(Microsoft for Developers)
ただし、次のような実装は動作確認だけでなく、コードやインフラの修正が必要です。
| 既存実装 | v2更新後に起こり得る問題 |
|---|---|
Mcp-Session-Idをキーにしたメモリ管理 | 新仕様のステートレス通信ではヘッダーが存在しない |
initialize時に利用者情報を保存 | 新仕様では初期化ハンドシェイクを行わない |
| インスタンス内のDictionaryに処理状態を保存 | 次のリクエストが別インスタンスへ届く |
| ロードバランサーでセッション固定 | 不要な固定によって負荷分散効果が下がる |
ConfigureSessionOptionsは接続時に1回だけ実行される前提 | ステートレスではHTTPリクエストごとに呼び出される |
| 従来のSSE GET接続を使用 | ステートレスでは単独のSSE GET/DELETEエンドポイントを公開しない |
| 非同期通知をサーバーから任意のタイミングで送信 | セッションを持たないため、そのままでは送信できない |
特にConfigureSessionOptionsは注意が必要です。ステートフルではセッション開始時に実行されますが、ステートレスではリクエストごとに新しいサーバーコンテキストが作られるため、毎回実行されます。ここで重い初期化処理を行っている場合は、キャッシュや依存関係の初期化方法を見直してください。(MCP C# SDK)
互換性があってもCIが失敗することがある
v2では、旧式のSSEやステートフル専用オプション、Roots、Sampling、Loggingなどに非推奨警告が追加されています。
代表的な診断コードは次のとおりです。
MCP9004:従来のSSE関連MCP9005:新仕様で非推奨になった機能やAPIMCP9006:ステートフル専用オプション
これらは基本的に削除エラーではなく警告です。ただし、プロジェクトでTreatWarningsAsErrorsを有効にしていると、SDK更新後のビルドが失敗することがあります。互換性確認では、コンパイルエラーだけでなく新しく発生した警告も確認してください。(Microsoft for Developers)
Tasksを使っている場合は別途移行が必要
v1.4系で提供されていた実験的なTasks機能は、v2でModelContextProtocol.Extensions.Tasksへ分離されました。以前の実験的TasksとはAPI、通信形式ともに互換性がありません。
Tasksを使用している場合は、単純なパッケージ更新だけでなく、次の対応が必要です。
ModelContextProtocol.Extensions.Tasksを追加する- サーバーを
.WithTasks(...)で構成する - クライアント側の呼び出し方法をv2用APIへ変更する
- 複数インスタンスで実行する場合は、永続化された共有タスクストアを用意する
一般的なツール呼び出しとは異なるため、Tasksを利用しているプロジェクトでは専用の移行計画を作成してください。(Microsoft for Developers)
initializeとMcp-Session-Idはどう変わったのか
v1までの通信
従来のStreamable HTTPでは、クライアントとサーバーが最初に初期化処理を行っていました。
クライアント
↓ initialize
サーバー
↓ InitializeResult + Mcp-Session-Id
クライアント
↓ initialized
クライアント
↓ tools/listやtools/call
Mcp-Session-Idを毎回送信
サーバーがMcp-Session-Idを発行した場合、クライアントは後続のHTTPリクエストに同じIDを付ける必要があります。その結果、ロードバランサーは同じセッションを発行元のインスタンスへ戻すか、別インスタンスへセッションを移行する仕組みを持つ必要がありました。(Model Context Protocol)
v2の2026-07-28仕様
新仕様では、HTTP通信からinitialize/initializedハンドシェイクとMcp-Session-Idが取り除かれました。
クライアント
↓ server/discover
サーバー
↓ 利用可能な機能を応答
クライアント
↓ tools/list、tools/callなど
必要なプロトコル情報と能力をリクエスト内に含める
server/discoverは、永続的なセッションを作るための処理ではありません。サーバーの能力を軽量に確認するための処理です。
各リクエストには、プロトコルバージョン、クライアント情報、クライアント能力などが_metaとして含まれます。サーバーは過去の初期化結果を参照せず、そのリクエストだけで処理に必要な情報を判断します。(GitHub)
initializeはSDKから完全に削除されたわけではない
ここは誤解しやすい点です。
initializeが廃止されたのは、2026-07-28仕様による新しいHTTP通信経路です。v2 SDKは旧クライアント・旧サーバーとの互換性を保つため、従来の初期化方式も内部的に維持しています。
v2クライアントは、まずserver/discoverで新仕様を試します。相手が旧サーバーであると判断した場合は、従来のinitialize方式へ切り替えます。v2サーバーも旧クライアントからのinitializeを受け付けます。(Microsoft for Developers)
そのため、アプリケーションコードで新仕様用と旧仕様用の通信処理を独自に分岐させる必要は通常ありません。プロトコル交渉はSDKへ任せ、実際に接続する旧クライアントとの結合テストを実施する方法が安全です。
MCP C# SDK v2.0へ更新する手順
使用しているパッケージを確認する
ASP.NET CoreでHTTPベースのMCPサーバーを構築している場合、通常はModelContextProtocol.AspNetCoreを使用します。
主なパッケージの役割は次のとおりです。
| パッケージ | 主な用途 |
|---|---|
ModelContextProtocol.AspNetCore | ASP.NET CoreによるHTTP MCPサーバー |
ModelContextProtocol | DIやホスティングを含む一般的なクライアント・サーバー |
ModelContextProtocol.Core | クライアントや低レベルAPIのみが必要な場合 |
ModelContextProtocol.Extensions.Tasks | 長時間処理を扱うTasks拡張 |
HTTPサーバーでは、参照しているMCP関連パッケージのバージョンをそろえてください。(GitHub)
パッケージを2.0.0へ更新する
HTTPサーバーの場合は、プロジェクトのディレクトリで次のコマンドを実行します。
dotnet add package ModelContextProtocol.AspNetCore --version 2.0.0
dotnet restore
dotnet build
dotnet test
Directory.Packages.propsで一元管理している場合は、該当するバージョンを変更します。
<ItemGroup>
<PackageVersion
Include="ModelContextProtocol.AspNetCore"
Version="2.0.0" />
</ItemGroup>
正式版のModelContextProtocol.AspNetCore 2.0.0は、HTTPベースのMCPサーバー向けパッケージとして公開されています。(NuGet)
セッションを使っていないサーバーの最短移行方法
ツール呼び出しが各リクエスト内で完結し、必要なデータをデータベースや外部APIから取得している場合は、次の構成で移行できます。
using ModelContextProtocol.Server;
using System.ComponentModel;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = true;
})
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp();
app.Run();
[McpServerToolType]
public static class EchoTools
{
[McpServerTool]
[Description("受け取った文字列を返します。")]
public static string Echo(string message)
{
return message;
}
}
v2ではStatelessの既定値がtrueなので、次のように省略しても同じです。
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
ただし、移行直後は設定を明示しておくと、コードレビュー時に通信方式を判断しやすくなります。安定稼働を確認した後で省略しても問題ありません。(Microsoft for Developers)
本番環境では段階的に移行する
セッション依存の有無が分からない既存サーバーでは、SDK更新とステートレス化を同時に実施しないほうが安全です。
まず既存動作を固定する
最初のリリースでは、一時的にStateless = falseを明示します。
builder.Services
.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = false;
})
.WithToolsFromAssembly();
これにより、パッケージをv2へ更新しながら、従来のセッションベース動作を維持できます。
ただし、v2におけるステートフルモードは旧仕様との互換性を保つための移行手段です。Stateless = falseの場合、新しい2026-07-28方式をそのまま処理するのではなく、対応クライアントに旧initialize方式への切り替えを促します。恒久的な新規構成としてではなく、移行期間中の互換モードとして扱ってください。(MCP C# SDK)
次にセッション依存を洗い出す
コード、テスト、インフラから次の文字列や設定を検索します。
Mcp-Session-Id
SessionId
initialize
initialized
RunSessionHandler
ConfigureSessionOptions
SessionMigrationHandler
EnableLegacySse
sticky
affinity
特に確認すべき場所は次のとおりです。
- ASP.NET Coreのミドルウェア
- MCPツール実装
- 認証・認可処理
- メモリキャッシュ
- リバースプロキシ
- ロードバランサー
- KubernetesのIngress設定
- 結合テスト
- 独自MCPクライアント
- ログ相関IDの生成処理
単にMcp-Session-Idという文字列が見つからないだけでは不十分です。「前回の呼び出しと同じプロセスへ届くこと」を暗黙に前提としていないかも確認してください。
アプリケーション状態を明示的なIDへ置き換える
ステートレス化は、業務データを一切保存できなくなるという意味ではありません。MCPの通信セッションに状態を隠すのではなく、通常のWeb APIと同じように、明示的な識別子と共有ストレージで管理します。
例えば、複数回のツール呼び出しで注文処理を継続する場合は、次のように設計します。
start_order
↓ orderIdを返す
add_order_item
↓ orderIdを引数として受け取る
confirm_order
↓ orderIdを引数として受け取る
C#側では、概念的に次の形になります。
[McpServerTool]
[Description("注文処理を開始し、後続処理で使用する注文IDを返します。")]
public static async Task<string> StartOrder(
IOrderStore orderStore,
CancellationToken cancellationToken)
{
var orderId = Guid.NewGuid().ToString("N");
await orderStore.CreateAsync(
orderId,
cancellationToken);
return orderId;
}
[McpServerTool]
[Description("指定された注文に商品を追加します。")]
public static async Task AddOrderItem(
IOrderStore orderStore,
string orderId,
string productCode,
int quantity,
CancellationToken cancellationToken)
{
await orderStore.AddItemAsync(
orderId,
productCode,
quantity,
cancellationToken);
}
IOrderStoreの実装先には、データベースや分散キャッシュなど、全インスタンスから参照できる保存先を使用します。
次のような保存方法は、ステートレス化後も問題を起こします。
private static readonly Dictionary<string, Order> Orders = new();
このDictionaryはプロセス内にしか存在しないため、次のリクエストが別インスタンスへ届いた場合や、コンテナが再起動した場合に参照できません。
ロードバランサーと水平スケールへの影響
ステートレスHTTPへ移行する最大の運用上の利点は、MCPリクエストを通常のHTTPリクエストとして分散できることです。
v1のステートフル構成
クライアント
↓ initialize
ロードバランサー
↓
インスタンスA
↓ Mcp-Session-Idを発行
以降のリクエスト
↓ 同じセッションID
ロードバランサー
↓ 必ずインスタンスAへ
インスタンスAが停止した場合、セッションを共有ストアから復元する仕組みや、別インスタンスへ移行する仕組みが必要になります。
v2のステートレス構成
1回目のリクエスト → インスタンスA
2回目のリクエスト → インスタンスC
3回目のリクエスト → インスタンスB
各リクエストが自己完結しているため、どのインスタンスでも処理できます。MCPプロトコルのためだけに、スティッキーセッションや共有セッションストアを用意する必要がなくなります。(Microsoft for Developers)
| 運用項目 | ステートフル | ステートレス |
|---|---|---|
| セッションアフィニティ | 必要になりやすい | 原則不要 |
| インスタンス追加 | セッション配置を考慮 | 通常の水平スケールが可能 |
| インスタンス削除 | セッション終了・移行を考慮 | 処理中リクエスト以外への影響を抑えやすい |
| ローリング更新 | セッション維持が課題 | 通常のWeb APIに近い運用 |
| プロセス再起動 | セッション消失の可能性 | プロトコルセッションへの影響なし |
| 業務データ | セッション内に置かれやすい | DBや共有キャッシュに明示的に保存 |
ただし、ステートレスなのはMCPプロトコル層です。ツールがローカルファイル、プロセス内キャッシュ、インメモリDictionaryなどに依存していれば、アプリケーションは実質的にステートフルなままです。
スティッキーセッションを外す順序
ロードバランサーのセッション固定は、次の順序で解除します。
- MCPサーバーを
Stateless = trueで起動する - 明示的な状態IDを使うようにツールを修正する
- 共有ストレージを導入する
- 複数インスタンスへリクエストを分散するテストを行う
- スティッキーセッションを無効化する
- ローリング再起動を含む障害試験を行う
SDK設定を変えた直後にロードバランサー設定まで変更すると、問題が通信層にあるのか、アプリケーション状態にあるのか切り分けにくくなります。
MCPエンドポイントをGETヘルスチェックに使わない
v2のステートレスHTTPサーバーは、従来の単独SSE用GET/DELETEエンドポイントを公開しません。そのため、ロードバランサーがMCPエンドポイントへGETリクエストを送り、200応答を期待する設定では、異常と判定される可能性があります。(GitHub)
ヘルスチェックはMCPエンドポイントと分けてください。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.MapHealthChecks("/health");
app.MapMcp();
app.Run();
ロードバランサーの監視先は/healthとし、MCP通信先とは分離します。
HTTPヘッダーを利用したルーティングも可能になる
2026-07-28仕様では、MCPのメソッド名やツール名などをHTTPヘッダーへ反映する仕組みが標準化されています。
例えば、tools/callでget_order_statusを呼び出す場合、次のような情報をプロキシやロードバランサーから確認できます。
Mcp-Method: tools/call
Mcp-Name: get_order_status
さらに、[McpHeader]を付けたツール引数は、Mcp-Param-*形式のヘッダーとして送信できます。
[McpServerTool(Name = "get_order_status")]
[Description("指定地域の注文状況を取得します。")]
public static async Task<string> GetOrderStatus(
OrdersServiceClient ordersService,
[McpHeader("Region")] string region,
string orderId)
{
return await ordersService.GetStatusAsync(
region,
orderId);
}
この場合、クライアントは地域情報を次のようなヘッダーへ反映できます。
Mcp-Method: tools/call
Mcp-Name: get_order_status
Mcp-Param-Region: eastus2
これにより、ロードバランサー、APIゲートウェイ、WAF、監視基盤は、JSON-RPC本文を解析せずにルーティングや観測を行えます。なお、正しい値の基準はあくまでリクエスト本文であり、ヘッダーと本文が一致しない場合はリクエストが拒否されます。(Microsoft for Developers)
ステートフルモードを残すべきケース
すべてのサーバーを直ちにステートレス化できるとは限りません。
次のような要件がある場合は、一時的にStateless = falseを選択します。
| 要件 | 推奨判断 |
|---|---|
| 単純なツール、リソース、プロンプト | ステートレス |
| データをDBや分散キャッシュに保存 | ステートレス |
| Kubernetesやサーバーレスで水平スケール | ステートレス |
| 旧クライアントとの基本的な互換性 | まずステートレスで検証 |
| セッションごとのインメモリ状態が必須 | 設計変更まで一時的にステートフル |
| 従来のSSEエンドポイントが必須 | 一時的にステートフル |
| 任意タイミングのサーバー発通知が必須 | 要件を再設計するか一時的にステートフル |
| 旧式のSamplingやRootsへ依存 | 互換モードを残しながら移行 |
| v1.4系の実験的Tasksを使用 | Tasks拡張へ個別移行 |
ステートフルモードは次のように指定します。
builder.Services
.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = false;
})
.WithToolsFromAssembly();
ただし、ステートフルモードでは2026-07-28方式をネイティブに処理できません。新しいクライアントには旧initialize方式へ切り替えてもらう動作になるため、長期的にはステートレス化を目標にしてください。(MCP C# SDK)
移行後に実施するテスト
SDK更新後は、単一インスタンスでツールが動くだけでは確認として不十分です。
最低限、次のテストを行います。
| テスト | 確認内容 |
|---|---|
| 新クライアント接続 | server/discoverからツールを利用できる |
| 旧クライアント接続 | 旧初期化方式でも基本機能を利用できる |
| ヘッダー確認 | 新方式でMcp-Session-Idに依存していない |
| 複数インスタンス | 連続する呼び出しを異なるインスタンスで処理できる |
| 再起動試験 | インスタンス再起動後も明示的な処理IDで継続できる |
| 認証試験 | すべてのリクエストで認証情報を検証できる |
| 負荷分散試験 | セッション固定なしで偏りなく分散される |
| ヘルスチェック | /mcpではなく専用URLを監視している |
| CIビルド | MCP9004~MCP9006などの警告を確認する |
| ログ確認 | セッションID以外の相関IDで追跡できる |
複数インスタンス試験が重要
次の順序でツールを呼び出すテストを用意すると、隠れたセッション依存を発見しやすくなります。
1. インスタンスAで処理を開始する
2. 明示的なworkflowIdを受け取る
3. インスタンスAを停止する
4. インスタンスBへworkflowIdを送る
5. 処理を継続できることを確認する
このテストに失敗する場合、通信プロトコルはステートレスでも、アプリケーション状態がインスタンス内に残っています。
よくある移行ミス
v1互換だから何も確認しなくてよいと考える
v1互換性は、旧クライアントや安定APIを直ちに切り捨てないという意味です。WithHttpTransport()の既定動作まで同一ではありません。
SDK更新後は必ず、セッション、SSE、ロードバランサー、インメモリ状態を確認してください。
Mcp-Session-Idを独自に発行し続ける
新仕様のステートレスHTTPで、アプリケーションが独自にMcp-Session-Idを発行してセッション管理を続けると、標準の動作と独自実装が混在します。
業務状態を識別する場合は、次のように用途が分かる名前を使用します。
orderId
workflowId
browserId
workspaceId
taskId
プロトコルセッションと業務上の処理状態を分離することが重要です。
ステートレス化したのにローカルメモリへ保存する
次の保存先は、水平スケール時に問題になります。
- staticフィールド
- Singletonサービス内のDictionary
- コンテナのローカルファイル
- インスタンス固有の一時ディレクトリ
- プロセス内メモリキャッシュだけに保存した処理状態
必要な状態は、データベース、分散キャッシュ、オブジェクトストレージなどへ保存します。
認証情報を初期化時だけ確認する
新仕様では初期化セッションに認証結果を保存する設計ではなく、各HTTPリクエストを独立して処理します。
認証・認可はASP.NET Coreの通常のミドルウェアを使い、各リクエストで検証される構成にしてください。利用者IDやテナントIDも、セッション内変数ではなく、リクエストの認証情報から取得します。
旧プロトコルバージョンを必要なく固定する
v2クライアントは、新仕様を試したうえで旧方式へ自動的に切り替える設計です。
特別な互換性要件がない限り、McpClientOptions.ProtocolVersionを旧バージョンへ固定せず、SDKの自動交渉を利用するほうが移行しやすくなります。厳密な接続先制限が必要な場合だけ、プロトコルバージョンを明示してください。(Microsoft for Developers)
MCP C# SDK v2.0移行時の最終チェックリスト
ステートレスHTTPへの移行前に、次の項目を確認してください。
- MCP関連パッケージを2.0.0へそろえた
- 新しく発生したSDK警告を確認した
Stateless = trueを明示して動作確認したinitializeを前提とする独自処理を削除したMcp-Session-Idへの依存を削除したConfigureSessionOptionsをリクエストごとに実行しても問題ない- 業務状態を明示的なIDで管理している
- 状態をDBや分散キャッシュへ保存している
- ロードバランサーのセッション固定なしで動作する
- MCPエンドポイントとは別にヘルスチェックURLを用意した
- 複数インスタンス間で処理を継続できる
- 旧クライアントとの接続を実機で確認した
- Tasksを利用している場合は拡張パッケージへ移行した
MCP C# SDK v2.0への更新では、通常のツールサーバーなら大規模なコード変更は必要ありません。まずパッケージを更新し、Stateless = trueを明示したうえで、セッションID、インメモリ状態、ロードバランサー設定を順番に見直してください。
重要なのは、SDKのステートレス設定だけで移行完了と考えないことです。次のリクエストが別インスタンスへ届いても処理できる状態まで確認して、初めて水平スケール可能なステートレスMCPサーバーになります。

コメント