Skip to main content
Glama
dsoziev1

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`.
```