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

Авторизація

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

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

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

У термінах OAuth ваш сервер — це сервер ресурсів. Він нікого не автентифікує і ніколи не видає токенів. Він робить одне: дивиться на заголовок 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

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

У 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. Канал (pipe) не має заголовка 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

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, які ви додали. Це і є зачіпка для правил на рівні окремих інструментів: прочитайте 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 публікує RFC 9728 Protected Resource Metadata за адресою /.well-known/oauth-protected-resource/... і відповідає на неавтентифіковані запити кодом 401, чий заголовок WWW-Authenticate вказує на них. Оце й уся історія виявлення.
  • get_access_token() у будь-якому обробнику — це той, хто викликає.
  • Авторизація — справа HTTP. stdio і тестовий клієнт у пам'яті ніколи її не бачать.

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