プロンプト
プロンプトは、ユーザーが選ぶメッセージテンプレートです。
ツールはモデルのためのものです。プロンプトはその逆です。ユーザーがクライアントのメニュー(スラッシュコマンドやボタン)から 1 つを選んで引数を入力すると、レンダリングされたメッセージが、ユーザー自身が入力したかのように会話に入ります。
プロンプトを宣言するには、テキストを返す関数に @mcp.prompt() を付けます。
最初のプロンプト
from mcp.server import MCPServer
mcp = MCPServer("Code Helper")
@mcp.prompt()
def review_code(code: str) -> str:
"""Review a piece of code."""
return f"Please review this code:\n\n{code}"
SDK が読み取るのは、ツールの場合と同じ 3 つです。
- 名前は関数名、つまり
review_codeです。 - クライアントが表示する説明は docstring、つまり
Review a piece of code.です。 - 引数はパラメーターから決まります。
codeにはデフォルト値がないので必須です。
クライアントが prompts/list で受け取るのは次のとおりです。
{
"name": "review_code",
"description": "Review a piece of code.",
"arguments": [
{"name": "code", "required": true}
]
}
ここには JSON Schema がありません。プロンプトの引数は、名前付きの文字列値が並んだフラットなリストです。モデルが組み立てるペイロードではなく、人が記入するフォームです。
レンダリングする
クライアントは prompts/get に引数を渡してテンプレートをレンダリングします。関数が実行され、返した str が 1 つのユーザーメッセージになります。
{
"description": "Review a piece of code.",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this code:\n\ndef add(a, b): return a + b"
}
}
],
"resultType": "complete"
}
プロンプトの一生はこれがすべてです。名前で一覧に載り、必要なときにレンダリングされ、チャットに差し込まれます。
Check
required のチェックは関数が実行される前に行われます。code なしで review_code をレンダリングすると、リクエスト自体が JSON-RPC エラー(コード -32603)で失敗します。
mcp.shared.exceptions.MCPError: Internal server error
モデルに返すためのツール形式のエラー結果はありません。そもそもモデルが関与していないからです。呼び出しは例外を送出します。理由(Missing required arguments: {'code'})はサーバーのログに記録されます。
試してみる
MCP Inspector でサーバーを実行してください。
uv run mcp dev server.py
Prompts タブを開いて review_code を選択してください。Inspector は、必須の code フィールドが 1 つあるフォームを表示します。入力してレンダリングすると、上のユーザーメッセージがそのまま返ってきます。
複数のメッセージ
コードレビューは 1 つのメッセージです。デバッグセッションは会話であり、プロンプトはその会話全体の出発点を用意できます。
str の代わりに、メッセージのリストを返します。
from mcp.server import MCPServer
from mcp.server.mcpserver.prompts.base import AssistantMessage, Message, UserMessage
mcp = MCPServer("Code Helper")
@mcp.prompt()
def review_code(code: str) -> str:
"""Review a piece of code."""
return f"Please review this code:\n\n{code}"
@mcp.prompt()
def debug_error(error: str) -> list[Message]:
"""Start a debugging conversation."""
return [
UserMessage("I'm seeing this error:"),
UserMessage(error),
AssistantMessage("I'll help debug that. What have you tried so far?"),
]
UserMessageとAssistantMessageはmcp.server.mcpserver.prompts.baseにあります。strを渡すと、TextContentにラップしてくれます。ロールはクラス名で決まります。Messageは両者に共通の基底クラスです。戻り値のアノテーションにはこれを使ってください。
debug_error をレンダリングすると、3 つのメッセージがこの順番で生成されるようになります。
{
"description": "Start a debugging conversation.",
"messages": [
{"role": "user", "content": {"type": "text", "text": "I'm seeing this error:"}},
{"role": "user", "content": {"type": "text", "text": "TypeError: 'int' object is not iterable"}},
{
"role": "assistant",
"content": {"type": "text", "text": "I'll help debug that. What have you tried so far?"}
}
],
"resultType": "complete"
}
最後のメッセージに注目してください。assistant のターンをあらかじめ埋めておくのは、誘導の文言をユーザー自身に入力させることなく、モデルの「次の」返答を方向づけるための方法です。
タイトルと引数の説明
review_code は関数名であって、ラベルではありません。ボタンに載せるのにもっとふさわしいものをクライアントに渡し、フォームを見ただけで意味がわかるように各引数に説明を付けます。
from typing import Annotated
from pydantic import Field
from mcp.server import MCPServer
mcp = MCPServer("Code Helper")
@mcp.prompt(title="Code review")
def review_code(
code: Annotated[str, Field(description="The code to review.")],
language: Annotated[str, Field(description="The language the code is written in.")] = "python",
) -> str:
"""Review a piece of code."""
return f"Please review this {language} code:\n\n{code}"
title="Code review"は人が読むための名前で、ツールのtitleとまったく同じです。Annotated[str, Field(description=...)]は、ツール でツールのパラメーターを説明するのに使うのと同じパターンです。ここでは、説明はスキーマの中ではなく引数に付きます。languageにはデフォルト値があるので、必須ではなくなります。
これで prompts/list のエントリには、クライアントがよいフォームを描くのに必要なものがすべてそろいます。
{
"name": "review_code",
"title": "Code review",
"description": "Review a piece of code.",
"arguments": [
{"name": "code", "description": "The code to review.", "required": true},
{"name": "language", "description": "The language the code is written in.", "required": false}
]
}
Info
ツール を読んでいれば、ここまでの内容はもうすべて知っています。同じデコレーター、同じく docstring が説明になる仕組み、同じ Annotated/Field です。変わるのは、誰が起動するか(ユーザー)と、結果がどこへ行くか(会話の中)だけです。
テキスト以外のコンテンツ
UserMessage と AssistantMessage は、str を受け取れる場所ならどこでも、コンテンツブロックや Image / Audio ヘルパーも受け取れます。プロンプトでよく出てくるケースは 2 つ、ドキュメントの添付と画像の添付です。
ファイルを埋め込む
from pathlib import Path
from mcp.server import MCPServer
from mcp.server.mcpserver import Message, UserMessage
from mcp.types import EmbeddedResource, TextResourceContents
mcp = MCPServer("Code Helper")
STYLE_GUIDE_FILE = Path(__file__).parent / "style-guide.md" # or the path to your file on disk
@mcp.resource("style://python", mime_type="text/markdown")
def style_guide() -> str:
"""The team's Python style guide."""
return STYLE_GUIDE_FILE.read_text(encoding="utf-8")
@mcp.prompt()
def review_code(code: str) -> list[Message]:
"""Review a piece of code against the team style guide."""
guide = TextResourceContents(uri="style://python", mime_type="text/markdown", text=style_guide())
return [
UserMessage(EmbeddedResource(resource=guide)),
UserMessage(f"Review this code against the style guide above:\n\n{code}"),
]
- スタイルガイドは
style://pythonにあるリソースで(リソースについては リソース で扱います)、server.pyの隣にあるstyle-guide.mdから読み込まれます。そこに任意の Markdown ファイルを置いてください。 EmbeddedResource(resource=TextResourceContents(...))(どちらもmcp.typesにあります)は、URI と MIME タイプ付きのファイルを最初のメッセージとして運びます。そのファイルに言及するリクエストは、プレーンテキストとして後に続きます。- ガイドを f-string に貼り付けるのではなく埋め込むことで、クライアントはそれを添付ファイルとして表示でき、後から
style://pythonを開き直せます。モデルはファイルをそのままの形で受け取ります。バイナリファイルの場合は、base64 のblobを持つBlobResourceContentsを使ってください。
レンダリングすると、最初のメッセージの content は resource ブロックです。
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
画像を添付する
from pathlib import Path
from mcp.server import MCPServer
from mcp.server.mcpserver import Image, Message, UserMessage
mcp = MCPServer("Code Helper")
DIAGRAM_FILE = Path(__file__).parent / "architecture.png" # or the path to your file on disk
@mcp.prompt()
def explain_component(component: str) -> list[Message]:
"""Explain one component using the architecture diagram."""
return [
UserMessage(Image(path=DIAGRAM_FILE)),
UserMessage(f"Where does {component} sit in this architecture, and what does it talk to?"),
]
Imageは 画像、音声、アイコン で紹介するヘルパーです。プロンプトがレンダリングされるとき、UserMessageはこれをImageContentブロック(ファイルは base64 エンコードされ、MIME タイプは.pngから推測されます)に変換します。Audioも同じようにAudioContentになります。server.pyの隣にarchitecture.pngという名前の PNG を何か置いてください。プロンプトの引数は文字列なので、画像は常にサーバー側から来ます。componentが与えるのは言葉だけです。
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
実行時にリストを変更する
プロンプトは、クライアントが接続している間にも追加できます。たとえば、ユーザーが指示を自分専用のメニュー項目として保存できるようにする場合です。プロンプトを登録してから、通知します。
from contextlib import suppress
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
from mcp.server.mcpserver.prompts import Prompt
mcp = MCPServer("Code Helper")
@mcp.prompt()
def review_code(code: str) -> str:
"""Review a piece of code."""
return f"Please review this code:\n\n{code}"
@mcp.tool()
async def save_template(name: str, instruction: str, ctx: Context) -> str:
"""Save an instruction as a prompt the user can pick from the menu."""
def template(code: str) -> str:
return f"{instruction}\n\n{code}"
with suppress(ValueError): # replace an existing entry of the same name
mcp.remove_prompt(name)
mcp.add_prompt(Prompt.from_function(template, name=name, description=instruction))
await ctx.notify_prompts_changed()
await ctx.session.send_prompt_list_changed()
return f"Saved '{name}' to the prompt menu."
mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))は@mcp.prompt()とまったく同じように関数を登録し、mcp.remove_prompt(name)はその逆です。add_promptは同名の既存エントリを上書きせずそのまま残すので、このツールは保存が置き換えになるよう、先に古いエントリを削除しています。prompts/listには変更がすぐに反映されます。await ctx.notify_prompts_changed()は、subscriptions/listenストリームで待ち受けているすべての2026-07-28クライアントにnotifications/prompts/list_changedを送ります(サブスクリプション)。await ctx.session.send_prompt_list_changed()は、呼び出し元のクライアントが 2026 年より前の世代のときに、そのクライアントへ送ります(レガシークライアントへの対応)。両方を呼んでください。どちらも、伝える相手がいなければ何もしません。- 通知を受け取ったクライアントは、もう一度
prompts/listを呼びます。Python のClientではasync with client.listen(prompts_list_changed=True) as sub:がそれにあたり、PromptsListChangedイベントが届きます。
まとめ
- 関数に
@mcp.prompt()を付けるとプロンプトになります。名前は関数から、説明は docstring から取られます。 - プロンプトはユーザーが制御するものです。クライアントが一覧を出し、ユーザーが 1 つ選んで引数を入力します。
- 引数は名前付き文字列のフラットなリストです(スキーマなし)。デフォルト値のあるパラメーターは省略可能です。
strを返すと 1 つのユーザーメッセージになります。UserMessage/AssistantMessageのリストを返すと、複数ターンの会話の出発点を用意できます。title=とField(description=...)は、クライアントが UI に表示するものです。- 必須の引数が欠けていると、リクエスト全体が失敗します。プロンプト単位のエラー結果はありません。
EmbeddedResourceやImageをUserMessageでラップすると、ドキュメントや画像を添付できます。- 実行時にプロンプトを追加・削除するには
mcp.add_prompt(...)/mcp.remove_prompt(...)を使い、その後await ctx.notify_prompts_changed()とawait ctx.session.send_prompt_list_changed()を呼びます。
プロンプト(やリソーステンプレート)の引数をサーバー側でオートコンプリートする機能については、補完 を参照してください。