docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api
uk/docs/api/mcp-bundle/http_security.md
новий файл +71
−0
HTTP і безпека
Версії протоколу
Транспорт — Streamable HTTP з офіційного mcp/sdk. Він обслуговує обидві епохи протоколу MCP на тому самому URL:
- Із сесіями (2024-11-05 … 2025-11-25):
initialize→ заголовокMcp-Session-Id→ запити з цим заголовком;DELETEзавершує сесію. Сесії зберігаються в пулі кешу ядра (див. Налаштування). Запит без сесії отримує 400, невідома чи прострочена сесія — 404 (клієнт повторюєinitialize). - Stateless 2026-07-28: без handshake і без сесії. Кожен запит містить
params._meta["io.modelcontextprotocol/protocolVersion"]і заголовкиMCP-Protocol-Version,Mcp-Method(дляtools/call— щеMcp-Name); результат містить_meta["io.modelcontextprotocol/serverInfo"].
Перевірено наскрізними тестами офіційним клієнтом SDK через HTTP на версіях 2025-06-18, 2025-11-25 і 2026-07-28, а також через MCP Inspector.
GET (окремий SSE-потік від сервера до клієнта) відповідає 405: сервер нічого не надсилає з власної ініціативи. JSON-RPC batch приймаються.
Порядок перевірок
McpRequestListener виконується перед кожною дією контролера бандла (ендпоінт, server.json, маніфести):
- 404 з порожнім тілом — група вимкнена (
McpAvailabilityCheckerInterface, типово — параметрenabled) або в групи немає методів у запитаній версії{ver}. Ця перевірка йде до перевірки токена, тож недоступний ендпоінт виглядає однаково з токеном і без нього. - OPTIONS (CORS preflight) проходить без токена; на нього відповідає транспорт.
- Токен —
IRpcSecurityядра:- MCP-ендпоінт:
isValidApiRequest()— діютьprotected_api, валідатори токенів іsecurity.providersгрупи; server.jsonі маніфести:isValidDocRequest()— захищені лише коли ввімкненоprotected_doc;- немає токена → 401
{"error": "Unauthorized. Token not found"}; недійсний → 403{"error": "Forbidden. Invalid token"}.
- MCP-ендпоінт:
Дія визначається за методом контролера, а не за назвою маршруту, тож name_prefix в імпорті не дозволяє обійти перевірку.
Заголовок із токеном
Використовується заголовок токена ядра (типово Ufo-RPC-Token, параметр ufo_json_rpc.security.token_name — див. Налаштування JsonRpcBundle). CORS-заголовок Access-Control-Allow-Headers містить його разом із заголовками MCP. Клієнти, що підтримують власні заголовки (Claude Code, Cursor, Gemini CLI, OpenAI Responses API), надсилають його; маніфести клієнтів прописують його на місці.
Немає /.well-known/oauth-protected-resource і заголовка WWW-Authenticate у відповіді 401. Специфікація MCP робить авторизацію необов'язковою, але клієнти, які підтримують лише OAuth, підключитися не зможуть (наприклад, MCP Inspector без заголовка з токеном запускає свій OAuth-flow).
CORS і DNS rebinding
McpAccessPolicyInterface (типово DefaultMcpAccessPolicy) керує двома шарами транспорту SDK:
getAllowedOrigins()— CORS. Типово['*'];[]— CORS-заголовки не надсилаються.getAllowedHosts()— захист від DNS rebinding. Типово[]— вимкнено. Якщо список задано, запит, чийHostу ньому відсутній, отримує 403. Коли запит має заголовокOrigin, SDK перевіряє і хост з Origin за тим самим списком, тож хост браузерного клієнта теж має бути в списку.
use Ufo\McpBundle\Security\McpAccessPolicyInterface;
final class AppMcpAccessPolicy implements McpAccessPolicyInterface
{
public function getAllowedHosts(): array
{
return ['api.example.com', 'app.example.com'];
}
public function getAllowedOrigins(): array
{
return ['https://app.example.com'];
}
}
# config/services.yaml
services:
Ufo\McpBundle\Security\McpAccessPolicyInterface: '@App\Mcp\AppMcpAccessPolicy'
Політика діє для всіх груп. Головний захист — токен; CORS і перевірка хоста — додаткові шари.
Ліміт тіла запиту
max_body_bytes (загальний або для групи): на більше тіло запиту ендпоінт відповідає 413 з помилкою JSON-RPC.