Перейти к содержимому

Машинный перевод. Авторитетным источником является английская версия; проверка носителем языка ещё не выполнена.

MCP

Управление серверами MCP с помощью RFC 9728 и RFC 8707 — что действительно работает

Автор Olivares AI 9 мин чтения

Вы подключаете Claude Code к серверу MCP. Сервер требует аутентификацию. Ваш клиент отправляет запрос; сервер отвечает заголовком 401 Unauthorized и WWW-Authenticate: Bearer, который содержит URL resource_metadata. То, что происходит дальше, — это процесс OAuth 2.1, определенный тремя RFC и набором специфических для MCP расширений, которые вместе решают одну из сложных задач безопасности агента: обеспечение того, чтобы токен, полученный для общения с этим MCP сервером, не мог быть повторно использован против того сервера.

В этом посте прослеживается полный процесс — что говорит спецификация, что на самом деле предоставляют RFC и где на практике скрываются уязвимости безопасности.

Процесс: от 401 до токена, привязанного к ресурсу

Модель авторизации MCP (ревизия 2025-11-25, перенесенная без изменений в кандидат на выпуск — замороженная 2026-05-21 — для окончательной спецификации, запланированной на 2026-07-28) является двухфазным процессом. Фаза 1 — обнаружение: 401, несущий WWW-Authenticate: Bearer resource_metadata="...", сообщает клиенту, что этот сервер защищен OAuth, и где найти его метаданные. Фаза 2 — авторизованный доступ: использовать метаданные, обнаружить сервер авторизации, получить токен, привязанный к конкретно этому серверу, и использовать его.

Конкретные шаги:

  1. 401 + WWW-Authenticate: Сервер MCP отклоняет неаутентифицированный запрос. Параметр resource_metadata в вызове указывает на документ метаданных защищенного ресурса сервера.

  2. PRM Получение (RFC 9728): Клиент выполняет GET-запрос к URL /.well-known/oauth-protected-resource. Ответ представляет собой JSON-документ, содержащий канонический URI ресурса сервера, сервер(ы) авторизации, которые его защищают, и поддерживаемые ресурсу области. Клиент проверяет, что поле resource в документе соответствует серверу, к которому он намеревался подключиться — несоответствие является сигналом попытки выдачи за другого и должно быть отклонено.

  3. AS обнаружение (RFC 8414): Используя URL издателя из массива authorization_servers PRM, клиент проверяет известных кандидатов (/.well-known/oauth-authorization-server, затем /.well-known/openid-configuration) для получения метаданных сервера авторизации. Издатель в возвращенном документе должен быть побайтно идентичен издателю, для которого он был запрошен — не «эквивалентен после нормализации», не «достаточно близкий». Побайтно идентичен.

  4. Получение токена (RFC 8707): Клиент запрашивает токен у эндпоинта токенов AS, включая resource=<canonical server URI> как в запросе авторизации, так и в запросе токена. Это связывает аудиторию токена с этим конкретным сервером MCP. AS выдает токен, аудитория которого является этим ресурсом, и клиент предъявляет его серверу.

  5. Авторизованный доступ: Клиент использует токен для вызова методов только для чтения сервера MCP (tools/list, resources/list и др.). Токен подтверждает, что клиент авторизован; индикатор ресурса подтверждает, что токен создан для этого сервера.

Что фактически предоставляет RFC 9728

RFC 9728 (Метаданные защищённого ресурса) является механизмом обнаружения. Он отвечает на вопрос: «какой сервер авторизации защищает этот ресурс и что он ожидает?» Он не аутентифицирует сервер, не проверяет токен и не обеспечивает контроль доступа. Это отдельные вопросы.

Документ PRM имеет одно обязательное поле (resource) и набор необязательных, из которых наиболее важным является authorization_servers. Спецификация MCP ужесточает необязательность деталей RFC: требуется как минимум один сервер авторизации. Документ PRM, не содержащий ни одного, считается ошибкой.

