Skip to content

Починить нерабочий пакет, привести запросы к API 2.4 и добавить каскадную модель инструментов - #1

Open
mazixs wants to merge 11 commits into
Nymaxxx:mainfrom
mazixs:upstream/api-2.4-and-mcp-2.x

Conversation

@mazixs

@mazixs mazixs commented Aug 3, 2026

Copy link
Copy Markdown

Что изменилось

Опубликованный пакет checko-mcp 0.1.0 сейчас не запускается вообще. Зависимость mcp>=1.0 без верхней границы резолвится в mcp 2.0.0 (вышла 28.07.2026, мажорная переработка SDK), и сервер падает при импорте:

AttributeError: 'Server' object has no attribute 'list_tools'

Пока чинил это, сверил все запросы с живым API 2.4 и нашёл ещё 10 расхождений, из которых часть работала молча: запрос уходил, приходил 200, а данные были не те, что просил агент.

PR закрывает всё найденное, переводит проект на mcp 2.x и добавляет каскадный слой инструментов. Все исправления проверены обращением к реальному API, не только по документации.


1. Пакет не запускался

У всех зависимостей появились верхние границы, код переведён на mcp 2.x (обработчики в конструкторе Server, явная сборка результатов, типы в mcp_types со snake_case-полями). HTTP-клиент — на httpx2: httpx 0.28.1 от 12.2024 больше не развивается, а mcp 2.x сам зависит от httpx2, так что иначе в дереве было бы два HTTP-стека.

В CI добавлен шаг настоящего MCP-хендшейка. Именно его отсутствие позволило мажорному апгрейду SDK пройти проверки незамеченным: unit-тесты реестров проходят и тогда, когда сервер вообще не поднимается.

2. Запросы не соответствовали API

Что было Что происходило на самом деле
/entrepreneur получал ogrnip Метод ждёт ogrn. Поиск ИП по ОГРНИП не работал: живой API отдаёт HTTP 400
/search объявлял date_from и date_to Таких параметров у метода нет. API их молча игнорирует — агент считал, что отфильтровал по дате, и получал невыборку
ИНН-12 и ОГРНИП-15 отвергались валидатором На legal-cases, contracts, inspections, timeline, fedresurs, bankruptcy-messages API их принимает. ИП и физлица были отрезаны от половины инструментов
/enforcements отсутствовал Метод есть в API с версии 2.0, в проекте не было ни одного упоминания. Исполнительные производства ФССП — один из сильнейших сигналов, и его не было
Критерии поиска обрезаны до by=name|okved, obj=org Открыты founder-name, leader-name, reg-date, upd-date, obj=ent. Поиск по ФИО учредителя и руководителя — способ найти все компании человека, не зная его ИНН
/contracts без law=94 и sort 94-ФЗ недоступен, сортировки по сумме нет
Имена полей в agent-guide не совпадали с ответом API /legal-cases описан как СписДел[] с НомерДела, фактически Записи[] с Номер, Дата, Ист, Ответ. Аналогично /enforcements
Правило про квоту в agent-guide На бесплатном тарифе meta.balance равен 0 постоянно, и 100 запросов при этом работают. Ориентир — meta.today_request_count

Плюс: /company получил okpo, /inspectionssort; сетевые сбои больше не дают пустой текст ошибки; сервер сообщает клиенту свою версию (SDK по умолчанию подставляет пустую строку).

Чтобы это не разъезжалось снова, tests/test_api_contract.py держит таблицу документированных параметров каждого метода и проверяет: схема инструмента не объявляет ничего, чего у метода нет, все документированные методы достижимы, а в запрос уходит ровно то, что ожидается.

3. Ответы не помещались в контекст

Замеры на живом API: карточка крупной организации — 113 081 символ, расширенная финансовая отчётность — 151 597, выдача поиска — 108 191. Claude Code обрезает вывод инструмента на 25 000 токенов и делает это молча — ассистент получает оборванный JSON, не заметив обрыва.

