Skip to main content
Glama
volokovartem12-a11y

MCP Scheduler

README.md
# День 18. MCP-планировщик и фоновые задачи

Готовый учебный проект: MCP-сервер принимает задания, фоновый worker выполняет их по расписанию, а все данные и результаты сохраняются в SQLite.

Проект работает локально и не требует API-ключей или платных запросов.

## Что реализовано

- одноразовые и повторяющиеся напоминания;
- периодический сбор числовых метрик;
- регулярная агрегированная сводка;
- хранение заданий, запусков, метрик и уведомлений в SQLite;
- восстановление просроченных заданий после перезапуска;
- атомарный захват заданий и защита от повторной записи результата двумя worker-процессами;
- управление заданиями: список, просмотр, пауза, продолжение и отмена;
- MCP через `stdio` или Streamable HTTP;
- режим постоянной работы с автоматическим перезапуском;
- полностью локальные тесты и короткая демонстрация.

## Как это устроено

```text
MCP-клиент
    │ создаёт задания / читает результаты
    ▼
MCP-сервер ───────► SQLite ◄─────── фоновый worker
                       │                 │
                       ├─ задания        ├─ проверяет расписание
                       ├─ метрики        ├─ выполняет задания
                       ├─ запуски        └─ сохраняет результаты
                       └─ уведомления
```

Если MCP-клиент отключён, напоминание или сводка не теряются. Worker сохраняет их в SQLite, а клиент получает их при следующем вызове `get_notifications`.

## Быстрый запуск в PowerShell

Откройте PowerShell и выполните:

```powershell
cd "$env:USERPROFILE\Desktop\day-18-mcp-scheduler"

# Нужно только если Windows запрещает запуск локальных ps1-файлов.
Set-ExecutionPolicy -Scope Process Bypass

.\scripts\setup.ps1
.\scripts\test.ps1
.\scripts\demo.ps1
```

Что произойдёт:

1. `setup.ps1` создаст отдельное Python-окружение `.venv` и установит зависимости.
2. `test.ps1` проверит базу, расписание, агрегацию и восстановление.
3. `demo.ps1` за несколько секунд создаст reminder, сборщик и summary, затем покажет JSON-результат.

## Запуск MCP-сервера

### Вариант 1: Streamable HTTP

```powershell
.\scripts\run-mcp.ps1
```

Адрес MCP-сервера:

```text
http://127.0.0.1:8000/mcp
```

В этом режиме MCP-сервер и worker работают вместе. Пока окно PowerShell открыто, задания выполняются по расписанию.

### Вариант 2: stdio

```powershell
.\scripts\run-mcp.ps1 -Transport stdio
```

`stdio` обычно запускает сам MCP-клиент. Такой процесс остановится вместе с клиентом, поэтому для постоянной работы удобнее HTTP-режим.

### Только фоновый worker

```powershell
.\scripts\run-worker.ps1
```

Это полезно, если MCP-сервер и worker нужно держать в разных процессах. Они используют один файл `data\scheduler.db`.

## Режим 24/7

```powershell
.\scripts\run-24x7.ps1
```

Скрипт держит HTTP-сервер и worker запущенными и перезапускает процесс после сбоя. Для остановки нажмите `Ctrl+C`.

Важно: на обычном компьютере «24/7» означает, что Windows включена, не находится в спящем режиме и окно PowerShell не закрыто. Для сервера или Docker используйте:

```powershell
docker compose up -d --build
docker compose logs -f
```

В `compose.yaml` включён `restart: unless-stopped`, а SQLite хранится в отдельном Docker volume.

Сервер по умолчанию доступен только на `127.0.0.1`. Не публикуйте его через
`0.0.0.0` в интернет без аутентификации на reverse proxy и правил firewall.
Защита от DNS rebinding включена явно. Для разрешённого внешнего домена задайте
`SCHEDULER_ALLOWED_HOSTS` и `SCHEDULER_ALLOWED_ORIGINS` списками через запятую.

## Проверка живого MCP-запроса

В первом окне PowerShell запустите сервер:

```powershell
.\scripts\run-mcp.ps1
```

Во втором окне выполните:

```powershell
.\scripts\smoke-live.ps1
```

Проверка подключится к `/mcp`, покажет список инструментов, создаст быстрое
напоминание и дождётся сохранённого уведомления.

Чтобы вызывать отдельные MCP-инструменты прямо из PowerShell, используйте
обёртку `scripts\call-tool.ps1`. Сервер должен быть запущен в другом окне.

```powershell
.\scripts\call-tool.ps1 -Tool list-tools
.\scripts\call-tool.ps1 -Tool scheduler_status
```

