Skip to main content
Glama
README.md
# scibot-mcp

MCP-сервер (stdio), дающий MCP-клиенту программный доступ к
[sci-bot.ru](https://sci-bot.ru): постановка научного вопроса в очередь,
ожидание генерации, чтение готового ответа с библиографией и управление
собственной учётной записью.

Написан по методологии SDD: единственный источник истины — спецификация
[`spec/spec.md`](spec/spec.md). Каждая нормативная запись связана с
исполняемыми тестами через маркеры `@covers`, а изменение поведения
проходит через запись `Delta`.

## Дисклеймер

У sci-bot.ru нет публичного API и нет разрешения на автоматизированный
доступ. Все контракты в этом проекте получены реверс-инжинирингом
фронтенда и живыми пробами, зафиксированы как `ASSUMPTION` в спецификации и
проверены контрактными тестами. Сервис может изменить протокол в любой
момент и без предупреждения.

Из этого следуют два правила, зашитые в код и проверяемые тестами:

- Сервер представляется честно: `User-Agent` по умолчанию
  `scibot-mcp/<version>`, подстановка строки браузера запрещена
  (`scibot:CST-002`). Владелец сервиса видит автоматизированный трафик и
  может его ограничить.
- Сервер не выполняет регистрацию, смену пароля, миграцию имени
  пользователя и любые платёжные операции (`scibot:CST-003`). Эти
  эндпоинты не упоминаются в `src/**`, что проверяется тестом.

Вы отвечаете за то, что ваше использование сервиса не нарушает его условий.

## Установка

Требуется Node.js 20 или новее.

```bash
npm install -g scibot-mcp
```

Либо без установки, прямо из конфигурации MCP-клиента, через `npx -y scibot-mcp`.

## Настройка

Конфигурация целиком в переменных окружения (`scibot:CTR-013`). Сам
сервер не принимает учётные данные аргументами командной строки и не читает
их ниоткуда, кроме окружения процесса.

| Переменная             | Обязательна | По умолчанию                                       | Назначение                           |
| ---------------------- | ----------- | -------------------------------------------------- | ------------------------------------ |
| `SCIBOT_USERNAME`      | да          | нет                                                | имя пользователя sci-bot.ru          |
| `SCIBOT_PASSWORD`      | да          | нет                                                | пароль                               |
| `SCIBOT_BASE_URL`      | нет         | `https://sci-bot.ru`                               | адрес сервиса для HTTP и WebSocket   |
| `SCIBOT_STATE_DIR`     | нет         | `${XDG_STATE_HOME:-$HOME/.local/state}/scibot-mcp` | каталог файла сессии                 |
| `SCIBOT_MAX_CHALLENGE` | нет         | `4000000`                                          | потолок перебора Altcha, 1..50000000 |
| `SCIBOT_USER_AGENT`    | нет         | `scibot-mcp/<version>`                             | заголовок исходящих запросов         |

Без учётных данных работают `scibot_read_answer` и
`scibot_queue_status`. Инструменты учётной записи и `scibot_ask_question`
подтверждают сессию перед работой: без учётных данных они отвечают
`CONFIG_MISSING_CREDENTIALS`, а на отвергнутые сервисом отвечают
`AUTH_FAILED`. Вопрос относится на вашу учётную запись и тратит её токены,
поэтому сессия подтверждается до того, как вопрос займёт место в очереди
(`scibot:CTR-001`, `scibot:DEL-001`). `scibot_check_question` и
`scibot_cancel_question` работают с тикетом в памяти процесса и за сессией
к сервису не обращаются.

Значение вне диапазона останавливает запуск с кодом
`SETTINGS_VALUE_OUT_OF_RANGE`.

Образец файла окружения для локальной разработки:
[`.env.example`](.env.example).

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

Общая для всех клиентов форма записи:

```json
{
	"mcpServers": {
		"scibot": {
			"command": "npx",
			"args": ["-y", "scibot-mcp"],
			"env": {
				"SCIBOT_USERNAME": "your-username",
				"SCIBOT_PASSWORD": "your-password"
			}
		}
	}
}
```

У Claude Code есть команда `claude mcp add` с флагами `--env`, но пароль
в ней попадает в argv, а значит в историю оболочки и в список процессов на
время выполнения. Записывайте его в конфигурационный файл клиента, а не в
командную строку.

## Как устроен поток вопроса

Очередь sci-bot.ru длинная (десятки вопросов), а генерация идёт в один
слот, поэтому ожидание ответа измеряется часами. Синхронный вызов
инструмента столько не живёт, и `scibot_ask_question` возвращает управление
сразу.

1. `scibot_ask_question` ставит вопрос в очередь и немедленно отдаёт
   `ticketId`, позицию в очереди и оценку ожидания. Бюджет отклика 2000 мс
   (`scibot:NFR-002`).
2. WebSocket-соединение остаётся жить в процессе сервера и накапливает
   ответ по мере генерации, переживая разрывы связи и переподключаясь к
   идущей генерации.
3. `scibot_check_question` с этим `ticketId` показывает текущее состояние:
   `queued`, `generating`, `answered`, `cancelled`, `expired` или `failed`.
   Он читает только состояние процесса и не обращается к сервису, поэтому
   опрашивать его дёшево.
4. В состоянии `answered` тот же вызов отдаёт итоговый markdown, ход
   рассуждений, разобранную библиографию, потраченные токены, длительность
   генерации, `handle` ответа и `questionId`. Последних двух достаточно,
   чтобы сразу опубликовать или удалить вопрос, не разыскивая его в
   `scibot_my_questions`.

Два ограничения, о которых стоит знать заранее:

- В один момент времени активен не более чем один тикет на процесс
  (`scibot:INV-004`). Второй вопрос до завершения первого получает
  `QUESTION_IN_FLIGHT`.
- Тикет живёт в памяти процесса. После перезапуска сервера он исчезает, но
  ответ остаётся доступен по `handle` через `scibot_read_answer`, который
  работает и без учётной записи.

Отмена (`scibot_cancel_question`) переводит тикет в `cancelled` только
после подтверждения сервисом (`scibot:BEH-003`); текст, накопленный к
моменту отмены, сохраняется.

## Инструменты

Вопросы и очередь:

| Инструмент               | Назначение                                                                  |
| ------------------------ | --------------------------------------------------------------------------- |
| `scibot_ask_question`    | Ставит вопрос в очередь, возвращает `ticketId`. Учётная запись нужна.       |
| `scibot_check_question`  | Состояние тикета и накопленный ответ. Сервис не опрашивается.               |
| `scibot_cancel_question` | Просит сервис остановить активный тикет.                                    |
| `scibot_read_answer`     | Читает опубликованный ответ и библиографию по `handle`. Без учётной записи. |
| `scibot_queue_status`    | Длина очереди, число слотов генерации, доступность. Без учётной записи.     |

Учётная запись:

| Инструмент                       | Назначение                                               |
| -------------------------------- | -------------------------------------------------------- |
| `scibot_account`                 | Имя пользователя и баланс токенов.                       |
| `scibot_my_questions`            | Собственные вопросы, разделённые на идущие и готовые.    |
| `scibot_token_history`           | Баланс и движения токенов без платёжных подробностей.    |
| `scibot_conversations`           | Список многоходовых диалогов.                            |
| `scibot_conversation`            | Полная стенограмма одного диалога.                       |
| `scibot_settings`                | Чтение настроек, а с патчем — их изменение.              |
| `scibot_set_question_visibility` | Публикация собственного вопроса или возврат в приватные. |
| `scibot_delete_question`         | Безвозвратное удаление собственного вопроса.             |
| `scibot_delete_conversation`     | Безвозвратное удаление собственного диалога.             |

Все инструменты возвращают структурированный результат: либо
`{ ok: true, ... }`, либо `{ ok: false, code, message }`. Ветвиться нужно
по `code`; текст `message` контрактом не является (`scibot:LOC-001`).

Схемы входа закрыты (`scibot:DEL-002`): аргумент, которого инструмент не
объявлял, отбивается проверкой MCP, и вызов возвращается ошибкой без
структурированного результата. Опечатка в имени поля становится видимой
ошибкой, а не вызовом без этого поля.

## Коды отказов

| Код                           | Когда возникает                                                  |
| ----------------------------- | ---------------------------------------------------------------- |
| `CONFIG_MISSING_CREDENTIALS`  | инструменту учётной записи не переданы имя пользователя и пароль |
| `AUTH_FAILED`                 | сервис отклонил учётные данные                                   |
| `AUTH_MIGRATION_REQUIRED`     | учётная запись требует миграции имени, что сервер не выполняет   |
| `SESSION_INVALID`             | сессия мертва; пустые данные вместо этого кода не возвращаются   |
| `QUESTION_EMPTY`              | вопрос пуст после обрезки пробелов                               |
| `QUESTION_TOO_LONG`           | вопрос длиннее 16384 символов                                    |
| `QUESTION_IN_FLIGHT`          | активный тикет уже есть                                          |
| `TICKET_NOT_FOUND`            | `ticketId` неизвестен процессу или истёк                         |
| `CHALLENGE_UNSUPPORTED`       | сервис прислал челлендж с другим алгоритмом или сверх потолка    |
| `CHALLENGE_UNSOLVED`          | решение не найдено в пределах `SCIBOT_MAX_CHALLENGE`             |
| `SERVICE_UNAVAILABLE`         | сервис недоступен или не принимает работу                        |
| `SERVICE_THROTTLED`           | сработало ограничение частоты, действует выдержка                |
| `UPSTREAM_PROTOCOL_ERROR`     | ответ сервиса не соответствует зафиксированному контракту        |
| `HANDLE_NOT_FOUND`            | по `handle` нет опубликованного ответа                           |
| `ANSWER_NOT_PARSEABLE`        | страница ответа не разобралась в markdown и библиографию         |
| `CONVERSATION_NOT_FOUND`      | диалога с таким идентификатором нет                              |
| `VISIBILITY_CHANGE_REJECTED`  | сервис отказал в смене видимости                                 |
| `DELETE_REJECTED`             | сервис отказал в удалении                                        |
| `SETTINGS_KEY_UNKNOWN`        | в патче настроек ключ вне контракта                              |
| `SETTINGS_VALUE_UNKNOWN`      | значение вне закрытого перечисления                              |
| `SETTINGS_VALUE_OUT_OF_RANGE` | числовое значение вне допустимого диапазона                      |

## Состояние и безопасность

- Куки сессии хранятся в `${SCIBOT_STATE_DIR}/session.json` с режимом
  доступа `0600` (`scibot:IMP-001`). Файл переживает перезапуск и избавляет
  от повторного решения Altcha-челленджа.
- Пароль, заголовок `Cookie`, значение сессионной куки и токен очереди
  никогда не появляются в результатах инструментов, тексте ошибок и
  `stderr` (`scibot:INV-003`). Редактирование результата рекурсивное.
- Замена секретов в выводе идёт по подстроке и без порога длины
  (`scibot:OQ-008`). Инвариант соблюдается при любом пароле, но пароль,
  совпавший с фрагментом добросовестного текста, вырежет этот фрагмент из
  ответа. Короткий или словарный пароль испортит выдачу молча.
- Платёжные поля вырезаются из ответов сервиса на границе исходящего
  адаптера, а не в прикладном слое (`scibot:POL-003`).
- Нагрузка на сервис ограничена: GET-ответы кэшируются на 10 секунд, а
  ответ `429` включает удвоение выдержки с потолком в 300 секунд
  (`scibot:POL-002`).
- Один процесс работает с одной учётной записью. Координация нескольких
  процессов над одной записью вне области видимости.

## Разработка

```bash
npm ci
npm run verify        # version:check, format:check, lint, typecheck, test, build
npm run verify:spec   # sdd lint, sdd check, sdd ready
```

Гейты спецификации требуют Node.js 22 или новее (ограничение `agent-sdd`),
рантайм сервера работает с Node.js 20.

Порядок работы задан SDD и TDD и обязателен:

1. Правка спецификации, затем `npm run spec:lint` до нулевого кода выхода.
2. Красный тест на каждое `Test obligation`, с маркером
   `@covers scibot:<ID>`, падающий на утверждении, а не на импорте.
3. Минимальная реализация до зелёного.
4. `npm run verify` и `npm run verify:spec` до нулевого кода выхода перед
   коммитом.

Изменение утверждённой записи спецификации проводится через `Delta` и цикл
`sdd approve` + `sdd finalize`, а не правкой файла. Подробности для
агентов: [`AGENTS.md`](AGENTS.md).

Архитектура: вертикальные срезы (`ask-question`, `account`), внутри среза
гексагональная схема `adapters -> ports -> application -> domain`.
Нормализация ответов сервиса живёт в исходящих адаптерах; прикладной слой
получает уже доменные значения.

## Лицензия

[MIT](LICENSE)

TDQS

A3.8/5.0

Scored across 14 tools

Disambiguation4/5

Most tools have clearly distinct purposes, especially the question lifecycle and deletion operations. Minor overlap exists between scibot_account and scibot_token_history (both report token balance) and between scibot_check_question and scibot_read_answer (both can surface finished answers), but descriptions are enough to disambiguate with careful reading.

Naming Consistency4/5

All tools share the scibot_ prefix and snake_case, and mutations generally use clear verb_noun names like ask_question, cancel_question, and delete_conversation. The main inconsistency is that read/list operations use plain nouns such as account, settings, conversations, and queue_status instead of a consistent get_ or list_ prefix, and conversation vs. conversations is mildly confusing.

Tool Count5/5

At 14 tools, the server is well-scoped for a service that covers queue-based Q&A, persistent answers, account settings, token history, and conversations. Each tool represents a meaningful operation, and the count is comfortably within the ideal 3-15 range.

Completeness3/5

The core one-shot question lifecycle is well covered: ask, check, cancel, read, list, delete, and visibility. However, the conversation feature is incomplete: the server can list, read, and delete conversations but provides no tool to create or continue one, leaving that part of the surface as a dead end.

Maintenance

ActivityMaintained
ResponsivenessNo issues