MCPのPrompts仕様を読み解く — Claude Codeがスラッシュコマンド化する仕組み
MCPのPrompts仕様はテンプレート化した指示をサーバーからクライアントへ渡す機能。Claude Codeがこれをスラッシュコマンドとして扱う実装の詳細と、引数設計の落とし穴を整理する。
MCPのPromptsとは何か
Model Context Protocol(MCP)のPromptsは、サーバーが用意した指示テンプレートをクライアントに公開する仕組みです。サーバー側で「このコードをレビューして」「この形式でissueを作って」といった定型の依頼文をname付きで登録しておき、クライアントはそれを一覧取得して呼び出します。
MCPにはサーバーが公開する概念が3つあります。ユーザーが直接呼ぶものが起点なのか、モデルが呼ぶものが起点なのかで役割が分かれます。
| プリミティブ | 誰が使うと決めるか | 典型例 |
|---|---|---|
| Tools | 誰が使うと決めるかモデルが判断して呼ぶ | 典型例ファイル読み書き、API呼び出し |
| Resources | 誰が使うと決めるかアプリがコンテキストとして取り込む | 典型例ファイル内容、DBスキーマ |
| Prompts | 誰が使うと決めるかユーザーが明示的に選んで実行する | 典型例コードレビュー依頼、定型issue作成 |
Promptsだけが「ユーザー制御」に設計されています。仕様が区別しているのはコンテンツの作者ではなく、実行のタイミングを誰が決めるかです。中身はサーバーの著者が決めますが、実行の引き金を引くのは常にユーザーです。
なぜ通信の前提から押さえる必要があるか
現行のプロトコル改訂(2026-07-28)は、直前の版(2025-11-25)から通信モデルが大きく変わっています。initializeハンドシェイクとセッションIDを廃止し、すべてのリクエストが_metaにプロトコルバージョンとクライアント情報を載せるステートレスな形に移行しました。Promptsの解説ページでも、掲載されているリクエスト例は簡潔さのために_metaを省略していますが、実装では省略できません。
この変更はPrompts単体の仕様ではなくMCP全体の話ですが、これを知らずに公式ページのJSON例をそのままコピーすると、必須フィールド欠落でリクエストが拒否されます。仕様を読むときは、まずこの土台の変更を頭に入れておく必要があります。
prompts/listとprompts/getのやり取り
Promptsの呼び出しは2段階です。まず一覧を取得し、次に個別の内容を取得します。
一覧取得(prompts/list)はページネーションとキャッシュに対応します。応答にはttlMs(何ミリ秒キャッシュしてよいか)とcacheScope(publicかprivateか)が必須で付き、クライアントは毎回問い合わせずに済みます。
{
"jsonrpc": "2.0",
"id": 1,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"code": "def hello():\n print('world')"
}
}
}prompts/getのargumentsはキーと値のペアで渡されます。サーバー側がarguments配列で宣言した引数名がここに対応します。引数の値はCompletion APIを使ってIDEのように補完候補を出すこともできますが、この機能はサーバー側の対応が前提です。
サーバーが処理に追加の入力を要求したい場合、通常の完了応答の代わりにInputRequiredResultを返せます。これはMulti Round-Trip Requests(MRTR)という共通パターンで、クライアントはinputResponsesを添えて同じリクエストを再送します。
InputRequiredResultにはrequestStateという項目が含まれることもあります。これはサーバー側だけが意味を持つ不透明な文字列で、クライアントは中身を解析したり書き換えたりしてはいけません。再送するリクエストには、受け取ったrequestStateをそのまま含めておく必要があります。サーバーはこの値を使って、複数回に分かれたやり取りのどこまで進んだかを追跡します。
Prompt定義そのものにも、name(一意な識別子)のほかにtitle(表示用の人間可読名)、description、icons、arguments(引数のリスト)という項目があります。ここで注意したいのは、titleはあくまで表示用のラベルという位置づけです。Claude Codeがスラッシュコマンドとして表示する/servername:promptnameのpromptname部分にはnameを使っています。サーバー実装者がtitleにどれだけ分かりやすい名前を用意しても、ユーザーが実際にコマンドとして打ち込む文字列はnameのままです。そのためname自体を短く覚えやすい形にしておく設計が実用面では効いてきます。
一覧の変更をどう検知するか
Promptsは接続後に固定されているとは限りません。プロジェクトの状態や権限によって、サーバーが動的に増減させることを仕様は想定しています。この変化をクライアントへ伝える仕組みがlistChangedです。
流れは次のとおりです。まずクライアントがprompts/listで一覧を取得します。次に、サーバーがlistChanged: trueを能力として宣言していれば、クライアントはsubscriptions/listenリクエストでpromptsListChanged: trueを指定し、通知専用のストリームを開きます。サーバー側でPromptsの構成が変わると、このストリームにnotifications/prompts/list_changedが流れます。クライアントはこれを受けてprompts/listを再送し、最新の一覧を取り直します。
この設計のポイントは、通知そのものには変更後の中身が含まれない点です。通知は「変わった」という合図でしかなく、実際の差分は改めて一覧を取り直して初めて分かります。サーバー実装者は、Promptsの数が多い環境ほど再取得のコストを意識する必要があります。
メッセージに含められる5種類のコンテンツ
prompts/getが返すmessagesはrole(userかassistant)とcontentを持ちます。contentには5種類あります。
- テキスト(
text) — もっとも一般的 - 画像(
image) — base64エンコード必須 - 音声(
audio) — base64エンコード必須 - リソースリンク(
resource_link) — URIだけを渡し、中身は必要になったら取得 - 埋め込みリソース(
resource) — サーバー管理のドキュメントやコード片を直接メッセージに含める
resource_linkとresourceの使い分けは実装イメージが要る箇所です。ファイルが大きい、または呼ばれるたびに内容が変わるならresource_linkでURIだけ渡し、クライアント側のresources/readに取得を任せます。逆に短い定型のコードサンプルやドキュメント片はresourceで埋め込んだほうが往復が減ります。
エラー処理とセキュリティで実装者が確認すべきこと
仕様が定める標準エラーは3種類です。存在しないPrompt名を指定された場合と、必須引数が欠けている場合はどちらも-32602(Invalid params)、サーバー内部の処理失敗は-32603(Internal error)を返すことになっています。どちらも同じコードに見えますが、クライアント側で原因を切り分けたいなら、messageフィールドに具体的な理由を書き添えておくのが実務上の対処です。
実装上の推奨事項は、サーバー側とクライアント側で分けて整理すると見通しがよくなります。
| 立場 | 推奨事項 |
|---|---|
| サーバー側 | 推奨事項引数を処理する前に必ずバリデーションを行う |
| クライアント側 | 推奨事項大量のPromptsに備えてページネーションを正しく処理する |
| 両者 | 推奨事項能力交渉(prompts能力の宣言と確認)に従う |
この表のうち、サーバー側の「引数を処理する前に必ずバリデーションを行う」は、直前で見た-32602の使い分けと直接つながっています。必須引数の欠落を検知するのはこのバリデーションの役目であり、ここを怠ると本来-32602で弾けるはずのリクエストがそのまま内部処理まで進んでしまい、原因の分かりにくい-32603として跳ね返ってくることになります。バリデーションを引数に触れる前の段階で固定しておくことが、2つのエラーコードを実際に使い分けるための前提です。
セキュリティ面では、仕様は「すべてのPrompt入出力をインジェクション攻撃やリソースへの不正アクセスから守るために慎重に検証しなければならない」とMUSTで明記しています。Promptsはユーザーが明示的に選ぶとはいえ、argumentsの値自体は外部から渡ってくる文字列です。サーバー側でこの値をそのまま別のツール呼び出しやシェルコマンドに埋め込むと、Prompts経由の間接的なインジェクション経路になり得ます。MCPサーバー全体のアクセス範囲の設計はMCPセキュリティガイドで扱っています。
Claude Codeはこの仕様をどう実装しているか
Claude Codeは接続済みのMCPサーバーが公開するPromptsを、そのままスラッシュコマンドとして扱います。/を入力すると/servername:promptname (MCP)の形式で候補に並び、/mcp__servername__promptnameと直接入力しても実行できます。
/mcp__github__list_prs引数を取るPromptsも同じ形で呼び出します。
/mcp__jira__create_issue login-bug highサーバーがlistChanged能力を宣言していれば、Promptsの追加・削除をClaude Codeが自動的に検知します。Claude Codeはこの通知を受け取ると再接続なしにコマンド一覧を更新するため、サーバー側で動的にPromptsを増減させる実装(権限やプロジェクトごとに出し分ける等)もそのまま反映されます。
Promptsは何のために「ユーザー制御」に固定されているか
ToolsやResourcesがモデルやアプリの判断で動く一方、Promptsだけをユーザー制御に固定したのは、実行の起点をはっきりさせるためです。理由は単純です。モデルが自律的に判断して実行してよい操作(Tools)と、ユーザーが明示的に選んで初めて動く操作(Prompts)は、仕様レベルで分けられています。そう分けておけば、クライアント実装者は「Promptsは常に人間の操作を経由する」という前提でUIを作れます。Claude Codeがこれをスラッシュコマンドという最も直接的な選択UIに落とし込んだのは、仕様の意図に忠実な実装だと言えます。
裏を返すと、モデルに自律的に使わせたい定型指示はPromptsでは実現できません。その用途はTools側で設計するのが仕様の想定に沿います。Promptsをモデル主導の呼び出しに使おうとすると仕様の想定と衝突し、クライアントによっては期待通りに動きません。
まとめ
MCPのPromptsは、サーバーが定義した指示テンプレートをユーザーが明示的に選んで実行する仕組みです。Claude Codeではスラッシュコマンドとして表面化します。実装時に押さえておきたい点は3つです。通信がステートレス化した現行プロトコルの前提を踏まえること、resource_linkとresourceを用途で使い分けること、そしてClaude Codeでの引数が空白区切りの単一トークンに制約される点です。これらを押さえておくと、実装後の手戻りが減ります。すでにサーバーを自作している場合は、MCPサーバー自作ガイドでツール定義からClaude Code接続までの手順を確認してください。動作確認にはMCP Inspectorが使えます。