Перейти до змісту

Авторизація

Машинний переклад

Цю сторінку перекладено автоматично з англомовної документації, і основною версією є англомовна сторінка. Якщо щось читається неправильно, на сторінці Переклади пояснено, як про це повідомити.

Через Streamable HTTP ваш MCP-сервер — це звичайний вебсервіс, і захищати його слід так само, як будь-який інший вебсервіс: bearer-токенами OAuth 2.1.

У термінах OAuth ваш сервер — це сервер ресурсів (resource server). Він ніколи нікого не автентифікує й ніколи не видає токенів. Він робить одне: дивиться на заголовок Authorization у кожному запиті й вирішує, чи придатний токен у ньому.

Ця сторінка — про серверний бік. Клієнт, який знаходить ваш сервер авторизації й отримує токен, описано на сторінці Клієнти OAuth.

Три сторони

  • Сервер авторизації автентифікує людей і видає токени доступу. Ви його не пишете. Це ваш постачальник ідентичності (Auth0, Keycloak, Entra, ваш власний).
  • Сервер ресурсів — це ваш MCP-сервер. Він перевіряє токен у кожному запиті.
  • Клієнт з'ясовує, якому серверу авторизації ви довіряєте, отримує від нього токен і надсилає його вам як Authorization: Bearer <token>.

Оце й увесь трикутник. Усе на цій сторінці стосується середнього пункту.

Верифікатор токенів

SDK не має власної думки про те, який вигляд має дійсний токен. Це визначаєте ви, реалізувавши TokenVerifier:

server.py
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-шлях — і отримаєте RFC 9728 Protected Resource Metadata, побудовані безпосередньо з вашого 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, який ваш верифікатор повернув для поточного запиту:

server.py
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 підтримує обидва боки цього обміну. Про цей grant і клієнта, що його пред'являє, — на сторінці Твердження ідентичності.

Підсумки

  • Через Streamable HTTP ваш сервер — це сервер ресурсів OAuth 2.1: він перевіряє токени й ніколи їх не видає.
  • TokenVerifier — це вся поверхня інтеграції: один асинхронний метод, на вході токен, на виході AccessToken | None.
  • token_verifier= і auth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...]) завжди йдуть разом.
  • SDK публікує RFC 9728 Protected Resource Metadata за адресою /.well-known/oauth-protected-resource/... і відповідає на неавтентифіковані запити кодом 401, чий заголовок WWW-Authenticate вказує на них. Оце й уся історія виявлення.
  • get_access_token() у будь-якому обробнику — це той, хто викликає.
  • Авторизація — справа HTTP. stdio та клієнт у пам'яті ніколи її не бачать.

Клієнтська половина (виявлення вашого сервера авторизації й отримання токена за вас) — на сторінці Клієнти OAuth. А клієнт, який стверджує ідентичність замість того, щоб запитувати її в користувача, — на сторінці Твердження ідентичності.