Поле resource является каноническим URI защищённого ресурса. Клиент должен сравнить его с сервером, с которым он намеревался взаимодействовать, и отклонить расхождение:

// RFC 9728 §3.3: значение ресурса PRM ДОЛЖНО идентифицировать защищенный
// ресурс, к которому обращается клиент — простое сравнение строк с
// указателем ресурса, к которому клиент привязывает свои токены.
if prm.Resource != c.resource {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: protected resource metadata declares resource %q, "+
        "expected %q (RFC 9728 §3.3 reject)", prm.Resource, c.resource)
}

Эта проверка предотвращает класс атак, когда злонамеренный или неправильно настроенный документ PRM утверждает, что действует от имени другого ресурса. Сравнение не нормализуется — это прямое сравнение строк с каноническим URI ресурса, который клиент вычислил для данного ему URL сервера.

Индикаторы ресурса RFC 8707 — почему имеет значение привязка токена

Без указателей ресурсов, полученный из AS токен доступа потенциально может быть использован на любом сервере ресурсов, который защищает AS. Если у вас есть два сервера MCP — например, только для чтения сервер документации и песочница для выполнения кода — оба за одним и тем же поставщиком идентификации, токен, полученный для одного, может быть предъявлен другому. Это и есть проблема «смешанного заместителя».

RFC 8707 решает эту проблему, добавляя параметр resource к запросам авторизации и токена. AS выдает токен, аудитория которого явно соответствует URI этого ресурса. Сервер ресурсов, который правильно проверяет токен, отклоняет токен, аудитория которого не совпадает с его собственной идентичностью.

На практике запрос токена включает указатель ресурса вместе с грантом:

form := url.Values{
    "grant_type": {"client_credentials"},
    "resource":   {c.resource}, // RFC 8707 — привязать токен к аудитории
}

Это встречается во всех типах грантов, которые использует коннектор: учетные данные клиента, обмен кода авторизации и ротация токена обновления. Индикатор ресурса не является необязательным — он присутствует в каждом запросе токена, поэтому каждый токен по своей конструкции привязан к аудитории.

Проверка идентичного байта издателя

Самая критичная с точки зрения безопасности проверка во всем процессе также является самой простой для формулировки и самой легкой для ошибки: значение издателя в метаданных документа AS должно быть идентично по байтам значению издателя, которое клиент использовал для построения известного URL.

Не равное без учета регистра. Не эквивалентное после нормализации схемы. Не одинаковое после удаления завершающего слеша. Идентичное по байтам. Раздел 3.3 документа RFC 8414 явно указывает на это, а спецификация MCP наследует это требование.

// discoverASMetadata: ОТКЛОНЯЕТ документ, чей issuer не
// ПОБАЙТОВО ИДЕНТИЧЕН issuer, для которого он был получен (RFC 8414 §3.3).
// Несовпадающий документ является сигналом подмены.
if as.Issuer != issuer {
    return authServerMetadata{}, fmt.Errorf(
        "mcp: oauth: AS metadata at %s declares issuer %q, "+
        "expected %q (RFC 8414 §3.3 reject)", cand, as.Issuer, issuer)
}

Почему такие строгие требования? Потому что злоумышленник, который контролирует DNS или находится на сетевом пути, может предоставить метаданные, которые указывают на его собственную конечную точку токена, выдавая себя за законного издателя. Если клиент нормализовал бы издателя перед сравнением, то https://auth.example.com и https://AUTH.example.com совпали бы — и документ злоумышленника был бы принят. Сравнение, идентичное по байтам, закрывает эту возможность.

Та же дисциплина применяется и в RFC 9207 (проверка издателя ответа авторизации). Когда клиент запускает поток с кодом авторизации, он записывает издателя из проверенных метаданных AS. При обратном редиректе параметр iss в ответе должен совпадать с записанным значением — опять же, посимвольно, без какой-либо нормализации — до того, как код авторизации будет использован. Это защита от путаницы: без нее злонамеренный AS мог бы перехватить код и заставить клиента использовать его на токен-эндпойнте злоумышленника.

