packmate/docs/MCP.md
2026-10-23 11:42:44 +03:00

256 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MCP для Packmate
MCP работает отдельным контейнером и предоставляет **22 инструмента с единым полным доступом**.
Любой действительный токен может использовать каждый из них, включая создание и удаление сервисов,
паттерны IGNORE, избранное и Lookback. Ролей analyst/operator/admin нет.
## Запуск
В `.env` включите MCP и задайте случайный токен длиной не менее 32 ASCII-символов без пробелов:
```dotenv
PACKMATE_MCP_ENABLED=true
PACKMATE_MCP_TOKEN=<случайный-токен>
PACKMATE_MCP_BIND=127.0.0.1
PACKMATE_MCP_PORT=65002
PACKMATE_MCP_ALLOWED_HOSTS=127.0.0.1:*,localhost:*,[::1]:*
PACKMATE_MCP_JOB_TIMEOUT_SECONDS=30
PACKMATE_MCP_JOB_MAX_MIB=32
PACKMATE_PUBLIC_URL=http://your-packmate-host:65000
```
Сгенерировать токен: `python3 -c 'import secrets; print(secrets.token_hex(32))'`.
Затем выполните обычную команду:
```bash
docker compose up --build -d
```
Endpoint: `http://127.0.0.1:65002/mcp`, транспорт **Streamable HTTP**.
Клиент должен передавать `Authorization: Bearer <токен>` в каждом запросе.
Токен задаётся в настройках MCP-клиента, а не в сообщениях агенту. Поле headers/переменная окружения
для него зависит от конкретного клиента. Клиент без возможности передавать Bearer header не поддерживается.
Встроенного OAuth-сервера нет.
Для подключения с другой машины можно использовать SSH-туннель:
```bash
ssh -N -L 65002:127.0.0.1:65002 user@your-packmate-host
```
Для прямого подключения через HTTPS reverse proxy настройте bind/allowed hosts под его адрес.
Не ставьте произвольный `*` в allowed hosts. Browser Origin-запросы к MCP не разрешены;
обычный серверный MCP-клиент работает без Origin. LIVE-захват рассчитан на Linux Docker Engine
с host network; Docker Desktop видит сеть своей VM, а не все интерфейсы Windows.
По умолчанию MCP отключён: `/healthz` отвечает состоянием, остальные запросы получают 503.
Отключение MCP не отключает Packmate. Активный MCP без корректного токена отказывается запускаться.
## Сервисы и паттерны видны людям
Создавайте сервисы до начала анализа трафика: Packmate сохраняет стримы только известных сервисов.
Все изменения MCP проходят через обычные backend-сервисы с их WebSocket-уведомлениями.
Созданный агентом сервис появляется в навбаре, паттерн — в меню Patterns.
FIND подсвечивает совпадения в новых пакетах; для истории используйте `start_lookback`.
IGNORE означает удаление/несохранение будущих совпавших стримов, а не только скрытие в UI.
Пример аргументов `create_service`:
```json
{
"port": 8080,
"settings": {
"name": "Shop",
"http": true,
"urldecode_http_requests": true,
"merge_adjacent_packets": true,
"parse_web_sockets": false,
"decrypt_tls": false
}
}
```
Пример аргументов `create_pattern`:
```json
{
"settings": {
"name": "Outgoing flags",
"match": {"target": "content", "type": "regex", "value": "FLAG\\{[A-Za-z0-9_]+\\}"},
"action": "FIND",
"direction": "OUTPUT",
"service_port": 8080,
"color": "#ff9800"
}
}
```
Для IP: `target` равен `source_ip`, `destination_ip` или `either_ip`, `type` — `regex`.
Паттерны создаются включёнными. При повторе идентичного запроса возвращается существующий
неудалённый объект; выключенный существующий паттерн автоматически не включается.
Это проверка текущих данных перед созданием, не гарантия exactly-once при одновременной записи
из нескольких независимых приложений. После ошибки сети проверьте список объектов.
`update_pattern` меняет имя и цвет. Для изменения выражения/направления/действия создайте новый
паттерн и отключите старый: так исторические совпадения не получают новое значение задним числом.
`update_service` принимает полную конфигурацию декодирования, а не частичный patch.
## Инструменты
| Инструменты | Назначение |
| --- | --- |
| `get_overview` | Захват, потери, очередь, настройки retention, сервисы, лимиты анализа |
| `search_traffic` | Самостоятельный поиск по сохранённым сообщениям без создания паттерна |
| `aggregate_traffic` | Число совпавших сообщений по сервисам, адресам или минутам |
| `get_stream`, `read_packet` | Метаданные стрима, страницы сообщений и фрагменты payload |
| `set_favorites` | Защита находок от retention, с подтверждением доступных ID |
| `list_services`, `create_service`, `update_service`, `delete_service` | Общие с UI сервисы |
| `list_patterns`, `validate_pattern`, `create_pattern`, `update_pattern`, `set_pattern_enabled`, `delete_pattern` | Общие с UI паттерны |
| `start_lookback` | Сохранение исторических совпадений и обновление UI |
| `get_job`, `get_job_results`, `cancel_job`, `delete_job` | Прогресс, результаты и управление задачами |
| `start_pcap_import` | Запуск уже настроенного файла в режиме FILE |
В этой версии нет изменения retention на лету, остановки/перезапуска LIVE capture, загрузки
новых PCAP/TLS-ключей, экспорта исходного PCAP и журнала событий с долговечным курсором.
Это следующие этапы API; ограничений по ролям на существующие инструменты нет.
## Поиск и задачи
```json
{
"query": {
"last_minutes": 5,
"service_ports": [8080],
"direction": "outgoing",
"client_ip": {"type": "cidr", "value": "10.60.0.0/16"},
"match": {"target": "content", "type": "regex", "value": "FLAG\\{[^}]+\\}"}
},
"limit": 100,
"context_bytes": 160
}
```
`search_traffic`, `aggregate_traffic` и `start_lookback` сразу возвращают `job_id`.
Вызывайте `get_job` примерно раз в секунду, затем `get_job_results` с `offset` и `limit`.
Результаты содержат ссылки на нужный стрим в браузере. Одна выдача — максимум 50 результатов.
Время фильтрует **начало сохранённого стрима**. `last_minutes` фиксируется при создании задачи;
эффективные `from_ms`, `to_ms`, `snapshot_ms` возвращаются в `query`.
Все абсолютные времена — Unix milliseconds UTC. Окно — максимум 24 часа.
`source_ip`/`destination_ip` относятся к сообщению, `client_ip` вычисляется с учётом incoming.
Для адресов доступны exact/CIDR/regex. `pattern_id` отбирает стримы с уже сохранённым паттерном.
Поиск выполняется по сохранённому преобразованному содержимому. Оно может быть распаковано,
декодировано или склеено настройками сервиса. Между отдельными сохранёнными сообщениями regex
не переносится. Текст — UTF-8 с заменой некорректных последовательностей, точные байты — hex/base64.
Конец диапазона в MCP — exclusive; текстовые смещения измеряются Unicode codepoints,
бинарные — байтами. В UI остаются его прежние позиции совпадений.
Проверяйте `coverage_complete`, `scan_exhausted`, `reason`, `skipped_oversize_packets` и `unavailable_packets`.
Завершившаяся задача с `result_limit` или `byte_budget` проверила только часть диапазона.
Контент сообщений больше 64 KiB не обрезается для поиска, а пропускается с отдельным счётчиком:
иначе regex с `$` мог бы дать ложное совпадение на конце обрезанного фрагмента.
Большой payload можно читать последовательно через `read_packet`.
Для продолжения частичного поиска передайте эффективный `query` предыдущей задачи и замените
его `cursor` на `next_scan_cursor`. Порядок: `(stream_timestamp, stream_id, packet_id)`.
Это read-committed чтение живой БД, а не замороженный snapshot. Cleaner может удалять стримы
между страницами; незакоммиченные или ещё собираемые соединения не входят в результаты.
`coverage_complete` означает отсутствие известных пропусков в просмотренной доступной выборке,
а не доказательство наличия всей истории сетевого трафика.
Lookback работает только для FIND; применяет сервис и направление самого паттерна.
Совпадения записываются порциями до 16 сообщений. Повторное применение не дублирует найденные
позиции. MCP Lookback сохраняет до 256 совпадений на сообщение, в том числе для старых literal-паттернов.
Уже начатая очистка, занятая блокировка, слишком большой payload или более 2048 старых
совпадений на сообщении дают `unavailable_packets`. Отмена останавливает дальнейшую работу;
выполненные записи сохраняются, а текущая транзакция могла успеть закоммититься.
Если ответ на запись Lookback не получен из-за отмены, таймаута или ошибки, курсор остаётся перед
неподтверждённой страницей, а сообщения учитываются в `unavailable_packets`. Продолжение с этим
курсором повторит страницу без дубликатов, вместо пропуска потенциально незаписанных подсветок.
Одновременно доступны две задачи, хранится максимум 32 задачи, TTL результатов — 15 минут
после завершения. Для освобождения места используйте `delete_job`. Задачи и результаты хранятся
в памяти MCP и теряются при его перезапуске; созданные сервисы/паттерны и Lookback-совпадения остаются в БД.
## Ограничение нагрузки
MCP имеет отдельный контейнер (1 CPU, 512 MiB), ограниченные кеши regex и две задачи анализа.
Backend читает через отдельный пул из двух read-only соединений с SQL timeout 2 секунды.
Страницы содержат максимум 32 сообщения и 64 KiB на сообщение. В MCP используется 16 сообщений
на запрос, между страницами есть пауза. При занятых слотах возвращается BUSY.
Бюджет задачи по умолчанию 30 секунд / 32 MiB прочитанного payload с оценкой метаданных.
Диск и PostgreSQL общие с захватом, поэтому физическая изоляция нагрузки не абсолютная.
Новые MCP regex-паттерны используют RE2J при обработке трафика и RE2 при разовом поиске.
Backreferences/lookaround не поддерживаются, неподдерживаемый синтаксис отклоняется.
`validate_pattern` проверяет regex в обоих движках, включая ограничения RE2J, до сохранения.
Для новых паттернов сохраняется до 256 совпадений на сообщение. Старые Java regex сохраняют
прежний движок; MCP Lookback для них требует создания нового RE2J-паттерна.
## Проверка
Unit checks:
```bash
docker build -t packmate-mcp:dev mcp
docker run --rm -v "$PWD/mcp:/app:ro" packmate-mcp:dev python -m unittest discover -s tests -p 'test_*.py' -v
./gradlew test --tests '*SafePatternMatcherTest' --tests '*PatternMatcherConcurrencyTest'
```
Интеграционный тест создаёт отдельную сеть и отдельную БД. Команды из корня репозитория:
```bash
./gradlew bootJar
docker build -t registry.gitlab.com/packmate/packmate:latest -f docker/Dockerfile_app .
docker build -t packmate-mcp:dev mcp
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml build target
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml up --no-build -d db target packmate mcp
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml run --rm --no-deps tester
docker compose -p packmate-mcp-test -f stress/network/docker-compose.yml -f mcp/tests/compose.yml down -v
```
Тест вызывает настоящий MCP Streamable HTTP, проверяет уведомления браузера, живые совпадения,
IP regex, Lookback и повторное применение. Затем выполняет 3000 TCP-соединений одновременно
с regex-задачами и удалением старой выборки из 200 стримов. Проверяет сохранность новых стримов,
избранного, отсутствие drops и orphan packets, REST p99 < 500 мс.
Отчёт: `build/reports/mcp/integration.json`. Это целевой тест MCP; большой профиль на 250000
соединений из `docs/CLEANUP.md` он не заменяет.
Увеличенный профиль (`PowerShell`, из корня, после сборки образов и `bootJar` выше):
```powershell
docker compose -p packmate-mcp-capacity -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/capacity.yml build target
./mcp/tests/run-capacity.ps1
```
Профиль проверяет 2000 исходных и 50000 новых TCP-соединений с минимальной длительностью передачи 120 секунд, по 8 сообщений
по 1024 байта в каждом направлении. Сверяются хеши полного содержимого **каждого** соединения,
IP, метаданные, live-паттерны и избранное. Одновременно работают cleaner и MCP, а MCP-контейнер
перезапускается во время нагрузки. Дополнительно проверяются BUSY, отмена, бюджеты, курсор,
96 KiB binary payload и повторный Lookback старого literal-паттерна с лимитом подсветок.
Тест намеренно использует бюджеты MCP 10 секунд / 2 MiB, чтобы проверить оба ограничения.
Захват использует 8 workers; это параметр теста, а не обещание такой скорости с любыми настройками.
`MCP_STRESS_STREAMS` и `MCP_STRESS_SECONDS` меняют нагрузку. Отчёт: `build/reports/mcp/capacity.json`.
Скрипт удаляет только свой disposable Compose-проект; `-KeepContainers` оставляет его для диагностики.
Длительный прогон на 35 минут, с обычным retention 15 минут и запуском cleaner раз в 10 секунд:
```bash
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml build target
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml up --no-build -d db target packmate mcp
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml run --rm --no-deps tester
docker compose -p packmate-mcp-soak -f stress/network/docker-compose.yml -f mcp/tests/compose.yml -f mcp/tests/soak.yml down -v
```
Не запускайте эти сетевые профили одновременно: они используют один тестовый subnet.
Soak создаёт пачки по 500 соединений за 10 секунд, сверяет все payload до истечения retention,
сохраняет один стрим каждой пачки в избранное и непрерывно выполняет MCP regex-поиск.
Время записей в БД не подменяется: проверяется естественное удаление старого трафика.
Отчёты: `soak.json`, `soak-samples.json`, `soak-loads.json`; текущий прогресс — `soak-progress.json`
в `build/reports/mcp`. `MCP_SOAK_SECONDS` позволяет увеличить длительность.
Фактические параметры и результаты прогона: [MCP_TEST_RESULTS.md](MCP_TEST_RESULTS.md).
`mcp/tests/deployment.py` дополнительно проверяет настоящий SDK, валидацию обоих regex-движков
и изменения общих сервисов/паттернов внутри отдельного production Compose в режиме VIEW.