Auth0 MCP Server
by dsoziev1
README.md
# День 17 — Первый инструмент MCP: write-инструменты + отдельный инстанс сервера
Развитие дня 16. Локальный **MCP-сервер** вокруг **Auth0 Management API v2**
(инструменты по категориям доков <https://auth0.com/docs/api/management/v2>).
**Новое в дне 17:**
- **Сервер — ОТДЕЛЬНЫЙ инстанс** на локальном порту (HTTP); агент **подключается к
нему по URL**, а не поднимает сам (раньше — stdio-подпроцесс).
- **Write-инструменты**: помимо чтения (GET) добавлены инструменты, которые **меняют
конфигурацию** — `*_create` (POST), `*_update` (PATCH/PUT),
`auth0_roles_add_permissions` (назначение permissions).
- **Удаления НЕТ**: инструментов `delete` в каталоге не существует. Снести
конфигурацию нельзя — страховка на трёх уровнях: `assert` в каталоге,
`Auth0Client` запрещает метод DELETE, сервер `call_tool` отклоняет DELETE.
Сервер подключается и к **Claude Code** (`.mcp.json`), и к **агенту** (мост
`mcp_bridge` по URL).
## Что внутри
| Файл | Роль |
|------|------|
| `server.py` | **MCP-сервер** (stdio + `--http`). `call_tool`: GET — чтение, POST/PATCH/PUT — изменение; DELETE отклоняется. |
| `client.py` | MCP-клиент: список инструментов + живой вызов (`--call`, `--body` для write); транспорты stdio/`--http`. |
| `categories.py` | Каталог: 64 read + 34 write инструмента, **0 delete** (`assert`). **Источник списка.** |
| `auth0_client.py` | Транспорт к Auth0: M2M-токен + `request(method,…)` (GET/POST/PATCH/PUT, **DELETE запрещён**). |
| `config.py` | Учётные данные из `.env` или `auth0.settings`. |
| `mcp_tools.py` | Мост MCP → агент (async): stdio **или HTTP по URL**; tools в формате DeepSeek + вызов. |
| `mcp_bridge.py` | **Синхронная** обёртка (фоновый loop, чистый teardown). `SyncAuth0MCP(url=…)` — подключение к инстансу. |
| `.mcp.json` | Регистрация сервера в Claude Code. |
| `agent.py` / `web.py` / … | Агент (Auth0 FSM) с инструментами MCP; промпт различает read/write, запрещает выдумывать и «удалять». |
| `day17.py` | **CLI-демо дня**: агент подключается к отдельному инстансу и вызывает write-инструмент (создаёт роль). |
## Объём инструментов (День 17)
**37 категорий → 98 инструментов: 64 чтение (GET) + 34 изменение (POST/PATCH/PUT) +
0 удаление.** Имя: `auth0_<категория>_<операция>`.
Write-инструменты (создают/меняют конфигурацию, НЕ удаляют):
- `*_create` (POST): `auth0_clients_create`, `auth0_connections_create`,
`auth0_resource_servers_create`, `auth0_roles_create`, `auth0_organizations_create`,
`auth0_users_create`, `auth0_actions_create`, `auth0_client_grants_create`,
`auth0_log_streams_create`, `auth0_custom_domains_create`, `auth0_network_acls_create`,
`auth0_self_service_profiles_create`;
- `*_update` (PATCH): те же ресурсы + синглтоны `auth0_tenants_update`,
`auth0_branding_update`, `auth0_prompts_update`, `auth0_emails_update`,
`auth0_email_templates_update`, `auth0_attack_protection_update_*` (3);
- `auth0_roles_add_permissions` (POST — добавляет permissions),
`auth0_guardian_update_factor` (PUT — включает MFA-фактор).
Тело write-инструмента — JSON-объект `body` с полями Auth0 (`additionalProperties`,
ключевые поля подсказаны в описании инструмента).
## Запуск дня 17 (отдельный инстанс сервера + агент по порту)
```bash
# ТЕРМИНАЛ 1 — отдельный инстанс MCP-сервера на локальном порту:
python3 server.py --http # http://127.0.0.1:8765/mcp
# ТЕРМИНАЛ 2 — агент подключается к нему и вызывает write-инструмент (создаёт роль):
python3 day17.py # РЕЗУЛЬТАТ дня
```
`day17.py` идемпотентен: агент сначала проверяет роль `auth0_roles_list`, и если её
нет — создаёт `auth0_roles_create`, затем читает обратно и сообщает `id`.
Проверить write напрямую клиентом (без агента):
```bash
python3 client.py --http --call auth0_roles_create \
--body '{"name":"demo-role","description":"создано MCP write-инструментом"}'
python3 client.py --http --call auth0_roles_update --arg id=rol_XXX \
--body '{"description":"обновлено через PATCH"}'
```
Веб с подключением к отдельному инстансу (под ответом видно ✏️ write-вызовы,
в панели «🔌 Auth0 MCP» — счётчик 📖64 / ✏️34 / 🗑0):
```bash
AUTH0_MCP_URL=http://127.0.0.1:8765/mcp DEEPSEEK_DB=day17.db uvicorn web:app --reload
```
> ⚠️ Write-инструменты меняют РЕАЛЬНЫЙ тенант. Удаления нет — созданное останется;
> убирайте вручную в Auth0 Dashboard.
## Свободный режим (без этапов и/или без инвариантов)
Два независимых переключателя на сессию (в веб-UI — кнопки «🧭 Этапы» и
«⛔ Инварианты», хранятся в БД):
- **🧭 Этапы ВЫКЛ** — машина этапов/оркестратор отключены: запрос пользователя идёт
НАПРЯМУЮ агенту (обычный чат). MCP-инструменты Auth0 остаются доступны.
- **⛔ Инварианты ВЫКЛ** — блок инвариантов безопасности не подмешивается и
валидатор ответов не запускается.
Оба ВЫКЛ = «просто запросы от пользователя к агенту». Переключатели независимы:
можно оставить инварианты, но убрать этапы, и наоборот. По умолчанию оба ВКЛ.
API: `POST /session/{id}/stages/{on|off}`, `POST /session/{id}/invariants/{on|off}`.
В коде агента: `agent.set_stages(False)`, `agent.set_invariants(False)`.
## Установка
```bash
pip install -r requirements.txt # mcp, httpx, python-dotenv
cp .env.example .env # впишите данные Auth0 M2M-приложения
```
`.env` (или файл `auth0.settings` рядом с проектом):
```
AUTH0_DOMAIN=dev-xxxx.us.auth0.com
AUTH0_CLIENT_ID=...
AUTH0_CLIENT_SECRET=...
```
M2M-приложение в Auth0 должно быть **авторизовано для «Auth0 Management API»**
(audience `https://<domain>/api/v2/`) с нужными read-scope (`read:users`,
`read:clients`, …). Секреты в репозиторий не попадают (`.env`, `auth0.settings`
в `.gitignore`).
## Где работает сервер, где клиент, как видеть логи
Есть ДВА транспорта — выбирайте по задаче:
### 1. stdio (по умолчанию; так работают Claude Code и наш агент)
Сервер **не висит постоянно** — клиент (или Claude Code, или агент) запускает
`server.py` как **дочерний подпроцесс** и общается с ним через stdin/stdout. Логи
сервера идут в его **stderr**. Чтобы видеть их отдельно — перенаправьте в файл и
смотрите `tail -f` во втором терминале:
```bash
# терминал 1 — «логи сервера» (вызовы инструментов + запросы к Auth0):
touch mcp_server.log && tail -f mcp_server.log
# терминал 2 — клиент; его собственные логи показывает флаг -v:
AUTH0_MCP_LOG=mcp_server.log python3 client.py -v --call auth0_clients_list --arg per_page=2
```
`[client]` — что делает клиент (initialize → list_tools → call_tool);
`[mcp]` / `[auth0]` — что делает сервер (◀ вызов инструмента → POST /oauth/token →
GET …/clients → HTTP 200 → ▶ результат).
### 2. HTTP (сервер — ОТДЕЛЬНЫЙ постоянный процесс в своём терминале)
Самый наглядный режим: сервер и клиент — два независимых процесса.
```bash
# ТЕРМИНАЛ 1 — СЕРВЕР (логи API видно прямо в консоли):
python3 server.py --http # слушает http://127.0.0.1:8765/mcp
# ТЕРМИНАЛ 2 — КЛИЕНТ (подключается к запущенному серверу):
python3 client.py --http -v --call auth0_clients_list --arg per_page=2
```
В терминале 1 на каждый вызов видно цепочку:
```
◀ ВЫЗОВ ИНСТРУМЕНТА auth0_clients_list args={'per_page': 2}
→ POST /oauth/token (client_credentials, audience=…/api/v2/)
← токен получен, действует ~86400s
→ GET …/api/v2/clients [per_page=2]
← HTTP 200 (136 байт) — список из 2 элементов
▶ РЕЗУЛЬТАТ auth0_clients_list: 179 символов JSON отдано клиенту
```
> Claude Code и агент (`mcp_bridge`) используют именно stdio — это стандарт для
> локальных MCP. HTTP-режим удобен, чтобы «вживую» наблюдать, как клиент дёргает
> сервер, а сервер — Auth0 API.
## Проверка (РЕЗУЛЬТАТ дня)
**1. Соединение + список инструментов** (учётные данные не требуются — сервер не
берёт токен, чтобы перечислить инструменты):
```bash
python3 client.py
```
Ожидается: `✅ MCP-соединение установлено` и `🧰 Инструментов получено: 64`,
сгруппированных по категориям.
**2. Живой вызов инструмента** (нужны учётные данные Auth0):
```bash
python3 client.py --call auth0_clients_list --arg per_page=2 --arg fields=name,app_type
python3 client.py --call auth0_tenants_settings
```
**3. Каталог без MCP/сети** (быстрый офлайн-просмотр):
```bash
python3 categories.py
```
**4. Что получит агент** (инструменты в формате DeepSeek tools):
```bash
python3 mcp_tools.py
```
## Подключение к Claude Code
Сервер зарегистрирован в `.mcp.json`. Claude Code обнаруживает его автоматически и
запрашивает подтверждение (`⏸ Pending approval`). Проверка/одобрение:
```bash
claude mcp list # покажет: auth0: … (статус)
```
После одобрения инструменты `auth0_*` доступны прямо в Claude Code.
## Подключение к агенту (стиль дня 15) — ВСТРОЕНО
Агент дня 15 (Auth0 FSM, оркестратор + команда под-агентов) теперь **сам пользуется
инструментами Auth0 через MCP**. Точка интеграции — единственный вызов модели в
`Agent._run_stage`: добавлен tool-calling цикл (`Agent._chat_stage`). Пока модель
просит вызвать инструмент — агент исполняет его через MCP-мост, кладёт результат в
диалог и спрашивает снова (до 4 раз), затем выдаёт финальный ответ по фактам.
- `mcp_bridge.SyncAuth0MCP` — синхронный мост (фоновый event loop + одна MCP-сессия),
чтобы синхронный агент мог звать async MCP без переписывания на async.
- `llm_client.chat(..., tools=...)` — добавлена поддержка function-calling: модель
может вернуть `tool_calls`, клиент возвращает их в `ChatResult`.
- Инструменты передаются только на не-training этапах; они **read-only**, поэтому
безопасны и согласуются с инвариантами дня 14.
Запуск с агентом:
```bash
python3 day16.py # CLI: агент отвечает по реальному тенанту через MCP
DEEPSEEK_DB=day16.db uvicorn web:app --reload # веб: под ответом видно 🔧 вызовы Auth0 MCP
```
В вебе агент создаётся с общим мостом (`web._mcp`); если учётных данных Auth0 нет —
мост не поднимается, и агент работает как раньше, без инструментов.
Низкоуровневый async-доступ (тот же сервер, для своих сценариев):
```python
async with Auth0MCP() as mcp:
tools = await mcp.deepseek_tools() # спецификации tools для модели
text = await mcp.call(name, arguments) # выполнить tool_call от модели
```
## Архитектура (слои)
```
client.py / Claude Code / агент ──MCP(stdio)──▶ server.py
│ categories.py (каталог)
▼
auth0_client.py ──HTTPS──▶ Auth0 Management API v2
▲
config.py (.env / auth0.settings)
```
`server.py` ничего не знает про конкретные эндпоинты — он разворачивает каталог
`categories.py` в инструменты и проксирует GET через `auth0_client.py`. Чтобы
добавить категорию/операцию — правим только `categories.py`.
```
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues