OAuth-клиенты - MCP Python SDK
https://py.sdk.modelcontextprotocol.io/ru/client/oauth-clients/ • 90 KB fetched
Open original page
OAuth-клиенты - MCP Python SDK
Перейти к содержанию
MCP Python SDK
OAuth-клиенты
*
en - English
*
de - Deutsch
*
es - español
*
fr - français
*
hi - हिन्दी
*
ja - 日本語
*
ko - 한국어
*
pt - português (Brasil)
*
ru - русский язык
*
tr - Türkçe
*
uk - українська мова
*
zh - 简体中文
*
zh-hant - 繁體中文
Поиск
modelcontextprotocol/python-sdk
MCP Python SDK
modelcontextprotocol/python-sdk
*
MCP Python SDK
*
Что нового в v2
*
Начало работы
Начало работы
*
Установка
*
Первые шаги
*
Подключение к настоящему хосту
*
Тестирование
*
Серверы
Серверы
*
Инструменты
*
Структурированный вывод
*
Ресурсы
*
Шаблоны URI и безопасность путей
*
Промпты
*
Автодополнение
*
Медиа
*
Обработка ошибок
*
Внутри обработчика
Внутри обработчика
*
Объект Context
*
Зависимости
*
Жизненный цикл
*
Элицитация
*
Многораундовые запросы (multi-round-trip)
*
Сэмплирование и корневые каталоги
*
Ход выполнения
*
Логирование
*
Подписки
*
Запуск сервера
Запуск сервера
*
Добавление в существующее приложение
*
Развёртывание и масштабирование
*
Авторизация
*
OpenTelemetry
*
Обслуживание клиентов старого поколения
*
Объект Client
Объект Client
*
Колбэки клиента
*
Клиентские транспорты
*
OAuth-клиенты
OAuth-клиенты
Содержание
*
Провайдер
*
Метаданные клиента
*
Хранилище токенов
*
Два обработчика
*
Подключение к Client
*
Что провайдер делает за вас
*
Попробуйте сами
*
Client ID Metadata Documents
*
Межмашинное взаимодействие
*
Когда возникает ошибка
*
Итоги
*
Утверждение идентичности
*
Группы сессий
*
Подписки
*
Подсказки по кэшированию
*
Версии протокола
*
Устаревшие возможности
*
Продвинутые темы
Продвинутые темы
*
Низкоуровневый Server
*
Пагинация
*
Middleware
*
Расширения
*
MCP Apps
*
Устранение неполадок
*
Переводы
*
Migration Guide: v1 to v2
*
API Reference
Содержание
*
Провайдер
*
Метаданные клиента
*
Хранилище токенов
*
Два обработчика
*
Подключение к Client
*
Что провайдер делает за вас
*
Попробуйте сами
*
Client ID Metadata Documents
*
Межмашинное взаимодействие
*
Когда возникает ошибка
*
Итоги
*
MCP Python SDK
*
Объект Client
OAuth-клиенты
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница . Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
Некоторые MCP-серверы защищены. Отправьте им запрос без токена — и в ответ придёт 401 Unauthorized .
OAuthClientProvider — это способ получить токен. Это вовсе не объект MCP. Это httpx2.Auth , стандартный хук httpx2 для задачи «сделать что-то с каждым запросом». Его подключают к httpx2.AsyncClient , передают этот клиент транспорту Streamable HTTP — и больше о нём не думают.
Эта страница — о клиентской стороне. Как заставить собственный сервер требовать токен, описано на странице Авторизация .
Провайдер
client.py from urllib.parse import parse_qs , urlparse
import httpx2
from pydantic import AnyUrl
from mcp import Client
from mcp.client.auth import AuthorizationCodeResult , OAuthClientProvider
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull , OAuthClientMetadata , OAuthToken
class InMemoryTokenStorage :
def __init__ ( self ) -> None :
self . tokens : OAuthToken | None = None
self . client_info : OAuthClientInformationFull | None = None
async def get_tokens ( self ) -> OAuthToken | None :
return self . tokens
async def set_tokens ( self , tokens : OAuthToken ) -> None :
self . tokens = tokens
async def get_client_info ( self ) -> OAuthClientInformationFull | None :
return self . client_info
async def set_client_info ( self , client_info : OAuthClientInformationFull ) -> None :
self . client_info = client_info
async def open_browser ( authorization_url : str ) -> None :
print ( f "Visit: { authorization_url } " )
async def wait_for_callback () -> AuthorizationCodeResult :
redirect_url = input ( "Paste the URL you were redirected to: " )
params = parse_qs ( urlparse ( redirect_url ) . query )
return AuthorizationCodeResult (
code = params [ "code" ][ 0 ],
state = params [ "state" ][ 0 ],
iss = params [ "iss" ][ 0 ] if "iss" in params else None ,
)
oauth = OAuthClientProvider (
server_url = "http://localhost:8001/mcp" ,
client_metadata = OAuthClientMetadata (
client_name = "Bookshop Agent" ,
redirect_uris = [ AnyUrl ( "http://localhost:3030/callback" )],
scope = "user" ,
),
storage = InMemoryTokenStorage (),
redirect_handler = open_browser ,
callback_handler = wait_for_callback ,
)
async def main () -> None :
async with httpx2 . AsyncClient ( auth = oauth ) as http_client :
transport = streamable_http_client ( "http://localhost:8001/mcp" , http_client = http_client )
async with Client ( transport ) as client :
result = await client . list_tools ()
print ([ tool . name for tool in result . tools ])
Ему передают четыре вещи:
* server_url : конечная точка MCP, к которой вы подключаетесь. Всё остальное провайдер выясняет по ней сам.
* client_metadata : то, что вы ввели бы в форму «зарегистрировать приложение» на сервере авторизации.
* storage : место, где токены хранятся между запусками.
* redirect_handler и callback_handler : два момента, когда участвует человек.
Больше нигде в файле OAuth не упоминается. main() токена не видит вовсе.
Метаданные клиента
OAuthClientMetadata — это настоящий регистрационный документ из RFC 7591 , оформленный как модель Pydantic.
Вы задаёте три поля. Остальное заполняют значения по умолчанию: grant_types уже равно ["authorization_code", "refresh_token"] , а response_types — ["code"] , и это ровно тот сценарий, который выполняет провайдер.
Check
Поскольку это модель Pydantic, она проверяется ещё до того, как хоть один байт уйдёт в сеть .
Опустите redirect_uris — и создание объекта тут же завершится ошибкой ValidationError ,
в которой названо поле:
redirect_uris
Field required [type=missing, input_value={'client_name': 'Bookshop Agent'}, input_type=dict]
Браузер не открылся, и на сервере авторизации не осталось недоделанной регистрации.
Хранилище токенов
TokenStorage — это Protocol с четырьмя асинхронными методами. Наследоваться ни от чего не нужно: напишите эти методы — и любой класс станет хранилищем токенов:
* get_tokens / set_tokens хранят OAuthToken : токен доступа, токен обновления, срок действия, область доступа (scope).
* get_client_info / set_client_info хранят OAuthClientInformationFull , который сервер авторизации выдал, когда провайдер вас зарегистрировал, — включая ваш client_id .
Версия в памяти из примера выше работает. Но при завершении процесса она всё забывает, так что при следующем запуске вся процедура повторяется с начала. Сохраняйте данные в файл или в связку ключей вашей платформы — и следующий запуск пройдёт тихо.
Tip
Сохраняйте client_info , а не только токены. Провайдер проходит динамическую регистрацию
в первый раз, когда не находит сохранённого client_info . Выбросьте его — и при каждом
запуске будет создаваться новая регистрация.
Два обработчика
Сценарию с кодом авторизации человек нужен ровно один раз: кто-то должен войти в систему и нажать «разрешить».
* redirect_handler асинхронно вызывается с полностью собранным URL авторизации. client_id , redirect_uri , state и PKCE challenge в нём уже есть. Ваша единственная задача — открыть его в браузере. Настольное приложение вызывает webbrowser.open ; в этом файле URL просто печатается.
* callback_handler вызывается следующим. Он ждёт, пока пользователь вернётся на ваш redirect_uri , и возвращает параметры запроса из этого перенаправления в виде AuthorizationCodeResult .
Настоящий клиент вместо вызова input() поднимает небольшой локальный HTTP-сервер на URI перенаправления. Схема та же: принять перенаправление, вернуть code , state и iss .
Warning
Передавайте state и iss ровно в том виде, в каком они пришли. Провайдер сравнивает
state со значением, которое сгенерировал сам, а iss — с издателем, которого обнаружил,
и при несовпадении отказывает. Это защита от CSRF и от подмены сервера (mix-up).
Подключение к Client
Посмотрите на main() . Провайдер подключается к клиенту httpx2 , клиент httpx2 передаётся в streamable_http_client(url, http_client=...) , а этот транспорт — в Client .
У streamable_http_client нет именованного параметра auth= . Всё, что относится к уровню HTTP (аутентификация, заголовки, тайм-ауты, прокси), настраивается на httpx2.AsyncClient , который вы приносите сами. Об этом разделении уровней — на странице Транспорты клиента .
Что провайдер делает за вас
Когда Client отправляет первый запрос, сервер отвечает 401 . Дальше действует провайдер:
* Обнаружение. Он читает заголовок WWW-Authenticate , загружает метаданные защищённого ресурса (Protected Resource Metadata) сервера с /.well-known/oauth-protected-resource , узнаёт, какой сервер авторизации защищает этот ресурс, и загружает метаданные уже того сервера. (У сервера постарше, который не публикует метаданные ресурса, вместо этого запрашиваются метаданные сервера авторизации по его собственному origin.) В любом случае метаданные должны называть в поле issuer тот сервер, для которого они были загружены; всё остальное отклоняется.
* Регистрация. В хранилище пусто? Он динамически регистрирует вас с вашим OAuthClientMetadata и сохраняет результат.
* Авторизация. Он генерирует пару PKCE и state , собирает URL авторизации, ждёт ваш redirect_handler , а затем ждёт от callback_handler код.
* Обмен. Он обменивает код на OAuthToken , сохраняет его и повторяет исходный запрос уже с Authorization: Bearer ... .
После этого он работает незаметно. Токены берутся из хранилища, истёкший токен доступа обновляется с помощью токена обновления, и только когда ничего из этого не срабатывает, сценарий запускается заново.
Ко всем этим запросам применяется одно транспортное правило: как и MCP-запрос, внутри которого они выполняются, они следуют перенаправлению только тогда, когда оно остаётся на том же origin и сохраняет метод (скажем, 307/308 на завершающий слеш), а любое другое перенаправление считают тем, что этот URL не отвечает.
Ничего из этого вы не писали. Остаются два именованных аргумента ( client_metadata_url и validate_resource_url ), и этому файлу не нужен ни один из них. О client_metadata_url стоит знать — ему посвящён отдельный раздел ниже.
Попробуйте сами
Client(server) в памяти, которым пользуются ваши тесты, здесь не поможет: весь смысл сценария в HTTP-ответе 401 , а между клиентом в памяти и его сервером никакого HTTP нет.
В репозитории есть живая версия. examples/servers/simple-auth/ запускает отдельный сервер авторизации и защищённый MCP-сервер; examples/clients/simple-auth-client/ — это клиент с этой страницы, выросший в небольшой CLI. В его README — две команды: запустите серверы, запустите клиент, подключив его к ним, — и наблюдайте, как проходят все четыре шага.
Client ID Metadata Documents
Ревизия спецификации 2026-07-28 объявляет динамическую регистрацию клиентов устаревшей в пользу Client ID Metadata Documents (CIMD). Вместо того чтобы отправлять POST-запросом новую регистрацию каждому встреченному серверу авторизации, клиент публикует один JSON-документ о себе по стабильному HTTPS URL — и этот URL и есть его client_id . Документ загружает сервер авторизации; провайдер его вообще не трогает.
SDK это уже умеет: передайте URL как client_metadata_url= при создании провайдера. Если метаданные сервера авторизации объявляют client_id_metadata_document_supported: true , провайдер полностью пропускает запрос /register : URL идёт в сценарий как client_id , а client_secret нет вовсе. Если сервер этого не объявляет (большинство пока не объявляет) или URL не передан, провайдер молча откатывается к динамической регистрации, и всё описанное выше работает ровно так, как описано. Сохранённый client_info по-прежнему имеет приоритет над обоими вариантами.
URL должен быть HTTPS и с некорневым путём; всё остальное — ValueError при создании, до любого обращения к сети. Поставляемый пример examples/clients/simple-auth-client/ принимает его в переменной окружения MCP_CLIENT_METADATA_URL .
Межмашинное взаимодействие
Ночное задание, шаг CI, другой сервис. Браузера нет, и нажать «разрешить» некому. Это грант client credentials : client_id и client_secret у вас уже есть, а весь сценарий сводится к конечной точке токенов.
ClientCredentialsOAuthProvider — тот же httpx2.Auth , только без человека:
client.py import httpx2
from mcp import Client
from mcp.client.auth.extensions.client_credentials import ClientCredentialsOAuthProvider
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull , OAuthToken
class InMemoryTokenStorage :
def __init__ ( self ) -> None :
self . tokens : OAuthToken | None = None
self . client_info : OAuthClientInformationFull | None = None
async def get_tokens ( self ) -> OAuthToken | None :
return self . tokens
async def set_tokens ( self , tokens : OAuthToken ) -> None :
self . tokens = tokens
async def get_client_info ( self ) -> OAuthClientInformationFull | None :
return self . client_info
async def set_client_info ( self , client_info : OAuthClientInformationFull ) -> None :
self . client_info = client_info
oauth = ClientCredentialsOAuthProvider (
server_url = "http://localhost:8001/mcp" ,
storage = InMemoryTokenStorage (),
client_id = "reporting-agent" ,
client_secret = "..." ,
scope = "user" ,
issuer = "http://localhost:9000" ,
)
async def main () -> None :
async with httpx2 . AsyncClient ( auth = oauth ) as http_client :
transport = streamable_http_client ( "http://localhost:8001/mcp" , http_client = http_client )
async with Client ( transport ) as client :
result = await client . list_tools ()
print ([ tool . name for tool in result . tools ])
Что изменилось:
* Нет OAuthClientMetadata , нет обработчиков. Вы передаёте client_id и client_secret ; провайдер строит вокруг них минимальную регистрацию client_credentials и полностью пропускает динамическую регистрацию.
* issuer называет сервер авторизации, который выдал эти учётные данные; используйте значение issuer , которое возвращает его документ /.well-known/oauth-authorization-server . Обнаружение по-прежнему проходит, как описано выше, но запросы токена строятся только по метаданным этого издателя; если MCP-сервер указывает куда-то ещё, сценарий останавливается с OAuthFlowError . Не указывать его — устаревший вариант, и в 3.0 параметр станет обязательным (см. Устаревшие возможности ); до тех пор провайдер выдаёт предупреждение и использует тот сервер авторизации, который найдёт обнаружение.
* scope — строка с разделением пробелами, формат OAuth для передачи по сети.
* Всё дальше по цепочке идентично: тот же TokenStorage , тот же httpx2.AsyncClient(auth=...) , тот же streamable_http_client .
По умолчанию секрет передаётся в запросе токена через HTTP Basic auth ( client_secret_basic ). Передайте token_endpoint_auth_method="client_secret_post" , чтобы вместо этого поместить его в тело формы. Некоторые серверы авторизации принимают только один из двух способов.
Tip
Читайте client_secret из окружения или менеджера секретов, никогда не из системы контроля версий.
Info
Ещё один провайдер находится в mcp.client.auth.extensions.client_credentials :
PrivateKeyJWTOAuthProvider — для клиентов, которые аутентифицируются с помощью JWT
вместо общего секрета ( private_key_jwt , вариант с парой ключей и workload identity).
Схема та же: создайте экземпляр (он принимает тот же необязательный issuer ) и передайте его в auth= . В том же модуле есть
SignedJWTParameters и static_assertion_provider — два вспомогательных средства,
которые собирают для него утверждение (assertion).
Есть ещё одна ситуация без человека: клиент принадлежит организации, чей провайдер удостоверений, а не пользователь, решает, к каким MCP-серверам он может обращаться. Это другой грант со своей моделью доверия и своей страницей — Подтверждение идентичности .
Когда возникает ошибка
Когда сценарий OAuth идёт не так, провайдер выбрасывает OAuthFlowError из mcp.client.auth . У него два подкласса. OAuthRegistrationError означает, что регистрация не дала пригодного клиента: сервер авторизации отказал в регистрации или всё же зарегистрировал вас, но с учётными данными, которые этот сценарий использовать не может (например, с методом аутентификации, который он не реализует). OAuthTokenError означает, что получить токен не удалось: конечная точка токенов ответила отказом, или в сохранённой записи клиента указан метод аутентификации, который этот клиент применить не может, — об этом сообщается при сборке запроса токена, а не после его отправки. Один except OAuthFlowError: охватывает обнаружение, регистрацию, авторизацию и обмен.
Не всё — ошибка сценария. Сеть по-прежнему может подвести; это обычные исключения httpx2 , и они проходят насквозь без изменений.
Итоги
* OAuthClientProvider — это httpx2.Auth . Подключите его к httpx2.AsyncClient , передайте тот в streamable_http_client(url, http_client=...) — и Client так и не узнает, что был OAuth.
* От вас нужны четыре вещи: URL сервера, OAuthClientMetadata , TokenStorage и пара обработчиков redirect/callback.
* TokenStorage — это Protocol : четыре асинхронных метода, без базового класса. Сохраняйте client_info наряду с токенами.
* Обнаружение, регистрация (динамическая или через Client ID Metadata Document ), PKCE, проверки state и iss и обновление токенов — забота провайдера, а не ваша.
* ClientCredentialsOAuthProvider — версия без человека: client_id + client_secret , без обработчиков, без браузера.
* Любой сбой OAuth — это OAuthFlowError ; OAuthRegistrationError и OAuthTokenError — его подклассы.
Вторая половина этого рукопожатия — как заставить ваш сервер требовать токен — на странице Авторизация .
К началу
Назад
Клиентские транспорты
Вперед
Утверждение идентичности
Made with
Zensical
Links found on this page
- Перейти к содержанию [direct]
- en - English [direct]
- de - Deutsch [direct]
- es - español [direct]
- fr - français [direct]
- hi - हिन्दी [direct]
- ja - 日本語 [direct]
- ko - 한국어 [direct]
- pt - português (Brasil) [direct]
- ru - русский язык [direct]
- tr - Türkçe [direct]
- uk - українська мова [direct]
- zh - 简体中文 [direct]
- zh-hant - 繁體中文 [direct]
- modelcontextprotocol/python-sdk [direct]
- Что нового в v2 [direct]
- Начало работы [direct]
- Установка [direct]
- Первые шаги [direct]
- Подключение к настоящему хосту [direct]
- Тестирование [direct]
- Серверы [direct]
- Инструменты [direct]
- Структурированный вывод [direct]
- Ресурсы [direct]
- Шаблоны URI и безопасность путей [direct]
- Промпты [direct]
- Автодополнение [direct]
- Медиа [direct]
- Обработка ошибок [direct]
- Внутри обработчика [direct]
- Объект Context [direct]
- Зависимости [direct]
- Жизненный цикл [direct]
- Элицитация [direct]
- Многораундовые запросы (multi-round-trip) [direct]
- Сэмплирование и корневые каталоги [direct]
- Ход выполнения [direct]
- Логирование [direct]
- Подписки [direct]
- Запуск сервера [direct]
- Добавление в существующее приложение [direct]
- Развёртывание и масштабирование [direct]
- Авторизация [direct]
- OpenTelemetry [direct]
- Обслуживание клиентов старого поколения [direct]
- Объект Client [direct]
- Колбэки клиента [direct]
- Клиентские транспорты [direct]
- Утверждение идентичности [direct]
- Группы сессий [direct]
- Подписки [direct]
- Подсказки по кэшированию [direct]
- Версии протокола [direct]
- Устаревшие возможности [direct]
- Продвинутые темы [direct]
- Низкоуровневый Server [direct]
- Пагинация [direct]
- Middleware [direct]
- Расширения [direct]
- MCP Apps [direct]
- Устранение неполадок [direct]
- Переводы [direct]
- Migration Guide: v1 to v2 [direct]
- API Reference [direct]
- английская страница [direct]
- RFC 7591 [direct]
- Zensical [direct]