Bearer в Authorization удобен, но копируется в логи, кэши и историю. mTLS (mutual TLS) убирает секрет из запроса в TLS-handshake: агент показывает X.509 сертификат до первого MCP-сообщения. Повтора из лога нет, но и завести его сложнее — нужен PKI.
Что такое mTLS для MCP
mTLS — взаимная аутентификация на транспорте: и агент (клиент), и MCP-сервер (или прокси перед ним) предъявляют сертификаты, подписанные доверенным CA. Спецификации MCP 2025-11-25 и 2026-07-28 делают authorization OPTIONAL и SHOULD по OAuth (RFC 8707, 6750, 9728 и др.), но не описывают mTLS — он вне спеки, а не против неё. Значит: работает, но не discoverable — ни один tools/list не скажет «тут нужен сертификат», каждый caller настраивается руками.
Сертификат живёт в tls.Config (Go), ssl.SSLContext (Python), undici Agent (TypeScript) — никогда в MCP payload. Запрос POST /mcp tools/list уходит без Authorization: Bearer.
Handshake в 4 шага
- Агент → сервер:
ClientHelloс SNImcp.example.com. - Сервер → агент:
CertificateRequestс листом допустимых CA. - Агент → сервер:
Certificate+CertificateVerify(CN=orders-agent, issuer — ваш CA). - Сервер проверяет цепочку до доверенного CA, читает subject — соединение аутентифицировано. Только теперь идёт первый MCP message, уже без credential.
Скопированный из лога запрос без приватного ключа не воспроизведётся — секрет привязан к соединению, а не к header.
Когда стоит / не стоит
Стоит, если:
- оба конца — ваши workloads с уже работающим CA;
- сеть — интернет или широкая внутренняя, нужен транспорт-уровень до приложения;
- compliance требует клиентский сертификат на каждое соединение;
- хотите убрать replayable secret из request.
Не стоит, если:
- к серверу должен попасть человек из чата: Claude, ChatGPT, Cursor, VS Code — не умеют подставлять клиентский сертификат (Claude docs — 6 типов auth без сертификата, ChatGPT — нельзя, Cursor/VS Code —
url/headers/oauthбез cert); - нужен per-user identity — сертификат идентифицирует workload, а не человека, для
on behalf ofнужен OAuth; - некому ротировать — expiry валит handshake до любого MCP-кода, и в логах — транспортная ошибка, а не 401.
Ротация и отзыв
Сертификаты истекают — handshake падает. План:
- 90-дневный cert → ротация на 60-й день, 30 дней запас;
- дистрибуция через ACME / Vault PKI / CM;
- сервер доверяет двум CA в переходный период;
- для отзыва — CRL или OCSP, для маленького флота проще перевыпустить CA и рестарт.
Сочетание с OAuth: defense in depth
mTLS и bearer — разные слои. Практика: terminate mTLS на edge-прокси, за ним — OAuth по спеке MCP. Прокси валидирует сертификат и прокидывает X-Client-Cert-CN в backend, backend уже решает user × scope. RFC 8705 (mTLS-bound token) формально не в списке MCP, поэтому их просто ставят рядом: mTLS = «какая машина», OAuth = «какой пользователь и какие права». Украденный bearer без сертификата бесполезен.
Где Codenik
Codenik gateway ставит mTLS там где он уместен: для workload-to-workload вызовов между вашими агентами и приватными MCP — mTLS на edge (Go/Python/TS агенты). Для чатов и desktop-клиентов — второй маршрут через OAuth с discovery (RFC 8414, DCR). Один и тот же MCP за двумя дверями: сертификат для машин, токен для людей, оба — в единый аудит agent_id × human_delegator × tool.
Короткий вывод
mTLS — afternoon в коде если PKI уже есть, и проект если нет. Берите его для своих агентов где нужен транспорт-без секрета, оставляйте OAuth для людей. Тогда replay из лога не работает, а человек всё равно проходит по своим правам.