HTTP і безпека

← HTTP і безпека

Ashterix

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

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

1 + # HTTP і безпека
2 +
3 + ## Версії протоколу
4 +
5 + Транспорт — Streamable HTTP з офіційного `mcp/sdk`. Він обслуговує обидві епохи протоколу MCP на тому самому URL:
6 +
7 + * **Із сесіями** (2024-11-05 … 2025-11-25): `initialize` → заголовок `Mcp-Session-Id` → запити з цим заголовком; `DELETE` завершує сесію. Сесії зберігаються в пулі кешу ядра (див. [Налаштування](/docs/api/mcp-bundle/config)). Запит без сесії отримує 400, невідома чи прострочена сесія — 404 (клієнт повторює `initialize`).
8 + * **Stateless 2026-07-28**: без handshake і без сесії. Кожен запит містить `params._meta["io.modelcontextprotocol/protocolVersion"]` і заголовки `MCP-Protocol-Version`, `Mcp-Method` (для `tools/call` — ще `Mcp-Name`); результат містить `_meta["io.modelcontextprotocol/serverInfo"]`.
9 +
10 + Перевірено наскрізними тестами офіційним клієнтом SDK через HTTP на версіях 2025-06-18, 2025-11-25 і 2026-07-28, а також через MCP Inspector.
11 +
12 + `GET` (окремий SSE-потік від сервера до клієнта) відповідає 405: сервер нічого не надсилає з власної ініціативи. JSON-RPC batch приймаються.
13 +
14 + ## Порядок перевірок
15 +
16 + `McpRequestListener` виконується перед кожною дією контролера бандла (ендпоінт, `server.json`, маніфести):
17 +
18 + 1. **404** з порожнім тілом — група вимкнена (`McpAvailabilityCheckerInterface`, типово — параметр `enabled`) або в групи немає методів у запитаній версії `{ver}`. Ця перевірка йде до перевірки токена, тож недоступний ендпоінт виглядає однаково з токеном і без нього.
19 + 2. **OPTIONS** (CORS preflight) проходить без токена; на нього відповідає транспорт.
20 + 3. **Токен** — `IRpcSecurity` ядра:
21 + * MCP-ендпоінт: `isValidApiRequest()` — діють `protected_api`, валідатори токенів і `security.providers` групи;
22 + * `server.json` і маніфести: `isValidDocRequest()` — захищені лише коли ввімкнено `protected_doc`;
23 + * немає токена → **401** `{"error": "Unauthorized. Token not found"}`; недійсний → **403** `{"error": "Forbidden. Invalid token"}`.
24 +
25 + Дія визначається за методом контролера, а не за назвою маршруту, тож `name_prefix` в імпорті не дозволяє обійти перевірку.
26 +
27 + ## Заголовок із токеном
28 +
29 + Використовується заголовок токена ядра (типово `Ufo-RPC-Token`, параметр `ufo_json_rpc.security.token_name` — див. [Налаштування JsonRpcBundle](/docs/api/json-rpc-bundle/config)). CORS-заголовок `Access-Control-Allow-Headers` містить його разом із заголовками MCP. Клієнти, що підтримують власні заголовки (Claude Code, Cursor, Gemini CLI, OpenAI Responses API), надсилають його; [маніфести клієнтів](/docs/api/mcp-bundle/discovery) прописують його на місці.
30 +
31 + :::warning subtitle="OAuth не реалізовано"
32 + Немає `/.well-known/oauth-protected-resource` і заголовка `WWW-Authenticate` у відповіді 401. Специфікація MCP робить авторизацію необов'язковою, але клієнти, які підтримують лише OAuth, підключитися не зможуть (наприклад, MCP Inspector без заголовка з токеном запускає свій OAuth-flow).
33 + :::
34 +
35 + ## CORS і DNS rebinding
36 +
37 + `McpAccessPolicyInterface` (типово `DefaultMcpAccessPolicy`) керує двома шарами транспорту SDK:
38 +
39 + * `getAllowedOrigins()` — CORS. Типово `['*']`; `[]` — CORS-заголовки не надсилаються.
40 + * `getAllowedHosts()` — захист від DNS rebinding. Типово `[]` — вимкнено. Якщо список задано, запит, чий `Host` у ньому відсутній, отримує 403. **Коли запит має заголовок `Origin`, SDK перевіряє і хост з Origin за тим самим списком**, тож хост браузерного клієнта теж має бути в списку.
41 +
42 + ```php
43 + use Ufo\McpBundle\Security\McpAccessPolicyInterface;
44 +
45 + final class AppMcpAccessPolicy implements McpAccessPolicyInterface
46 + {
47 + public function getAllowedHosts(): array
48 + {
49 + return ['api.example.com', 'app.example.com'];
50 + }
51 +
52 + public function getAllowedOrigins(): array
53 + {
54 + return ['https://app.example.com'];
55 + }
56 + }
57 + ```
58 +
59 + ```yaml
60 + # config/services.yaml
61 + services:
62 + Ufo\McpBundle\Security\McpAccessPolicyInterface: '@App\Mcp\AppMcpAccessPolicy'
63 + ```
64 +
65 + :::info
66 + Політика діє для всіх груп. Головний захист — токен; CORS і перевірка хоста — додаткові шари.
67 + :::
68 +
69 + ## Ліміт тіла запиту
70 +
71 + `max_body_bytes` (загальний або для групи): на більше тіло запиту ендпоінт відповідає 413 з помилкою JSON-RPC.