Авторизация
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
При работе через Streamable HTTP ваш MCP-сервер — обычный веб-сервис, и защищают его так же, как любой веб-сервис: bearer-токенами OAuth 2.1.
В терминах OAuth ваш сервер — это сервер ресурсов. Он никогда не выполняет вход пользователей и никогда не выдаёт токены. Он делает одно: смотрит на заголовок Authorization в каждом запросе и решает, годится ли токен в нём.
Эта страница — о серверной стороне. Клиент, который находит ваш сервер авторизации и получает токен, описан на странице OAuth-клиенты.
Три стороны
- Сервер авторизации выполняет вход пользователей и выдаёт токены доступа. Его вы не пишете. Это ваш провайдер идентификации (Auth0, Keycloak, Entra или собственный).
- Сервер ресурсов — это ваш MCP-сервер. Он проверяет токен в каждом запросе.
- Клиент выясняет, какому серверу авторизации вы доверяете, получает у него токен и присылает его вам в виде
Authorization: Bearer <token>.
Вот и весь треугольник. Всё на этой странице — про средний пункт.
Верификатор токенов
У SDK нет мнения о том, как выглядит действительный токен. Это сообщаете вы, реализуя TokenVerifier:
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
RESOURCE = "http://127.0.0.1:8000/mcp"
KNOWN_TOKENS = {
"alice-token": AccessToken(token="alice-token", client_id="alice", scopes=["notes:read"], resource=RESOURCE),
}
class StaticTokenVerifier(TokenVerifier):
async def verify_token(self, token: str) -> AccessToken | None:
return KNOWN_TOKENS.get(token)
mcp = MCPServer(
"Notes",
token_verifier=StaticTokenVerifier(),
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://auth.example.com"),
resource_server_url=AnyHttpUrl(RESOURCE),
required_scopes=["notes:read"],
validate_token_resource=True,
),
)
@mcp.tool()
def list_notes() -> list[str]:
"""List every note in the notebook."""
return ["Buy milk", "Ship the release"]
TokenVerifier— протокол с одним асинхронным методом.verify_tokenполучает сырой токен из заголовкаAuthorizationи возвращаетAccessToken, если токен действителен, иNone, если нет. Больше реализовывать нечего.- Этот верификатор ищет токен в таблице; в каждой записи указан ресурс, для которого токен выдан. Настоящий проверяет подпись JWT или вызывает эндпоинт интроспекции токенов сервера авторизации и сообщает, для кого выдан токен (его
aud), вAccessToken.resource. Этот код пишете вы; SDK его только вызывает. token_verifier=иauth=всегда идут в паре. Передайте один без другого — иMCPServer(...)выброситValueErrorещё до того, как обслужит хоть один запрос.
AuthSettings — публичное лицо вашего сервера ресурсов:
issuer_url: сервер авторизации, который выдаёт ваши токены.resource_server_url: публичный URL этого MCP-эндпоинта. Он указывает, для какого ресурса предназначен токен, и по нему же располагается документ обнаружения.required_scopes: каждый токен должен содержать их все.validate_token_resource: отклонять любой токен, у которогоAccessToken.resourceне равенresource_server_url. Если оставить его незаданным при заданномresource_server_url, выдаётся предупреждение (MCPDeprecationWarning), а поведение такое же, как приFalse; в версии 3.0 значением по умолчанию для серверов ресурсов станетTrue.- Включите его, если ваш сервер авторизации привязывает токены к параметру
resource, который запросил клиент, — MCP-клиенты передают его всегда. Следите, чтобыresource_server_urlв точности совпадал с URL, к которому подключаются клиенты. - Оставьте выключенным, если сервер авторизации использует собственные идентификаторы аудитории (идентификатор API в Auth0, идентификатор приложения в Entra), и вместо этого проверяйте
audв верификаторе, возвращаяNoneдля токена, предназначенного не этому серверу. - Если
aud— список, поместите вresourceтот элемент, который равенresource_server_url.
Tip
В репозитории SDK, в examples/servers/simple-auth/, есть IntrospectionTokenVerifier, который обращается
к эндпоинту RFC 7662 настоящего сервера авторизации. Так устроено большинство верификаторов в реальных развёртываниях.
Что появляется по HTTP
Авторизация живёт в HTTP-заголовках, поэтому существует только на HTTP-транспортах. Запускайте её на том, который развёртываете: mcp.run(transport="streamable-http") поднимает сервер на http://127.0.0.1:8000/mcp, а остальное — на странице Запуск сервера. Теперь у приложения два маршрута:
/mcp
/.well-known/oauth-protected-resource/mcp
Вы зарегистрировали один инструмент. Второй маршрут добавил SDK.
Обнаружение
Выполните GET по этому well-known-пути — и получите Protected Resource Metadata по RFC 9728, собранные прямо из ваших AuthSettings:
{
"resource": "http://127.0.0.1:8000/mcp",
"authorization_servers": ["https://auth.example.com/"],
"scopes_supported": ["notes:read"],
"bearer_methods_supported": ["header"]
}
По этому документу клиент, никогда не слышавший о вашем сервере, находит дорогу внутрь: читает authorization_servers и идёт туда за токеном. Ни строчки из него вы не писали.
Check
Вызовите /mcp без токена (или с токеном, для которого верификатор вернул None) — и запрос
остановят на пороге:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Authentication required", resource_metadata="http://127.0.0.1:8000/.well-known/oauth-protected-resource/mcp"
{"error": "invalid_token", "error_description": "Authentication required"}
Ничего не было разобрано, и ни один инструмент не выполнился. А указатель resource_metadata в WWW-Authenticate —
именно то, что делает обнаружение автоматическим: 401 -> документ метаданных -> сервер авторизации -> токен -> повтор запроса.
Warning
Ничто из этого не защищает stdio. У канала нет заголовка Authorization, поэтому к token_verifier там никогда
не обращаются. Граница безопасности stdio-сервера — процесс, который его запустил. То же
относится к Client(mcp) в памяти, который используется в тестах: он подключается прямо к объекту сервера
и минует HTTP-уровень вместе с авторизацией.
Личность вызывающей стороны
Внутри любого обработчика get_access_token() — это AccessToken, который ваш верификатор вернул для текущего запроса:
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.middleware.auth_context import get_access_token
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
RESOURCE = "http://127.0.0.1:8000/mcp"
KNOWN_TOKENS = {
"alice-token": AccessToken(token="alice-token", client_id="alice", scopes=["notes:read"], resource=RESOURCE),
}
class StaticTokenVerifier(TokenVerifier):
async def verify_token(self, token: str) -> AccessToken | None:
return KNOWN_TOKENS.get(token)
mcp = MCPServer(
"Notes",
token_verifier=StaticTokenVerifier(),
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://auth.example.com"),
resource_server_url=AnyHttpUrl(RESOURCE),
required_scopes=["notes:read"],
validate_token_resource=True,
),
)
@mcp.tool()
def whoami() -> str:
"""Report which OAuth client is calling."""
token = get_access_token()
if token is None:
return "anonymous"
return f"{token.client_id} (scopes: {', '.join(token.scopes)})"
- Работает в инструментах, ресурсах и промптах, и передавать ничего не нужно: middleware авторизации сохраняет его в контекстной переменной для каждого запроса.
- Возвращается тот самый объект, который собрал ваш верификатор:
client_id,scopes,subject,expires_atи любые дополнительныеclaims, которые вы прикрепили. Это и есть точка для правил на уровне инструмента: прочитайте области действия и откажите. - Вне аутентифицированного HTTP-запроса возвращается
None. В памяти и поstdioэто всегдаNone.
Вызовите whoami с Authorization: Bearer alice-token — и модель прочитает:
alice (scopes: notes:read)
Половина, которой в SDK нет
SDK даёт вам половину сервера ресурсов: проверить, объявить, отказать. Он не даёт ни страницы входа, ни экрана согласия, ни токена.
Чтобы увидеть в движении все три стороны, запустите examples/servers/simple-auth/ из репозитория SDK (небольшой сервер авторизации и сервер ресурсов, настроенный ровно как на этой странице), а затем направьте на него examples/clients/simple-auth-client/ — получится полный цикл обнаружения и получения токена.
Info
Есть и второй аргумент конструктора, auth_server_provider=, который встраивает полноценный сервер
авторизации внутрь MCP-сервера. Он появился раньше разделения на AS и RS, вокруг которого построена
спецификация авторизации MCP. В новых серверах обращаться к нему не следует.
Сервер авторизации может также принять подписанное утверждение корпоративного провайдера идентификации вместо того, чтобы пользователь проходил экран согласия, — и SDK поддерживает обе стороны этого обмена. Сам грант и клиент, который его предъявляет, описаны на странице Утверждение идентичности.
Итоги
- По Streamable HTTP ваш сервер — сервер ресурсов OAuth 2.1: он проверяет токены и никогда их не выдаёт.
TokenVerifier— вся поверхность интеграции: один асинхронный метод, на входе токен, на выходеAccessToken | None.token_verifier=иauth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])всегда идут в паре.- SDK публикует Protected Resource Metadata по RFC 9728 по адресу
/.well-known/oauth-protected-resource/...и отвечает на неаутентифицированные запросы кодом 401, заголовокWWW-Authenticateкоторого указывает на этот документ. В этом и состоит всё обнаружение. get_access_token()в любом обработчике — это тот, кто вызывает.- Авторизация — дело HTTP.
stdioи тестовый клиент в памяти её никогда не видят.
Клиентская половина (найти ваш сервер авторизации и получить токен за вас) — на странице OAuth-клиенты. А клиент, который утверждает идентичность, вместо того чтобы спрашивать её у пользователя, — на странице Утверждение идентичности.