Добавлен параметр detail (compact по умолчанию, full по запросу). Свёртывание универсальное — по длине списков, а не по белому списку полей: белый список молча терял бы новые поля API, а скалярные поля несут факторы риска. Рядом указывается, сколько элементов было всего.

Те же ответы в compact — 4 373–7 053 токена.

4. Каскадная модель инструментов

13 тонких обёрток заставляли агента самому знать модель данных Checko: какой метод вызвать по числу цифр в ИНН, в каком порядке опросить шесть реестров, что не забыть про исполнительные производства. Число инструментов осталось тем же, но они выстроены в три уровня:

Уровень 1resolve (наименование, ФИО или любой идентификатор → короткий список кандидатов) и profile (карточка с автоопределением вида субъекта по числу цифр: 8 ОКПО, 9 БИК, 10 ИНН юрлица, 12 ИНН физлица, 13 ОГРН, 15 ОГРНИП; для 12 цифр сначала ЕГРИП, и только при отсутствии ИП — физлицо, о чём сказано в ответе).

Уровень 2due_diligence_report (шесть реестров параллельно, 5–6 запросов) и bankruptcy_risk (четыре источника, 4–5 запросов).

Уровень 3 — девять обёрток над отдельными методами для детализации.

Заменяют search, get_company, get_entrepreneur, get_person — без потери возможностей.

Отчёты считают сигналы по формальным правилам, с уровнем и источником каждого, но вердикт о сделке не выносят: правила проверяемы, суждение — нет. Это закреплено тестом.

Проверка крупной организации на живом API — 6 запросов, 4 295 символов (~1 431 токен) на всё:

🔴 критично: Включён в санкционные списки
🔴 критично: Кредиторы заявили о намерении подать на банкротство: 8
🟠 важно:    Активных исков как ответчик: 716, сумма 6 764 112 883 руб.
🟠 важно:    Непогашенный остаток у приставов: 2 311 750 руб., 115 производств

5. Кэш ответов

TTL-кэш в CheckoClient.get() (CHECKO_CACHE_TTL, по умолчанию 900 с). Повторный отчёт по тому же субъекту стоит 1 запрос вместо 5 — при бесплатном тарифе в 100 запросов в сутки это существенно. Ошибки не кэшируются. Плюс повторы на 429 и 5xx с учётом Retry-After.

Валидация аргументов перенесена до создания клиента: раньше без API-ключа любая ошибка в аргументах маскировалась сообщением про ключ, а невалидный вызов расходовал платную квоту. Это проверяется тестом.

6. Тесты: 83 → 282

Покрытие 75 % → 91 %, server.pyс 0 % до 94 %. Появились test_server.py (настоящая MCP-сессия в памяти через create_client_server_memory_streams), test_api_contract.py, test_routing.py, test_shape.py, test_reports.py, test_cache.py, test_repo_hygiene.py.

Сервер собирается фабрикой build_server(client_factory=None), а не на уровне модуля — именно это сделало MCP-слой тестируемым.

test_repo_hygiene.py проверяет правило «только синтетические идентификаторы» из AGENTS.md. Оно существовало только как текст, и я сам, работая над этим PR, успел вписать в пример настоящий ИНН крупного банка. Настоящий идентификатор в документации — не опечатка: агент читает её как инструкцию и начинает обращаться к API по реальному субъекту, а в случае ИНН-12 — запрашивать персональные данные конкретного человека без законного основания.

7. Беларусь

У портала Checko есть данные по организациям Беларуси (ЕГР Минюста РБ, идентификатор УНП), но в API 2.4 нет ни одного метода для них: все методы работают по ОГРН, ИНН, ОКПО и БИК. Проверял — на УНП API отвечает 200 с пустым data, а не ошибкой. Это хуже явного отказа: выглядит как «не найдено». Теперь сказано прямо в описаниях инструментов, в agent-guide и в README, а profile отдаёт примечание.


