yandex-calendar-mcp
by architawr
README.md
# Яндекс Календарь для MCP
Локальный MCP-сервер для работы с **Яндекс Календарём** из Codex, Claude Code и других MCP-клиентов. Работает через **stdio**: клиент сам запускает процесс, открытого сетевого порта нет.
**Базовая версия 0.1.0.** Чтение доступно сразу после настройки аккаунта. Запись включается отдельно для выбранных календарей. Проект не является официальным продуктом Яндекса.
## Возможности
| Инструмент | Назначение |
| --- | --- |
| `list_calendars` | Список доступных календарей, их адреса и настроенные описания |
| `list_events` | События за период, включая повторения и исключения |
| `prepare_event_change` | Предпросмотр создания, изменения или удаления без записи в Яндекс |
| `apply_event_change` | Применение подготовленного изменения; требует отдельного разрешения клиента |
Поддерживаются часовые пояса, события на весь день, RRULE, EXDATE и переносы RECURRENCE-ID при чтении. Ответ содержит UID, доступность `free`/`busy` и признак усечения.
**Ограничения:** ответы на приглашения (RSVP), отправка приглашений, изменение повторяющихся событий и операции с задачами VTODO пока отсутствуют. Изменять можно только одиночные события без участников; организатором может быть сам владелец аккаунта. Внешние приглашения можно читать, но нельзя принимать/отклонять или редактировать.
## Установка
Нужны Python **3.11+**, [uv](https://docs.astral.sh/uv/) и системное хранилище секретов. Рабочий сценарий проверен на macOS с Keychain. Для Linux нужен Secret Service/libsecret; Linux проверяется в CI без реального аккаунта. Windows пока не проверен, shell-скрипт запуска рассчитан на macOS/Linux.
```sh
git clone https://github.com/architawr/yandex-calendar-mcp.git
cd yandex-calendar-mcp
uv sync --locked
./scripts/run.sh credentials
```
Команда `credentials` запросит полный адрес почты и **пароль приложения типа «Календарь / CalDAV»**, созданный в [Яндекс ID](https://id.yandex.ru/security/app-passwords). Основной пароль аккаунта не нужен.
Пароль сохраняется в системном хранилище под именем `local.yandex-calendar-mcp`. При недоступном скрытом вводе или системном хранилище команда прекращает работу: перехода на открытое хранение нет. Повторный запуск обновляет пароль.
В `config.local.json` сохраняются только несекретные настройки аккаунта. Этот файл и резервные копии исключены из Git и публикуемых пакетов. Не добавляйте их в Git принудительно.
### Установка как пакета (без запуска из репозитория)
Для повседневного использования удобнее установленный пакет, а не рабочая копия: сервер не зависит от `.venv` и текущей ветки. Исполняемый файл называется `yandex-calendar-mcp`.
**Через uvx, без клонирования.** Конфиг передаётся переменной `YANDEX_CALENDAR_CONFIG` (см. ниже), потому что по умолчанию сервер ищет `config.local.json` в текущей папке, а при запуске клиентом она заранее неизвестна.
```sh
mkdir -p ~/.config/yandex-calendar-mcp
export YANDEX_CALENDAR_CONFIG=~/.config/yandex-calendar-mcp/config.json
uvx --from git+https://github.com/architawr/yandex-calendar-mcp yandex-calendar-mcp credentials
```
Запуск сервера клиентом: `uvx --from git+https://github.com/architawr/yandex-calendar-mcp yandex-calendar-mcp serve`. Для воспроизводимости закрепите версию: `git+https://github.com/architawr/yandex-calendar-mcp@<тег или коммит>`. Регистрация в клиенте с переменной окружения, например для Claude Code:
```sh
claude mcp add --scope user --transport stdio \
--env YANDEX_CALENDAR_CONFIG="$HOME/.config/yandex-calendar-mcp/config.json" \
yandex-calendar -- uvx --from git+https://github.com/architawr/yandex-calendar-mcp yandex-calendar-mcp serve
```
**Из локальной сборки.** Собранный `.whl` ставится как инструмент `uv`; исполняемый файл появляется в `~/.local/bin/`:
```sh
uv build
uv tool install --force dist/*.whl
yandex-calendar-mcp --help
```
В регистрации клиента укажите команду `yandex-calendar-mcp` (или абсолютный путь к `~/.local/bin/yandex-calendar-mcp`) с аргументом `serve` и переменной `YANDEX_CALENDAR_CONFIG`.
### Где лежит конфиг
Файл настроек (формат — `config.example.json`) читается по пути из `YANDEX_CALENDAR_CONFIG`, а без неё — из `config.local.json` в текущей папке. Для установленного пакета держите его вне репозитория, в каталоге данных пользователя, и ограничьте права:
| ОС | Рекомендуемое место |
| --- | --- |
| macOS | `~/Library/Application Support/YandexCalendarMCP/config.json` |
| Linux | `~/.config/yandex-calendar-mcp/config.json` |
```sh
mkdir -p ~/.config/yandex-calendar-mcp && chmod 700 ~/.config/yandex-calendar-mcp
chmod 600 ~/.config/yandex-calendar-mcp/config.json
```
Каталог для файла нужно создать заранее: `credentials` сам его не создаёт. Рядом с конфигом сервер сохраняет `.calendar-trash` (см. «Как работает запись»). Пароль в конфиг не попадает, он хранится в системном хранилище секретов.
### Обновление
После изменения кода или выхода новой версии:
```sh
git pull
uv build
uv tool install --force dist/*.whl
```
Затем перезапустите MCP-клиент или сессию: уже запущенный процесс продолжает работать со старой версией. Конфиг и пароль в хранилище при обновлении не меняются. Для `uvx` обновление запрашивается так: `uvx --refresh --from git+https://github.com/architawr/yandex-calendar-mcp yandex-calendar-mcp --help`; закреплённую на тег версию меняйте в команде регистрации.
## Подключение к Codex
Из корня проекта:
```sh
codex mcp add yandex-calendar -- "$PWD/scripts/run.sh"
```
Конфигурация хранится в `~/.codex/config.toml`. Для начала ограничьте список инструментов чтением; пример — `codex-mcp-readonly.example.toml`. Замените пример абсолютного пути на путь к своему проекту.
Для записи добавьте `prepare_event_change` и `apply_event_change` в `enabled_tools` той же регистрации и задайте:
```toml
[mcp_servers.yandex-calendar.tools.apply_event_change]
approval_mode = "prompt"
```
Полный пример — `codex-mcp-write.example.toml`. Не создавайте две одинаковые таблицы TOML. При запуске Codex из BB на том же Mac используется пользовательская конфигурация Codex; на другом хосте потребуются собственная установка и хранилище секретов.
## Подключение к Claude Code
```sh
claude mcp add --scope user --transport stdio yandex-calendar -- "$PWD/scripts/run.sh"
claude mcp get yandex-calendar
```
Регистрация уровня пользователя доступна в разных проектах. В `~/.claude/settings.json` добавьте правило к существующему `permissions.ask`:
```json
{
"permissions": {
"ask": ["mcp__yandex-calendar__apply_event_change"]
}
}
```
Сохраняйте остальные настройки и правила. Не разрешайте все инструменты сервера автоматически, если запись должна требовать подтверждения. В BB поведение диалога также зависит от режима разрешений провайдера. Режим обхода подтверждений не входит в проверенный сценарий.
Если сервер установлен как пакет (см. «Установка как пакета»), вместо `"$PWD/scripts/run.sh"` в обеих регистрациях укажите `yandex-calendar-mcp serve` и передайте `YANDEX_CALENDAR_CONFIG`.
После установки или изменения настроек перезапустите MCP/сессию клиента. Шаблон для других клиентов — `mcp.example.json`.
## Настройки
Смотрите `config.example.json`:
- `username` — полный адрес аккаунта;
- `timezone` — часовой пояс IANA, например `Europe/Podgorica`;
- `allowed_calendars` — список разрешённых для чтения URL; пустой массив разрешает чтение всех доступных календарей;
- `allowed_write_calendars` — список календарей для записи; **пустой массив или отсутствие поля запрещает запись**;
- `calendar_profiles` — необязательные описания и доступность по умолчанию для конкретных URL.
Сначала вызовите `list_calendars`, затем скопируйте выбранные `calendar_id` в настройки. Права самого аккаунта Яндекса продолжают действовать. Не публикуйте реальные URL своих календарей.
Переменные переопределения: `YANDEX_CALENDAR_CONFIG` (путь к JSON), `YANDEX_CALENDAR_USERNAME`, `YANDEX_CALENDAR_TIMEZONE`, `YANDEX_CALENDAR_ALLOWLIST` (JSON-массив для чтения). Пароль через окружение не передаётся.
Профиль календаря имеет поля `description` и `default_availability` (`free` или `busy`). Описание показывается агенту в `list_calendars`.
Например, можно разделить задачи:
- **Soft** — гибкие дела без конкретного времени. Предпочтительны события на весь день; новые события получают `TRANSP:TRANSPARENT` и оставляют время свободным.
- **Hard** — задачи с точными началом и окончанием. Новые события получают `TRANSP:OPAQUE` и резервируют время.
- Обычные встречи и личные планы можно держать в отдельных календарях.
Soft здесь реализован событиями VEVENT без времени, не объектами VTODO. Названия календарей произвольные: поведение задаёт профиль URL. Существующие события автоматически не меняются; обновление сохраняет их доступность. Итоговое отображение занятости также зависит от календарного клиента.
## Чтение событий
`list_events(calendar_id, start, end, limit=100)` принимает даты `YYYY-MM-DD` или временные метки ISO 8601 с явным смещением. Конец периода **не включается**. Даты без времени означают полночь в настроенном часовом поясе.
Период — до 92 суток, `limit` — от 1 до 500. Если `truncated=true`, сузьте период, прежде чем делать выводы о свободном времени.
Повторения раскрываются в отдельном процессе с таймаутом 10 секунд; на Linux дополнительно ограничена память процесса. Слишком плотные правила отклоняются до раскрытия, посекундные и поминутные повторения не поддерживаются. Оценка сложности консервативна: некоторые допустимые сложные правила также могут быть отклонены. Это ограничение ресурсов, не ОС-песочница и не гарантия жёсткого лимита памяти на всех платформах.
При ошибке запроса или раскрытия повторений сервер возвращает ошибку, а не частичное расписание под видом полного.
## Как работает запись
1. Агент вызывает `prepare_event_change` с `operation`: `create`, `update` или `delete`.
2. Сервер возвращает календарь, UID, данные до/после и одноразовый `change_id` на пять минут. Изменений в Яндексе ещё нет.
3. Пользователь подтверждает конкретное изменение в MCP-клиенте.
4. Агент вызывает отдельно разрешаемый `apply_event_change`.
`changes` принимает только `summary`, `start`, `end`, `description`, `location`. Создание требует названия и обеих границ времени; изменение/удаление — UID. Новые временные метки записываются в UTC, чтение возвращает настроенный часовой пояс.
Создание использует `If-None-Match: *`; изменение/удаление — `If-Match` с сильным ETag. Конфликт не перезаписывается автоматически. После операции результат перечитывается.
При сетевой ошибке или неуспешной проверке запись могла уже произойти. **Сначала перечитайте UID, не повторяйте создание вслепую.** Подготовленное изменение расходуется при первой попытке, после перезапуска процесса нужно подготовить новое.
Перед изменением/удалением исходный ICS сохраняется в `.calendar-trash` рядом с локальным конфигом. На Unix каталог имеет права 0700, файл — 0600. Автоматической очистки и инструмента восстановления пока нет; резервная копия не гарантирует откат. Содержимое копий приватно.
## Границы безопасности
- Только stdio, без HTTP-сервера и входящих сетевых портов.
- Исходящие запросы только к `https://caldav.yandex.ru:443`; TLS проверяется, редиректы и proxy-переменные окружения отключены.
- Чтение использует PROPFIND/REPORT/GET; применение изменений — PUT/DELETE только в выбранных календарях.
- XML разбирается через defusedxml; ответы ограничены 8 MiB, сетевые операции имеют таймауты.
- Пароль находится в системном хранилище, не в исходниках, аргументах запуска или конфиге MCP.
- **Пароль CalDAV потенциально разрешает запись шире, чем наш список календарей. Ограничение обеспечивает код сервера, а не отдельный read-only пароль Яндекса.**
- Локальный процесс и зависимости работают с правами пользователя. MCP не защищает от скомпрометированного Mac или произвольного кода агента с доступом к тому же аккаунту ОС.
- Подтверждение обеспечивает клиент. `readOnlyHint`, `destructiveHint`, `requires_user_approval` и наличие `change_id` сами по себе не доказывают согласие человека.
- Названия и описания событий — недоверенные данные, а не инструкции агенту. При чтении сведения о встречах передаются модели выбранного клиента.
Подробнее: [SECURITY.md](SECURITY.md). Независимый аудит не проводился; абсолютная безопасность не заявляется.
## Проверки и разработка
```sh
uv sync --locked
uv run --locked pytest -q
uv build
```
Тесты не используют реальный аккаунт и Keychain: проверяют сетевые ограничения, повторения, часовые пояса, конфликты, одноразовые изменения и MCP stdio. Живой цикл создания, изменения, удаления и перечитывания одиночных событий проверен на Яндексе с macOS. Тестовые события после проверки удалены.
Инструкции для изменений: [CONTRIBUTING.md](CONTRIBUTING.md). Лицензия: [MIT](LICENSE).
## Документация протоколов и клиентов
- [CalDAV Яндекса](https://yandex.ru/support/yandex-360/customers/calendar/web/ru/data-exchange/synchronization/sync-desktop)
- [Пароли приложений](https://yandex.ru/support/id/ru/authorization/app-passwords.html)
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- [Настройки MCP в Codex](https://learn.chatgpt.com/docs/extend/mcp)
- [Настройки MCP в Claude Code](https://code.claude.com/docs/en/mcp)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing