scibot-mcp
# 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
Scored across 14 tools
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.
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.
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.
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.