MCP-сервер для 1С на Python SDK 2.x: проверка ответа по контракту
Разработка - DevOps и автоматизация разработки
Файлы
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний
Оформите подписку на компанию для решения рабочих задач
Оформить подписку и скачать решение со скидкойВы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
Разработчику MCP-сервера к HTTP-сервису 1С: как описать ответ моделью Pydantic, где проверять его в инструменте и что агент получает при каждом из восьми отказов. Прогон 05.10.2026 на mcp 2.2.0 с заглушкой вместо 1С.
- Аргументы и ответ проверяются отдельно: верный вызов инструмента не означает допустимый ответ сервиса.
extra="forbid",AwareDatetimeи шаблоны полей отклоняют лишнее ФИО, время без зоны и ссылку не по формату.- Ожидаемые отказы оформлены через
ToolError: иначе клиент видит общее «Error executing tool». - Журнал вызовов связывает ответ агенту с записью по TraceID и не хранит отвергнутые значения.
Ответ 1С для агента - такой же внешний ввод, как аргументы: его проверяют по контракту до возврата, а отказ объясняют путём поля и TraceID без самого значения.
Для кого: 1С-разработчикам, которые пишут свой MCP-сервер к HTTP-сервису базы.
Я проверила прототип MCP-сервера, который получает журнал регистрации через HTTP и отдаёт записи агенту. В хорошем ответе есть время UTC, замаскированный пользователь и навигационная ссылка. Если сервис добавляет полное ФИО, присылает время без зоны или меняет формат ссылки, сервер возвращает ошибку. Агент получает признак isError и причину отказа, а для ошибок после входа в инструмент - ещё и TraceID.
Ниже - контракт, фрагменты его реализации и результаты восьми проверок отказа. Все записи в этом опыте синтетические. Вместо платформы работает HTTP-заглушка, поэтому вы можете проверить Python-часть до публикации сервиса 1С. Саму публикацию и права пользователя базы этот опыт не подтверждает.
🔬 Что именно я проверила
Сервер предоставляет инструмент event_log. Входные параметры - metadata, since_utc, events. Выход - модель EventLogAnswer, внутри которой находится список EventLogRecord. Я разделила проверку аргументов и проверку ответа: корректный вызов инструмента ещё не означает, что HTTP-сервис вернул допустимые данные.
Версии сохранённого прогона:
| Компонент | Версия или режим |
|---|---|
| Python | 3.12.10 |
Python SDK MCP, пакет mcp |
2.2.0 |
| Pydantic | 2.13.5 |
| Источник данных | HTTP-заглушка на 127.0.0.1:18081 |
| Клиент | Client, сервер передан непосредственно |
| Таймаут HTTP в проверке | 1 секунда |
| Медленный ответ заглушки | Пауза 2 секунды |
Последний прогон я сделала 05.10.2026 на Windows 11 на текущем коде сервера. Если повторяете у себя, начните с фиксации своих версий: строка mcp 2.2.0, pydantic 2.13.5 печатается первой.
В начале проверки клиент запросил список инструментов. Существенная строка вывода выглядит так:
mcp 2.2.0, pydantic 2.13.5 1 инструмент event_log: readOnlyHint=True, вход=['events', 'metadata', 'since_utc'], outputSchema=есть
Наличие outputSchema подтверждено для этого инструмента. Публичная схема помогает клиенту понять форму результата; дополнительные правила Python проверяются при построении модели на сервере. Например, собственный валидатор UTC нельзя заменить одной лишь проверкой наличия схемы в списке инструментов.
Аннотация readOnlyHint=True описывает намерение автора инструмента. Она не выдаёт и не отбирает права пользователя 1С. В редакции спецификации MCP 2025-11-25 прямо сказано, что клиент обязан считать аннотации недоверенными, если сервер не входит в число доверенных. Ограничения на запись задаются правами пользователя базы, под которым работает HTTP-сервис.
🧱 Какие поля допускает контракт
Я начала с полей, которые агент использует для разбора события. Для записи обязательны time_utc, user, event, metadata, link; comment допускает строку или None. Дополнительные поля запрещены. Такой запрет распространяется и на оболочку ответа, где находятся trace_id, records и truncated.
Основные объявления из прототипа:
AwareDatetime требует информацию о часовом поясе. Проверка utcoffset() добавляет требование нулевого смещения. Строка с +03:00 содержит зону, но этот контракт её отклоняет. Автоматического перевода времени в UTC в показанном валидаторе нет. Если ваш сервис возвращает местное время, согласуйте преобразование на стороне сервиса или осознанно измените контракт.
Регулярное выражение для пользователя допускает до трёх символов перед тремя звёздочками. Оно проверяет наличие согласованной маски. Маскирование выполняет источник данных: Python-сервер не превращает полное ФИО в сокращение. В частности, строка Тестов Т. Т. до агента как успешная запись не доходит.
Ссылка может вести на объект через e1cib/data/...?... либо на список через e1cib/list/.... Для ссылки на объект требуется ref из 32 строчных шестнадцатеричных знаков без дефисов. Это узкий формат нашего контракта. Из него нельзя вывести, что любые другие навигационные ссылки платформы неправильны.
В текущем коде есть дополнительная зависимость между полями: изменение ссылочного объекта должно сопровождаться ссылкой на сам объект. Фрагмент проверки:
Этот метод находится внутри EventLogRecord. В восьми основных сценариях отдельного опыта с подменой объектной ссылки на ссылку списка нет. Я показываю правило как часть текущей реализации, не включаю его в число проверенных отказов.
🔌 Где ответ HTTP становится ответом MCP
Для SDK 2.2.0 в прототипе используются следующие импорты:
Инструмент объявлен с возвращаемым типом EventLogAnswer. Ниже приведён фрагмент регистрации и главная строка преобразования. AuditedServer - наш наследник MCPServer, который добавляет журналирование отказов; его поведение разберу дальше.
Это фрагмент функции, с опущенными ветвями ошибок. Он показывает место, где пришедший JSON проверяется моделью. До model_validate() данные HTTP-сервиса считаются сырым ответом. Возврат из инструмента происходит после успешной валидации.
TraceID создаётся в Python-сервере. В ответе поле trace_id устанавливается после распаковки raw, поэтому одноимённое значение источника будет заменено локальным идентификатором. При HTTP-вызове тот же идентификатор передаётся в заголовке X-Trace-ID.
Параметры запроса собираются через urlencode, список событий соединяется запятыми. В коде прототипа это связано с тем, что HTTP-обработчик 1С принимает параметры как соответствие. Произвольный текст запроса к базе инструмент не принимает: в его схеме такого параметра нет.
При HTTP 4xx или 5xx сервер формирует сообщение по коду ответа. Тело ошибки не читается. Так содержимое диагностического ответа веб-сервера не становится частью сообщения агенту через эту ветвь. При проблеме транспорта обрабатываются TimeoutError и URLError.
В этом прогоне проверены HTTP 502 и таймаут. Поведение для остальных кодов видно из ветвления, но отдельными строками результата здесь не подтверждено.
🧪 Что агент видит при каждом отказе
Для эталона я использовала одну синтетическую запись. Время внутри неё - тестовое значение, оно не обозначает дату запуска проверки.
Заглушка возвращает {"records": [GOOD]}. Для каждого сценария изменяется одно поле либо режим HTTP-ответа. Клиент вызывает тот же event_log, а имя сценария передаётся через metadata. Такое использование параметра принадлежит тестовой заглушке; в рабочем сервисе это имя объекта метаданных.
| Сценарий | Что изменено | Что получил клиент |
|---|---|---|
ok |
Эталонная запись | isError=False, одна запись |
extra_fio |
Добавлено user_full_name |
Ошибка лишнего поля |
naive_time |
Время без зоны | Ошибка отсутствия timezone |
msk_time |
Смещение +03:00 |
Сообщение «время не в UTC» |
empty_link |
Пустая строка ссылки | Ошибка шаблона link |
guid_dashes |
ref записан с дефисами |
Ошибка шаблона link |
unmasked |
Пользователь без маски | Ошибка шаблона user |
502 |
HTTP 502 | Ошибка источника с TraceID |
slow |
Пауза дольше таймаута | Ошибка доступности с TraceID |
Итого восемь отрицательных сценариев: шесть ответов вне контракта и два сбоя получения ответа. Успешный сценарий в эти восемь не входит. Фрагмент сохранённого вывода:
2 ok isError=False записей=1
2 extra_fio isError=True Error executing tool event_log: ответ 1С вне контракта, TraceID 9e430c99-1bac-42a3-afbd-cbe2bab6fc36: records.0.user_full_name: Extra inputs are not permitted
2 naive_time isError=True Error executing tool event_log: ответ 1С вне контракта, TraceID 9faa7938-668b-4520-86b1-d0798f03788d: records.0.time_utc: Input should have timezone info
2 msk_time isError=True Error executing tool event_log: ответ 1С вне контракта, TraceID a69db99e-2562-4a81-ad44-8eb476bfd199: records.0.time_utc: Value error, время не в UTC
2 empty_link isError=True Error executing tool event_log: ответ 1С вне контракта, TraceID e59cc6f3-f26f-413f-977c-79171073b6ab: records.0.link: String should match pattern '^e1cib/(data/
2 guid_dashes isError=True Error executing tool event_log: ответ 1С вне контракта, TraceID 8faf5551-d701-4178-955b-5e4a4a25f154: records.0.link: String should match pattern '^e1cib/(data/
2 unmasked isError=True Error executing tool event_log: ответ 1С вне контракта, TraceID 43ada938-6ec2-4462-a79e-763568f9af1d: records.0.user: String should match pattern '^[^*]{0,3}\*\
2 502 isError=True Error executing tool event_log: 1С ответила HTTP 502, TraceID 1371eee7-99ed-4241-bc81-b9e8695c30fb
2 slow isError=True Error executing tool event_log: 1С недоступна: timed out, TraceID 17f443c3-7fd0-4cde-8ce9-a32302060c24
Строки ошибок шаблона обрезаны самим проверочным скриптом: он печатает первые 160 символов результата. Полный шаблон виден в объявлении поля выше.
Путь records.0.time_utc означает поле первой записи списка. При лишнем поле ошибка указывает records.0.user_full_name. Такой путь помогает разработчику сервиса найти нарушенный участок ответа без публикации всей записи в журнале MCP.
Сборка описания ошибки в прототипе использует расположение и сообщение валидатора:
Поле input из структуры ошибки здесь не включается в problems. Это имеет значение для лишних данных: полное значение отвергнутого поля не требуется для объяснения причины. Однако свободные строки допустимых полей требуют отдельной проверки, о чём говорится в ограничениях.
📒 Почему отказ оформлен через ToolError
DoorError в нашем коде наследует ToolError. В проверенном SDK ожидаемая ошибка инструмента доходит до клиента с текстом. Для сравнения я создала отдельный инструмент boom, который выбрасывает обычный RuntimeError с сообщением о HTTP 502.
Результат сравнения:
5 обычное исключение вместо ToolError: агент видит «Error executing tool boom»
Поэтому ожидаемые отказы источника оформлены через ToolError. Иначе клиент в этом опыте видит лишь общее сообщение. Выбор исключения влияет на диагностику, но не отменяет необходимость фильтрации текста перед его отправкой.
Кроме восьми отказов ответа, проверка содержит два отказа до вызова HTTP-сервиса: since_utc без зоны и запрос неизвестного инструмента run_query. Их результат:
3 since_utc без зоны: isError=True Error executing tool event_log: 1 validation error for event_logArguments
since_utc
Input should have timezone info [t
4 чужой инструмент run_query: isError=True Unknown tool: run_query
6 журнал вызовов: 11 строк, статусы {'ok': 1, 'unavailable': 2, 'refused': 2, 'rejected': 6}
Сообщение проверки времени также обрезано скриптом. Для этих двух ранних отказов идентификатор создан в журнале обёрткой AuditedServer; приведённые ответы клиенту TraceID не содержат. Обещание «в каждой ошибке агент получает TraceID» было бы шире результата опыта.
Журнал содержит один успешный вызов, шесть отказов контракта, два сбоя доступности и два отказа входа. Это даёт 11 строк. Отдельный инструмент boom принадлежит другому серверу и в этот журнал не входит.
Я сохраняю в строке журнала trace_id, имя инструмента, аргументы, статус, длительность и краткое описание. Тексты проходят redact(), строки внутри аргументов - рекурсивную обработку redact_all(). В текущем коде описание ограничено 300 символами, длительность округляется до двух знаков после запятой.
Обёртка проверяет само исключение и его __cause__. Это нужно, чтобы уже записанный инструментом DoorError не появился второй строкой как ранний отказ. Для постороннего внутреннего сбоя предусмотрен статус crashed. В данном прогоне такого статуса нет; наличие ветви видно в реализации.
Если вы повторяете опыт, сначала проверьте число строк по статусам, затем сопоставьте конкретный TraceID ответа с журналом. Идентификаторы нового запуска будут другими. Само совпадение текстов ошибок со снимком не подтверждает, что журналирование сохранилось после изменения сервера.
🧭 Ограничения
Прогон сделан на mcp 2.2.0, Pydantic 2.13.5 и Python 3.12.10 с HTTP-заглушкой вместо опубликованной базы. Права пользователя базы, авторизацию веб-сервера и запрет записи этот опыт не проверяет: их проверяют на публикации HTTP-сервиса 1С.
Схема проверяет форму и несколько согласованных правил. Она не подтверждает достоверность события, существование объекта по ссылке или правильность имени метаданных. Синтетическая ссылка с ref, оканчивающимся на 1, проходит наш шаблон; переход в клиенте 1С в этом опыте не выполнялся.
Допустимые строки event, metadata, comment в текущем сервере дополнительно проверяются на признаки секрета. Я прочитала отдельную проверку утечек: она рассматривает аргументы, лишнее поле, комментарий и значение в форме UUID. Универсального распознавания секретов здесь нет. Произвольное значение в форме UUID без поясняющего имени по виду неотличимо от обычного идентификатора. Значения тестовых маркеров в статью не включены.
Принимается весь HTTP-ответ через resp.read(), затем разбирается JSON. Лимит размера HTTP-тела в этой функции не задан. Ошибки разбора JSON и все возможные внутренние сбои не входят в восемь рассмотренных сценариев. Политики повтора запроса в показанном вызове тоже нет.
Проверка журнала при запуске подтверждает возможность открыть файл в тот момент. Последующую потерю доступа к диску, заполнение тома и ротацию журнала я не проверяла. Для эксплуатации нужны отдельные решения по хранению и доступу к журналу: аргументы содержат имена объектов и интервалы запросов даже после удаления похожих на секреты строк.
Имена импортов сверены со страницей миграции SDK: MCPServer вместо FastMCP, ToolError в mcp.server.mcpserver.exceptions. Соответствие версии 2.2.0 всем требованиям спецификации MCP я не проверяла.
📚 Источники
- Миграция Python SDK MCP с 1.x на 2.x -
MCPServer,ToolError, отказ отmcp.server.fastmcp. - Инструменты MCP, спецификация 2025-06-18 -
outputSchema,isError, результаты инструментов. - Инструменты MCP, спецификация 2025-11-25 - аннотации инструментов считаются недоверенными.
- Рекомендации MCP по безопасности.
Практическая основа - проверка check_door.py и сервер archive_door.py; объявления и вывод прогона приведены в статье.
Предшественники на Infostart:
- Синтакс-помощник 1С как MCP-инструмент: индекс из справки платформы и сервер на Python SDK (Ninel_S, 01.10.2026) - та же библиотека, другая задача: индекс справки.
- MCP-сервер для проверки ИИ-кода 1С на соответствие API платформы (Sorm, 26.05.2026).
- MCP-сервер внутри 1C:EDT: даём AI-агентам «руки», не открывая ни одного порта наружу (galich, 08.07.2026).
- Бесплатный MCP-сервер для 1С: ИИ читает данные базы и структуру метаданных (MCP:RSV Data) (Prepod2003, 22.06.2026).
Здесь разобран контракт ответа 1С, причины отказа и сверка журнала вызовов.
📎 Файл к статье
В архиве к статье - 13.1-companion.zip: сервер archive_door.py, проверка check_door.py с HTTP-заглушкой вместо 1С, вывод прогона 05.10.2026 output-2026-10-05.txt и README. Установите mcp==2.2.0, запустите python check_door.py и сравните восемь отказов и счётчики журнала с этим выводом. Перед подключением своего сервиса согласуйте поля ответа, формат времени, маску пользователя и правила ссылок.
Вступайте в нашу телеграмм-группу Инфостарт