惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как я «подружил» банковскую АБС с AI через MCP
sergres (Диа · 2026-04-27 · via Все публикации подряд на Хабре

Привет, Хабр!

Я Сергей Жилко, руководитель продукта «Брокерское обслуживание» в Диасофт.

В этой статье расскажу, как и зачем сделал MCP (Model Context Protocol) для автоматизированной банковской системы.

Важный дисклеймер: продукт, который описан в этой статье, не является продуктом компании Диасофт. Это community-версия, которую я разработал для проверки архитектур MCP и выкладываю с целью популяризации ИИ в банках, т.к. это, пожалуй, одна из самых закрытых отраслей для использования ИИ в продакшн, особенно в закрытых контурах. 

Предыстория: зачем банку MCP

Я, как правильный продакт, делал очередной цикл исследования (discovery) на тему прикладного использования ИИ-агентов в банке, если быть конкретнее, в бэк-офисе банка. В рамках интервью внезапно выявилась боль, которая подтвердилась потом еще у нескольких клиентов. Сценарий выглядел так: приходит Центробанк или другой регулятор и просит выбрать разные виды клиентов или виды операций клиента по определенным признакам, которые не получить просто в интерфейсе или с помощью готовых отчетов. Такие выборки делает разработчик и отдает бизнесу в запрашиваемом формате.

В результате анализа стало понятно, что можно попробовать сделать MCP (Model Context Protocol), т.к. запросы практически всегда разные. Казалось, что можно было бы их скормить LLM и получить результат в ожидаемом формате.

Но с нашей системой не все так просто.

Представьте, что вы – разработчик в банке. Перед вами автоматизированная банковская система от Диасофт (ранее – Diasoft FA#), которая насчитывает тысячи API. Каждое API – это хранимая процедура в MS SQL или PostgreSQL, которая принимает параметры и сессионные pAPI-таблицы на вход, а на выход заполняет другие pAPI-таблицы. (Да-да, у нас есть SOAP и REST обертки поверх. Но суть в том, что этих API – тысячи. Чтобы получить результат, надо вызвать целый каскад API).

Чтобы найти сделки клиента по ценным бумагам, нужно:

  1. Найти клиента по ФИО (API_Prs_FindListByFIO) -- получить PersonID

  2. По PersonID найти счета (API_Acc_FindListByListOwnerID) -- получить AccountID

  3. По AccountID найти сделки (API_DSec_FindListIDByAccID) -- получить DealSecurityID

  4. По DealSecurityID получить полные данные сделки (API_DealSecurity_FindListByID)

Четыре вызова, каждый со своей спецификой.

Идея заключалась в том, чтобы дать ИИ-ассистенту возможность самостоятельно находить нужные API, понимать их контракты и вызывать хранимые процедуры через MCP (Model Context Protocol). Звучит просто, да?

Первая попытка: четыре метатула и 24-мегабайтный JSON

Первая реальная попытка была вполне рабочей на бумаге. Идея та же: не регистрировать тысячи инструментов, а дать LLM четыре метатула для "ленивой" навигации:

1. list_api_categories()       → список категорий ("DRSec", "CRM", "DEPO"...)
2. get_apis_in_category(cat)   → API внутри категории
3. get_api_schema(cat, api)    → полный контракт
4. execute_api(cat, api, ...)  → вызов Api

Категории определялись по префиксу имениAPI_DRSec_* → категория DRSecAPI_CRM_* → CRMAPI_Acc_* → Acc. Просто парсим имя процедуры по _, берем второй сегмент. Звучит логично.

Как собирались метаданные

Данные для реестра вытягивались из двух источников и склеивались в один JSON:

graph LR
    subgraph "Источник 1: SQL Server"
        SYS["sys.procedures<br/>sys.parameters"]
    end

    subgraph "Источник 2: HTML-документация"
        HTML["~1700 .htm файлов<br/>windows-1251"]
    end

    SYS -->|"extractMetadata.ts"| SQL_JSON["sql_metadata.json<br/>21 МБ"]
    HTML -->|"parseHtml.ts"| HTML_JSON["html_parsed.json<br/>15 МБ"]

    SQL_JSON --> MERGE["mergeRegistry.ts"]
    HTML_JSON --> MERGE
    MERGE --> REG["api_registry.json<br/>24 МБ — один файл на всё"]

    style REG fill:#ffe1e1

SQL-экстрактор ходил в sys.procedures и sys.parameters, вытаскивал имена хранимок, имена параметров и их SQL-типы (intbigintnvarchar). Никаких бизнес-описаний – только техническая мета.

HTML-парсер перебирал документацию АБС Диасофт, вытаскивал "Название", "Краткое описание", описания таблиц.

Merger пытался склеить эти два мира: взять техническую схему из SQL, обогатить бизнес-описаниями из HTML и на выходе сгенерировать JSON Schema для каждого API. Результат – один монолитный api_registry.json на 24 мегабайта, который загружался в память при старте сервера.

Что пошло не так 

1. Категории по префиксу имени – это не бизнес-объекты

Группировка API_DRSec_* → "DRSec" выглядит логично, пока не сталкиваешься с реальностью. В АБС Диасофт бизнес-объект (BO) – это отдельная сущность со своим файлом BO_*.htm, визой архитектора и списком привязанных API. Но API могут называться не по BO, к которому относятся. А некоторые категории по префиксу содержали по 150+ API без какой-либо фильтрации – LLM просто тонула в этом списке.

2. SQL-типы вместо Diasoft-типов

Из sys.parameters мы получали bigintnvarchar(255)int. Но АБС Диасофт использует свою систему типов: DSIDENTIFIER (bigint, но это ID сущности), DSVARFULLNAME (nvarchar, но это ФИО), DSMONEY (decimal, но это деньги). Информация о типах АБС Диасофт живет только в HTML-документации, и merger не всегда мог их правильно сопоставить. В итоге LLM видела "type": "integer" вместо "type": "integer", "description": "ID персоны [DSIDENTIFIER]" и не понимала семантику параметров.

3. Нет графа зависимостей

LLM видела 1700 API (без фильтрации!) и понятия не имела, что для вызова API_DSec_FindListIDByAccID нужен AccountID из API_Acc_FindListByListOwnerID, а для него –PersonID из API_Prs_FindListByFIO. Модель должна была догадываться сама по именам параметров и описаниям. Спойлер: догадывалась через раз.

4. Нет фильтрации: 1700 вместо 1098

В реестр попадали ВСЕ процедуры: запрещенные, устаревшие, нереализованные, внутренние, с визой "не ОК". Во второй версии мы фильтруем через 4-ступенчатую воронку (Find/Get/Check/Brows → Виза архитектора → Реализовано → Не запрещено) и из 3158 API оставляем 1098. В первой версии модель должна была разбираться с полным зоопарком.

5. 24 МБ JSON в памяти

Один api_registry.json на 24 мегабайта загружался целиком при старте. Перезапуск парсинга означал регенерацию всего файла. Отлаживать конкретный API – это Ctrl+F по 24-мегабайтному JSON. Во второй версии метаданные разложены по отдельным файлам: /business-objects/DSec.json/api-services/API_DSec_FindListIDByAccID.json/table-definitions/pAPI_Person_FindList.json.

Итог первой попытки

Архитектура с 4 метатулами была правильным направлением – lazy loading действительно решает проблему масштаба. Но реализация страдала на каждом уровне: кривая склейка метаданных, отсутствие бизнес-структуры и зависимостей между API, опасная работа с таблицами. Модель находила нужные API через раз, а когда находила – вызывала их с неправильными параметрами.

Нужно было переосмыслить не количество инструментов (четыре –  это правильно), а качество метаданных и навигацию по ним.

Вторая попытка: архитектура, которая работает

Текущее решение – DiasoftMCP – состоит из двух полностью независимых модулей:

DiasoftMCP(Unofficial Community Version)/
├── src/                    # Модуль 1: Парсер документации → JSON-метаданные
│   ├── parsers/            # Cheerio-парсеры для BO, API, Table .htm
│   ├── pipeline/           # 10-шаговый конвейер обработки
│   ├── analysis/           # Граф зависимостей, статистика
│   └── output/             # JSON, отчёты, Mermaid-диаграммы
│
├── parsed-metadata/        # Результат работы парсера (JSON-файлы)
│
└── mcp-server/             # Модуль 2: MCP-сервер
    ├── data/               # Копия метаданных (бандл)
    ├── src/
    │   ├── tools/          # 7 MCP-инструментов
    │   ├── db/             # Клиенты MS SQL / PostgreSQL
    │   └── schema-builder.ts  # Динамическая генерация JSON Schema
    └── tests/

Ключевая идея: парсер – это offline-инструмент, который запускается при обновлении документации. MCP-сервер – это runtime, который загружает готовые JSON и динамически строит контракты. Обновили документацию API? Перезапустили парсер, скопировали метаданные (npm run import-metadata), перезапустили сервер. Готово.


Модуль 1: Парсер документации

Что парсим

Структура документации АБС Диасофт:

Api_Fa/
├── BObjects/                   # 264 файла бизнес-объектов│   ├── BO_Person.htm           
│   ├── BO_DealSecurity.htm
│   └── Services/               # 3 158 файлов описаний APi│       ├── API_Prs_FindListByFIO.htm    
│       ├── API_DSec_FindListIDByAccID.htm
│       └── Tables/
│           └── T_API_*.htm     # 7 807 файлов описания таблиц

 Структура представляет собой таблицы без CSS-классов и ID, просто <table width=1000>. Парсим через Cheerio с предварительным декодированием через iconv-lite

Дизайн метаданных

Метаданные спроектированы так, чтобы быть полезными для LLM на каждом уровне:

Бизнес-объект (BO) -- точка входа в domain:

{
  "abbreviation": "DSec",
  "primaryPurpose": "Сделка с ценной бумагой",
  "description": "Бизнес-объект для работы со сделками...",
  "methods": [
    { "name": "API_DSec_FindListIDByAccID", "description": "..." },
    { "name": "API_DSec_FindListIDForPeriod", "description": "..." }
  ]
}

API -- полный контракт метода:

{
  "name": "API_Prs_FindListByFIO",
  "shortDescription": "Поиск физического лица по ФИО",
  "detailedDescription": "Метод осуществляет поиск ФЛ...",
  "inputParams": [
    { "name": "SurName", "dataType": "DSVARFULLNAME", "isRequired": true }
  ],
  "inputTables": [...],
  "outputTables": [
    { "name": "pAPI_Person_FindList", "direction": "out" }
  ],
  "errorCodes": [...]
}

Таблица -- описание колонок с типами:

{
  "tableName": "pAPI_Person_FindList",
  "direction": "out",
  "columns": [
    { "name": "PersonID", "dataType": "DSIDENTIFIER", "description": "ID ФЛ" },
    { "name": "SurName", "dataType": "DSVARFULLNAME", "description": "Фамилия" }
  ]
}

Конвейер обработки

flowchart TD
    A["11 000 .htm файлов<br/>windows-1251"] --> B["iconv-lite<br/>декодирование"]
    B --> C["Cheerio<br/>парсинг HTML"]

    C --> D["264 BO"]
    C --> E["3 158 API"]
    C --> F["7 807 Tables"]

    D --> G["Фильтр BO<br/>Виза ГА = Ok"]
    E --> H["4-ступенчатый<br/>фильтр API"]

    H --> |"Name: Find/Get/Check/Brows"| H1["1 354"]
    H1 --> |"Виза ГА = Ok"| H2["1 194"]
    H2 --> |"Реализовано = Да"| H3["1 114"]
    H3 --> |"Запрещено = Нет"| H4["1 098 API"]

    G --> I["229 BO"]
    H4 --> J["Cross-reference<br/>BO ↔ API ↔ Tables"]
    F --> J
    I --> J

    J --> K["Overlay Merger<br/>пользовательские подсказки, в каких сценариях использовать APi.md"]
    K --> L["Анализ зависимостей<br/>4 уровня"]

    L --> M["JSON-метаданные"]
    L --> N["Граф зависимостей<br/>31 284 ребра"]
    L --> O["Отчёт + Mermaid"]

    style A fill:#f9d,stroke:#333
    style H4 fill:#9f9,stroke:#333
    style I fill:#9f9,stroke:#333
    style M fill:#9df,stroke:#333
    style N fill:#9df,stroke:#333

Из 3 158 API после четырехступенчатого фильтра остается 1 098 – только реализованные, одобренные архитектором, незапрещенные поисковые/read-only методы.

Граф зависимостей

Граф зависимостей – это то, чего нет ни в одной документации, и что делает все решение действительно полезным.

Парсер автоматически строит граф зависимостей между API на 4 уровнях уверенности:

1

HIGH

Явное упоминание API_* в описании метода

"Предварительно вызовите API_Prs_FindListByFIO"

2

MEDIUM

Сопоставление *ID колонок входных таблиц с выходными таблицами других API

AccountID на входе ← AccountID на выходе API_Acc

3

MEDIUM

Семантический анализ описаний колонок

"Идентификатор клиента" → BO Person → API_Prs_*

4

LOW

Общие имена таблиц между API

Два API используют одну pAPI-таблицу

Итого: 31 284 ребра в графе, 404 точки входа (API, которые можно вызвать, зная только даты, ФИО или номера счетов).

Граф позволяет LLM понять: "Чтобы вызвать API_DSec_FindListIDByAccID, мне нужен AccountID. Его можно получить из API_Acc_FindListByListOwnerID, а для него нужен PersonID из API_Prs_FindListByFIO". Вся цепочка выстраивается автоматически.


Модуль 2: MCP-сервер

Архитектура шести слоев

Когда у тебя 1098 API, нельзя просто зарегистрировать 1098 MCP-инструментов – ни одна LLM не переварит такой список. Нужна многоуровневая навигация. Мы спроектировали 6 слоев (+ 1 новый), каждый из которых решает свою задачу:

Слой 1: diasoft_list_business_objects
  "Какие бизнес-области существуют?"
  → 229 BO × ~100 символов = ~20KB

Слой 2: diasoft_get_bo_apis
  "Какие API есть у бизнес-объекта Prs?"
  → 2-30 API × ~200 символов = 1-6KB

Слой 3: diasoft_get_api_details
  "Покажи полный контракт API_Prs_FindListByFIO"
  → inputSchema + outputSchema + errorCodes = 1-5KB

Слой 4: diasoft_search_apis
  "Найди API для работы со счетами"
  → Полнотекстовый поиск по имени, описанию, BO

Слой 5: diasoft_get_dependency_chain
  "Что нужно вызвать перед API_DSec_FindListIDByAccID?"
  → upstream/downstream граф с глубиной обхода

Слой 6: diasoft_execute_api
  "Вызови API_Prs_FindListByFIO с SurName='Иванов'"
  → EXEC хранимой процедуры в СУБД, маппинг результата

Слой 7: diasoft_execute_chain  ← NEW
  "Выполни цепочку Prs → Acc → DSec за один вызов"
  → Все шаги в одной транзакции, маппинг данных между шагами

Почему именно 7 слоев, а не 1098 инструментов?

LLM работает с MCP по принципу "tool discovery": сначала видит список доступных инструментов, потом выбирает нужный. Если дать 1098 инструментов – модель утонет.

Наш подход:

Слой 1 – каталог. LLM видит 229 бизнес-объектов, не 1098 API.

Слой 2 – зум в конкретную область. 5-30 API для выбранного BO.

Слой 3 – полный контракт. Только когда LLM решила, какой API вызвать.

Слой 4 – поиск. Когда LLM не знает, в каком BO искать.

Слой 5 – навигация. "Что вызвать до/после этого API?" (Эту фичу пока отлаживаем).

Слой 6 – исполнение. Реальный вызов хранимки.

Слой 7 – каскад вызовов в один проход (Эта фича пока не подтвердила реальную пользу).

Это как навигация в файловой системе: сначала ls /, потом ls /home/, потом cat file.txt.

Описание самих инструментов крайне важно.

Я убедился в том, что в такой архитектуре, когда LLM сама выбирает инструменты, описание самих инструментов крайне важно. Некоторые LLM пытались всегда просто звать diasoft_search_apis и угадать по моему запросу, как бы называлась API или кусочек API. Они не шли через цепочку list_business_objects → get_bo_apis. В итоге поигравшись с описаниями самих инструментов, нашел баланс на текущий момент, который позволяет спуститься по дереву инструментов, но не всегда на нем зацикливаться.

diasoft_list_business_objects 

ВСЕГДА вызывайте этот инструмент ПЕРВЫМ перед использованием diasoft_search_apis. Он предоставляет контекст обо всех доступных бизнес-доменах и помогает сделать последующие поиски более точными.

diasoft_get_bo_apis

Получить все доступные API для конкретного бизнес-объекта (БО). Передайте аббревиатуру БО (например, "Prs", "Acc", "DSec"), чтобы получить список API с названиями, описаниями и категориями. Используйте diasoft_list_business_objects предварительно для обнаружения доступных БО.

diasoft_get_api_details

Получить полные сведения о конкретном API: входные/выходные схемы, коды ошибок и имя хранимой процедуры. Передайте точное название API (например, "API_Prs_FindListByFIO"). Возвращает inputSchema (параметры и входные таблицы с типами), outputSchema (выходные таблицы с описанием столбцов), коды ошибок и зависимости. Используйте для понимания параметров, которые нужно передать в diasoft_execute_api.

diasoft_search_apis

Поиск API Diasoft по ключевому слову. Поиск выполняется по названиям API, описаниям и аббревиатурам БО. Используйте, когда нужно найти API, не зная точного названия или БО. Примеры запросов: "фамилия", "счет", "ценные бумаги", "FindList", "Person".

diasoft_execute_api

 Выполнить API Diasoft, вызвав его хранимую процедуру в базе данных. Требует настроенного подключения к БД (DB_TYPE, DB_HOST и т.д. в переменных окружения). Предварительно используйте diasoft_get_api_details для понимания требуемых параметров. Передайте название API и аргументы в соответствии с inputSchema. Возвращает результаты хранимой процедуры, сопоставленные с выходными таблицами.

diasoft_get_dependency_chain

ВАЖНО: Вызывайте этот инструмент ПЕРЕД выполнением любого API, для которого у вас нет необходимых параметров. Он показывает, какие API нужно вызвать предварительно для получения входных данных (восходящие зависимости), а также какие API можно вызвать следующими с полученными результатами (нисходящие). Пример: для вызова API_DSec_FindListIDByAccID нужен AccountID — этот инструмент покажет, что его предоставляет API_Acc_FindListByListOwnerID. Также используйте с api_name='*', чтобы найти точки входа — API, принимающие данные, которые уже известны пользователю (имена, даты, номера документов).

diasoft_execute_chain

 Выполнить цепочку API Diasoft в рамках одной транзакции базы данных. Удобно для многошаговых запросов (например, Клиент → Счёт → Ценные бумаги), которые иначе потребовали бы нескольких обращений к серверу. Каждый шаг может отображать строки результатов предыдущего шага во входные таблицы следующего через inputTableMapping. Все шаги разделяют одну сессию БД (SPID), поэтому сессионные таблицы видны на всех этапах цепочки.

Динамическая генерация контрактов

Контракты MCP-инструментов не захардкожены. Они строятся на лету из метаданных.

Когда LLM вызывает diasoft_get_api_details("API_Prs_FindListByFIO")schema-builder.ts превращает распарсенные метаданные в JSON Schema:

// Diasoft-тип → JSON Schema тип
export const DIASOFT_TYPE_MAP: Record<string, { type: string; format?: string }> = {
  DSIDENTIFIER:    { type: 'integer' },
  DSDATETIME:      { type: 'string', format: 'date-time' },
  DSMONEY:         { type: 'number' },
  DSVARFULLNAME:   { type: 'string' },
  DSACCNUMBER:     { type: 'string' },
  // ... всего 50+ типов
};

А на выходе LLM получает готовый контракт:

{
  "inputSchema": {
    "type": "object",
    "properties": {
      "SurName": {
        "type": "string",
        "description": "Фамилия [DSVARFULLNAME]",
        "example": "Иванов Иван Иванович"
      },
      "OnlyOpenFlag": {
        "type": "integer",
        "description": "Флаг поиска [DSTINYINT]",
        "example": 0
      }
    },
    "required": ["SurName"]
  }
}

Обновили версию API в документации? Перепарсили, пересобрали метаданные – контракты обновились автоматически. 

Параметры подключения к БД

Сейчас параметры подключения лежат в .env файле или передаются через переменные окружения в конфиге MCP-клиента:

{
  "mcpServers": {
    "diasoft": {
      "command": "node",
      "args": ["dist/src/index.js"],
      "env": {
        "DB_TYPE": "mssql",
        "DB_HOST": "db-server.local",
        "DB_PASSWORD": "secret"
      }
    }
  }
}

Да, пароль в открытом виде в конфиге. Для community-версии вполне подходит, для dev-окружения – приемлемо, но в планах – миграция на Vault для получения доступов по токену. Структура кода уже позволяет: getDbConfig() в config.ts – единственная точка чтения параметров подключения, так что рефакторинг будет минимальным.


Chain Execution: убираем лишние round-trips

Свежая фича, которую мы сейчас отлаживаем -- diasoft_execute_chain. Проблема: для поиска сделок клиента нужно 3-5 последовательных вызовов MCP, каждый из которых – это round-trip к LLM (3-5 секунд на inference). Итого выходит 15-25 секунд, из которых 80% – ожидание модели.

Решение – композитный инструмент, который выполняет всю цепочку за один round-trip:

{
  "steps": [
    {
      "api": "API_Prs_FindListByFIO",
      "args": { "SurName": "Михалов" }
    },
    {
      "api": "API_Acc_FindListByListOwnerID",
      "args": { "OnlyOpenFlag": 0 },
      "inputTableMapping": {
        "pAPI_Account_OwnerID": {
          "sourceStep": 0,
          "sourceTable": "pAPI_Person_FindList",
          "columnMapping": { "OwnerID": "PersonID" }
        }
      }
    }
  ]
}

inputTableMapping описывает, как маппить результаты предыдущего шага на входные таблицы текущего: "возьми PersonID из выходной таблицы pAPI_Person_FindList шага 0 и положи его как OwnerID во входную таблицу pAPI_Account_OwnerID".

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

Для этого мы отрефакторили DbClient:

export interface DbClient {
  // Существующие методы
  connect(): Promise<void>;
  disconnect(): Promise<void>;
  executeStoredProc(...): Promise<SpResult>;
  isConnected(): boolean;

  // Новые: транзакционная работа для цепочек
  beginTransaction(): Promise<unknown>;
  commitTransaction(txn: unknown): Promise<void>;
  rollbackTransaction(txn: unknown): Promise<void>;
  executeStoredProcInTransaction(txn, ...): Promise<SpResult>;
}

executeStoredProc стал тонкой оберткой: begin → executeStoredProcInTransaction → commit. Никакой дупликации логики.


Как это выглядит в работе

Типичный сценарий проверяли через в Claude Desktop, Windsurf, а также нашу внутреннюю платформу Digital Q.GPT:

Пользователь задает: "Найди сделки клиента Михалова по ценным бумагам"

ИИ (через MCP) проходит следующие шаги:

  1. diasoft_list_business_objects -- обнаруживает Prs, Acc, DSec, Sec

  2. diasoft_get_dependency_chain("API_DSec_FindListIDByAccID") -- понимает цепочку

  3. diasoft_execute_api("API_Prs_FindListByFIO", { SurName: "Михалов" }) -- находит PersonID

  4. Маппит PersonID → OwnerID, вызывает API_Acc_FindListByListOwnerID

  5. Маппит AccountID, вызывает API_DSec_FindListIDByAccID

  6. Отдаёт результат пользователю

Вот мой пример простых запросов к MCP. Ищем Петровых в нашей тестовой базе...

Вот мой пример простых запросов к MCP. Ищем Петровых в нашей тестовой базе...

Цепочку вызовов строит примерно так, если попросить не просто сущность найти а она спрятана за цепочкой вызовов:

Цепочку вызовов строит примерно так, если попросить не просто сущность найти а она спрятана за цепочкой вызовов:

Стек

Парсер

Node.js, TypeScript, Cheerio, iconv-lite

MCP-сервер

@modelcontextprotocol/sdk, Zod

СУБД

MS SQL (mssql) / Digital Q.DataBase

Тесты

Node.js built-in test runner

AI-клиенты

Claude Desktop, Windsurf, Claude Code, Diasoft Q.GPT

Как это было сделано

Весь проект – и парсер, и MCP-сервер – создан в паре с Claude Code. От постановки задачи и проектирования архитектуры до написания кода и тестов.

Рабочий процесс выглядел так:

  1. Я формулирую задание и контекст (какие файлы парсить, какая структура HTML).

  2. Claude Code исследует реальные .htm файлы, находит паттерны.

  3. Вместе проектируем план.

  4. Я задаю десятки уточнений, корректирую план до тех пор пока он не будет идеальным. Только после этого начинаем кодить.

  5. Claude Code пишет код, я делаю ревью и корректирую.

  6. Тесты, исправления, следующая итерация.

Самый поучительный момент: и план, и реализацию лучше всего делать с Opus 4.6. Это дорого, но надежно. (Просто поразительно, насколько за год шагнули модели и кодинг на них!). 

Что дальше

  1. MCP через HTTP  добавить помимо STDIO также общение с MCP по HTTP и TLS, плюс работа с токенами, а также Rate Limit

  2. Мониторинг – логирование вызовов для аудита (кто, когда, какой API, какие параметры) 

  3. Docker-деплой – multi-stage Dockerfile + docker-compose для сетевого развертывания

  4. OpenTelemetry – traces + metrics по каждому tool call → Grafana (Tempo + Mimir) логирование вызовов для аудита (кто, когда, какой API, какие параметры) 

  5. Chain Execution – доотладить diasoft_execute_chain на реальных цепочках

  6. Vault – убрать креденшлы из .env в HashiCorp Vault ну или любой другой

Итоги и ощущения от работы

Весь проект – от создания первой версии и до текущей стадии – занял у меня пару месяцев работы после работы. Оказалось, сложнее всего подобрать правильную архитектуру парсинга документации и обогащения документации информацией.
Повезло, конечно, что у нас сама документация на API – это не технически написанные мануалы, аналитики пишут ее с любовью и заботой. Там изначально были расписаны сценарии, как и зачем вызывать, поэтому нейронка очень редко путается и верно выбирает API.

Тем не менее, я заложил и реализовал схему с возможностью дописать Overlay поверх описания работы самой API, чтобы можно было менять выбор LLM API без изменения описания самого контракта.

Если честно, то первая версия ввела меня в уныние, и пару недель пришлось потратить на то, чтобы очистить мозг и перепридумать архитектуру. Зато момент, когда все сработало, был незабываем. Первые запросы "Найди клиента Иванова", "А какой у него адрес", а тем более, "А давай найдем депозиты Иванова" выдали верный результат, да еще и быстрый, да еще и с ответным запросом "У нас тут 15 Ивановых, какого хотим посмотреть?". Скрины в чате коллегам, отправленные в 3 ночи, надеюсь, были им приятны с утра. 

Весь этот проект является сommunity-версией MCP, т.е. неофициальной версией. Она задумывалась мной, во-первых, ради проверки концепции по архитектурам MCP, во-вторых, для популяризации ИИ в банковской отрасли, т.к. нет ничего лучше, чем показать работающий результат внутри, а не картинки на дашбордах.

Если есть желание, чтобы community-версия заработала на вашей версии АБС от Диасофт (Diasoft #FA), то нужно запросить у поддержки Диасофт документацию по продуктам, положить ее в каталог, сконвертировать и настроить MCP.

Overlay
Overlay выглядит как инъекция в описание контракта API с двумя посылами "Когда использовать" и "Важно".

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

## Когда использовать
- Получение остатка по счёту
- Для определения текущего баланса

## Важно- Принимает AccountID- Для оборотов — API_Acc_GetRestTurn- Для справочной информации о счёте — API_Acc_FindByID- Отличие от API_Acc_FindByID: FindByID возвращает справочные данные, GetRest — остаток

Ссылка на GitHub

Community-версия доступна на GitHub: https://github.com/szhilko196/MCP_DiasoftFA-unofficial-

Больше технической информации стараюсь отражать в README.

Выводы

Из двух попыток извлек следующие уроки: 

  1. Lazy loading – правильный паттерн, но дьявол кроется в деталях. Идея с метатулами работает. Но если метаданные кривые, навигация по префиксам имен, а зависимости между API не прослеживаются – LLM будет блуждать вслепую. Качество метаданных важнее количества инструментов.

  2. Разделяй парсинг и runtime. Документация – это offline-процесс. MCP-сервер – это runtime. Смешивать их – гарантированная боль. Перепарсил метаданные, скопировал JSON, перезапустил сервер – готово.

  3. Многоуровневая навигация – не опция, а необходимость. При тысячах API единственный способ дать LLM эффективно работать – это каскад: каталог → область → контракт → зависимости → вызов. Бизнес-объекты как навигационный слой, а не категории по префиксу имени.

Банковские системы славятся своей консервативностью: HTML в windows-1251, хранимые процедуры часто предпочитают вместо REST, сессионные таблицы вместо JSON-ответов. Но оказалось, что даже поверх всего этого можно построить работающий ИИ-интерфейс.