## Команды PowerShell для функций программы

### 1. Отложенное напоминание

Напоминание через 60 секунд:

```powershell
.\scripts\call-tool.ps1 -Tool schedule_reminder -Arguments '{"message":"Проверить отчёт","delay_seconds":60}'
```

Или в заданное время (нужен часовой пояс):

```powershell
.\scripts\call-tool.ps1 -Tool schedule_reminder -Arguments '{"message":"Начать встречу","run_at":"2026-09-24T10:00:00+03:00"}'
```

### 2. Повторяющееся напоминание

Повторять каждые 3600 секунд, всего 5 раз:

```powershell
.\scripts\call-tool.ps1 -Tool schedule_reminder -Arguments '{"name":"Перерыв","message":"Пора размяться","delay_seconds":10,"repeat_seconds":3600,"max_runs":5}'
```

### 3. Периодический сбор метрики

Собирать значение `orders` раз в 10 секунд; значения будут 10, 12, 14 и 16:

```powershell
.\scripts\call-tool.ps1 -Tool schedule_metric_collection -Arguments '{"metric":"orders","interval_seconds":10,"initial_value":10,"increment":2,"max_runs":4}'
```

### 4. Регулярная сводка

Сводка по `orders` каждые 60 секунд за последний час:

```powershell
.\scripts\call-tool.ps1 -Tool schedule_summary -Arguments '{"metric":"orders","interval_seconds":60,"lookback_seconds":3600}'
```

### 5. Записать метрику вручную

```powershell
.\scripts\call-tool.ps1 -Tool record_metric -Arguments '{"metric":"orders","value":42,"labels":{"source":"manual"}}'
```

### 6. Посмотреть список заданий

```powershell
.\scripts\call-tool.ps1 -Tool list_jobs -Arguments '{"limit":100}'
```

В ответе каждого задания есть поле `id`; оно понадобится для управления.

### 7. Посмотреть, приостановить, возобновить или отменить задание

Скопируйте UUID из поля `id` и вставьте его без символов `<` и `>`. Например,
возьмём активное задание из списка:

```powershell
$jobId = "3c3ee3ac-22ea-4326-aa62-c82e0a1b5a21"
$jobArgs = @{ job_id = $jobId } | ConvertTo-Json -Compress

.\scripts\call-tool.ps1 -Tool get_job -Arguments $jobArgs
.\scripts\call-tool.ps1 -Tool pause_job -Arguments $jobArgs
.\scripts\call-tool.ps1 -Tool resume_job -Arguments $jobArgs
.\scripts\call-tool.ps1 -Tool cancel_job -Arguments $jobArgs
```

Возобновить можно только задание в статусе `paused`. Выполненное (`completed`)
или отменённое (`cancelled`) задание повторно запустить нельзя. `cancel_job`
отменяет следующие запуски периодического задания.

### 8. Получить уведомления и готовые сводки

```powershell
.\scripts\call-tool.ps1 -Tool get_notifications -Arguments '{"unread_only":true,"limit":100}'
```

Чтобы сразу пометить полученные уведомления прочитанными, добавьте
`"mark_read":true`.

### 9. Посчитать агрегат

```powershell
.\scripts\call-tool.ps1 -Tool get_aggregate -Arguments '{"metric":"orders"}'
```

Можно ограничить период, передав `since` и `until` в ISO 8601 с часовым поясом.

### 10. История запусков и статус базы

```powershell
.\scripts\call-tool.ps1 -Tool get_executions -Arguments '{"limit":100}'
.\scripts\call-tool.ps1 -Tool scheduler_status
.\scripts\status.ps1
```

### 11. Восстановление после перезапуска

Worker запускается вместе с HTTP MCP-сервером. Нажмите `Ctrl+C`, чтобы его
остановить, затем снова выполните `run-mcp.ps1` или `run-24x7.ps1`. Задания,
метрики и уведомления останутся в `data\scheduler.db`.

### 12. Очистка старой истории

Worker автоматически очищает метрики и историю запусков старше 30 дней; старые
прочитанные уведомления тоже удаляются. Чтобы изменить срок в текущем окне
PowerShell перед запуском сервера:

```powershell
$env:SCHEDULER_RETENTION_DAYS = "7"
.\scripts\run-mcp.ps1
```

Значение `0` выключает автоматическую очистку.

## MCP-инструменты

