Skip to main content
Glama
dreamcatchered

yandex-mail-mcp

README.md
<div align="center">

# 📬 yandex-mail-mcp

**Научите вашего ИИ-агента работать с вашей почтой.**

Обычный ChatGPT ничего не знает о вашей почте: не видит входящие,
не найдёт письмо от бухгалтерии, не ответит и не отправит. И добраться
до почты через промпт не выйдет — у модели нет инструмента, который
её открывает.

Этот проект даёт ChatGPT такой инструмент. Он подключается как
коннектор, один раз спрашивает код доступа — и дальше вы просто
пишете в чате обычные слова: *«что нового в почте»*, *«найди письма
от Иванова за сентябрь»*, *«ответь, что всё оплачено»*.

[![Python](https://img.shields.io/badge/Python-3.11+-3776AB?logo=python&logoColor=white)](https://www.python.org/)
[![Protocol](https://img.shields.io/badge/Protocol-MCP-8A2BE2)](https://modelcontextprotocol.io)
[![ChatGPT](https://img.shields.io/badge/ChatGPT-connector-10A37F?logo=openai&logoColor=white)](#-подключение-к-chatgpt)
[![License](https://img.shields.io/badge/License-MIT-green)](LICENSE)

</div>

---

> **Как я к этому пришёл.**
> Кастомные MCP для обычного ChatGPT я открыл для себя относительно недавно —
> в том самом чате, которым пользуюсь каждый день. Первым был коннектор для
> «Пятёрочки», и я быстро понял, насколько это удобно: не надо копировать промпты,
> не надо заводить отдельного агента — просто включил коннектор в настройках
> и спрашиваешь обычными словами.
> Этот проект — второй такой коннектор. Первый научил ИИ ходить в магазин,
> второй учит его читать вашу почту.

---

## ✨ Что внутри

| | |
|---|---|
| 📨 **14 инструментов** | чтение, поиск, отправка, ответ с сохранением цепочки, пересылка, черновики, метки, перемещение, удаление |
| 🔐 **OAuth 2.1** | authorization code + PKCE, клиент регистрируется сам — подключение в два клика |
| 🪶 **35 МБ RAM** | один процесс, одна зависимость (Flask), IMAP и SMTP из стандартной библиотеки |
| 🔒 **Слушает только localhost** | наружу не торчит, TLS на твоём обратном прокси, пароль почты не покидает сервер |
| 🌐 **Кириллица из коробки** | папки и поиск по-русски, темы и тела писем не ломаются |



```
                  HTTPS
ChatGPT ──────────────────────────────┐
   │ OAuth 2.1 │                       ▼
   │◀─JSON-RPC─┼──────▶  reverse proxy (nginx / Caddy / …)
   │           │                       │ http
   │           │                       ▼
   └───────────┘         127.0.0.1:5180  yandex-mail-mcp
                                    │ IMAP / SMTP
                                    ▼
                         imap.yandex.ru / smtp.yandex.ru
```

## 📋 Содержание

- [Что умеет](#что-умеет)
- [Требования](#требования)
- [Установка](#установка)
- [Подключение к ChatGPT](#подключение-к-chatgpt)
- [Инструменты](#инструменты)
- [Примеры запросов](#примеры-запросов)
- [Тесты](#тесты)
- [Безопасность](#безопасность)
- [Важно про Яндекс OAuth](#важно-про-yandex-oauth)
- [Особенности реализации](#особенности-реализации)
- [Обслуживание](#обслуживание)
- [Связанные проекты](#связанные-проекты)

## 🎯 Что умеет

Чтение и поиск по всем папкам (входящие, отправленные, черновики, корзина,
спам, архив) с поддержкой кириллицы, чтение писем с вложениями, отправка,
ответ с сохранением цепочки, пересылка, черновики, перемещение, метки
прочитано/важное/звезда, удаление. Плюс пара инструментов `search`/`fetch` —
обёртка, без которой коннектор не работает в Deep Research.

## 📋 Требования

- Linux-сервер с Python 3.11+ (проверено на 3.12) и выходом в интернет
- Домен с валидным TLS-сертификатом: его видит ChatGPT, поэтому самоподписанный не подойдёт
- Аккаунт Яндекс Почты с включённым IMAP

Зависимости: только `Flask`. IMAP, SMTP, криптография и разбор MIME — из
стандартной библиотеки.

## 🚀 Установка

### 1. Пароль приложения в Яндексе

У Яндекса **нет OAuth-скоупа для почты** (подробности ниже), поэтому единственный
способ — пароль приложения:

1. Открыть https://id.yandex.ru/security/apppasswords
2. «Создать пароль приложения», назвать как угодно
3. Скопировать выданный пароль
4. Отдельно убедиться, что IMAP включён: mail.yandex.ru → Настройки →
   «Доступ по IMAP»

Пароль приложения не заменяет основной пароль и не даёт доступа к веб-интерфейсу.
Отзывается там же.

### 2. Файлы и конфиг

```bash
sudo mkdir -p /opt/yandex-mail-mcp
sudo cp app.py mail.py oauth.py /opt/yandex-mail-mcp/
sudo cp config.example.json /opt/yandex-mail-mcp/config.json
sudo pip3 install 'Flask>=3.0,<4'
```

Заполнить `/opt/yandex-mail-mcp/config.json`:

```json
{
  "access_code": "любой-длинный-случайный-код",
  "port": 5180,
  "public_url": "https://yandex-mail-mcp.example.com",
  "address": "your-mailbox@yandex.ru",
  "app_password": "пароль-из-шага-1",
  "imap_host": "imap.yandex.ru",
  "imap_port": 993,
  "smtp_host": "smtp.yandex.ru",
  "smtp_port": 465,
  "oauth_clients": []
}
```

`access_code` — код, который вводится на экране согласия при подключении
ChatGPT. Сгенерировать можно так:

```bash
python3 -c "import secrets; print(secrets.token_urlsafe(24))"
```

Дальше — `chmod 600 /opt/yandex-mail-mcp/config.json`.

### 3. Автозапуск

```bash
sudo cp yandex-mail-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now yandex-mail-mcp
curl -s http://127.0.0.1:5180/healthz
```

### 4. Доступ снаружи

ChatGPT обращается к коннектору по обычному HTTPS, поэтому перед публикацией
нужен домен с валидным сертификатом — Let's Encrypt или коммерческий.

Сам сервис слушает только `127.0.0.1:5180` и наружу не выходит, так что любой
реверс-прокси подойдёт: nginx, Caddy, Traefik. Например, за nginx:

```nginx
server {
    listen 443 ssl;
    http2 on;
    server_name mail.example.com;

    ssl_certificate     /etc/letsencrypt/live/mail.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mail.example.com/privkey.pem;

    client_max_body_size 2m;

    location / {
        proxy_pass http://127.0.0.1:5180;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 120s;
    }
}
```

Почему так: сервис никогда не выходит в интернет сам и не занимает порт —
это значит, что его нельзя случайно обнаружить сканированием, а доступ
контролируется правилами прокси. Порт 443 на сервере при этом остаётся
свободным для других служб.

## 🔗 Подключение к ChatGPT

1. Включить Developer Mode: https://platform.openai.com/docs/guides/developer-mode
2. ChatGPT → Settings → Connectors → Create
3. Заполнить:
   - **Name** — например, «Яндекс Почта»
   - **URL** — `https://yandex-mail-mcp.example.com/mcp`
   - **Auth** — `OAuth`
4. В чате: «+» или `@` → выбрать коннектор → **Sign in**
5. Откроется экран согласия — ввести `access_code` из конфига
6. Вернуться в ChatGPT, коннектор подключён

Сразу после подключения стоит зайти в Settings → Plugins → приложение →
Permissions и выбрать **«Всегда спрашивать»**: коннектор умеет отправлять
письма твоим именем, и по умолчанию лучше подтверждать каждое действие.

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

| Инструмент | Что делает |
|---|---|
| `unread_count` | Сколько всего и сколько непрочитанных. Ответ на «что нового?» |
| `list_folders` | Все папки с назначением. Первым делом, если что-то «не нашлось» |
| `search_emails` | Поиск. `query` — синтаксис IMAP: `ALL`, `UNSEEN`, `FROM "a@b.c"`, `SUBJECT "счёт"`, `SINCE 01-Dec-2025` |
| `read_email` | Полный текст письма и список вложений. Не помечает прочитанным |
| `send_email` | Отправка. Поддерживает cc, bcc, html |
| `reply_email` | Ответ с сохранением цепочки, `all_reply` — ответить всем |
| `forward_email` | Пересылка с исходным текстом |
| `create_draft` | Черновик без отправки |
| `move_email` | Переместить в другую папку |
| `mark_email` | Прочитано/не прочитано, флаги «важное» и «звезда» |
| `delete_email` | В Корзину; `expunge: true` — безвозвратно. Требует `confirm: true` |
| `search` / `fetch` | Обёртка для Deep Research: поиск и полный текст по `id` |
| `verify_connection` | Проверка IMAP/SMTP и счётчики. Первое, что стоит звать, если что-то не работает |

Идентификатор письма — строка вида `папка|uid`, например `INBOX|41703`.
Её возвращает `search_emails` и её же нужно передавать обратно в остальные
инструменты; после перемещения письма идентификатор меняется, поэтому
устаревший `id` даёт понятную ошибку, а не молчаливый «успех».

Папки можно называть по-русски или по-английски: `INBOX`/«Входящие»,
`Sent`/«Отправленные», `Drafts`/«Черновики», `Trash`/«Корзина»,
`Junk`/«Спам», `Archive`/«Архив».

## 💬 Примеры запросов

- «Что нового в почте?»
- «Найди непрочитанные от hr@company.ru за последнюю неделю»
- «Что в последнем письме от банка?»
- «Подготовь ответ: подтверждаю, спасибо» — создаст **черновик**, не отправляя
- «Отправь письмо vasya@mail.ru с текстом…»
- «Перешли последнее письмо о счёте в бухгалтерию»
- «Перемести все рассылки от рекламы в Корзину»

## 🧪 Тесты

Два независимых теста. Токен в обоих — из переменной окружения, в коде его нет.

```bash
# протокол: OAuth-цикл, PKCE, 401/WWW-Authenticate, tools/list, кэш и ротация
# токенов, revoke. Почту не трогает.
YCP_TOKEN=<код> python selftest.py https://yandex-mail-mcp.example.com

# боевая проверка на живом ящике. По умолчанию только чтение.
YCP_TOKEN=<код> python live_check.py https://yandex-mail-mcp.example.com

# добавить проверку записи (создаёт черновик и письмо самому себе)
YCP_TOKEN=<код> YCP_ADDRESS=your-mailbox@yandex.ru \
  python live_check.py https://yandex-mail-mcp.example.com --write
```

`selftest.py` — 61 проверка, закрывает весь OAuth-цикл, который повторит
ChatGPT, включая негативные случаи.

## 🔐 Безопасность

| Что | Как |
|---|---|
| Доступ к MCP | OAuth 2.1: authorization code + PKCE (S256), токены 12 ч, refresh 90 дней с ротацией |
| Клиенты | DCR — ChatGPT регистрируется сам; вручную описанные клиенты — в `oauth_clients` |
| Экран согласия | Требует `access_code`, независимо от того, кто пришёл |
| Пароль почты | Только в `config.json` с правами 600, никогда не попадает в ответы и логи |
| Поверх сети | сервис только на localhost, наружу — HTTPS через твой обратный прокси |
| Опасные действия | `delete_email` требует `confirm: true`; защиту подтверждает ChatGPT, а не сервер |
| Случайные токены | Клиент регистрируется сам; старые клиенты и коды подчищаются |

Про выданные токены: `/oauth/sessions` по тому же `access_code` показывает
счётчик активных токенов. Токен можно отозвать через `/oauth/revoke`
или удалив `oauth.json` и перезапустив сервис.

Если доступ идёт через CDN или WAF, проверь, что он не режет небраузерные
запросы: ChatGPT ходит с бот-агентом, и часть защит отсекает такие запросы
по User-Agent. Лечится исключением для адреса коннектора.

## ⚠️ Важно про Яндекс OAuth

Часто встречается совет «сделать OAuth 2.1 с обменом токенов по RFC 8693,
чтобы ИИ ходил в почту по токену Яндекса». **Это невозможно**, и вот почему:

- Яндекс OAuth поддерживает ровно два `grant_type`: `authorization_code`
  и `refresh_token`. Обмен токенов (RFC 8693) не реализован.
- Скоупа `mail:imap` / `mail:smtp` у Yandex ID **не существует** вообще.
  Есть `login:*` и API отдельных сервисов (Диск, Календарь, Переводчик).
  Поэтому XOAUTH2 для личной Яндекс Почты невозможен в принципе.

Следствие: сервер не хранит и не проксирует токен Яндекса. Он хранит
**пароль приложения** и работает по IMAP/SMTP. Свой OAuth 2.1 нужен только
для того, чтобы сам ChatGPT мог подключиться к этому серверу, — и это
единственная роль, которую он здесь играет.

Сервисные приложения Яндекс 360 (для организаций с собственным доменом) —
отдельная история, они требуют прав администратора организации.

## 🧠 Особенности реализации

Три места, где пришлось повозиться, и почему так:

**Имена папок приходят в modified-UTF7.** Яндекс отдаёт `&BBAEQARFBDgEMg-`
вместо «Архив». Реализован декодер/кодер по RFC 3501. На провод уходит
исходная форма, человеку — читаемая.

**Поиск по кириллице требует `CHARSET UTF-8`.** Без него `SEARCH` отвечает
`NO` вместо результата — молча, без ошибки. Критерий с не-ASCII
автоматически отправляется с `CHARSET UTF-8`.

**IMAP-соединение переиспользуется.** Яндекс отвечает на приветствие нового
соединения с задержкой, которая после серии частых подключений доходит до
30 секунд. Все команды после `LOGIN` при этом выполняются за миллисекунды.
Поэтому держится одно соединение, а папка выбирается повторно только при
смене. Первый вызов после запуска может быть медленным, дальше — доли секунды.

## 🛠 Обслуживание

```bash
systemctl status yandex-mail-mcp
journalctl -u yandex-mail-mcp -f
tail -f /opt/yandex-mail-mcp/yandex-mail-mcp.log     # лог приложения
systemctl restart yandex-mail-mcp
```

Логи: `yandex-mail-mcp.log` с ротацией, 1 МБ × 2. Ошибки почты возвращаются клиенту
как `isError` с понятным текстом, а не как HTTP 500.

Если сменишь пароль приложения — поправь `config.json` и перезапусти сервис.

## 🔗 Связанные проекты

Другие MCP-коннекторы для тех же ИИ-агентов:

- 🛒 [**pyaterochka-mcp-tool**](https://github.com/dreamcatchered/pyaterochka-mcp-tool) — MCP-сервер и AI-бот для каталога «Пятёрочки»: магазины, товары, акции и цены по всей России. Первый коннектор, с которого всё началось.

## 📄 Лицензия

MIT — можно использовать и дорабатывать свободно.