Перейти к содержанию

Промпты

Машинный перевод

Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.

Промпт — это шаблон сообщения, который выбирает пользователь.

Инструменты предназначены для модели. Промпт — наоборот: пользователь выбирает его из меню в своём клиенте (слэш-команда, кнопка), заполняет аргументы, и отрендеренные сообщения попадают в диалог так, будто он набрал их сам.

Чтобы объявить промпт, поставьте @mcp.prompt() над функцией, которая возвращает текст.

Первый промпт

server.py
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 читает те же три вещи, что и у инструмента:

  • Имя — это имя функции: review_code.
  • Описание, которое показывает клиент, — это строка документации: 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 становится одним сообщением пользователя:

{
  "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 проверяется до запуска функции. Попробуйте отрендерить review_code без 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. Заполните его, отрендерите — и в ответ придёт ровно то сообщение пользователя, что показано выше.

Больше одного сообщения

Ревью кода — это одно сообщение. Сессия отладки — это диалог, и промпт может задать его целиком.

Верните список сообщений вместо str:

server.py
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 при рендеринге даёт три сообщения по порядку:

{
  "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 — имя функции, а не подпись. Дайте клиенту что-нибудь получше для надписи на кнопке и опишите каждый аргумент, чтобы форма была понятна сама по себе:

server.py
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

Если вы читали страницу Инструменты, всё сказанное до этого места вам уже знакомо. Тот же декоратор, та же строка документации в роли описания, те же Annotated/Field. Меняется только то, кто запускает промпт (пользователь), и куда идёт результат (в диалог).

Больше, чем текст

UserMessage и AssistantMessage везде, где принимают str, принимают также блок содержимого или вспомогательный объект Image / Audio. В промптах встречаются два случая: вложить документ и вложить картинку.

Встраивание файла

server.py
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 (о ресурсах — на странице Ресурсы), который читается из файла style-guide.md рядом с server.py. Положите туда любой файл Markdown.
  • EmbeddedResource(resource=TextResourceContents(...)) (оба из mcp.types) несёт файл вместе с его URI и MIME-типом первым сообщением; запрос, который на него ссылается, идёт следом обычным текстом.
  • Встраивание, в отличие от вставки руководства прямо в f-строку, позволяет клиенту показать его как вложение и позже снова открыть style://python, а модель получает файл дословно. Для двоичного файла используйте BlobResourceContents с blob в base64.

После рендеринга content первого сообщения — это блок resource:

{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}

Вложение изображения

server.py
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 любой PNG с именем architecture.png. Аргументы промпта — строки, поэтому картинка всегда берётся с сервера; component даёт только слова.
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}

Изменение списка во время работы

Промпты можно добавлять, пока клиенты подключены, — например, чтобы пользователь мог сохранить инструкцию как собственный пункт меню. Зарегистрируйте промпт, затем отправьте уведомление:

server.py
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() отправляет notifications/prompts/list_changed каждому клиенту 2026-07-28, который слушает поток subscriptions/listen (Подписки). await ctx.session.send_prompt_list_changed() отправляет его вызывающему клиенту, если тот старше поколения 2026 (Обслуживание клиентов старого поколения). Вызывайте оба; каждый ничего не делает, когда сообщать некому.
  • Клиент, получивший уведомление, снова вызывает prompts/list. В классе Client на Python это async with client.listen(prompts_list_changed=True) as sub:, который выдаёт событие PromptsListChanged.

Итоги

  • @mcp.prompt() над функцией делает её промптом. Имя — из функции, описание — из строки документации.
  • Промпты управляются пользователем: клиент их перечисляет, пользователь выбирает один и заполняет аргументы.
  • Аргументы — плоский список именованных строк (без схемы). Параметр со значением по умолчанию необязателен.
  • Верните str — и она станет одним сообщением пользователя. Верните список UserMessage / AssistantMessage, чтобы задать многоходовой диалог.
  • title= и Field(description=...) — это то, что клиент показывает в интерфейсе.
  • Отсутствующий обязательный аргумент проваливает весь запрос. Отдельного результата с ошибкой у промпта нет.
  • Оберните EmbeddedResource или Image в UserMessage, чтобы вложить документ или картинку.
  • Добавляйте и удаляйте промпты во время работы через mcp.add_prompt(...) / mcp.remove_prompt(...), затем вызывайте await ctx.notify_prompts_changed() и await ctx.session.send_prompt_list_changed().

Автодополнение аргументов промпта (или шаблона ресурса) на стороне сервера — на странице Автодополнение.