ASP.NETとC#でOpenAI向けのMCPサーバーを作る際のはまりどころ
概要
HigLaboでChatGPTからASP.NETのMCPサーバーのエンドポイントを呼び出す機能を作成しました。
この記事では最低限必要なコードとちょっとしたはまりどころについて解説します。
初期化処理
まずはNugetパッケージでModelContextProtocol.AspNetCoreをインストールします。
まだプレビュー版です。
インストール後、Program.csに以下の初期化処理を書きます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = true;
})
.WithToolsFromAssembly()
.WithPromptsFromAssembly()
.WithResourcesFromAssembly();
var app = builder.Build();
app.Use(async (context, next) =>
{
var accept = context.Request.Headers.Accept.ToString();
if (!accept.Contains("text/event-stream"))
{
context.Request.Headers.Accept = $"{accept}, text/event-stream";
}
await next();
});
app.MapMcp("/mcp");
普通に作っていくとOpenAIからの呼び出しがエラーになります。
はまりどころが3つもあります。
ステートレスの設定
まずは
options.Stateless = true;
という形でステートレスに設定をしないとうまくいきません。
これがないと「mcp_protocol_error: Session terminated」というエラーが発生します。これはツールを呼び出した直後にサーバー側のセッションが破棄されてしまうことが原因です。Streamable HTTP におけるセッション管理の既知の問題で、OpenAI 側の実装がツール呼び出し後に DELETE /mcp を送ってしまうために起こります。同様の症状は他の開発者からも報告されており、Streamable HTTP ではツール呼び出しが常に「Session terminated」で失敗するという報告が複数あります
ステートレスモードではクライアント情報を Mcp-Session-Id ヘッダーにエンコードし、サーバー側でセッションオブジェクトを保持しないため、OpenAI クライアントがセッションを削除しても問題になりません。
ステートレスモードにすると以下の点が変わります。
サーバーは /sse や /message エンドポイントを公開しません。代わりに、クライアントは各リクエストごとに独立した POST を行い、その応答でツールの実行結果を受け取ります。
- セッション管理が不要になるので、OpenAI クライアントがセッションを削除しても「Session terminated」が発生しません。
- 進捗通知や長時間のストリーミング応答など、サーバー側からの非同期メッセージ送信は利用できません。
ステートレスモードへ切り替えても GetTodaysWeather のように即時に結果を返すツールであれば特に問題はありませんので、まずは上記の変更を試してみてください。
ヘッダーの調整
AddMcpServer().WithHttpTransport() で生成される Streamable HTTP エンドポイントは、MCP 仕様に沿ってリクエスト/レスポンスの形式やヘッダーをかなり厳密に検査します。Streamable HTTP の POST は Accept ヘッダーに application/json と text/event-stream の両方が含まれていることを要求します。実装では Accept ヘッダーの配列を走査し、双方が含まれていなければ 406 エラーを返し、その JSON‐RPC エラーコードとして –32600(Invalid Request)を返します。OpenAI のツール呼び出しでは通常ヘッダーにapplication/jsonのみセットされているためこのチェックに引っ掛かります。
以下のようにヘッダーを修正します。
app.Use(async (context, next) =>
{
// リクエストに text/event-stream を強制追加。本来であればUser-AgentがOpenAIからかどうかもチェックしたほうが良いかも?
var accept = context.Request.Headers.Accept.ToString();
if (!accept.Contains("text/event-stream"))
{
context.Request.Headers.Accept = $"{accept}, text/event-stream";
}
await next();
});
Accept ヘッダーを書き換えるミドルウェアは app.MapMcp() より 前 に登録する必要があります。
ルートページの上書きの防止
最後にMCPのマップするパスをルート以外に設定します。
app.MapMcp("/mcp");
通常ドメインのルートはトップページとしてサービスの概要などのページになっているはずです。引数無しの
app.MapMcp();
だとルートのページがMCPサーバー用に使われて正しく表示されなくなります。
ツールの作成
ツールの作成はクラスを作成してその中にメソッドを定義し、クラストメソッドのそれぞれに属性をセットするだけです。
using ModelContextProtocol.Server;
using System.ComponentModel;
namespace HigLaboApp.Core;
[McpServerToolType]
public class WeatherTools
{
[McpServerTool(ReadOnly = true), Description("本日の天気を取得します。")]
public string GetTodaysWeather()
{
var today = DateTime.Now.ToDateOnly();
var temperatureC = Random.Shared.Next(-20, 55);
return $"本日の天気({today}):{temperatureC}°C、暑いです";
}
}
動作確認
OpenAIのテスト用ページから動作確認ができます。
https://platform.openai.com/chat/edit?models=gpt-4.1

+AddからMCPサーバーを選択し https://your-service.com/mcp という感じでサーバーURLを入力してあげるとテストできるようになります。