Авторизация
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
Через 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
KNOWN_TOKENS = {
"alice-token": AccessToken(token="alice-token", client_id="alice", scopes=["notes:read"]),
}
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("http://127.0.0.1:8000/mcp"),
required_scopes=["notes:read"],
),
)
@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 или обращается к эндпоинту интроспекции токенов на сервере авторизации. Этот код — ваш; SDK его только вызывает.
token_verifier=иauth=всегда идут в паре. Передайте один без другого — иMCPServer(...)выброситValueErrorещё до того, как обслужит хоть один запрос.
AuthSettings — это публичное лицо вашего сервера ресурсов:
issuer_url: сервер авторизации, который выдаёт ваши токены.resource_server_url: публичный URL этого MCP-эндпоинта. Он указывает, для какого ресурса предназначен токен, и по нему же размещается документ обнаружения.required_scopes: каждый токен должен содержать их все.
Tip
В examples/servers/simple-auth/ в репозитории SDK есть 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
KNOWN_TOKENS = {
"alice-token": AccessToken(token="alice-token", client_id="alice", scopes=["notes:read"]),
}
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("http://127.0.0.1:8000/mcp"),
required_scopes=["notes:read"],
),
)
@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, которые вы прикрепили. Это и есть точка для правил на уровне отдельных инструментов: прочитайте scopes и откажите. - Вне аутентифицированного 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-клиенты. А клиент, который утверждает личность вместо того, чтобы запрашивать её у пользователя, — на странице Утверждение личности.