Тип изменения

  • Багфикс (исправление, не ломающее обратную совместимость)
  • Новая функциональность (изменение, не ломающее обратную совместимость)
  • Breaking change (изменение, ломающее обратную совместимость)
  • Документация / инфраструктура

Breaking: удалены search, get_company, get_entrepreneur, get_person; минимальная версия Python поднята до 3.11 (3.10 выходит из поддержки 31.10.2026). Поэтому версия в PR — 0.2.0. Если удобнее самому нарезать релиз, перенесите секцию [0.2.0] из CHANGELOG.md в [Unreleased] и откатите версию в pyproject.toml — на код это не влияет.

Чеклист

  • Запустил ruff check src/ tests/ локально — без ошибок
  • Запустил pytest локально — 282 теста проходят
  • Добавил/обновил тесты для своих изменений
  • Обновил CHANGELOG.md
  • Обновлены docs/api/, docs/instructions/agent-guide.md, README.md, AGENTS.md и плейбуки в playbooks/audit/ (последние отдаются как MCP-ресурсы, поэтому устаревшие имена инструментов в них активно дезинформировали агента)
  • В коде нет секретов. Ключ, которым проверял живой API, лежал вне репозитория; проверено поиском по всем ревизиям

Что стоит знать перед мерджем

После мерджа нужен релиз на PyPI. Сейчас там 0.1.0, которая не запускается, и uvx checko-mcp ставит именно её. README в этом PR описывает установку штатным путём через uvx checko-mcp — она заработает после публикации 0.2.0.

Ветку CI стоит посмотреть отдельно: матрица расширена до Python 3.11–3.14, добавлен шаг MCP-хендшейка (scripts/smoke.py --no-api с фиктивным ключом, без обращений к сети).

Готов разбить PR на части, если так удобнее ревьюить: исправления запросов, миграция SDK и каскадный слой независимы друг от друга.

mazixs and others added 11 commits August 3, 2026 15:38
…3.11

Без верхней границы `mcp>=1.0` резолвился в mcp 2.0.0 (28.07.2026) — мажорную
переработку SDK, где low-level Server принимает handlers в конструкторе вместо
декораторов. Опубликованный пакет падал при импорте:

    AttributeError: 'Server' object has no attribute 'list_tools'

Unit-тесты этого не ловили (server.py покрыт на 0%), поэтому в CI добавлен шаг
реального MCP-хендшейка через scripts/smoke.py --no-api.

- mcp>=1.28,<2, httpx>=0.28,<0.29, python-dotenv>=1.0,<2
- dev: pytest 9.x, pytest-asyncio 1.x, ruff 0.16.x, pytest-cov
- requires-python >=3.11 (3.10 EOL 31.10.2026), матрица CI 3.11–3.14
- asyncio_default_fixture_loop_scope=function под pytest-asyncio 1.x

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…API 2.4

SDK 2.x: обработчики передаются в конструктор Server, результаты собираются явно,
типы протокола переехали в mcp_types со snake_case полями. Сервер теперь собирается
фабрикой build_server() — это и сделало MCP-слой тестируемым.

Исправления запросов (сверено со страницами методов checko.ru/integration/api):

- /entrepreneur получал `ogrnip`, а API ждёт `ogrn` — поиск ИП по ОГРНИП не работал
- добавлен инструмент get_enforcements (/enforcements, ФССП): метод существует
  с версии 2.0, но в проекте не было ни одного упоминания
- ИНН 12 цифр и ОГРНИП 15 цифр больше не отвергаются там, где API их принимает:
  legal-cases, contracts, inspections, timeline, fedresurs, bankruptcy-messages
- /search: открыты founder-name, leader-name, reg-date, upd-date и obj=ent,
  добавлены okved, opf, active; убраны date_from/date_to, которых у метода нет
- /contracts: добавлено значение law=94 и параметр sort (включая -price)
- /inspections: добавлен sort
- /company: добавлен okpo