Идентификация клиента: CIMD заменяет DCR

Спецификация MCP определяет приоритетный порядок того, как клиент идентифицирует себя перед сервером авторизации:

  1. Предварительно зарегистрированные учетные данные — оператор заранее предоставляет client_id и client_secret
  2. CIMD (Метаданные документов клиента) — клиент размещает JSON-документ по HTTPS URL; этот URL является client_id
  3. Динамическая регистрация клиента (RFC 7591) — клиент регистрирует себя на конечной точке регистрации AS
  4. Запросить у пользователя — не применяется для безголовых агентов

DCR устарел в выпускаемом кандидате на окончательную спецификацию, запланированную на 2026-07-28, в пользу CIMD. Причина операционная: DCR создает постоянное состояние клиента на сервере авторизации. Каждый агент, который регистрируется, оставляет после себя пару client_id/client_secret, которую AS должен хранить, и никто их не отслеживает и не отзывает. Для парка агентов это неконтролируемое распространение учетных данных.

CIMD меняет модель с ног на голову. Клиент размещает документ по URL, который он контролирует. AS получает документ, когда ему нужно проверить клиента, кэширует его в соответствии с HTTP-заголовками кэша и ничего не сохраняет на постоянной основе. Ротация ключей — это обновление документа. Вывод из эксплуатации клиента — это удаление документа. Никто из зарегистрированных записей у AS не остается сиротами.

Компромисс: CIMD требует от клиента запуска HTTPS-эндпоинта. Для самоуправляемой плоскости управления это естественно — плоскость уже запускает HTTPS-сервисы. Для CLI-инструмента на ноутбуке разработчика это менее естественно, поэтому заранее зарегистрированные учетные данные остаются первым вариантом в порядке приоритета.

Идентификатор CIMD не может нести общий секрет (URL документа является публичным — секрет в нем будет утечкой учетных данных). Аутентификация клиента использует private_key_jwt (RFC 7523): клиент подписывает краткоживущий JWT с помощью закрытого ключа, публичный вариант которого опубликован в поле jwks документа CIMD.

Правило «никогда не передавать»

Структурная защита, которую легко не заметить: коннектор использует только токен, который он получил сам, для конкретного сервера через вышеописанный процесс обнаружения. Он никогда не принимает токен от третьей стороны и не пересылает его на сервер MCP. Он никогда не считывает токен из входящего запроса и не передает его дальше.

Это защита «сбившегося с толку помощника» на уровне протокола. Если бы клиент пересылал полученные токены, злоумышленник мог бы предоставить токен, предназначенный для ресурса с низкими привилегиями, и заставить клиента переслать его на ресурс с высокими привилегиями (или наоборот — получить токен с высокими привилегиями, заставив клиента представить его серверу, контролируемому злоумышленником). Получая собственные токены и никогда не касаясь токенов других, коннектор не может быть использован в качестве ретранслятора токенов.

Передача токена не просто не рекомендуется — это структурно невозможно. HTTP-клиент коннектора для потоков OAuth отделен от любого обработчика входящих запросов. Нет ни одного пути выполнения кода, который считывает токен-переносчик из входящего запроса и записывает его в исходящий.

SSRF: защита от повторного связывания DNS

Каждый URL метаданных и токен-эндпоинтов, который получает коннектор, защищен от SSRF на двух уровнях. Первый уровень — предварительная проверка: URL должен быть HTTPS (кроме loopback для локальной разработки) и не должен быть литеральным зарезервированным IP-адресом.

Второй — это проверка во время набора, которая закрывает уязвимость DNS-rebinding TOCTOU. Имя хоста, которое разрешается в публичный IP во время предварительной проверки, к моменту установки сокета может переназначиться на приватный IP. HTTP-клиент соединителя устанавливает функцию net.Dialer.Control, которая проверяет конкретный разрешённый IP во время подключения и отклоняет любой зарезервированный адрес. Это авторитетная проверка — предварительная проверка выполняет быструю отбраковку для очевидных случаев, но проверка во время набора имеет решающее значение.

