MCP エンドポイントのブラウザアクセス対策
本体の標準機能ではありません
1.5.8.1 では、ブラウザで /mcp を開くと MCP の SDK が返す JSON-RPC のエラーがそのまま表示されます。このページは、ブラウザからのアクセスにプリザンターのエラー画面を返すための改修メモです。
前提にした 1.5.8.1 の実装
原因の詳細は プリザンターの MCP の「ブラウザで /mcp を開くと JSON のエラーが出る」を参照してください。
McpServer.jsonのEnabledがtrueのとき、ModelContextProtocol.AspNetCore(1.4.1)のAddMcpServer().WithHttpTransport()を登録し、MapMcp("/mcp")でエンドポイントを追加します(Startup.cs#L368-L376、Startup.cs#L795-L798)。パスはMcpConstants.BasePath(/mcp)です。- パイプラインは
UseStatusCodePages→UseStaticFiles→UseRouting→UseCors→ (MCP が有効なら)MCP 用レート制限・UseMcpContextMiddleware→UseSession→UseAuthentication→UseAuthorization→UseEndpointsの順です(Startup.cs#L528-L669)。 UseStatusCodePagesは本文が空の 400・405 などを/errors/badrequestに、404 を/errors/notfoundにリダイレクトします。ErrorsControllerのアクションはHtmlTemplates.Error()で、テーマや表示言語が反映された標準のエラー画面を返します。SDK は本文に JSON を書いて返すため、このリダイレクトは働きません。
対策の候補
対策 A: ミドルウェアで既存のエラー画面へリダイレクトする
UseMcpContextMiddleware() より前に、/mcp への GET で Accept に text/html を含むもの(ブラウザの通常の遷移)だけを /errors/badrequest にリダイレクトするミドルウェアを置きます。MCP のクライアントは Accept: text/event-stream などで接続するため影響しません。
// Startup.Configure 内。既存の if (Parameters.McpServer?.Enabled == true) の中、
// レート制限と UseMcpContextMiddleware() より前に置く
app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments(McpConstants.BasePath)
&& HttpMethods.IsGet(context.Request.Method)
&& context.Request.GetTypedHeaders().Accept
.Any(a => a.MediaType.Equals("text/html", StringComparison.OrdinalIgnoreCase)))
{
context.Response.Redirect("/errors/badrequest");
return;
}
await next();
});図を読み込み中…
- 独自の HTML を持たずに済み、テーマと表示言語(
App_Data/Displaysの定義)がそのまま反映されます。 - パスは
McpConstants.BasePathを使い、Parameters.McpServer?.Enabled == trueの条件の中に置きます。
対策 B: 静的ファイルとフォールバックのルート
wwwroot に案内の HTML を置き、/mcp への GET で text/html を受け付けるときだけ返すルートを足します。MapMcp と同じパスに MapGet を登録することになり、ルーティングが競合するおそれがあります。
対策 C: リバースプロキシで振り分ける
nginx などで Accept に text/html を含むリクエストだけ静的な案内ページを返し、それ以外をプリザンターに渡します。本体を改修せずに済みます。
location /mcp {
if ($http_accept ~* "text/html") {
rewrite ^ /mcp-landing.html break;
}
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_buffering off;
proxy_cache off;
}対策 D: MapMcp の前に最小のエンドポイントを置く
MapMcp より先に /mcp の MapGet を登録してブラウザ向けの HTML を返します。同じパスに複数の MapGet を登録すると競合し、ブラウザ以外の GET を MCP のハンドラへ渡せないため、勧めません。
比較
| 対策 | 方式 | 実装の手間 | 仕様への適合 | 保守 | 推奨 |
|---|---|---|---|---|---|
| A | ミドルウェア + 既存のエラー画面 | 小 | 高 | 高 | ◎ |
| B | 静的ファイル + フォールバック | 中 | 中(ルートの競合) | 中 | ○ |
| C | リバースプロキシ | 中 | 高(本体は無改修) | 中 | ○ |
| D | 最小のエンドポイント | 小 | 低(ルートの競合) | 中 | △ |
MCP の接続方法やツールの一覧をブラウザに表示したくなった場合は、ErrorsController に専用のアクションを足すか、Error.Types に MCP 用の種類を足して Displays に説明文を定義し、対策 A のリダイレクト先を変えます。