Для розробників

← Для розробників

Ashterix

docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api

uk/docs/api/mcp-bundle/developers/README.md новий файл +71 −0

Для розробників

Як MCP Bundle працює всередині: принципи, компоненти, шлях запиту і що кешується. Бандл — розширення JsonRpcBundle і дотримується загальних положень для розширень ядра.

Принципи

  1. Тонкий адаптер над JsonRpcBundle. Як і REST Bundle, цей бандл додає протокол, а не другий сервер. Бізнес-логіка лишається в RPC-сервісах (IRpcService); виклик інструмента — звичайний RPC-запит, який виконує ядро.
  2. Перевикористовувати, а не дублювати. Карта сервісів, групи й версії API, переклади, узгодження мови, безпека на токенах, пули кешу, імпорт маршрутів і виконання запитів беруться з ядра. Коли в ядрі бракує точки розширення, її додають у ядро: так з'явилися IApiRouteImport у 10.4.1 і RpcExecutor у 10.4.2.
  3. Маршрут — джерело істини. Група API береться з імпорту маршрутів (api_group), версія — із сегмента {ver}. Конфігурація лише налаштовує сервер групи.
  4. Публікація = членство в групі API. Кожен метод групи стає інструментом; ні allowlist, ні окремої реєстрації для MCP немає.
  5. Протокол реалізує офіційний SDK. Handshake, сесії, stateless-протокол 2026-07-28, перевірка аргументів за inputSchema, batch, middleware CORS і DNS rebinding — це mcp/sdk (^0.8.1). Бандл будує сервер SDK на кожен запит і підключає до нього RPC-методи.
  6. Падати голосно. Метод, чия назва не є коректною назвою MCP-інструмента, ламає свою групу (HTTP 500 з поясненням у debug), а не зникає зі списку мовчки.

Компоненти

Компонент Роль
Routing\McpRouteImport Реєструє config/router.yaml у завантажувачі маршрутів ядра (IApiRouteImport): кожен імпорт отримує назви <group>_ufo_mcp_* і атрибут _mcp_name_prefix
Controller\McpController Три дії: MCP-ендпоінт, server.json, маніфести клієнтів
EventListener\McpRequestListener Слухач kernel.controller лише для McpController: група API, доступність (404), токен (401/403)
Server\McpHandler Узгодження мови, транспорт, сервер SDK, Content-Language / Vary
Server\Transport\StreamableHttpTransportFactory Streamable HTTP-транспорт SDK з CORS, захистом від DNS rebinding і лімітом тіла запиту групи
Server\McpServerFactory Сервер SDK для одного запиту: ідентичність, instructions, сховище сесій, інструмент на кожен метод групи й версії
Server\Session\RpcCacheSessionStore Сесії протоколу із сесіями в пулі кешу ядра rpc.cache.pool
Tool\ToolSchemaFactory MCP Tool з метаданих Service ядра: перевірка назви, тексти, inputSchema, outputSchema, анотації; кешується в RpcMetadataCache ядра
Tool\RpcToolHandler, Tool\RpcToolExecutor tools/call → RpcExecutor ядра → CallToolResult
Server\RequestPrepare\Holders\RpcFromMcp Носій запиту (IRpcRequestCarrier): RpcRequest з інструмента, аргументів і версії
DocAdapters\Outputs\ServerJsonAdapter server.json для MCP Registry
ClientManifest\* Маніфести клієнтів (Claude Code, Cursor, Gemini CLI, OpenAI), тегований locator
ConfigService\McpConfig, McpGroupConfig, McpServerInfoConfig Типізоване подання конфігурації ufo_mcp; група без запису отримує загальні налаштування
Security\McpAccessPolicyInterface, Availability\McpAvailabilityCheckerInterface Точки розширення — див. Розширення

Шлях запиту

HTTP request — маршрут з config/router.yaml, імпортований для кожної групи API
  → McpRequestListener (kernel.controller, лише McpController)
      ApiGroupContext ← api_group / _mcp_name_prefix маршруту
      404: група вимкнена (McpAvailabilityCheckerInterface) або {ver} немає серед версій групи в ServiceMap
      OPTIONS (CORS preflight) — без токена
      IRpcSecurity ядра: isValidApiRequest() для ендпоінта, isValidDocRequest() для server.json / маніфестів
      401 (немає токена) / 403 (недійсний) як {"error": "..."}
  → McpController::mcpAction → McpHandler::handle()
      RpcLocaleNegotiator ядра → мова запиту
      StreamableHttpTransportFactory → PSR-7 запит + транспорт SDK (CORS, DNS rebinding, max_body_bytes)
      McpServerFactory::create($ver, $locale) → Server SDK (інструменти з ServiceMap::getServices($ver) групи)
      SDK: initialize / ping / tools/list / tools/call / DELETE сесії ...
      Content-Language, Vary: Accept-Language
  → tools/call → RpcToolHandler → RpcToolExecutor
      RpcFromMcp(service, arguments, version) → RpcExecutor::execute() ядра
        (той самий конвеєр, що й /rpc; виняток проходить через rpc.error, відповідь — завжди RpcResponse)
      RpcRequestHandler::responseToArray() ядра → CallToolResult (text + structuredContent | isError)
  → kernel.terminate: PostResponseQueue ядра запускає rpc.post_response для кожного виконаного виклику

Детальніше про етапи ядра — Життєвий цикл.

Що будується на кожен запит, що кешується

  • Сервер SDK і список інструментів створюються для кожного HTTP-запиту: між запитами сервер не зберігає стану, сесії живуть у пулі кешу.
  • Кожен Tool кешується в RpcMetadataCache ядра (cache.system) з ключем за групою, версією, методом і мовою. Кеш скидається при кожному перезбиранні контейнера і вимкнений у debug — те саме правило, що ядро застосовує до власної документації.
  • Compiler pass свідомо не використовується: контейнер не відстежує класи DTO, а для побудови схем потрібні сервіси застосунку.
  • Сесії (лише для протоколу із сесіями) живуть у rpc.cache.pool під ключем ufo_mcp_<group>_<session id> з session_ttl на кожен запис.

Що є в розділі

  • Розширення — точки розширення бандла і ядра.
  • Тести — набори тестів, фікстури, покриття, MCP Inspector.
  • Відомі помилки — що працює не так, як очікується, і як це обійти.

Помилки й пропозиції — в issue tracker репозиторію бандла.