Повышение области

Сервер MCP может ответить на запрос WWW-Authenticate: Bearer error="insufficient_scope" scope="mcp:tools:list mcp:resources:read". Спецификация (SEP-835/SEP-2350) определяет, как клиент должен это обрабатывать: вычислить объединение областей, которые он запрашивал ранее, и областей, которые сервер только что потребовал, затем заново получить токен с этим расширенным набором. Объединение сохраняет ранее предоставленные разрешения и добавляет новые. Повышение уровнем происходит один раз — второе требование с недостаточными правами для того же запроса не повторяется, чтобы избежать бесконечных циклов.

Серверу разрешается быть статeless при вызове: он указывает только области, необходимые для текущей операции, а не полный набор, который клиент мог запрашивать ранее. Аккумуляция на стороне клиента позволяет это реализовать без отслеживания сервером истории областей каждого клиента.

Что это означает на практике

Поток OAuth для серверов MCP хорошо определен и при строгой реализации решает реальные уязвимости: повторное использование токенов между серверами, подделка метаданных, SSRF через DNS-переназначение и неконтролируемое распространение учетных данных клиента. Проверка идентичного байта издателя, индикатор ресурса в каждом запросе токена и структурное правило “без прохождения” являются основными средствами защиты. Это не функции, которые можно реализовать частично — каждая из них является строгой проверкой, которая в случае ошибки блокирует доступ.

Более сложная проблема носит операционный характер. Командам, развертывающим серверы MCP, необходимо:

  • Опубликовать действительный документ PRM на /.well-known/oauth-protected-resource с полем resource, которое соответствует каноническому URI их сервера
  • Используйте AS, который поддерживает индикаторы ресурсов — многие поставщики идентификации до сих пор не поддерживают это или трактуют параметр resource как рекомендательный, а не привязанный к аудитории
  • Перейдите с DCR на CIMD до того, как устаревание станет удалением, или заранее зарегистрируйте учетные данные явно
  • Привяжите учетные данные к издателю, чтобы предотвратить тихое повторное использование между издателями при изменении топологии AS

Документация по коннектору MCP охватывает эксплуатационную конфигурацию, а модель безопасности описывает, как токен, привязанный к ресурсам, вписывается в более широкую карту доступа.

Похожие материалы

Часто задаваемые вопросы

Гарантирует ли RFC 9728, что сервер MCP, с которым я общаюсь, является подлинным?

Нет. RFC 9728 позволяет клиенту обнаружить, какой сервер авторизации защищает ресурс и какие области (scopes) он требует, но это метаданные о ресурсе, а не доказательство идентичности ресурса. Сертификат TLS подтверждает имя хоста сервера; PRM сообщает вам, как аутентифицироваться на нем. Оба они дополняют друг друга: PRM без проверки TLS — это обнаружение на неподтвержденном хосте, а TLS без PRM оставляет клиента угадывать, как получить токен.

Почему динамическая регистрация клиента устарела в спецификации MCP, если она все еще работает?

DCR создаёт постоянное состояние клиента на сервере авторизации — client_id и client_secret AS должны хранить и управлять этим состоянием. Для флота безголовых агентов это превращается в неконтролируемую проблему разрастания учетных данных: каждый агент регистрирует себя, и никто не отслеживает и не обновляет эти регистрации. CIMD (Документы метаданных идентификаторов клиентов) заменяет это размещённым документом, которым управляет клиент, а AS получает его по требованию — никакого постоянного состояния на AS, никаких сиротских регистраций, а ротация ключей осуществляется обновлением документа, а не повторной регистрацией.

Узнайте, до чего могут добраться ваши агенты

Olivares AI — открытая self-hosted платформа для управления вашим парком AI. Разверните её на собственной инфраструктуре и получите карту доступа, которую давно запрашивают ваши команды безопасности и платформ.