.. _gs3_rest_mcp: MCP-сервер (readonly) ===================== .. versionadded:: 1.29.0-ms2 .. _gs3_rest_mcp_overview: Обзор ----- Readonly MCP-сервер — реализация `Model Context Protocol `_ поверх сервера приложений Global3, дающая внешнему агенту (LLM-клиенту) доступ к прикладным :term:`выборкам <Выборка>` (Selection) **только на чтение**. Сервер выступает MCP-сервером для агента-клиента: агент через свой MCP-транспорт вызывает инструменты (tools), которыми сервер публикует :term:`метаданные <Метаданные>` выборок для discovery и постраничное чтение их строк. Ключевые свойства: - **Протокол** — JSON-RPC 2.0 поверх транспорта Streamable HTTP (без SSE-стрима). - **Область (Этап 1, readonly-MVP)** — discovery выборок, разрешённых агенту серверной политикой доступа, и headless-чтение их строк. Инструменты записи (``set_field``, ``run_operation``, ``commit`` и т.п.) намеренно исключены. - :term:`Exclusive Session` — ``initialize`` создаёт монопольный серверный :term:`рабочий сеанс <Рабочий сеанс>` (по образцу сервиса :ref:`es/pkg `); каждый вызов инструмента исполняется под монопольным захватом сессии («один tool-call за раз»). - **Политика доступа** — какие выборки видны агенту, определяет серверная политика в секции конфига :xsd:class:`Configuration.Mcp` (см. :ref:`gs3_rest_mcp_governance`). Внешний путь эндпоинта — ``/app/sys/rest/es/mcp`` (см. :ref:`uri_endpoint_reference`). .. attention:: MCP-сервер поддерживается **только на решениях PostgreSQL** (база с секцией ``eclipseLink`` в конфиге, режим соединений ``proxyShared``). Oracle-режим **не поддерживается**: там применяется другой контур работы с БД — соединение захватывается на всё время жизни выборки, а курсор остаётся открытым до её закрытия, поэтому долгоживущие открытые выборки MCP удерживали бы соединения пула. .. _gs3_rest_mcp_transport: Подключение и транспорт ----------------------- **Адрес сервиса** - ``http://{server:port}/app/sys/rest/es/mcp`` Ресурс смонтирован Jersey-сервлетом ``/app/sys/rest/*``; отдельной настройки ``web.xml`` для MCP не требуется — контейнерная аутентификация уже покрывает эту зону. HTTP-методы ``````````` .. list-table:: :header-rows: 1 :widths: 12 88 * - Метод - Назначение * - ``POST`` - Единая точка входа: диспетчеризация конверта JSON-RPC 2.0 (``Content-Type: application/json``, ``Accept: application/json``). * - ``DELETE`` - Закрытие сессии по заголовку ``Mcp-Session-Id`` (каскадно закрывает открытые выборки). * - ``GET`` - Не поддерживается: всегда ``405 Method Not Allowed`` с заголовком ``Allow: POST, DELETE``. Транспорт — Streamable HTTP: тело запроса и ответа несёт конверт JSON-RPC 2.0, SSE-стрим не используется. Заголовки запроса ````````````````` .. list-table:: :header-rows: 1 :widths: 24 12 64 * - Заголовок - Обяз. - Значение * - ``Authorization`` - да - ``Basic `` **или** ``Bearer `` (схема распознаётся регистронезависимо). См. :ref:`gs3_rest_mcp_auth`. * - ``Database`` - нет - Алиас целевой БД/решения; при отсутствии берётся ``defaultDatabaseAlias`` из ``global3.config.xml``. Регистр свободный — сервер нормализует имя в UPPER-CASE. * - ``Mcp-Session-Id`` - для ``tools/call`` и ``DELETE`` - Идентификатор сессии, выданный сервером в ответе на ``initialize``. * - ``MCP-Protocol-Version`` - нет - Проверяется на НЕ-``initialize``-запросах: если задан, но не из поддерживаемого набора — ``400``. Пустой/отсутствует — берётся дефолт. .. note:: Заголовок ``Database`` можно слать в любом регистре — сервер приводит имя решения к верхнему регистру. Но для схемы **Bearer** claim ``sln`` внутри JWT сверяется с нормализованным именем **побайтно** (регистрозависимо), поэтому имя решения в самом токене должно быть уже в ВЕРХНЕМ регистре, иначе ``401``. См. :ref:`gs3_rest_mcp_auth`. .. _gs3_rest_mcp_protocol_versions: Версии протокола MCP ```````````````````` Версия по умолчанию — ``2025-06-18``. Поддерживаемый набор (от новой к старой): - ``2025-06-18`` - ``2025-03-26`` Согласование: в ``initialize`` берётся ``params.protocolVersion``, если он входит в набор, иначе — дефолт; согласованная версия возвращается в ``result.protocolVersion``. На последующих запросах заголовок ``MCP-Protocol-Version``, если присутствует, должен принадлежать этому же набору, иначе ответ — ``400`` с JSON-RPC-ошибкой ``Invalid Request``. .. _gs3_rest_mcp_methods: Методы JSON-RPC и коды ответа ````````````````````````````` Диспетчер поддерживает ровно пять методов; прочие → ``METHOD_NOT_FOUND`` (-32601). .. list-table:: :header-rows: 1 :widths: 26 74 * - Метод - Назначение и результат * - ``initialize`` - Запрос (обязан нести ``id``). Согласует версию и возможности, создаёт ES-сессию, возвращает ``Mcp-Session-Id`` в заголовке ответа. ``result``: ``protocolVersion``, ``capabilities.tools.listChanged=false``, ``serverInfo`` (``name: "Global3 AS MCP"``, ``version``). * - ``notifications/initialized`` - Нотификация (без ``id``). Ответа нет → HTTP ``202``. * - ``ping`` - Запрос. ``result`` — пустой объект ``{}``. Сессию не трогает. * - ``tools/list`` - Запрос. ``result.tools`` — массив из 10 дескрипторов ``{name, description, inputSchema}`` (JSON Schema draft-07) в стабильном порядке. Сессию не трогает. * - ``tools/call`` - Запрос. Требует валидный ``Mcp-Session-Id``. ``params``: ``name`` (строка) и ``arguments`` (объект). Результат — MCP-контент (см. :ref:`gs3_rest_mcp_tools`). .. list-table:: HTTP-коды ответа :header-rows: 1 :widths: 12 88 * - Код - Условие * - ``200 OK`` - Успешный JSON-RPC-ответ. **Важно:** протокольные ошибки JSON-RPC (``PARSE_ERROR``, ``INVALID_REQUEST``, ``METHOD_NOT_FOUND``, ``INVALID_PARAMS``, ``INTERNAL_ERROR`` — включая превышение лимита сессий) тоже отдаются с HTTP 200, а тело несёт объект ``error``. Результат инструмента с ``isError:true`` — это тоже нормальный ``200``. * - ``202 Accepted`` - Принята нотификация (запрос без ``id``); тело пустое. * - ``204 No Content`` - ``DELETE`` нашёл и закрыл сессию. * - ``400 Bad Request`` - Единственный случай не-200 для JSON-RPC-ошибки: неподдерживаемая ``MCP-Protocol-Version`` на НЕ-``initialize``-запросе. * - ``401 Unauthorized`` - Нет/невалидные учётные данные; ответ несёт ``WWW-Authenticate: Bearer``. * - ``404 Not Found`` - ``tools/call`` с неизвестным/пустым ``Mcp-Session-Id`` либо ``DELETE`` несуществующей сессии. * - ``405 Method Not Allowed`` - ``GET``; ответ несёт ``Allow: POST, DELETE``. * - ``503 Service Unavailable`` - Сервер в режиме обслуживания (пользователь не SYSTEM); тело text/plain. Порядок хендшейка ````````````````` 1. ``POST initialize`` (с ``Authorization`` и ``Database``) → ``200``; из заголовков ответа клиент запоминает ``Mcp-Session-Id``, из тела — ``result.protocolVersion``/``serverInfo``. 2. ``POST notifications/initialized`` (без ``id``) → ``202``. 3. Рабочие запросы: ``tools/list`` (сессия не нужна), ``tools/call`` (обязателен ``Mcp-Session-Id``). Каждый ``tools/call`` исполняется монопольно. 4. ``POST ping`` — опциональная проверка живости эндпоинта. Сессию **не продлевает** (исполняется без неё), поэтому от закрытия по простою не спасает. 5. ``DELETE`` (с ``Mcp-Session-Id``) → ``204``; сессия и её открытые выборки закрываются каскадно. Пример: initialize `````````````````` .. code-block:: bash curl -sS -D - "http://localhost:8080/app/sys/rest/es/mcp" \ -H "Authorization: Bearer $JWT" -H "Database: GSTEST" \ -H "Content-Type: application/json" -H "Accept: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' Ответ — HTTP ``200``, заголовок ``Mcp-Session-Id: ``, тело: .. code-block:: json { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-06-18", "capabilities": {"tools": {"listChanged": false}}, "serverInfo": {"name": "Global3 AS MCP", "version": "1.29.0"} } } Конфигурация MCP-клиента (Streamable HTTP): .. code-block:: json { "type": "http", "url": "http://localhost:8080/app/sys/rest/es/mcp", "headers": {"Authorization": "Bearer ", "Database": "GSTEST"} } .. _gs3_rest_mcp_auth: Аутентификация -------------- MCP-эндпоинт не содержит собственного кода логина: аутентификацию выполняет транспортный слой (сервлет-фильтр), а ресурс дополнительно делает defense-in-depth (проверяет режим обслуживания и наличие principal) и берёт готовый principal из контейнера. Запрос без валидных учётных данных → ``401`` с ``WWW-Authenticate: Bearer``. Эндпоинт принимает **обе** HTTP-схемы — :ref:`Basic` и :ref:`Bearer` (один фильтр обслуживает обе; источники учётных данных перебираются в порядке: cookie ``access_token`` → заголовок ``Authorization`` → параметр ``access_token``). Выбор решения (алиас БД) резолвится единой цепочкой fallback: request-атрибут → заголовок ``Database`` → request-параметр ``Database`` → ``defaultDatabaseAlias`` из ``global3.config.xml``. Прежде чем использовать, сервер нормализует имя решения в UPPER-CASE. .. warning:: Для схемы **Bearer** имя решения зашито в JWT как приватный claim ``sln`` и сверяется с нормализованным (UPPER-CASE) значением **побайтно**. Поэтому токен, выпущенный с решением в нижнем регистре (``sln="gstest"``), даёт ``401`` — имя БД в токене должно совпадать с нормализованным, то есть быть в ВЕРХНЕМ регистре (``sln="GSTEST"``). Токены, которые сервер выпускает сам после логина, всегда несут уже заглавное имя решения; проблема воспроизводится для токенов, собранных вне сервера. Пример «сырого» запроса: .. code-block:: http POST /app/sys/rest/es/mcp HTTP/1.1 Authorization: Bearer ast_ Database: GSTEST MCP-Protocol-Version: 2025-06-18 Content-Type: application/json {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}} Полное описание типов аутентификации — :ref:`spec_services_http_authentication`. .. _gs3_rest_mcp_session: Сессия и её жизненный цикл -------------------------- MCP-сессия — это серверный :term:`рабочий сеанс <Рабочий сеанс>` (:term:`Exclusive Session`), создаваемый обработкой метода ``initialize`` (только запрос с ``id``; ``initialize`` без ``id`` сессию НЕ создаёт). При создании сервер генерирует случайный ``UUID`` и возвращает его клиенту в заголовке ``Mcp-Session-Id`` ответа. Внутренний ключ рабочего сеанса детерминирован от тройки ``(db, user, mcpSessionId)`` (вид ``MCP-{db}/{user}[mcp-{mcpSessionId}]``); последующие ``tools/call`` и ``DELETE`` реконструируют его из principal и заголовка ``Mcp-Session-Id``. Сессия строго привязана к тому же principal (db + user). Открытые выборки ```````````````` Инструмент ``open_selection`` открывает выборку в **собственном рабочем контексте**, переживающем вызов, и регистрирует её в реестре сессии по handle (UUID), возвращаемому агенту как ``sessionRef``. Инструменты ``get_opened_meta``/``read_rows`` резолвят выборку по ``sessionRef``. Отдельного инструмента ``close_selection`` в каталоге НЕТ — открытые выборки закрываются только вместе со всей сессией (каскадно). Что открытая выборка удерживает до закрытия сессии: сам объект выборки с ``DataStore`` и **прочитанными строками в памяти** сервера, свой ``WorkSessionContext`` и объект соединения движка. Физическое соединение с БД при этом НЕ занимается: на PostgreSQL оно берётся из пула на время выполнения запроса и сразу возвращается (короткая транзакция), серверный курсор между вызовами не живёт — строки уже прочитаны в кэш выборки. Поэтому цена открытой выборки измеряется памятью сервера, а не занятыми соединениями пула. Когда сессия закрывается ```````````````````````` Единый путь закрытия каскадно закрывает все открытые выборки сессии (а затем и их контексты). Триггеры: 1. **Явный** ``DELETE`` с ``Mcp-Session-Id`` → ``204``. Закрытие идёт под монитором сессии: если параллельно исполняется ``tools/call``, ``DELETE`` дождётся его завершения. 2. **Простой по таймауту.** Если клиент разорвал HTTP без ``DELETE``, сессию закрывает фоновый поток-уборщик ``RestSession-Eviction`` (общий для всех Rest-сессий). Таймаут простоя — **15 минут**, период обхода — тоже **15 минут**, поэтому фактическая зачистка простаивающей сессии наступает через **15–30 минут** после последней активности. Уборщик закрывает только ПРОСТАИВАЮЩИЕ просроченные сессии (не рвёт активный ``tools/call``). 3. **Остановка сервера** (``contextDestroyed``) — закрываются все Rest-сессии. Лимиты `````` .. list-table:: :header-rows: 1 :widths: 40 12 48 * - Лимит - Значение - Поведение при достижении * - Открытых выборок на сессию - 32 - ``open_selection`` возвращает ошибку инструмента «Достигнут лимит открытых выборок MCP-сессии (32). Закрой сессию (DELETE) перед открытием новых.»; только что открытая выборка сразу закрывается (ресурсы не текут). * - Живых MCP-сессий на пользователя - 8 - ``initialize`` отклоняется JSON-RPC-ошибкой ``INTERNAL_ERROR`` (HTTP 200): «Достигнут предел MCP-сессий на пользователя (8)…». Проверка+создание атомарны на per-user-мониторе (защита от TOCTOU). Оба значения — константы кода (``McpSessionAbst``, ``McpRestServiceImpl``); конфигом они не настраиваются. .. note:: Утечки выборок и их контекстов в штатных путях нет: каждый ``open_selection`` завершается либо регистрацией в сессии (гарантированное каскадное закрытие), либо немедленным закрытием на месте (при исчерпании лимита или гонке с закрытием сессии). Брошенная клиентом сессия не живёт вечно — её закроет уборщик простоя за 15–30 минут. Накопление сверху ограничено произведением лимитов (8 × 32). .. _gs3_rest_mcp_governance: Политика доступа агента к выборкам ---------------------------------- Какие :term:`выборки <Выборка>` доступны агенту, решает серверная политика — секция конфига :xsd:class:`Configuration.Mcp` файла ``global3.config.xml``. Это отдельный слой поверх обычной авторизации: даже успешно аутентифицированный агент видит через MCP только разрешённые политикой выборки. Права самого пользователя политика не меняет и строки данных не фильтрует — она сужает перечень выборок, которые агенту вообще можно назвать. Проверку проходит **каждый** инструмент, называющий выборку: ``find_selections`` ищет только среди разрешённых имён, а адресные инструменты (включая ``open_selection``) отвергают выборку вне перечня ошибкой «Выборка '…' не найдена или недоступна для MCP-discovery». Два атрибута и правило их чтения ```````````````````````````````` Политика описывается элементом :xsd:class:`Configuration.Mcp.AgentAccess` с двумя атрибутами, и они не равноправны — у каждого своя роль: - :xsd:attr:`Configuration.Mcp.AgentAccess.override` — **рубильник**: переключает весь MCP-контур целиком, не трогая базовую политику. По умолчанию ``off`` — «не вмешиваться». - :xsd:attr:`Configuration.Mcp.AgentAccess.default` — **базовая политика**: ответ сервера, когда рубильник отпущен. По умолчанию ``deny`` — «запрещено». Отсюда правило чтения ровно в два шага: #. Сервер смотрит ``override``. Любое значение, кроме ``off``, определяет исход само — ``default`` при этом не читается вовсе. #. И только при ``override="off"`` исход задаёт ``default``. .. list-table:: Что доступно агенту при каждом значении ``override`` :header-rows: 1 :widths: 20 50 30 * - ``override`` - Агенту доступны - Читается ли ``default`` * - ``off`` (по умолчанию) - при ``default="deny"`` — ни одной выборки, при ``default="allow"`` — все - да, это единственный такой режим * - ``allowAll`` - все выборки - нет * - ``denyAll`` - ни одной выборки - нет * - ``list`` - только перечисленные в :xsd:class:`Configuration.Mcp.Selections` - нет «Все выборки» здесь — все существующие в решении (``*.avm.xml``). Открыть несуществующее имя не даёт ни один режим: в ``list`` лишние имена перечня просто ни на что не отображаются, а ``allowAll`` открывает ровно то, что в решении есть. .. attention:: Конфигурация без секции ```` — равно как и пустой ```` — означает ``override="off"`` плюс ``default="deny"``: внешний агент **не видит ни одной выборки**, пока доступ не открыт явно (**fail-closed**). .. note:: Двух атрибутов кажется много там, где исход почти всегда решает один. Они отвечают за разное: ``default`` описывает намерение решения («в норме доступ закрыт»), а ``override`` — рубильник поверх него. Положения ``allowAll`` и ``denyAll`` при этом **отладочные**: они открывают или закрывают контур целиком, не читая ни ``default``, ни перечень :xsd:class:`Configuration.Mcp.Selections`, — место им на стендах разработки и тестирования (``allowAll`` стоит в dev-конфиге). Рабочий контур на Этапе 1 настраивают перечнем: ``default="deny"`` плюс ``override="list"`` — так выглядит пример в шаблоне конфига дистрибутива. Примеры XML-конфигурации ```````````````````````` Режим ``list`` — рабочая настройка: агенту разрешены ровно перечисленные выборки: .. code-block:: xml Режим ``allowAll`` — отладочный: агенту открыты все выборки решения. Так публикует их dev-конфиг стенда разработки: .. code-block:: xml В обоих примерах ``default="deny"`` записан для явности намерения, но на исход не влияет: рубильник переведён. Перечень допустимых значений обоих атрибутов — в справочнике XSD: :xsd:class:`Configuration.Mcp.DefaultAccess` и :xsd:class:`Configuration.Mcp.OverrideMode`. .. _gs3_rest_mcp_tools: Инструменты ----------- Каталог публикует **ровно 10** readonly-инструментов в стабильном порядке регистрации. Все вызываются методом ``tools/call`` (``params.name`` + ``params.arguments``); ``inputSchema`` каждого — JSON Schema draft-07. Упаковка результата и ошибки ```````````````````````````` Результат любого инструмента — MCP-контент ``{content:[{type:"text",text}], isError}``; полезная нагрузка лежит **строкой JSON** в ``content[0].text``. Успех — ``isError:false``. Любая прикладная ошибка (неизвестный ``sessionRef``, недоступная выборка, отсутствующий обязательный аргумент, неизвестное имя инструмента) — НЕ протокольная ошибка JSON-RPC, а нормальный ``result`` с ``isError:true`` и текстом сообщения. Протокольную ошибку ``INVALID_PARAMS`` даёт лишь невалидный конверт вызова (отсутствующий/нестроковый ``name``). .. list-table:: Каталог инструментов :header-rows: 1 :widths: 24 20 56 * - Инструмент - Группа - Назначение * - ``find_selections`` - A. Discovery - Найти выборки по подстроке в имени/заголовке. * - ``find_in_selection`` - A. Discovery - Сквозной поиск операций/атрибутов/полей фильтра по подстроке. * - ``get_selection_meta`` - A. Discovery - Узкие метаданные выборки: caption, description, представления (с caption/description/documentation), поля фильтра. * - ``list_attributes`` - A. Discovery - Список атрибутов выборки. * - ``get_attribute_meta`` - A. Discovery - Статические метаданные одного атрибута. * - ``list_operations`` - A. Discovery - Список операций выборки (без служебных). * - ``get_filter_meta`` - A. Discovery - Поля фильтра представления (ключи для ``open_selection.filter``). * - ``open_selection`` - B. Открытая выборка - Открыть выборку headless, вернуть ``sessionRef``. * - ``get_opened_meta`` - B. Открытая выборка - Метаданные открытой выборки с наложенными настройками администрирования. * - ``read_rows`` - B. Открытая выборка - Страница строк открытой выборки. .. attention:: Расхождение discovery ↔ open: инструменты группы A отдают статическое описание из метаданных выборки, к которому ещё не применены настройки системы администрирования. Поэтому в ``get_attribute_meta`` поля ``visible``/``readOnly`` приходят нейтральными заглушками (жёстко ``true``/``false``), а ``fieldType`` у прикладных сущностных атрибутов — ``ftUnknown``: реальный тип движок разворачивает лишь при активации выборки. Значения, отражающие открытую выборку — с наложенными настройками администрирования и развёрнутым типом поля, — даёт ТОЛЬКО ``get_opened_meta``. Группа A. Discovery без открытия выборки ```````````````````````````````````````` Семь инструментов читают статические и рантайм-метаданные разрешённых агенту выборок (:ref:`политика доступа `) **без открытия выборки** — каждый работает под своим короткоживущим контекстом с соединением, освобождаемым по выходу из вызова. Типичный маршрут: ``find_selections`` → ``get_selection_meta`` → ``list_attributes``/``list_operations``/``get_attribute_meta``/``get_filter_meta`` → переход к ``open_selection``. find_selections +++++++++++++++ - **Назначение:** найти доступные агенту выборки по подстроке в имени ИЛИ заголовке. - **Аргументы:** ``query`` (string, обязателен). - **Результат:** ``{selections:[{name, caption, module}], truncatedResults?, truncatedCaptionSearch?, note?}``. - **Ограничения/флаги:** список усечён верхним пределом **200** результатов (``truncatedResults:true``); поиск по caption ограничен бюджетом **500** загрузок (``truncatedCaptionSearch:true``). Каждый флаг сопровождается человекочитаемым ``note``. При флаге клиент должен сузить ``query`` или указать точное имя. - **Ошибки:** пустой ``query`` → «Не задан обязательный аргумент 'query'». find_in_selection +++++++++++++++++ - **Назначение:** поиск операций и/или атрибутов (и полей фильтра) по подстроке — по всем доступным выборкам или в одной. - **Аргументы:** ``query`` (string, обязателен); ``selection`` (string, опц.) — имя выборки; ``scope`` (string enum, опц.) — строго ``operation`` | ``attribute``. - **Результат:** ``{matches:[{selection, kind, name, caption, description}], truncated?, note?}``; ``kind`` ∈ {``operation``, ``attribute``, ``filter``} (``filter`` — только когда ``scope`` не задан). - **Ограничения/флаги:** без ``selection`` обход ограничен **200** просмотренными выборками, совпадений — не более **500**; достижение любого лимита → ``truncated:true``. - **Ошибки:** пустой ``query``; недопустимый ``scope`` → «Недопустимый scope '…'. Допустимые значения: operation, attribute (или не задавать).» get_selection_meta ++++++++++++++++++ - **Назначение:** метаданные выборки — ``caption``, ``description`` (человекочитаемое описание выборки из ```` уровня view), представления (с ``caption``/``description``/ ``documentation``) и поля фильтра (список атрибутов НЕ возвращает — для него ``list_attributes``). - **Аргументы:** ``name`` (string, обязателен). - **Результат:** ``{caption, description, representations:[{name, caption, description, documentation}], detailSelections:[string], filters:[{name, caption, fieldType, defaultValue, editorType}]}``. Тексты представлений (``caption``/``description``/ ``documentation``) могут быть ``null``, если соответствующий узел метаданных не заведён. Замечание: ``detailSelections`` лёгким путём не резолвятся — всегда пустой список. - **Ограничения:** при недоступности живых метаданных деградирует до минимальной нагрузки (``caption`` равен имени, ``description`` = ``null``, ``representations`` и прочие списки пусты) без падения. - **Ошибки:** пустой ``name``; выборка не разрешена политикой доступа. list_attributes +++++++++++++++ - **Назначение:** список атрибутов выборки (статические discovery-метаданные). - **Аргументы:** ``name`` (string, обязателен). - **Результат:** ``{attributes:[{name, caption, description}]}``. - **Ошибки:** пустой ``name``; выборка не разрешена политикой доступа. get_attribute_meta ++++++++++++++++++ - **Назначение:** статические метаданные одного атрибута. - **Аргументы:** ``name`` (string, обязателен) — имя выборки; ``attr`` (string, обязателен) — имя атрибута. - **Результат:** ``{name, caption, fieldType, visible, readOnly, required}``. - **Ограничения:** ``visible`` всегда ``true``, ``readOnly`` всегда ``false`` — заглушки, НЕ реальные права (см. ``get_opened_meta``); ``fieldType`` часто ``ftUnknown``. - **Ошибки:** пустой ``name``/``attr``; атрибут не найден → «Атрибут '' не найден в выборке ''.»; выборка не разрешена политикой доступа. list_operations +++++++++++++++ - **Назначение:** список операций выборки (служебные исключены). - **Аргументы:** ``name`` (string, обязателен). - **Результат:** ``{operations:[{name, caption, description}]}``. - **Ошибки:** пустой ``name``; выборка не разрешена политикой доступа. get_filter_meta +++++++++++++++ - **Назначение:** поля фильтра представления; их имена — КЛЮЧИ объекта ``filter`` в ``open_selection``. - **Аргументы:** ``name`` (string, обязателен). - **Результат:** ``{filters:[{name, caption, fieldType, defaultValue, editorType}]}``. - **Ограничения:** при недоступности метаданных фильтра деградирует до пустого списка без падения. Для detail-представлений ключ мастера — ``super$id``. - **Ошибки:** пустой ``name``; выборка не разрешена политикой доступа. Группа B. Работа по открытой выборке ```````````````````````````````````` Три инструмента работают ТОЛЬКО по уже открытой выборке, адресуемой ``sessionRef``. Жизненный цикл и лимиты — см. :ref:`gs3_rest_mcp_session`. open_selection ++++++++++++++ - **Назначение:** открыть выборку headless на чтение и вернуть ``sessionRef``. - **Аргументы:** ``name`` (string, обязателен); ``representation`` (string, опц.) — имя представления (по умолчанию основное); ``filter`` (object, опц.) — свободный объект имя_поля→значение (``additionalProperties:true``). Имена полей ``filter`` берутся из ``get_filter_meta`` (например ``flt_dOrderDateFrom``); для detail-представлений ключ мастера — ``super$id``. - **Результат:** ``{sessionRef, name, representation}``. - **Ограничения:** регистрация в сессии с лимитом **32** открытых выборок; открытие выборки, не разрешённой политикой доступа, запрещено. - **Ошибки:** пустой ``name``; выборка не разрешена политикой доступа; превышение лимита 32; ошибка активации/сборки выборки (как ``isError``, ресурсы освобождаются). get_opened_meta +++++++++++++++ - **Назначение:** прочитать метаданные открытой выборки — поля и операции с наложенными настройками системы администрирования. - **Аргументы:** ``sessionRef`` (string, обязателен). - **Результат:** ``{name, representation, caption, attributes:[{name, caption, fieldType, visible, readOnly, required}], operations:[{name, caption, description, visible, enabled}]}``. У атрибутов ``visible``/``readOnly`` — значения системы администрирования (``Attribute.isAdminVisible``/``isAdminReadOnly``), ``fieldType`` — развёрнутый тип живой выборки. У операций ``visible``/``enabled`` — настройки администрирования вместе с прикладной логикой (``isVisible() && isAdminVisible()``, ``isEnabled() && isAdminEnabled()``). - **Ошибки:** пустой ``sessionRef``; неизвестный/закрытый ``sessionRef`` → «Неизвестный sessionRef: » / «Открытая выборка не найдена по ссылке '…' (не открыта или уже закрыта).» read_rows +++++++++ - **Назначение:** прочитать страницу строк открытой выборки. Фильтр задаётся ТОЛЬКО в ``open_selection`` — повторная фильтрация не поддерживается. - **Аргументы:** ``sessionRef`` (string, обязателен); ``page`` (object, опц.) — ``{offset, limit}``. По умолчанию ``offset=0``, ``limit=100``. - **Результат:** ``{columns:[string], rows:[{колонка→значение}], offset, loadedCount, allFetched}``. - **Ограничения:** ``limit`` ограничивается сверху значением **1000** (защита от вычитывания всей таблицы); отрицательные ``offset``/``limit`` нормализуются к 0; читается только запрошенная страница. Пагинация: увеличивать ``offset``, пока ``allFetched=false``; ``allFetched=true`` означает конец данных. - **Ошибки:** пустой ``sessionRef``; неизвестный ``sessionRef`` (те же два слоя, что у ``get_opened_meta``). .. _gs3_rest_mcp_scenario: Типовой сценарий ---------------- Discovery → открытие заказа → чтение строк → lookup клиента (на фикстурах ``Gs3_McpOrder``/``Gs3_McpCustomer``). Заголовки во всех запросах: ``Authorization``, ``Database``, ``Content-Type: application/json``, а для ``tools/call`` — ещё ``Mcp-Session-Id`` (из ответа на ``initialize``). **1. Найти выборку заказов** (``find_selections``): .. code-block:: json {"jsonrpc":"2.0","id":3,"method":"tools/call", "params":{"name":"find_selections","arguments":{"query":"Gs3_McpOrder"}}} Ответ (нагрузка — строкой JSON в ``content[0].text``): .. code-block:: json {"jsonrpc":"2.0","id":3,"result":{ "content":[{"type":"text", "text":"{\"selections\":[{\"name\":\"Gs3_McpOrder\",\"caption\":\"Заказы\",\"module\":\"gs3\"}]}"}], "isError":false}} **2. Открыть выборку** в списочном представлении (``open_selection``) — возвращает ``sessionRef``: .. code-block:: json {"jsonrpc":"2.0","id":4,"method":"tools/call", "params":{"name":"open_selection", "arguments":{"name":"Gs3_McpOrder","representation":"List"}}} .. code-block:: json {"jsonrpc":"2.0","id":4,"result":{ "content":[{"type":"text", "text":"{\"sessionRef\":\"a1b2c3d4-...\",\"name\":\"Gs3_McpOrder\",\"representation\":\"List\"}"}], "isError":false}} **3. Прочитать первую страницу строк** (``read_rows``) по полученному ``sessionRef``: .. code-block:: json {"jsonrpc":"2.0","id":5,"method":"tools/call", "params":{"name":"read_rows", "arguments":{"sessionRef":"a1b2c3d4-...","page":{"offset":0,"limit":100}}}} В ответе ``content[0].text`` несёт ``{columns, rows, offset, loadedCount, allFetched}``. Если ``allFetched=false`` — продолжать, увеличивая ``offset``. **4. Lookup клиента:** взять из строки заказа идентификатор клиента, открыть ``Gs3_McpCustomer`` (шаг ``open_selection`` с ``filter`` по нужному полю, имена полей берутся из ``get_filter_meta``) и прочитать его строку через ``read_rows``. **5. Завершить диалог** — ``DELETE`` с ``Mcp-Session-Id`` → ``204`` (открытые выборки закрываются каскадно). .. _gs3_rest_mcp_next: За рамками Этапа 1 ------------------ Этап 1 намеренно ограничен чтением и грубой серверной политикой доступа. Ниже перечислено то, чего в нём нет, — чтобы отсутствие не выглядело недоработкой. .. note:: Состав и сроки следующего этапа не зафиксированы, имена настроек и аннотаций в этом разделе рабочие и могут измениться. Раздел описывает направление, а не принятые решения. Запись `````` Инструментов записи (``set_field``, ``set_param``, ``run_operation``, ``run_validate``, ``commit``) в каталоге нет. Дело не в правах и не в headless: открытым остаётся вопрос **транзакционной границы**. ``insert``/``edit``/установка значений копят черновик в памяти ``DataStore``, фиксация идёт отдельной операцией — и между двумя вызовами инструментов агент может оставить наполовину заполненный объект или частично зафиксированные данные. Этап записи начинается с прототипа именно этой границы, а не с добавления инструментов. Права: декларация на уровне выборки ``````````````````````````````````` Сегодня доступ агента задаётся только серверной секцией ````: она одна на весь контур и ничего не знает о конкретной выборке. Прорабатывается отдельная ось «доступно агенту» — декларативная характеристика в самом прикладном коде: признак на классе выборки плюс свойство операции, наследуемое от выборки по умолчанию. Смысл оси в том, что права пользователя не отличают «человек осознанно нажал кнопку» от «модель решила вызвать операцию», и решать это должен прикладной разработчик, а не только администратор контура. Когда декларации появятся, перечень доступного агенту переедет в код выборок, а серверная секция останется тем, чем она полезна на стенде, — переключателем контура целиком. Описания для агента ``````````````````` Качество работы агента упирается в описания: у многих выборок ``description`` пуст, а у атрибутов роль описания играет заголовок. Поэтому Этап 1 отдаёт ровно то, что уже есть в коде — ``@Oper`` и заголовки ``avm``/``dvm``. Отдельный слой описаний, написанных специально для агента (в том числе примеры значений), в рантайм MCP не загружается и остаётся предметом проработки. .. seealso:: - :ref:`uri_endpoint_reference` — справочник URI (пункт ``/es/mcp``). - :ref:`spec_services_http_authentication` — типы HTTP-аутентификации (Basic/Bearer). - :ref:`gs3_rest_pkg` — соседний сервис ``es/pkg`` в режиме :term:`Exclusive Session`. - :xsd:class:`Configuration.Mcp` — секция конфига с политикой доступа, а также :xsd:class:`Configuration.Mcp.AgentAccess`, :xsd:attr:`Configuration.Mcp.AgentAccess.default`, :xsd:attr:`Configuration.Mcp.AgentAccess.override`, :xsd:class:`Configuration.Mcp.Selections`.