diff --git a/swarm-report/mcp-server-2026-10-24.md b/swarm-report/mcp-server-2026-10-24.md new file mode 100644 index 0000000..f4daada --- /dev/null +++ b/swarm-report/mcp-server-2026-10-24.md @@ -0,0 +1,61 @@ +# Feature: клиентский MCP-сервер для openrusrouter.ru +Дата: 2026-10-24 + +## Задача +Клиентский MCP-сервер (TypeScript/Node, npm-пакет, stdio) для быстрой настройки +интеграции и выбора модели прямо из Claude Desktop/Code/Cursor/OpenCode. + +## Research (консилиум) +- architect/api/security/devops. Ключевая находка security-агента: Bearer-ключ + сам по себе НЕ read-only (доступны платежи/промокоды/тикеты) — периметр + «только чтение» держится ИСКЛЮЧИТЕЛЬНО дисциплиной самого MCP-пакета + (узкий HTTP-клиент на 7 строго заданных GET-методов, без универсального + `fetch(url, method)`). +- Мутации API-ключей (создание/отзыв) физически недоступны Bearer-ключом — + серверный инвариант `cabinet.sessionOnly`, не наше решение, а факт из кода + бэкенда (`cabinet/middleware.go:118`). +- Решение владельца: список ВСЕХ ключей аккаунта показывать (поля обрезаны, + секрет никогда не возвращается). + +## Plan +Детальный план по файлам (Plan-агент), с уточнениями по факту: +`/api/keys` и `/v1/auth/key` оборачивают массив/объект в `{keys:[...]}`/`{data:{...}}` +(не так, как в OpenAPI-спеке бэкенда — расхождение зафиксировано в коде и README). + +## Реализация (3 волны, параллельные exec-агенты) +- Волна 1: репозиторий `pub/openrusrouter-mcp`, живые проверки 7 эндпоинтов + с фикстурами, package.json/tsconfig/eslint, утилиты (config/redact/errors/ + money/sanitize/cache) с тестами. +- Волна 2: `client.ts` (заморожен, ровно 7 методов, никакого произвольного + HTTP), `catalog.ts` (join `/api/models`+`/v1/models`), 6 MCP-инструментов + (list_models, pick_model, estimate_cost, get_balance, get_usage, + list_api_keys), entry point с перенаправлением console в stderr. +- Волна 3: README (быстрый старт для 4 клиентов, явный блок «чего тут нет и + почему»), CHANGELOG, CI (`.gitea/workflows/ci.yaml`: test на каждый push, + publish только по тегу `v*`, секрет `NPM_TOKEN` в `env:` шага). + +## Validation +- `npm run typecheck && npm test && npm run build && npm run lint && npm pack --dry-run` + — чисто, 64/64 теста. +- **Живой smoke-тест через настоящий MCP-протокол** (`StdioClientTransport`) + против `https://openrusrouter.ru` реальным ключом: + - `get_balance` — сходится с кабинетом. + - `list_models` (text, speech) — корректные данные, у speech `price_note` + без цифр. + - `pick_model` — работает. + - `estimate_cost` — верно отказывает для speech-модели и несуществующего + id, верно считает для текстовой модели (сверено вручную). + - `get_usage` — реальные данные за 7 дней, совпадают со вчерашними TTS- + тестами (elevenlabs, whisper). + - `list_api_keys` — показывает все ключи аккаунта, `is_current_key` верно + определяет используемый, секрет нигде не виден. + - stderr пуст, ключ нигде не утёк (`KEY_LEAK_IN_STDERR: false`). +- Побочно замечено: каталог TTS-моделей на проде изменился со вчера (Gemini/ + voxtral пропали из списка speech) — это действие администратора в кабинете, + не баг MCP-сервера; подтверждено, что `estimate_cost`/`list_models` честно + отражают актуальный каталог, а не кэш старых данных. + +## Статус +Done (код + живая проверка). НЕ запушено в удалённый репозиторий, НЕ опубликовано +в npm — оба действия требуют отдельного подтверждения (публикация — +необратимое публичное действие, нужен секрет `NPM_TOKEN`, которого у агента нет).