Інструменти (tools)

← Інструменти (tools)

Ashterix

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

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

1 + # Інструменти (tools)
2 +
3 + Кожен RPC-метод групи і версії API ендпоінта — це MCP-інструмент. `tools/list` повертає їх, `tools/call` виконує через ядро JsonRpcBundle. Окремого переліку дозволених методів чи реєстрації для MCP немає: публікацію визначає лише членство в [групі API](/docs/api/json-rpc-bundle/functionality/groups).
4 +
5 + ## Назва
6 +
7 + Назва інструмента — назва RPC-методу (`Catalog.search`). MCP дозволяє назви за шаблоном `^[a-zA-Z0-9_.-]{1,128}$`.
8 +
9 + :::danger
10 + Метод групи, чия назва не відповідає шаблону (наприклад, `#[RPC\Info('Bad', concat: ':')]` дає `Bad:ping`), змушує **кожен запит до MCP-ендпоінта цієї групи** завершуватися HTTP 500; у debug-режимі помилка пояснює, який метод і чому. Інші групи й решту застосунку це не зачіпає, методи поза MCP-групами не перевіряються. Бандл свідомо не пропускає такий метод мовчки.
11 + :::
12 +
13 + ## Заголовок і опис
14 +
15 + Беруться з [перекладів ядра](/docs/api/json-rpc-bundle/documentation) мовою, узгодженою за `Accept-Language` (`RpcLocaleNegotiator` ядра). Відповідь містить `Content-Language` і `Vary: Accept-Language`.
16 +
17 + ```
18 + translations/rpc.uk.yaml # базовий переклад, усі групи
19 + translations/rpc/mcp.uk.yaml # лише для групи mcp — наприклад, тексти для AI-агента
20 + ```
21 +
22 + ```yaml
23 + # translations/rpc/mcp.uk.yaml
24 + Catalog.search:
25 + description: Пошук товарів за назвою. Повертає до limit товарів, найрелевантніші першими.
26 + ```
27 +
28 + Кожне поле має власний ланцюжок fallback: група + мова клієнта → група + типова мова → базовий + мова клієнта → базовий + типова мова → `#[RPC\Info]` / PHPDoc. Якщо заголовка немає ніде, заголовком стає назва інструмента. Ключ перекладу — назва методу, тож переклад діє для всіх версій методу.
29 +
30 + :::warning
31 + Не називайте групу так само, як домен Symfony Translator (`messages`, `validators`, …).
32 + :::
33 +
34 + ## inputSchema
35 +
36 + Будується з метаданих методу в ядрі — того самого джерела, що й документ OpenRPC:
37 +
38 + * параметри → `properties`; параметри без значення за замовчуванням → `required`; значення за замовчуванням → `default`;
39 + * описи з `@param` → `description`;
40 + * DTO та enum → `$ref` на власні `$defs` інструмента: посилання OpenRPC `#/components/schemas/...` переписуються, тож кожен інструмент самодостатній, включно з рекурсивними DTO;
41 + * `additionalProperties: false`.
42 +
43 + ```php
44 + /**
45 + * Ping.
46 + * @param string $who Who is pinging
47 + */
48 + public function ping(string $who = 'x'): string
49 + ```
50 +
51 + ```json
52 + {
53 + "type": "object",
54 + "properties": {
55 + "who": {"type": "string", "description": "Who is pinging", "default": "x"}
56 + },
57 + "additionalProperties": false
58 + }
59 + ```
60 +
61 + SDK перевіряє `arguments` за схемою ще до виконання: невідомий аргумент, відсутній обов'язковий або неправильний тип дають помилку JSON-RPC `-32602` у відповіді MCP (а не результат інструмента з `isError`), метод не викликається. Це стосується і спеціального параметра ядра `$rpc`, тому асинхронні виклики й webhook через MCP неможливі.
62 +
63 + ## outputSchema
64 +
65 + Тип повернення методу в тому вигляді, як ядро описує його для OpenRPC, обгорнутий так само, як повертається результат:
66 +
67 + ```json
68 + {"type": "object", "properties": {"result": {"type": "string"}}, "required": ["result"]}
69 + ```
70 +
71 + * Кожен `"type": "array"` стає `["array", "object"]`: PHP-масив серіалізується в JSON-список або JSON-об'єкт залежно від ключів (`['a' => 1]` → `{"a":1}`), і клієнти, що перевіряють `structuredContent` за схемою, відхилили б об'єкт.
72 + * DTO — `$ref` на власні `$defs` інструмента.
73 + * `outputSchema` немає, якщо метод не має типу повернення або повертає `object`: ядро описує такий результат як «будь-який об'єкт», а така схема відхилила б скаляр.
74 +
75 + ## Анотації: `#[McpTool]`
76 +
77 + Необов'язкові підказки MCP `ToolAnnotations` на методі. Атрибут не впливає на публікацію.
78 +
79 + ```php
80 + use Ufo\McpBundle\Attribute\McpTool;
81 +
82 + #[McpTool(readOnlyHint: true, idempotentHint: true)]
83 + public function search(string $query, int $limit = 10): array
84 + ```
85 +
86 + | Параметр | Значення `true` |
87 + | --- | --- |
88 + | `readOnlyHint` | Інструмент не змінює середовище |
89 + | `destructiveHint` | Може виконувати руйнівні зміни (`false` — лише додавання) |
90 + | `idempotentHint` | Повторний виклик з тими самими аргументами нічого не змінює |
91 + | `openWorldHint` | Працює з «відкритим світом» зовнішніх сутностей (наприклад, вебом) |
92 +
93 + Усі параметри мають тип `?bool`: `null` означає «не задано» і не потрапляє у вивід.
94 +
95 + ## tools/call
96 +
97 + Виклик перетворюється на `RpcRequest` (носій `RpcFromMcp`: метод — назва інструмента, параметри — аргументи, версія — з маршруту) і виконується через `RpcExecutor` ядра — той самий конвеєр, що й `/rpc`: [конвертація параметрів і DTO](/docs/api/json-rpc-bundle/add_rpc_service/params), [`#[RPC\Assertions]`](/docs/api/json-rpc-bundle/add_rpc_service/assertions), [`#[RPC\Cache]`](/docs/api/json-rpc-bundle/add_rpc_service/cache), [`#[RPC\Lock]`](/docs/api/json-rpc-bundle/add_rpc_service/lock), [події](/docs/api/json-rpc-bundle/functionality/events). Відповідь нормалізує серіалізатор ядра — так само, як її повертає `/rpc`.
98 +
99 + **Успіх:**
100 +
101 + ```json
102 + {
103 + "content": [{"type": "text", "text": "{\"id\":7,\"name\":\"n7\"}"}],
104 + "structuredContent": {"result": {"id": 7, "name": "n7"}},
105 + "isError": false
106 + }
107 + ```
108 +
109 + `structuredContent` — для клієнтів, що читають структурований вивід (перевіряється за `outputSchema`); той самий JSON у `TextContent` — для решти.
110 +
111 + **Помилка** — виняток у методі, помилка валідації або виняток поза методом (наприклад, у слухачі `rpc.request`) — це RPC-помилка ядра з `isError: true` і без `structuredContent`:
112 +
113 + ```json
114 + {
115 + "content": [{"type": "text", "text": "{\"code\":-32603,\"message\":\"...\",\"data\":{\"exception\":\"Ufo\\\\RpcError\\\\RpcInternalException\"}}"}],
116 + "isError": true
117 + }
118 + ```
119 +
120 + `data` формується за [правилами ядра](/docs/api/json-rpc-bundle/error_handling) для середовища: повні деталі (`file`, `line`, `trace`) у `dev` / `test`, в інших — лише клас винятку та `extra` (деталі валідації). Кожна помилка проходить через подію `rpc.error` і логування ядра.
121 +
122 + **Події та кеш:**
123 +
124 + * [`rpc.post_response`](/docs/api/json-rpc-bundle/functionality/events/post_response) спрацьовує на `kernel.terminate`, після відправлення відповіді, один раз на кожен виконаний виклик — і для кількох `tools/call` в одному JSON-RPC batch. Для відповіді з помилкою чи з кешу не спрацьовує (правило ядра).
125 + * `#[RPC\Cache]` працює так само, як на `/rpc`: закешований виклик не виконує метод.
126 +
127 + `id` JSON-RPC-конверта MCP-запиту належить клієнту; внутрішній `id` RPC-запиту генерується для кожного виклику і назовні не віддається.