Клиент: httpx2, повторы на 429 и 5xx с backoff и Retry-After, непустой текст
сетевых ошибок, учёт meta.balance. Валидация аргументов идёт до создания клиента,
поэтому невалидный вызов не расходует квоту и не маскируется ошибкой ключа.

Ошибки инструментов помечаются is_error=True, успешные ответы дублируются
в structured_content, инструменты получили title и read-only annotations.

Тесты: 83 → 159, покрытие 75% → 94%, server.py 0% → 93%.
Добавлены tests/test_api_contract.py (сверка схем с документированными
параметрами и проверка фактически уходящего запроса) и tests/test_server.py
(настоящая MCP-сессия в памяти).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…равка справочника

README: раздел установки переписан на четыре шага — ключ, uv, одна команда
под выбранный клиент (глобально и для проекта отдельно), проверка. Синтаксис
сверён с официальной документацией каждого клиента. Ссылки переведены с апстрима
на форк: пакет checko-mcp на PyPI принадлежит исходному проекту, поэтому
`uvx checko-mcp` без `--from` поставил бы не этот код.

agent-guide.md (отдаётся агенту как ресурс checko://docs/agent-guide) содержал
те же ошибки, что и код, то есть активно вводил агента в заблуждение:

- утверждал, что поиск по ФИО невозможен, хотя search by=founder-name
  и by=leader-name — основной способ раскрутить связи человека без ИНН
- указывал параметр ogrnip и законы только 44/223
- не упоминал исполнительные производства ФССП

Добавлены: предупреждение про объём ответа при source=true, разница между иском
и исполнительным производством, отсутствие белорусских данных (УНП) в API.

docs/api: исправлены entrepreneur.md (ogrn вместо ogrnip), search.md (полный
набор критериев, убраны несуществующие date_from/date_to), contracts.md
(law=94, sort); добавлена enforcements.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Замеры на реальном API показали, что перерасход контекста — не теоретическая
проблема, а норма. Claude Code предупреждает на 10 000 токенах и обрезает
на 25 000; из 21 проверенного ответа порог обрезки превышали шесть:

    /finances?extended=true          151 597 символов  (~50 500 токенов)
    /search?by=name                  108 191           (~36 000)
    /company (крупная организация)   113 081           (~37 700)
    /contracts?law=94                 92 470           (~30 800)
    /search?by=reg-date              120 352           (~40 100)
    /enforcements                     59 106           (~19 700)

Обрезка молчаливая: агент получает оборванный JSON и достраивает домыслом.

Сжатие сделано универсальным свёртыванием длинных списков, а не белым списком
полей: белый список молча терял бы поля, которые Checko добавит в новой версии
API, а скалярные поля несут именно факторы риска. Исключение — /finances, где
в сжатом виде остаются ключевые строки отчётности за все доступные годы.

Результат на тех же ответах: сжатие в 4,5–8,4 раза, максимум 7 053 токена —
ни один ответ больше не достаёт даже до порога предупреждения. Факторы риска,
статус и сводные показатели (ЗапВсего, ОбщСуммИск, ОбщКолич, ОбщСум, ОстЗадолж)
сохраняются полностью; в блоке `сжатие` указано, сколько элементов было всего
и как получить полный ответ.

source=true теперь требует detail="full" явно: сворачивать запрошенный дамп ФНС
бессмысленно.

Исправлено по итогам живой проверки — реальные имена полей не совпадали
с документацией:

- /enforcements: ИспПрНомер, ПредмИсп, СумДолг, ОстЗадолж и сводные ОбщКолич,
  ОбщСум, ОстЗадолж вместо выдуманных Номер, ПредметИсп, СуммаДолг, Остаток
- /legal-cases в agent-guide: Записи[] с Номер, Дата, Ист, Ответ, ПрАктуал,
  ПрАктив вместо несуществующих СписДел[] с НомерДела, Истец, Ответчик, Стадия
- /company: Учред.РосОрг/ИнОрг/ПИФ вместо Учред.ЮЛ, РМСП вместо МСП.Кат
- правило про квоту было неверным: на бесплатном тарифе meta.balance равен 0
  постоянно, и 100 запросов в сутки при этом работают. Ориентир —
  meta.today_request_count
- на белорусский УНП API отвечает 200 с пустым data, а не ошибкой: агент мог
  принять это за «компания не найдена»

Тесты: 159 → 193, покрытие 95%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Проблема была не в числе инструментов, а в том, что 13 тонких обёрток заставляли
агента самому знать модель данных Checko: какой метод вызвать по числу цифр в ИНН,
в каком порядке опросить шесть реестров, что не забыть про исполнительные
производства. Каждый шаг — место для ошибки и лишний расход контекста.

Новая поверхность (13 инструментов, три уровня):

- resolve — вход по наименованию, ФИО или любому идентификатору, возвращает
  короткий список кандидатов вместо 100 полных записей поиска
- profile — карточка с автоопределением вида субъекта по числу цифр: 8 ОКПО,
  9 БИК, 10 ИНН юрлица, 12 ИНН физлица, 13 ОГРН, 15 ОГРНИП. Для 12 цифр сначала
  ЕГРИП, и только при отсутствии ИП — данные физлица, о чём сказано в ответе
- due_diligence_report — шесть реестров параллельно, 5–6 запросов
- bankruptcy_risk — четыре источника сигналов, 4–5 запросов
- девять обёрток над отдельными методами остались для детализации

Заменены: search, get_company, get_entrepreneur, get_person.

Отчёты считают сигналы по формальным правилам с указанием источника и уровня,
но вердикт о сделке не выносят: правила проверяемы, суждение — нет.

Кэш ответов с TTL (CHECKO_CACHE_TTL, по умолчанию 900 с): повторный отчёт по тому
же субъекту стоит 1 запрос вместо 5. Ошибки не кэшируются.

Проверено на живом API:

- due_diligence_report по крупной организации — 6 запросов, 4 295 символов
  (~1 431 токен) на всю проверку. Только сырая карточка той же организации —
  113 081 символ. Сигналы найдены верно: санкции, 8 намерений кредиторов о
  банкротстве, 716 активных исков на 6,76 млрд руб., 115 исполнительных
  производств с остатком 2,31 млн руб.
- bankruptcy_risk сразу после отчёта — 1 запрос вместо 5, кэш переиспользован
- resolve by=leader-name работает

ToolSpec теперь либо endpoint (обёртка), либо handler (каскад) — ровно одно,
это проверяется в __post_init__. Эндпоинты каскадов объявлены в HANDLER_ENDPOINTS,
иначе тест покрытия методов API их не увидит.

Prompts переведены на каскады: check_counterparty вызывает due_diligence_report,
assess_bankruptcy_risk — bankruptcy_risk.

Тесты: 193 → 272, покрытие 91 %. Добавлены test_routing.py, test_reports.py,
test_cache.py. README описывает каскадную модель и сжатие ответов с замерами.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Плейбуки аудита отдаются агенту как MCP-ресурсы checko://playbooks/audit/*,
поэтому ссылки на убранные get_company, get_entrepreneur и get_person
инструктировали его несуществующими инструментами — тот же класс проблемы,
что был в agent-guide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Единственный её пункт — закрепление верхних границ зависимостей — уже описан
в 0.2.0, а самой версии не было ни на PyPI, ни в тегах.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SDK 2.x подставляет в version пустую строку, если её не передать в конструктор
Server. Отсутствие версии ничего не ломает — сервер поднимается, инструменты
работают, — поэтому заметить это можно только тестом. При этом версия в
server_info единственное, по чему клиент отличает сборки: 0.1.0 падала при
импорте.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
В README успел попасть настоящий ИНН крупного банка, а в тесты — настоящие
по виду ОГРНИП и БИК. Правило было записано в AGENTS.md, но существовало
только как текст, поэтому нарушить его не стоило ничего.

Настоящий идентификатор в примере — не опечатка: агент читает документацию как
инструкцию и начинает обращаться к API по реальному субъекту, а в случае ИНН-12
запрашивать персональные данные конкретного человека без законного основания.

Проверка ищет закавыченные последовательности из 8-15 цифр: идентификаторы
всегда закавычены, а денежные суммы в примерах ответов идут числами, поэтому
кавычки отделяют одно от другого без белого списка полей. Второй тест валит
мёртвые записи в ALLOWED, чтобы набор сужался, а не копился.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ветка форка ставила пакет из git, потому что на PyPI лежит нерабочая 0.1.0.
Для апстрима установка снова идёт штатным путём через uvx checko-mcp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Все три невидимы на моках и в документации - вскрылись только настоящим
вызовом через MCP-клиента.

1. Кэш отдавал устаревший meta.today_request_count, и счётчик суточных
   запросов убывал: resolve показал 51, отчёт истратил ещё 6, следующий
   вызов вернул 54. Агенту предписано следить по этому полю за квотой и
   предупреждать у 100, так что расход занижался - ошибка в опасную сторону.
   В ответах из кэша счётчика больше нет, вместо него meta.из_кэша.

2. ОгрДоступ=true выглядел как отсутствие данных. ФНС вправе закрыть
   сведения о руководителе и участниках, тогда ФИО, ОГРН и ИНН приходят
   пустыми. В отчёт уходило "руководитель": null - агент читает это как
   "руководителя нет", хотя на деле состав управления и владения проверить
   нельзя. Причина названа, добавлен сигнал.

3. Убытки несколько лет подряд не попадали в сигналы, хотя
   playbooks/audit/methodology.md этого требует - а методология отдаётся
   агенту как MCP-ресурс. Сервер отдавал правила, которым сам не следовал.
   Правило по строке 2400: убыток два и более года подряд, считая от
   последнего; один убыточный год - обычная волатильность.

Проверено, что каждый новый тест падает без своего исправления.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mazixs

mazixs commented Aug 3, 2026

Copy link
Copy Markdown
Author

Добавил коммит 1cb5217 — три дефекта, найденные приёмочной проверкой уже после открытия PR. Сервер был подключён к реальному MCP-клиенту и опрошен по живым данным; все три невидимы и на моках, и в документации.

1. Кэш отдавал устаревший meta.today_request_count. Счётчик суточных запросов убывал: resolve показал 51, каскадный отчёт истратил ещё 6, следующий вызов вернул 54. Агенту в agent-guide.md предписано следить за квотой именно по этому полю и предупреждать у 100 — то есть расход занижался, ошибка в опасную сторону. В ответах из кэша счётчика больше нет, вместо него meta.из_кэша: true.

2. ОгрДоступ: true выглядел как отсутствие данных. ФНС вправе закрыть сведения о руководителе и участниках, и тогда ФИО, ОГРН, ИНН приходят пустыми при ОгрДоступ: true. В отчёт уходило "руководитель": null — агент читает это как «руководителя нет», хотя на деле состав управления и владения по этим данным проверить нельзя. Для проверки контрагента это разные вещи. Причина теперь названа, добавлен сигнал 🟡.

3. Убытки несколько лет подряд не попадали в сигналы. playbooks/audit/methodology.md этого требует («убытки несколько лет подряд = риск»), и методология отдаётся агенту как MCP-ресурс — то есть сервер выдавал правила, которым сам не следовал. Добавлено правило по строке 2400: убыток два и более года подряд, считая от последнего. Один убыточный год сигналом не считается — это обычная волатильность.

Для каждого исправления проверено, что новый тест падает без него. Тестов 282 → 291, ruff чистый, MCP-хендшейк проходит.

Отдельно отмечу, чем это полезно за пределами трёх правок: первые два дефекта внесены изменениями из этого же PR (кэш и каскадные отчёты — новый код), и ни один из них не поймали бы ни юнит-тесты, ни сверка с документацией. Живой вызов остаётся обязательным шагом приёмки.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant