Skip to main content
Glama
smolnikov-k

CapyAgent MAX MCP

by smolnikov-k
README.md
# CapyAgent MAX MCP

Подключает личный аккаунт мессенджера MAX к CapyAgent, Codex, Cursor и любым другим MCP-клиентам. Первая версия, read-first: список чатов, история, поиск по чату, сводка непрочитанного, контакты. Запись (отправка, ответ, отметка прочитанным, удаление) есть, но выключена по умолчанию.

Сделано по образцу [capy-tg-mcp](https://github.com/smolnikov-k/capy-tg-mcp) — те же принципы (read-only по умолчанию, allowlist чатов, один процесс на сессию), другой транспорт.

## Главное про безопасность и риски

MAX не даёт официального API для чтения личных чатов пользователя (см. ниже, «Почему не Bot API»). Этот сервер идёт неофициальным путём — библиотекой [MaxApiTeam/PyMax](https://github.com/MaxApiTeam/PyMax) (`maxapi-python`), которая реализует реверс-инжиниренный протокол MAX. Это означает:

- **Юридически** — прямое нарушение пользовательского соглашения MAX ([legal.max.ru/ps](https://legal.max.ru/ps)): п. 4.3.7 запрещает автоматизированные скрипты без разрешения компании, п. 4.3.10 требует пользоваться только официальным интерфейсом, п. 10.1.2.2 разрешает блокировку без объяснений за любое разовое нарушение.
- **Технически** — протокол не документирован официально, может измениться без предупреждения. PyMax поддерживается активно (пуши в день, когда собирался этот сервер), но гарантий нет.
- Поэтому `maxapi-python` — **не обязательная зависимость**, а optional extra (`pyproject.toml`, `[project.optional-dependencies].transport`). Базовая установка (`uv sync`) ставит сервер и тесты без него; тесты гоняются на фейковом транспорте, без сети и без реального аккаунта. Подключаться к реальному MAX — осознанный отдельный шаг: `uv sync --extra transport`.

По умолчанию сервер отдаёт агенту только инструменты чтения (`MAX_EXPOSED_TOOLS=read-only`). Запись включается явно:

- `read-only+send_message,reply_to_message` — чтение плюс перечисленные инструменты;
- `all` — всё, включая удаление сообщений.

Дополнительно можно ограничить чаты: `MAX_ALLOWED_CHAT_IDS=123456,987654`.

Один файл сессии нельзя открывать двумя процессами одновременно (см. «Один процесс на сессию» ниже).

## Установка

```bash
git clone <repo> capy-max-mcp && cd capy-max-mcp
uv sync                        # база: сервер + тесты, без реального транспорта
uv sync --extra transport      # + maxapi-python, когда готовы подключаться к реальному MAX
cp .env.example .env
uv run capy-max-mcp-login      # вход по QR (или --token, см. «Вход» ниже)
```

## Вход

1. **QR (по умолчанию).** `uv run capy-max-mcp-login` откроет ASCII QR-код в терминале (PyMax сам рисует его и ждёт сканирования — та же логика, что при обычном входе на web.max.ru). Отсканировать в приложении MAX. После подтверждения PyMax сохраняет сессию в SQLite-файл (`MAX_WORK_DIR/MAX_SESSION_NAME`, по умолчанию `./max_session.db`) — это и есть аналог `TELEGRAM_SESSION_STRING` у capy-tg-mcp, только файл, а не переносимая строка (так устроен PyMax, не наше решение).

2. **Токен вручную (запасной путь).** Если QR не проходит: в браузере, уже залогиненном на web.max.ru, открыть консоль разработчика и выполнить
   ```js
   copy(JSON.parse(localStorage.__oneme_auth).token)
   ```
   и передать результат как `uv run capy-max-mcp-login --token <вставленный токен>` (или положить в `.env` как `MAX_LOGIN_TOKEN` перед первым запуском). PyMax поддерживает вход по готовому токену из коробки (`ExtraConfig(token=...)`) — этот сервер просто передаёт его через.

3. Дальше `capy-max-mcp` переиспользует файл сессии без повторного входа, пока MAX его не отозвал.

4. Если сервер начал отвечать «сессия истекла» — повторить шаги 1-2, как переавторизация Телеграма при «новом устройстве». Важно: по данным статьи на Хабре про этот же токен, `logout` в одном месте гасит сессию везде — не входить в тот же аккаунт вторым процессом одновременно (см. ниже).

## Один процесс на сессию

Как и у Телеграма, два процесса не должны одновременно держать одну и ту же сессию — это может её сломать. `capy-max-mcp` берёт файловую блокировку (`max_mcp/singleton.py`, аналог `telegram_mcp/singleton.py`) на файл сессии перед подключением: второй процесс с той же сессией отказывается стартовать вместо того, чтобы рисковать обеими.

## Инструменты первой версии

Read-only (включены по умолчанию):

| Инструмент | Что делает |
|---|---|
| `list_chats` | Список чатов, опционально только с непрочитанным (`unread_only`) |
| `get_chat_info` | Название, тип, число участников и непрочитанных одного чата |
| `get_messages` | История сообщений одного чата, свежие сверху |
| `search_messages` | Поиск подстроки в истории чата — **не серверный поиск**: у MAX/PyMax нет вызова полнотекстового поиска (проверено по исходникам PyMax 2.4.1), поэтому тул листает последние `scan_limit` сообщений и фильтрует на своей стороне. Для старой переписки может не найти совпадение за пределами `scan_limit` |
| `summarize_unread` | Сводка чатов с непрочитанным: id, название, счётчик, превью последнего сообщения. Строго read-only — никогда не вызывает пометку прочитанным как побочный эффект (тот же принцип, что у `export_unread_messages` в capy-tg-mcp) |
| `get_contacts` | Список контактов, опционально фильтр по имени |

Write (выключены по умолчанию, включаются через `MAX_EXPOSED_TOOLS`):

| Инструмент | Что делает |
|---|---|
| `send_message` | Отправить сообщение в чат |
| `reply_to_message` | Ответить на конкретное сообщение |
| `mark_as_read` | Отметить прочитанным до сообщения — сделано write-тулом (не read-only), потому что меняет видимый другой стороне статус на сервере, как и `mark_as_read` в capy-tg-mcp |
| `delete_message` | Удалить сообщение (`for_me` — только у себя или для всех, где MAX это позволяет) |

Реакции, пересылка, медиа, группы — не в этой версии; при необходимости добавляются по тому же паттерну (`max_mcp/transport.py` → `pymax_transport.py` → `tools/`).

## Почему не официальный Bot API

Бот видит только диалоги, куда его добавили, или которые сами написали ему первыми — историю личных чатов владельца ему не отдают. Личные 1-на-1 диалоги в MAX вообще не входят в список чатов бота (это только групповые), адресуются по `user_id` собеседника, а не `chat_id`. «Прочитать мои существующие переписки, сводка непрочитанного по всем чатам» ботом не сделать в принципе — нужен пользовательский протокол, отсюда PyMax вместо Bot API.

## Что не проверено на живом аккаунте

Этот сервер собран без доступа к реальному аккаунту MAX (задача явно это исключала). Проверено по исходникам PyMax 2.4.1 (скачан с PyPI, прочитан файл за файлом: `client_web.py`, `base.py`, `infra/{chat,message,user}.py`, `types/domain/{chat,message,user,profile}.py`) — то есть названия методов и полей реальные, не выдуманные. НЕ проверено вживую:

- Сам вход по QR и по токену — что PyMax при реальном сканировании ведёт себя так, как обещает его код и докстринги.
- Формат и текст реальных ошибок сервера MAX (блокировка, невалидный токен, флуд-контроль) — `pymax_transport._classify_api_error` раскладывает `ApiError` на «сессия истекла» / «заблокировано» по ключевым словам в тексте ошибки, подобранным по чтению кода, не по наблюдению за настоящим ответом сервера. После первой реальной ошибки этого рода стоит свериться и поправить списки `_BLOCKED_KEYWORDS`/`_SESSION_KEYWORDS` в `max_mcp/pymax_transport.py`.
- Реальные значения полей `Chat`/`Message`/`User` на боевых данных (пустые ли, в каких единицах время и т.п.) — типы взяты из pydantic-моделей PyMax, но не сверены с живым ответом.
- Поведение `read_message` (аналог `mark_as_read`) — PyMax предупреждает, что `WebClient` ждёт `message_id` как строку, а `Client` (TCP) как `int`; этот сервер использует только `WebClient`, но сам факт не проверялся вызовом.
- Устойчивость сессии при реальном простое/реконнекте — файловая блокировка (`singleton.py`) протестирована юнит-тестами, но не под реальным MAX-соединением.

**Что нужно сделать владельцу перед боевым использованием:** поставить `uv sync --extra transport`, прогнать `uv run capy-max-mcp-login` на своём аккаунте, руками вызвать `list_chats`/`get_messages` через MCP-клиент и убедиться, что содержимое соответствует реальным чатам, прежде чем включать запись (`MAX_EXPOSED_TOOLS`).

## Тесты

```bash
uv sync --group dev && uv run pytest -q
```

Юнит-тесты работают на `tests/fakes.py::FakeTransport` — сеть и `maxapi-python` им не нужны. Тест `test_errors.py::test_classify_api_error_requires_pymax_installed` пропускается, если `maxapi-python` не установлен (`pytest.importorskip`).

## Подключение к MCP-клиенту

Сервер работает по stdio:

```bash
uv --directory /path/to/capy-max-mcp run main.py
```

Переменные `MAX_WORK_DIR`, `MAX_SESSION_NAME` (или `MAX_LOGIN_TOKEN` для разового входа по токену) передаются в окружении процесса — см. `.env.example`.