Skip to main content
Glama
README.md
# MCP Idle Game

Идл-игра, в которой играет LLM-агент. Агент подключается по MCP как клиент,
ставит активное действие, добывает ресурсы, крафтит, продаёт, качает скиллы,
бьёт монстров, выполняет квесты и делает престиж. Наблюдатель смотрит на ту же
игру через дебаг-страницу.

Стек: Node.js 24, TypeScript (ESM, strict), чистая архитектура, `node:sqlite`,
`@modelcontextprotocol/server`, `zod`, `neverthrow`, Vite (HMR дебаг-страницы),
Vitest, ESLint, pnpm.

## Быстрый старт

```sh
pnpm install
pnpm build
pnpm start
```

Сервер поднимается на `http://127.0.0.1:3000` и создаёт дефолтного игрока. В
режиме разработки авторизация выключена (`MCPG_AUTH=off`), ключ не нужен.
Дальше:

- `http://127.0.0.1:3000/mcp` — точка входа MCP (Streamable HTTP);
- `http://127.0.0.1:3000/` — дебаг-страница с состоянием игры.

Для разработки: `pnpm dev` — два процесса рядом: API-сервер на `3000`
(`node --watch`, TS напрямую через strip-types) и Vite на `5173` с HMR.
Дебаг-страница открывается на `http://127.0.0.1:5173/`, запросы `/api` и `/mcp`
Vite проксирует на API-сервер. По отдельности: `pnpm dev:api` и `pnpm dev:web`.

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

Локально (авторизация выключена) ключ не нужен:

```json
{
  "mcpServers": {
    "idle-game": {
      "type": "http",
      "url": "http://127.0.0.1:3000/mcp"
    }
  }
}
```

С ключом (`MCPG_AUTH=keys`) он передаётся заголовком
`Authorization: Bearer <key>`. Фолбэк — заголовок `X-API-Key`. Параметр `?key=`
работает только в dev-режиме.

```json
{
  "mcpServers": {
    "idle-game": {
      "type": "http",
      "url": "http://127.0.0.1:3000/mcp",
      "headers": {
        "Authorization": "Bearer mcp_live_..."
      }
    }
  }
}
```

Клиент видит инструменты: `game_state`, `game_action`, `game_craft`,
`game_upgrades`, `game_trade`, `game_use`, `game_bank`, `game_combat`,
`game_quests`, `game_market`, `game_skills`, `game_prestige`, `game_log`,
`game_tick`, плюс ресурсы `game://state`, `game://skills`, `game://market`,
`game://quests` и промпт `game_strategy`.

При `MCPG_AUTH=keys` запрос без ключа отклоняется с 401, с неверным или
отозванным ключом — с 403. При `MCPG_AUTH=off` ключ не нужен: сервер работает с
дефолтным игроком, а переданный валидный ключ всё равно выбирает своего игрока.

## Ключи

```sh
pnpm run keys -- create --label "ноутбук"   # печатает ключ один раз
pnpm run keys -- list                       # игроки и их ключи
pnpm run keys -- revoke --id <key id>       # отзыв ключа
```

Ключи нужны только при `MCPG_AUTH=keys`. В базе лежит только SHA-256 хэш ключа и
его префикс `mcp_live_xxxx`. Ключ с scope `read` может только читать состояние,
с `play` — ещё и менять игру.

## Дебаг-страница

`/` отдаёт статику дебаг-страницы: кошелёк, активное действие с прогрессом,
скиллы, инвентарь, апгрейды с ценами и кнопкой «Купить», рынок, квесты, лента
событий. Данные приходят из `GET /api/state`, действия — из `POST /api/action`.
Страница обновляется раз в две секунды и показывает, чей это игрок. Ключ с
правами `read` получает страницу без кнопок мутаций. При `MCPG_AUTH=off` поле
ключа скрыто, а рядом горит метка «auth off».

## Баланс и симулятор

Числа игры лежат в `content/`, а не в коде. Симулятор прогоняет прогрессию через
чистый тик и считает метрики: время до престижа, доход по этапам, долю простоя,
окупаемость апгрейдов, узкие места.

```sh
pnpm run sim -- 24                     # прогон на 24 часа, печатает метрики
pnpm run autotune                      # подбор кривых, пишет content/balance.json
```

Критерии баланса: апгрейд окупается за 3–15 минут, престиж доступен через 2–6
часов, простой не занимает больше 60% времени.

## Структура

```sh
src/
  domain/            чистое ядро: состояние, тик, действия по видам
                     (start-gather/craft/fight, switch-zone), зоны, бои,
                     торговля, банк, квесты, престиж
  application/       сценарии и порты; use-cases разрезаны по фичам вокруг
                     ядра (core.run, actions, state, combat, trade, bank,
                     quests, upgrades, items, prestige, players)
  infrastructure/    node:sqlite, контент и zod-схемы, MCP (tools/ по фичам,
                     views, texts), HTTP, симулятор и автотюн (sim/)
  bootstrap/         composition root, CLI ключей, симулятор, автотюн
content/             данные игры: скиллы, предметы, рецепты, апгрейды, зоны, квесты
web/                 дебаг-страница: index.html, debug.js, style.css (корень Vite)
tests/               vitest: домен, use cases, инфраструктура, интеграция, e2e
```

Домен и application не знают про MCP, HTTP, SQLite и таймеры. `tick(state, now)`
— чистая функция: вся мутабельность возвращается новым состоянием. Время и
случайность идут через порты, тик детерминирован по seed из состояния.

Направление зависимостей и раскладку слоёв проверяет
`tests/architecture.test.ts`: домен ничего не импортирует извне себя,
application не знает про infrastructure, инструменты баланса живут в
`infrastructure/sim`, а не в домене.

## Переменные окружения

| Переменная         | По умолчанию  | Назначение                            |
| ------------------ | ------------- | ------------------------------------- |
| `MCPG_PORT`        | `3000`        | порт HTTP-сервера                     |
| `MCPG_HOST`        | `127.0.0.1`   | адрес привязки                        |
| `MCPG_DATA_DIR`    | `./data`      | каталог файла базы                    |
| `MCPG_CONTENT_DIR` | `./content`   | каталог данных игры                   |
| `MCPG_PUBLIC_DIR`  | `dist/web`    | каталог статики: сборка или `web/`    |
| `MCPG_ENV`         | `development` | `production` отключает `?key=`        |
| `MCPG_AUTH`        | по окружению  | `off` — без ключей, `keys` — по ключу |

## Проверки

```sh
pnpm test        # vitest, без сети и без внешней БД
pnpm run lint    # eslint
pnpm run typecheck
pnpm run build
```

## Лицензия и атрибуция

Код распространяется по лицензии MIT (файл `LICENSE`).

Структура слоёв прогрессии — референс Antimatter Dimensions (MIT); код и тексты
не копируются. Числовые ориентиры кривых — «по мотивам Melvor Idle» (порядок
величин, не копия таблиц).

Как устроено — `docs/DESIGN.md`, план оставшихся работ — `docs/PLAN.md`.