Структурированный ответ MCP tool: схема, которую можно проверить до следующего вызова

AI-ассистент получает просьбу проверить остаток товара и оформить заказ. Сначала он обращается к складу, затем передаёт результат следующему инструменту. Здесь важно, чтобы программа точно понимала ответ склада: есть ли товар, сколько его и удалось ли вообще выполнить проверку.

Ответ «на складе примерно двенадцать штук» выглядит нормально в чате. Для программы он неудобен: где здесь число, насколько оно точное и что вернуть, если склад временно недоступен? Ещё хуже ответ «нет данных». Он может означать, что товар не найден, запрос упал или остаток пока неизвестен. Если следующий шаг вытащит число регулярным выражением или приравняет отсутствие числа к нулю, бизнес-решение начнёт зависеть от формулировки текста.

Результат MCP-инструмента может содержать и текст, и данные в отдельных полях. Текст можно показать человеку. А число единиц товара и статус проверки поместим в structuredContent, чтобы следующему шагу не пришлось извлекать их из предложения. Формат этих данных опишем в outputSchema — схеме, по которой можно автоматически проверить ответ.

Сначала определим состояния, а не поля

Для проверки остатка нам нужны три результата:

  • available: товар найден, количество известно;
  • unknown: товар найден, но достоверного количества сейчас нет;
  • not_found: склад не знает такой SKU.

Свяжем статус с количеством. Например, {"status": "unknown", "quantity": 0} содержит противоречие: количество одновременно неизвестно и равно нулю. В схеме зададим правило, которое не пропустит такой ответ:

OUTPUT_SCHEMA = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
        "status": {
            "type": "string",
            "enum": ["available", "unknown", "not_found"],
        },
        "quantity": {"type": ["integer", "null"], "minimum": 0},
        "checked_at": {"type": "string", "format": "date-time"},
    },
    "required": ["status", "quantity", "checked_at"],
    "additionalProperties": False,
    "allOf": [
        {
            "if": {"properties": {"status": {"const": "available"}}},
            "then": {"properties": {"quantity": {"type": "integer"}}},
            "else": {"properties": {"quantity": {"type": "null"}}},
        }
    ],
}

TOOL = {
    "name": "get_stock",
    "description": "Return the current stock state for one SKU",
    "inputSchema": {
        "type": "object",
        "properties": {"sku": {"type": "string", "minLength": 1}},
        "required": ["sku"],
        "additionalProperties": False,
    },
    "outputSchema": OUTPUT_SCHEMA,
}

Здесь status задаёт смысл результата. Ноль остаётся допустимым известным остатком: available и quantity=0. Для unknown и not_found количество обязано быть null. Поле checked_at позволяет следующему шагу проверить свежесть данных, не извлекая время из предложения.

Проверяем результат на стороне инструмента

Схема в описании tool полезна только тогда, когда обработчик действительно собирает и валидирует тот же объект. Для проверки примера установим валидатор:

python -m pip install "jsonschema[format]"

Функция ниже валидирует результат до отправки. Тот же объект попадает в structuredContent, а сериализованная копия — в текстовый блок для совместимости с клиентами, которые читают только content:

import json
from jsonschema import Draft202012Validator, FormatChecker

validator = Draft202012Validator(
    OUTPUT_SCHEMA,
    format_checker=FormatChecker(),
)

def make_result(status: str, quantity: int | None, checked_at: str) -> dict:
    data = {
        "status": status,
        "quantity": quantity,
        "checked_at": checked_at,
    }
    validator.validate(data)

    return {
        "resultType": "complete",
        "content": [
            {
                "type": "text",
                "text": json.dumps(data, ensure_ascii=False),
            }
        ],
        "structuredContent": data,
        "isError": False,
    }

Теперь успешный ответ не требует разбора естественного языка:

result = make_result(
    status="available",
    quantity=12,
    checked_at="2026-10-22T10:15:00Z",
)
print(result["structuredContent"])
{'status': 'available', 'quantity': 12, 'checked_at': '2026-10-22T10:15:00Z'}

А противоречивый объект останавливается в обработчике:

make_result(
    status="unknown",
    quantity=0,
    checked_at="2026-10-22T10:15:00Z",
)
jsonschema.exceptions.ValidationError: 0 is not of type 'null'

Теперь противоречивый ответ отклоняется до отправки. Получив данные, клиент тоже проверяет их по outputSchema: так он не продолжит оформление заказа, если сервер прислал объект неправильного формата.

Что делать с ошибкой источника

Отдельно обработаем недоступность склада. В нашей схеме unknown означает: товар найден, но количество ещё не известно, например инвентаризация не закончена. Если же складской сервис вообще не ответил, возвращаем ошибку вызова с isError: true. Клиент сможет отличить сбой соединения от нормального ответа склада.

Дальнейшие действия можно описать явно. Для заказа нужны статус available, достаточное количество и свежие сведения — время проверки хранится в checked_at. При not_found сообщаем, что товар не найден. При unknown предлагаем проверить остаток позже. А при ошибке сервиса разбираемся со сбоем. Эти решения больше не зависят от того, какой фразой инструмент описал результат.

Как продолжить до работающей MCP-интеграции

На курсе AI Agent Integrations PRO: MCP вы пройдёте весь путь вызова: опишете инструмент на сервере, подключите клиент, получите список доступных инструментов и обработаете результат. Дальше добавите проверки ошибок и доступа, журналы вызовов и тесты. Итоговое AI-приложение будет получать данные и выполнять действия через MCP.

Посмотреть программу курса MCP

Для знакомства с протоколом можно начать с открытого урока «Что такое MCP и зачем он нужен AI-агентам». Затем пригодятся разборы пагинации результатов tool и изменения схемы без поломки клиентов.

Техническая справка: MCP 2026-07-28: Tools, JSON Schema Draft 2020-12.