Как сервер MCP вызывает твое API от имени пользователей: стратегия производственных токенов
Объясняет стратегию исходящих токенов для серверов MCP: почему не подходят сквозная передача токена и M2M, и как обмен токенов обеспечивает согласование прав с пользователями.
YijunDeveloper
Хватит тратить недели на аутентификацию пользователей
Запускайте безопасные приложения быстрее с Logto. Интегрируйте аутентификацию пользователей за считанные минуты и сосредоточьтесь на вашем основном продукте.
Как сервер MCP вызывает твое API от имени пользователей: стратегия производственных токенов#
В нашей предыдущей статье мы поделились общим опытом создания удалённого сервера MCP для Logto. В этой статье подробно рассматривается архитектурный дизайн и процесс OAuth.
С сервером MCP в качестве границы аутентификация для удалённого MCP сервера состоит из двух частей: входящая и исходящая.
Входящая: клиент MCP (VS Code, Cursor и т. д.) входит через OAuth, получает access token и использует его для доступа к твоему серверу MCP
Исходящая: при обработке вызова инструмента сервер MCP запрашивает твое бизнес-API от имени пользователя
Входящий сценарий хорошо определён в спецификации MCP: в сообществе много дискуссий и реализаций. Мы также писали гайд по реализации ранее. Исходящая часть обсуждается гораздо меньше: у сервера MCP есть только токен для доступа к самому серверу MCP. Как он может вызывать бизнес-API от имени пользователя?
Это вопрос, с которым мы долго боролись при разработке сервера Logto MCP. Logto Cloud — типичный B2B продукт с несколькими арендаторами: пользователь может принадлежать разным организациям, а права определяются его ролью в каждой из них. ИИ должен не только действовать от имени пользователя, но и выбирать правильного арендатора.
В статье представлены этапы реальной работы над решением:
Почему сервер MCP должен разворачиваться независимо, как отдельный защищённый ресурс
Два отклонённых исходящих подхода (сквозная передача токена и M2M) и их недостатки
Итоговая архитектура: обмен токенов плюс subject token, включая то, как организационные токены поддерживают мультитенантность
Несколько принципов, которых стоит придерживаться при построении похожего решения
С точки зрения OAuth удалённый сервер MCP выполняет сразу две роли:
Для клиента MCP — это ресурс-сервер: клиент должен передавать токен для доступа
Для бизнес-API — это клиент: подставляет токен для обращения к другому сервису
Иными словами, сервер MCP выступает в разных ролях с каждой стороны, и каждая из сторон использует разный токен. Токен, который клиент MCP получает по OAuth, адресован серверу MCP, поэтому бизнес-API его отвергнет при проверке audience.
Таким образом, исходящий сценарий — это проблема делегирования: как сервер MCP может вызывать downstream API с полномочиями пользователя, не имея его учетных данных?
Архитектурное решение: сервер MCP как отдельная служба#
Построение MCP endpoint внутри бизнес-API с общим процессом и общей auth-логикой кажется простым вариантом. Мы всё же выбрали отдельное развертывание:
Изоляция рисков: на момент принятия решения официальный MCP SDK был не готов для продакшена, а сам протокол быстро менялся (пример — переход транспорта с SSE на Streamable HTTP). Logto — IAM-сервис: доступность входа — критична. При отдельном разворачивании, если MCP выходит из строя, падает только AI-точка входа, а основной сервис продолжает работать.
Независимая разработка: MCP-экосистема развивается еженедельно, проблемы совместимости клиентов требуют быстрых правок, в то время как ядро сервиса выпускается строго и с регрессией. Раздельные деплойменты позволяют не мешать друг другу.
Свобода runtime: отдельный сервис можно запускать в наиболее подходящей среде. Наш сервер MCP работает на Cloudflare Workers: без состояния, масштабируется на каждый запрос, практически не требует сопровождения. Встроенный вариант такой свободы не даёт.
Сервер MCP размещён на отдельном домене, mcp.logto.io, без приватных связей с основным сервисом. Если захочется его опубликовать с открытым исходником — никаких препятствий.
В embedded-варианте проблема с токеном тоже никуда не девается: MCP endpoint и бизнес-API будут иметь одинаковый resource identifier, и токен клиента MCP будет обладать полномочиями полного API. Разница только в том, что вопрос «чьи права?» перемещается из обмена межсервисных токенов в внутреннюю передачу разрешений — проблема остаётся. Раздельное развертывание вынуждает явно проектировать границу прав, и дальнейшая часть статьи — именно об этом.
У сервера MCP должен быть собственный resource identifier#
Обсуждение исходит из более базового вопроса: какой audience должен быть у токена клиента MCP?
Использовать идентификатор бизнес-API — самое простое решение: Management API Logto уже защищён стандартным OAuth, так что клиент MCP мог бы напрямую получить токен для него, а сервер MCP просто пересылал бы токен дальше. Так делали многие первые реализации MCP-серверов.
Но тогда граница разрешений стирается:
Работа твоих инструментов MCP становится условной, токен позволяет напрямую обращаться к API
Размещение утёкшего токена расширяется от "отдельных опасных точек" до "всего Management API"
Проведение аудита, лимиты и детальная скоупировка прав для MCP становятся невозможны
Наш первый вывод: сервер MCP — это отдельный защищённый ресурс со своим resource identifier (https://mcp.logto.io) и отдельным скоупом.
Теперь граница чётко очерчена, и исходящий вопрос становится конкретным: токен позволяет попасть только на сервер MCP; чем он пользуется для вызова бизнес-API?
Logto Console — SPA. User проходит OAuth, получает токен для Management API как audience, и фронтенд напрямую вызывает API c этим токеном.
Может ли сервер MCP работать как особая версия Console? Пусть клиент MCP получает токен для бизнес-API на входе, а MCP просто его пересылает без изменений:
Плюс такого варианта — простота: сервер MCP только передаёт токены. Но и проблемы очевидны:
Во-первых, это напрямую противоречит решению о границе прав. Сквозная передача требует, чтобы клиент MCP получал токен для бизнес-API, а это как раз то, от чего мы отказались.
Во-вторых, сервер MCP перестаёт быть защищённым ресурсом. Audience токенов — не он сам, так что валидация audience теряет смысл, остаётся только проверять подпись и issuer. Это размазывает концепцию auth согласно RFC 9728 (сервер MCP должен объявляться защищённым ресурсом). Фактически backend выдаёт себя за браузерный SPA.
В-третьих, MCP-клиенты не поддержат такую схему. Согласно спецификации, MCP-клиент будет запрашивать токены для защищённого ресурса MCP — и audience будет соответствовать серверу MCP. Нет стандартного способа заставить клиента запрашивать токен для бизнес-API.
Если пользовательский токен не подходит — что, если взять собственную "личность" сервера MCP?
Сделать для MCP отдельное M2M-приложение, получить токен по client credentials и вызывать бизнес-API от его имени. Это стандартная практика для служебных интеграций.
Входящая авторизация работает: токен клиента направлен на сервер MCP, тот его валидирует, а затем всё делает с помощью собственного M2M токена.
Но главный недостаток — права M2M токена никак не связаны с правами пользователя:
Слишком широкие права: у M2M токена права сервера MCP, а не конкретного пользователя. User с только для чтения может удалять приложения через MCP — у токена есть такое право
Потеря идентичности: вниз по цепочке всегда идёт приложение M2M, логирование невозможно вязать с конкретным человеком
Проблема "запутанного заместителя": сервер MCP становится слишком привилегированным прокси, и любой, кто может уговорить его сделать вещь — использует его полномочия
M2M логика подходит для задач без пользователя (cron, интеграция между системами). Сервер MCP наоборот — каждый вызов инициирует конкретный пользователь, и его права должны сохраняться.
Токен клиента MCP может использоваться только для доступа к серверу MCP (вывод из варианта 1)
Для вызова downstream API сервер MCP должен действовать от имени пользователя и в его правах (вывод из варианта 2)
Это указывает на стандартный механизм: обмен токенов (RFC 8693). MCP передаёт credential на пользователя, обменивает его на сервере авторизации на токен для downstream API. Личность и права пользователя сохраняются.
В Logto credential в этом сценарии — это user impersonation, или subject token. Это короткоживущий credential, который сервер MCP запрашивает для конкретного пользователя, чтобы "следующий обмен будет от имени этого пользователя". Не требует действий пользователя, всё происходит автоматически. Subject token — одноразовый и живёт недолго.
Архитектура включает четыре роли. Сервер MCP развернут отдельно на mcp.logto.io:
Теперь весь токенный обмен при одном вызове инструмента:
Пошагово:
① Входящая проверка. Клиент MCP вызывает инструмент и передаёт user token. Его audience — resource ID сервера MCP, scope — mcp:all. Сервер MCP валидирует подпись, issuer, audience, scope и получает identity пользователя. На этом вход заканчивается. Этот токен не передаётся дальше.
② Идентичность сервиса. Сервер получает M2M токен по своим client credentials для скоупа access:mcp:api. Этот scope нужен только для следующего шага.
③ Запрос subject token — ключевой шаг всей цепочки. MCP вызывает POST /api/mcp/subject-tokens (специальный endpoint в Cloud), предъявляя две учётки:
Заголовок Authorization: M2M токен — "Я настоящий сервер MCP"
Заголовок x-mcp-user-token: user token — "Этот пользователь меня авторизовал, и авторизация всё ещё валидна"
Cloud полностью валидирует user token: подпись, issuer, время жизни, audience строго MCP server, scope содержит mcp:all. userId берётся только из claim sub токена. Endpoint НЕ принимает параметра userId.
Это исключает злоупотребления: если бы endpoint принимал произвольный userId, любой с M2M credential мог бы подделать пользователя. Теперь сервер MCP может обменять credential только для пользователя, у которого есть живая авторизация.
Subject token — короткоживущий, одноразовый; cache не делается, на каждый вызов требуется свежий экземпляр.
④ Обмен на рабочие токены. С subject token проводится стандартный обмен, чтобы получить два вида токенов:
Cloud API токен: для пользовательских операций — например, список арендаторов/создание
Org token: ограничен целевым tenant (каждый tenant в Logto — это организация). Токен выдаётся от имени пользователя, его права — ровно роль пользователя в tenant-е
⑤ Исходящий вызов. Вызов бизнес-API с обменянным токеном; результат возвращается MCP-клиенту.
Контекст мультиаренды тоже решается при обмене: tenant — это параметр обмена, а не соединения. list_tenants отдает список с помощью Cloud API токена, пользователь выбирает один в диалоге, сервер MCP обменивает org token на выбранного tenant-а. Один endpoint — для всех tenants, не нужен отдельный деплой на арендатора; только что созданный tenant появляется мгновенно.
Проверяя по болевым точкам предыдущих подходов — каждый пункт закрыт:
User token ограничен (вывод из варианта 1): audience — только сервер MCP. Если токен утечёт, можно только контролируемо вызывать MCP-инструменты
M2M token не может быть злоупотреблён (вывод из варианта 2): больше не идёт напрямую к бизнес-API, а только доказывает auth MCP-сервиса. Для subject token нужно предъявить валидный user token, так что сервер может act only with authorization by this user. Проблема "запутанного заместителя" исчезает
Нет хранения user credentials: subject token получается по требованию и сразу отбрасывается. MCP хранит только свои client credentials
Ревокация связана: если пользователь отзывает доступ MCP, user token становится невалидным, x-mcp-user-token check фейлится, цепочка мгновенно рвётся
Права строго пользователя: итоговые токены выпускаются от имени пользователя. read-only user останется только с чтением, попытка повысить права провалится на уровне auth-сервера. Для аудита реальный user identity
M2M credential в финальной схеме — на своём месте: доказывает "кто я", а "действовать как пользователь" возможно только через обмен с валидным user token прямо сейчас.
Входящая и исходящая части auth — независимы, audience токенов MCP всегда MCP server
Исходящую часть решает token exchange: если твоя auth server поддерживает обмен сразу по inbound token, стандартного обмена достаточно; если нет (например, Logto требует subject token), именно impersonation-запрос стартует обмен со стороны сервера
Мультиарендность не меняет подход: контекст организации — это параметр при обмене; однопользовательские продукты просто пропускают его
AI всегда действует от пользователя, так что downstream токены всегда в правах конкретного пользователя
Простой тест: если токен клиента MCP утёк — максимум, что может сделать злоумышленник — вызвать только контролируемые инструменты на MCP с правами пользователя. Если удаётся попасть сразу в бизнес-API — значит, граница разрешений сломана.
Экосистема MCP быстро развивается. Входная авторизация чётко прописана в спецификации, а "исходящий вызов MCP" пока что зависит от реализации. Надеемся, наш опыт будет полезен.