kfu-itis
by GreenSLA
README.md
# ИТИС Навигатор · MCP Demo
Локальное приложение для вопросов об ИТИС Казанского федерального университета.
В чате можно открыть **расписание конкретной группы**: карточки пар, время, преподаватели,
аудитории, выбор группы и переключение дней недели. Для общих вопросов подключается
модель через OpenRouter, которая получает информацию с сайтов КФУ через MCP.
## Быстрый запуск
Нужны **Node.js 22.13 или новее** и npm. Команды выполняются в терминале:
```bash
git clone https://github.com/GreenSLA/MCP-Demo.git
cd MCP-Demo
npm ci
npm run dev
```
Откройте **http://127.0.0.1:3000**. Держите терминал открытым; остановка — `Ctrl+C`.
`npm start` запускает то же локальное приложение. Порты **3000** и **8788** должны быть свободны.
В Windows команды подходят для PowerShell. На macOS после установки зависимостей также
можно открыть `Запустить.command` двойным щелчком (при необходимости `chmod +x Запустить.command`).
### Расписание — без ключа и облачной модели
Работает сразу после запуска, в том числе без интернета. Нажмите **«Расписание группы»**
или напишите:
- «Покажи расписание 11-401»;
- «Какие пары у 11-402 в понедельник?»;
- «Расписание 11-401 на завтра»;
- «Расписание» — приложение предложит выбрать группу.
В ответе откроется интерактивная карточка. Выберите другую группу или день недели.
Время, названия предметов и остальные сведения отображаются **из данных таблицы без пересказа LLM**.
Общие лекции из объединённых ячеек включаются во все соответствующие группы.
Основной источник — [актуальная Google-таблица](https://docs.google.com/spreadsheets/d/12m_Ze1NOnVvdVuSDY5bj0v4r24xLY5RhtuBxNjS26yQ/edit).
При запуске MCP-сервер загружает её через публичный экспорт **XLSX**, разбирает
библиотекой **ExcelJS** и затем автоматически проверяет обновления с интервалом
около **5 минут**, даже если никто не задаёт вопросы. Серверный таймер проверяет
срок кэша каждые 30 секунд; после сбоя повторяет попытку через 30 секунд.
Одновременные запросы используют одну загрузку. Открытые карточки в видимой
вкладке запрашивают текущие данные сервера раз в минуту.
Последняя успешная загрузка сохраняется атомарно в `data/timetable-cache.json`
(файл исключён из Git) и используется после перезапуска. Если интернет недоступен,
показывается эта копия с предупреждением и датой последней успешной загрузки.
`server/data/timetable.json` используется только как аварийный резерв, если новой
копии на диске ещё нет. Пустая или повреждённая таблица не заменяет рабочие данные.
Дата загрузки не гарантирует, что сам составитель таблицы внёс все изменения.
Кнопка **«Обновить из таблицы»** запрашивает данные немедленно, не дожидаясь таймера.
Для ручного обновления дискового кэша:
```bash
npm run schedule:update
```
Перезапуск для регулярных обновлений не нужен. Пока приложение закрыто, таймер
не работает; при следующем запуске сервер снова проверит источник. Ключ Google API,
OpenRouter или нейросеть для загрузки расписания не нужны. Исходная таблица не редактируется.
Чтобы изменить интервал, передайте переменную окружения при запуске (в миллисекундах):
```bash
SCHEDULE_REFRESH_INTERVAL_MS=60000 npm run dev
```
После изменения настроек интервала перезапустите сервер. Минимум — 1000 мс.
### Общие вопросы — подключение OpenRouter
1. Создайте ключ на https://openrouter.ai/settings/keys.
2. В приложении нажмите **«Нужен API-ключ»**, вставьте ключ, выберите модель и сохраните.
3. Задайте вопрос, например «Как связаться с ИТИС?» или «Какие компании сотрудничают с ИТИС?».
Ключ сохраняется только в локальном `.env`. Его значение не возвращается браузеру
при чтении настроек и не включается в Git. Для платной модели нужен баланс OpenRouter;
бесплатный вариант есть в настройках, но возможны ограничения и очереди.
Вместо настройки через интерфейс можно скопировать `.env.example` в `.env` и заполнить:
```dotenv
OPENROUTER_API_KEY=YOUR_OPENROUTER_KEY
OPENROUTER_MODEL=google/gemini-2.5-flash
```
Не добавляйте настоящий ключ в README, исходники или коммиты.
## Архитектура
```text
React-интерфейс → локальный Node.js API
│
┌────────┴────────┐
│ │
Вопрос о парах Общий вопрос
│ ↕
Разбор группы OpenRouter → LLM
и дня │ tool calling
└────────┬────────┘
↓
MCP-клиент
↕ JSON-RPC / stdio
Отдельный MCP-сервер
┌───────────┼───────────────────┐
search_kfu read_kfu_page get_group_schedule
↓ ↓ ↓
Сайты КФУ Страница КФУ Копия / Google Sheets
```
MCP реализован официальным `@modelcontextprotocol/sdk`, включая обнаружение инструментов
через `listTools`, вызовы `callTool` и ресурс `itis://about`. Это отдельный процесс,
к которому можно подключить сторонний MCP-клиент. Нейросеть не обучалась.
| Файл | Назначение |
| ------------------------------ | ----------------------------------------------- |
| `server/mcp-server.mjs` | Три MCP-инструмента и ресурс |
| `server/agent.mjs` | Цикл модели, вызовов инструментов и результатов |
| `server/knowledge.mjs` | HTML КФУ, кодировки, лексический поиск, кэш |
| `server/schedule.mjs` | Разбор таблицы, объединённых ячеек и дней |
| `server/schedule-cache.mjs` | Автообновление, срок кэша и восстановление после сбоев |
| `server/schedule-request.mjs` | Распознавание запросов расписания без LLM |
| `server/api.mjs` | Локальный HTTP API и настройка ключа |
| `components/schedule-card.tsx` | Карточки пар и переключатели |
| `app/page.tsx` | Чат, настройки и журнал MCP |
### Подключение к другому MCP-клиенту
Установите зависимости и укажите абсолютные пути в конфигурации клиента:
```json
{
"mcpServers": {
"kfu-itis": {
"command": "node",
"args": ["/absolute/path/MCP-Demo/server/mcp-server.mjs"]
}
}
}
```
Самому MCP-серверу ключ OpenRouter не нужен. Модель предоставляет выбранный клиент.
## Проверка
```bash
npm test # извлечение, объединения, маршрутизация, агентный цикл
npx tsc --noEmit # проверка TypeScript
npm run build # сборка интерфейса
npm run check:mcp # настоящий MCP и поиск по КФУ; нужен интернет
```
Для демонстрации запускайте **`npm run dev`**, а не отдельно артефакты сборки:
приложению нужны одновременно интерфейс и Node.js API. Публичное развёртывание не настроено.
## Ограничения и источники
- «Сегодня/завтра» вычисляются по Москве. Показывается шаблон дня недели;
каникулы, чётность учебной недели и отмены автоматически не вычисляются.
Все условия из ячеек остаются в тексте занятий.
- Расписание охватывает предоставленную таблицу, а не произвольные группы университета.
Изменение структуры таблицы может потребовать обновления парсера.
- Поиск по КФУ — ограниченный корпус HTML и релевантные ссылки, без векторной базы.
PDF, закрытые личные кабинеты и JavaScript-страницы не извлекаются.
- Общие ответы модели могут содержать ошибки; проверяйте ссылки. История чата хранится
в памяти вкладки до перезагрузки. Приложение предназначено для локального использования.
Источники: [сайт ИТИС](https://kpfu.ru/itis),
[расписание Google Sheets](https://docs.google.com/spreadsheets/d/12m_Ze1NOnVvdVuSDY5bj0v4r24xLY5RhtuBxNjS26yQ/edit).
Шпаргалка для демонстрации: [ЗАЩИТА.md](./ЗАЩИТА.md).
## Эксперименты с токенами и температурой
[Отчёт с результатами](experiments/REPORT.md): сравнение русского и английского,
переполнение контекста Qwen в Ollama, четыре температуры, расход реального MCP-агента
и прогноз на студента в день. Исходные ответы API — в `experiments/results/`.
```bash
node experiments/run.mjs local # Ollama, установленная qwen3.5:9b
node experiments/run.mjs cloud # OpenRouter, ключ из .env; платные вызовы
node experiments/analyze.mjs # сводка без сети
```
Повторный запуск перезаписывает результаты выбранного режима. Перед ним сохраните
копию результатов, если нужно оставить исходные замеры. Настройки и ограничения
эксперимента описаны в отчёте; интерфейс приложения менять не нужно.
## Практика E1–E5: качество ответов
Готовые материалы для сдачи:
- [Презентация](deliverables/ITIS_E1-E5.pptx)
- [Шпаргалка для выступления](deliverables/DEFENSE.md)
- [Отчёт и примеры «было/стало»](eval/REPORT.md)
Системный промпт и три пары примеров находятся в `prompts/`. Общие ответы
приложения проходят проверку JSON в `server/answer.mjs`: один повтор при ошибке,
затем честный запасной ответ. Прямое расписание продолжает возвращаться из данных.
Реальные измерения выполнены на локальной `qwen3.5:9b` через Ollama.
Для повторения с установленной и запущенной Ollama:
```bash
node eval/run.mjs v0
node eval/run.mjs v1
node eval/run.mjs system-only
node eval/run.mjs validated
node eval/analyze.mjs
```
Сохранённые результаты уже входят в репозиторий. Скрипт продолжает только отсутствующие
записи и не смешивает разные конфигурации. Для нового эксперимента сначала сохраните
копию `eval/results/`; разметку после нового прогона нужно проверить заново.
Сравнение изолирует формирование ответа по фиксированным результатам MCP,
а не автономный выбор инструментов. Методика и ограничения описаны в отчёте.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues