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-запиту генерується для кожного виклику і назовні не віддається.