OAuth 2.1 resource-server support and the licence gate, shared by the MCP fleet
Find a file
airaneel 4a91bdf6e2 Снят потолок fastmcp: пакет работает и на 3.x
Потолок в общей библиотеке сильнее любого решения в сервере: он держал
бы на legacy весь флот, потому что зависимость одна на всех.

Проверено, а не предположено — на 3.4.7 все 20 тестов проходят без
правок: auth=, RemoteAuthProvider.get_routes(mcp_path), Middleware,
get_access_token и ToolError в 3.x на месте. Из того, чем пользуется
флот, 3.0 убрал только tool_serializer, а его этот пакет не трогает.
2026-08-17 15:09:01 +03:00
mcp_fleet_auth Общая обвязка OAuth и лицензии для флота MCP 2026-08-17 14:52:29 +03:00
tests Общая обвязка OAuth и лицензии для флота MCP 2026-08-17 14:52:29 +03:00
.gitignore Общая обвязка OAuth и лицензии для флота MCP 2026-08-17 14:52:29 +03:00
pyproject.toml Снят потолок fastmcp: пакет работает и на 3.x 2026-08-17 15:09:01 +03:00
README.md Снят потолок fastmcp: пакет работает и на 3.x 2026-08-17 15:09:01 +03:00

mcp-fleet-auth

Обвязка OAuth 2.1 и гейт лицензии — одна на весь флот MCP-серверов.

Токены выпускает общий издатель mcp-auth, здесь — только проверка предъявленного bearer, публикация метаданных RFC 9728 и отказ инструмента тому, у кого нет лицензии на этот сервис.

Зачем пакет, а не файл в каждом сервере

Он и был файлом. К третьему серверу их стало три разных: excel гейтил инструменты декоратором, rusprofile — прослойкой, web-search двумя своими модулями. Одна работа, три ответа, и не сказать, какой текущий. Здесь это опаснее обычного дублирования: код решает, кому дать платный доступ, поэтому разъехавшаяся копия либо раздаёт доступ, либо запирает — и молча в обоих случаях.

Установка

Пин по тегу, чтобы обновление было осознанным:

dependencies = [
    "mcp-fleet-auth @ https://git.dev.andyjazzy.cloud/airaneel/mcp_fleet_auth/archive/v0.2.0.tar.gz",
]

Использование

Три строки в сервере:

from mcp_fleet_auth import LicenseMiddleware, auth_provider

mcp = FastMCP("Закупки ЕИС", auth=auth_provider("Zakupki MCP"))
mcp.add_middleware(LicenseMiddleware())

Всё остальное — окружение:

Переменная Смысл
MCP_AUTH_URL корень издателя. Пусто — авторизации нет вовсе (stdio, разработка)
PUBLIC_URL адрес, по которому сервер видят клиенты, без /mcp
REQUIRED_SCOPE имя сервиса: и область токена, и срез лицензии
LICENSE_URL куда идти за лицензией. Видно только внутри отказа

MCP_AUTH_URL задан, а PUBLIC_URL или REQUIRED_SCOPE нет — сервер не поднимется. Оба уезжают в метаданные, по которым клиент строит запрос авторизации, и угаданное значение сломается при подключении, где ничто не укажет обратно сюда.

За фронтом с префиксом пути

nginx на raidmen раздаёт флот по /mcp-servers/<имя>/ и проксирует метаданные, срезая префикс. Поэтому документ отдаётся по двум путям сразу: /.well-known/oauth-protected-resource/<путь ресурса> для клиента и голый /.well-known/oauth-protected-resource — в него фронт превращает первый. Один документ, не копия: алиас переиспользует тот же обработчик.

PUBLIC_URL в этом случае содержит путь целиком: https://raidmen.ru/mcp-servers/zakupki.

Проверки

pytest