Инструменты
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
Инструмент — это функция, которую может вызвать модель.
Чтобы объявить инструмент, достаточно повесить @mcp.tool() на обычную функцию Python. Вот и весь API.
Ваш первый инструмент
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.tool()
def search_books(query: str, limit: int) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r} (showing up to {limit})."
Посмотрите, что получилось. Ни схем, ни JSON, ни протокола — просто функция. SDK извлекает из неё три вещи:
- Имя инструмента — это имя функции:
search_books. - Описание, которое видит модель, — это строка документации:
Search the catalog by title or author. - Аргументы, которые модели разрешено передавать, берутся из аннотаций типов:
query: strиlimit: int.
Входная схема
По этим аннотациям типов SDK генерирует JSON Schema и отправляет её клиенту в ответе на tools/list:
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
"limit": {"title": "Limit", "type": "integer"}
},
"required": ["query", "limit"],
"title": "search_booksArguments"
}
Оба аргумента попали в required, потому что ни у одного нет значения по умолчанию. Сейчас это исправим. (Ключи title — артефакты Pydantic; контракт составляют свойства, их типы и required.)
Tip
Аннотации типов здесь не документация. Это и есть контракт. Если клиент пришлёт "limit": "ten",
SDK отклонит вызов ещё до того, как запустится функция.
Что получает модель в ответ
Вызовите инструмент с {"query": "dune", "limit": 5} — результат состоит из двух частей:
result.content # [TextContent(text="Found 3 books matching 'dune' (showing up to 5).")]
result.structured_content # {'result': "Found 3 books matching 'dune' (showing up to 5)."}
content — это текст, который читает модель. structured_content — типизированные данные для клиентского приложения. Они появились потому, что тип возвращаемого значения объявлен как -> str.
Пока не думайте о structured_content. Возвращайте из инструментов настоящие объекты Python, и всё сработает как надо; этому целиком посвящена страница Структурированный вывод.
Попробуйте сами
Запустите сервер через MCP Inspector:
uv run mcp dev server.py
Откройте URL, который он напечатает, перейдите на вкладку Tools и вызовите search_books.
Inspector отрисует форму с обязательным текстовым полем query и обязательным числовым полем limit. Эту форму он построил по аннотациям типов. Так же поступит любой другой MCP-клиент.
Необязательные аргументы
Дайте параметру значение по умолчанию, и он перестанет быть обязательным. Вот и всё. Это обычный Python.
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.tool()
def search_books(query: str, limit: int = 10) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r} (showing up to {limit})."
Схема меняется соответственно:
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
"limit": {"default": 10, "title": "Limit", "type": "integer"}
},
"required": ["query"],
"title": "search_booksArguments"
}
limit ушёл из required и получил "default": 10. Клиент, который его не укажет, получит 10 — ровно так же, как в Python.
Более подробные схемы с Field
Аннотации типов дают очень многое, но иногда аргумент хочется описать или ограничить.
Оберните тип в Annotated и добавьте Field из Pydantic:
from typing import Annotated, Literal
from pydantic import Field
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.tool()
def search_books(
query: Annotated[str, Field(description="Title or author to search for.")],
limit: Annotated[int, Field(ge=1, le=50, description="Maximum number of results.")] = 10,
genre: Literal["fiction", "non-fiction", "poetry"] | None = None,
) -> str:
"""Search the catalog by title or author."""
where = f" in {genre}" if genre else ""
return f"Found 3 books matching {query!r}{where} (showing up to {limit})."
Три нововведения, и все на параметрах:
Field(description=...): описание отдельного аргумента, которое модель читает вместе со строкой документации.Field(ge=1, le=50): числовые границы. В схему они попадают как"minimum": 1, "maximum": 50.Literal["fiction", "non-fiction", "poetry"]: перечисление. Модель может выбрать только одно из этих значений.
Check
Ограничения — не украшение. Вызовите инструмент с limit=999, и SDK ответит
ошибкой инструмента ещё до запуска функции:
Input should be less than or equal to 50
Эта ошибка возвращается модели как результат инструмента, модель её читает и повторяет вызов
с допустимым значением. Вы один раз написали le=50 и бесплатно получили самокорректирующихся агентов.
Info
Если вы работали с FastAPI или Pydantic, всё это вам уже знакомо. Это тот же Field,
тот же Annotated, та же валидация. Ничего специфичного для MCP здесь учить не нужно.
Модель в качестве параметра
Когда инструмент принимает больше пары аргументов, сгруппируйте их в модель Pydantic:
from pydantic import BaseModel, Field
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
class Book(BaseModel):
title: str
author: str
year: int = Field(ge=1450, description="Year of first publication.")
@mcp.tool()
def add_book(book: Book) -> str:
"""Add a book to the catalog."""
return f"Added {book.title!r} by {book.author} ({book.year})."
Схема Book вкладывается во входную схему инструмента (как ссылка в $defs), модель заполняет её как JSON-объект, а функция получает настоящий экземпляр Book, уже проверенный, с атрибутами .title, .author и .year.
Можно сочетать как угодно: обычные параметры рядом с параметрами-моделями, вложенные модели, списки моделей. Везде один и тот же Pydantic.
async def
Если инструмент занимается вводом-выводом (вызывает API, читает файл, обращается к базе данных), объявите его через async def и используйте await внутри. SDK дождётся его выполнения.
Инструмент с обычным def тоже работает: SDK запускает его в отдельном потоке, так что сервер он не блокирует.
Больше ничего настраивать не нужно.
Имена, заголовки и аннотации
Всё, что SDK выводит сам, можно переопределить в декораторе:
from mcp.server import MCPServer
from mcp.types import ToolAnnotations
mcp = MCPServer("Bookshop")
@mcp.tool(
title="Search the catalog",
annotations=ToolAnnotations(read_only_hint=True, open_world_hint=False),
)
def search_books(query: str) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r}."
title— понятное человеку имя для интерфейсов. Клиенты покажут «Search the catalog» вместоsearch_books.annotations— поведенческие подсказки для клиента:read_only_hint=True: этот инструмент ничего не меняет.open_world_hint=False: он работает с закрытым набором объектов (этим каталогом), а не с открытым интернетом.- Две другие,
destructive_hintиidempotent_hint, описывают инструмент, который пишет: может ли он что-то удалить и равносилен ли двойной вызов одному? Спецификация определяет обе только для инструментов не только для чтения, так что наsearch_booksони ничего бы не значили.
Добросовестный клиент использует их, чтобы решать вопросы вроде «нужно ли спросить пользователя, прежде чем это запускать?». Это подсказки, а не средство безопасности. Никогда не полагайтесь на то, что клиент будет их соблюдать.
Tip
@mcp.tool() также принимает name= и description=, если не хочется выводить их
из имени функции и строки документации. Чаще всего хочется.
Итоги
@mcp.tool()на функции делает её инструментом. Имя — от функции, описание — из строки документации.- Аннотации типов и есть входная схема. Значения по умолчанию делают аргументы необязательными.
Annotated[..., Field(...)]добавляет описания и ограничения;Literalдобавляет перечисления.- Параметр — модель Pydantic — это способ принять структурированное «тело».
- Неправильные аргументы отклоняются за вас, с ошибкой, которую модель может прочитать и исправиться.
async defдля ввода-вывода, обычныйdefдля всего остального.
О том, что происходит со значением, которое вы возвращаете через return, — на странице Структурированный вывод.