| Инструмент | Что делает |
|---|---|
| `schedule_reminder` | Создаёт reminder на дату или через указанное число секунд |
| `schedule_metric_collection` | Периодически записывает тестовую числовую метрику |
| `schedule_summary` | Периодически считает и сохраняет сводку за окно времени |
| `record_metric` | Записывает числовую метрику вручную |
| `list_jobs` | Показывает задания |
| `get_job` | Показывает одно задание |
| `pause_job` | Ставит задание на паузу |
| `resume_job` | Возобновляет задание |
| `cancel_job` | Отменяет будущие запуски |
| `get_notifications` | Возвращает накопленные reminders и summaries |
| `get_aggregate` | Возвращает `count`, `sum`, `average`, `minimum`, `maximum`, последнее значение |
| `get_executions` | Показывает историю запусков и результаты |
| `scheduler_status` | Показывает общую статистику SQLite |

Все ответы инструментов структурированы как JSON.

## Пример сценария для агента

Попросите подключённого MCP-агента:

```text
1. Создай сбор метрики orders каждые 5 секунд:
   initial_value=10, increment=2, max_runs=5.
2. Создай summary по orders каждые 15 секунд за последние 60 секунд.
3. Через 20 секунд покажи get_notifications и get_aggregate для orders.
```

Ожидаемый рост метрики:

```text
10 → 12 → 14 → 16 → 18
```

Итоговый агрегат:

```json
{
  "count": 5,
  "sum": 70.0,
  "average": 14.0,
  "minimum": 10.0,
  "maximum": 18.0
}
```

## Формат времени

Для `run_at`, `recorded_at`, `since` и `until` нужен ISO 8601 с часовым поясом:

```text
2026-09-23T18:00:00+03:00
2026-09-23T15:00:00Z
```

Время без `Z` или смещения отклоняется, чтобы напоминание не сработало в неверный момент. В SQLite всё хранится в UTC.

## Что происходит после простоя

- одноразовое просроченное задание выполнится после запуска worker;
- у периодического задания выполняется один актуальный запуск;
- сотни пропущенных интервалов не создают «лавину» запусков;
- следующий запуск рассчитывается от расписания, а не от времени окончания, поэтому интервалы не дрейфуют;
- lease в SQLite позволяет другому worker продолжить задание после аварии первого процесса.

По умолчанию worker раз в час удаляет метрики и историю запусков старше 30 дней,
а также старые **прочитанные** уведомления. Непрочитанные reminders и summaries
не удаляются. Срок меняется через `SCHEDULER_RETENTION_DAYS`; значение `0`
отключает очистку. Демо использует очень короткие интервалы только для быстрой
проверки — для постоянной работы выбирайте разумный интервал и `max_runs`.

## Файлы данных

По умолчанию база находится здесь:

```text
data\scheduler.db
```

Можно указать другой файл только для текущего окна PowerShell:

```powershell
$env:SCHEDULER_DB_PATH = "C:\scheduler-data\scheduler.db"
.\scripts\run-mcp.ps1
```

Текущий статус базы:

```powershell
.\scripts\status.ps1
```

Файлы `.db`, `.db-wal` и `.db-shm` исключены из Git.

## Выгрузка на GitHub

Создайте на GitHub пустой репозиторий, затем в PowerShell выполните команды.
В последней команде замените `ВАШ_ЛОГИН` на свой логин GitHub:

```powershell
cd "$env:USERPROFILE\Desktop\day-18-mcp-scheduler"
git init
git add .
git commit -m "Day 18: add persistent MCP scheduler"
git branch -M main
git remote add origin https://github.com/ВАШ_ЛОГИН/day-18-mcp-scheduler.git
git push -u origin main
```

Папка `.venv`, SQLite-файлы и локальные настройки исключены из Git. Не добавляйте
API-ключи в проект: для этой работы они не нужны.

## Соответствие заданию

- **сохраняет данные:** SQLite;
- **выполняется по расписанию:** отдельный async worker;
- **отложенный запуск:** `schedule_reminder`;
- **периодический запуск:** collector, recurring reminder и summary;
- **возвращает агрегат:** `get_aggregate` и summary-уведомления;
- **работает постоянно:** Streamable HTTP + worker, PowerShell restart loop или Docker;
- **результат не пропадает:** история и уведомления остаются в SQLite после перезапуска.

## Технологии

- Python 3.11+;
- официальный MCP Python SDK `2.2.0`;
- SQLite из стандартной библиотеки Python;
- `asyncio` для фонового цикла;
- `pytest` для тестов.

## Официальные материалы MCP

- [Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- [Запуск: stdio и Streamable HTTP](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/run/index.md)
- [Lifespan для ресурсов и фоновых задач](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/handlers/lifespan.md)