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