Skip to main content
Glama
ChurikovSV

outlook-mcp-server

by ChurikovSV
README.md
# outlook-mcp-server

Локальный MCP-сервер для Microsoft Outlook в Windows: создание черновиков писем, подготовка встреч, работа с календарём и получение занятости сотрудников.

Почта и календарь используют настроенный профиль Outlook через `pywin32` / `win32com`. Для занятости дополнительно реализованы прямые HTTP-запросы к Outlook Web App (OWA) и запросы из авторизованной вкладки браузера через Chrome DevTools Protocol (CDP).

**Сервер не вызывает Outlook `Send()`.** Письма создаются как черновики; приглашения, обновления встреч и отмены автоматически не отправляются. Инструменты календаря при этом могут сохранять, изменять и удалять элементы.

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

- Windows и установленный Outlook с поддержкой COM-автоматизации.
- Настроенный профиль Outlook и права на необходимые ящики и календари.
- Python 3.11 или новее.
- Разрешение корпоративных политик на используемые операции Outlook.
- Для OWA — доступ к корпоративному серверу и подходящая авторизация.
- Для браузерного OWA — запущенный Chromium-совместимый браузер с доступным CDP и авторизованной вкладкой OWA.

OWA-реализация настроена на `https://mail.sberbank.ru` и часовой пояс `Russian Standard Time`. Эти значения заданы в коде; автоматического обнаружения другого сервера Exchange нет.

## Установка

В PowerShell:

```powershell
git clone https://github.com/ChurikovSV/outlook-mcp-server.git
cd outlook-mcp-server
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e .
```

Активация виртуального окружения не требуется. Зависимости перечислены в [pyproject.toml](pyproject.toml): MCP SDK, pywin32, Pydantic, openpyxl, requests, SSPI, truststore и Playwright.

Установка из исходников в режиме `-e` сохраняет ожидаемое расположение каталога `templates`.

## Запуск и подключение MCP-клиента

### Streamable HTTP — по умолчанию

```powershell
.\.venv\Scripts\python.exe -m outlook_mcp.server
```

Адрес MCP: `http://127.0.0.1:8000/mcp`.

Другой порт:

```powershell
.\.venv\Scripts\python.exe -m outlook_mcp.server --port 8765
```

В MCP-клиенте выберите Streamable HTTP и укажите адрес сервера. Клиент должен иметь сетевой доступ к компьютеру с сервером: `127.0.0.1` обозначает компьютер самого клиента.

### stdio

```powershell
.\.venv\Scripts\python.exe -m outlook_mcp.server --transport stdio
```

Для stdio клиент сам запускает процесс. В настройках клиента укажите абсолютный путь к `.venv\Scripts\python.exe` как команду и `-m`, `outlook_mcp.server`, `--transport`, `stdio` как отдельные аргументы.

### SSE

```powershell
.\.venv\Scripts\python.exe -m outlook_mcp.server --transport sse --port 8765
```

Используйте этот режим для клиента с поддержкой SSE. Путь SSE при стандартных настройках FastMCP — `/sse`.

| Параметр | По умолчанию | Назначение |
| --- | --- | --- |
| `--transport` | `streamable-http` | `streamable-http`, `stdio` или `sse` |
| `--host` | `127.0.0.1` | Адрес привязки HTTP/SSE |
| `--port` | `8000` | Порт HTTP/SSE |

Также доступна команда `outlook-mcp` из виртуального окружения.

В проекте не настроена аутентификация MCP-клиентов. Для локальной работы оставьте привязку к `127.0.0.1`; при сетевом размещении отдельно ограничьте доступ.

## Доступные инструменты

Сервер регистрирует 22 инструмента.

### Письма

| Инструмент | Назначение |
| --- | --- |
| `create_draft` | Один черновик с текстом, таблицами и вложениями |
| `create_bulk_drafts` | Отдельный черновик каждому получателю с общим содержимым |
| `create_drafts_batch` | Пакет писем с индивидуальными адресатами и содержимым |
| `create_draft_from_markdown` | Черновик из Markdown, файла или именованного шаблона |
| `create_bulk_drafts_from_template` | Персональный черновик для каждой строки CSV/XLSX |

### Календарь и занятость

| Инструмент | Назначение |
| --- | --- |
| `list_calendar_events` | Чтение основного календаря или календаря дополнительного хранилища |
| `prepare_calendar_meeting` | Открытие окна встречи для проверки и ручной отправки |
| `create_calendar_event` | Создание и сохранение события |
| `update_calendar_event` | Изменение и сохранение события |
| `delete_calendar_event` | Удаление элемента по `entry_id` |
| `get_employee_free_busy` | Занятость одного сотрудника через Outlook COM |
| `get_owa_free_busy` | Занятость нескольких сотрудников через прямой HTTP-запрос к OWA |
| `get_browser_owa_free_busy` | Занятость через авторизованную вкладку OWA и CDP |

### Диагностика

| Инструмент | Назначение |
| --- | --- |
| `get_outlook_status` | Проверка создания почтового объекта, версия Outlook |
| `diagnose_outlook` | Поэтапная проверка COM, MAPI и учётных записей |
| `diagnose_calendar` | Проверка основного календаря и фильтрации |
| `diagnose_mailbox_calendar` | Проверка календаря дополнительного/общего хранилища |
| `diagnose_send_as_account` | Проверка подготовки письма от имени другого ящика |
| `diagnose_meeting_attendee` | Проверка добавления и разрешения адреса участника |
| `diagnose_free_busy` | Проверка получения занятости через COM |
| `diagnose_owa_free_busy` | Проверка прямого HTTP-доступа к OWA |
| `diagnose_browser_owa` | Проверка CDP, вкладки OWA и получения canary-токена |

## Письма

JSON ниже — аргументы соответствующего MCP-инструмента.

### Один черновик

`create_draft`:

```json
{
  "to": ["user@example.com"],
  "subject": "Протокол встречи",
  "body": "Коллеги, направляю протокол встречи.",
  "cc": ["manager@example.com"],
  "attachments": ["C:\\Work\\reports\\protocol.pdf"]
}
```

Поддерживаются `cc`, `bcc` и `from_email`. Для другого отправителя используется свойство Outlook `SentOnBehalfOfName`. Создание черновика не подтверждает право отправки от имени этого ящика.

### Получатели из TXT, CSV или XLSX

Для `create_draft` и `create_bulk_drafts` можно указать `recipient_file`. Адреса из файла объединяются с переданными напрямую; дубликаты удаляются без учёта регистра.

```json
{
  "recipient_file": "C:\\Work\\mail\\users.xlsx",
  "recipient_file_sheet": "Получатели",
  "recipient_file_column": "Почта",
  "subject": "Уведомление",
  "body": "Коллеги, встреча начнётся в 15:30."
}
```

- TXT: адреса в текстовом файле.
- CSV: строка заголовков и столбец адресов; разделитель определяется среди запятой, точки с запятой и табуляции.
- XLSX: по умолчанию первый лист и столбец `email`; лист и столбец можно выбрать параметрами.
- TXT и CSV читаются в UTF-8, в том числе с BOM.

Для общего письма используйте `create_draft`, для отдельных писем каждому адресату — `create_bulk_drafts`:

```json
{
  "recipients": ["user1@example.com", "user2@example.com"],
  "subject": "Уведомление",
  "body": "Напоминаю о встрече."
}
```

### Разные письма в одном вызове

`create_drafts_batch`:

```json
{
  "drafts": [
    {
      "to": ["user1@example.com"],
      "subject": "Задача по проекту",
      "body": "Подготовьте отчёт к пятнице."
    },
    {
      "to": ["user2@example.com"],
      "subject": "Согласование",
      "body": "Проверьте приложенный документ.",
      "attachments": ["C:\\Work\\agreement.docx"]
    }
  ]
}
```

Массовые операции возвращают `created`, `failed` и `drafts`. При частичной ошибке уже созданные черновики остаются. Повтор всего запроса может создать дубликаты.

### Вложения через MCP-клиент

Если клиент может передать содержимое файла, используйте `uploaded_attachments`. Пример с корректным Base64 небольшого текстового файла:

```json
{
  "to": ["user@example.com"],
  "subject": "Вложение",
  "uploaded_attachments": [
    {
      "filename": "hello.txt",
      "content_base64": "SGVsbG8K"
    }
  ]
}
```

Сервер проверяет Base64, создаёт временный файл, прикладывает его, сохраняет черновик и удаляет временную копию. Лимит декодированного файла — 20 МиБ на вложение. Поддерживаются Base64 data URL.

`attachments` и `uploaded_attachments` можно сочетать. Все локальные пути относятся к компьютеру сервера; загрузка файла в чат сама по себе не делает файл доступным серверу.

### Таблицы

Аргументы `create_draft`:

```json
{
  "to": ["user@example.com"],
  "subject": "План работ",
  "body": "Согласованные действия:",
  "tables": [
    {
      "title": "Задачи",
      "columns": ["Задача", "Ответственный", "Срок"],
      "rows": [["Подготовить отчёт", "Иванов", "25.09.2026"]]
    }
  ]
}
```

Текст и таблицы преобразуются в HTML. Произвольный HTML в обычном `body` экранируется.

## Markdown и персональные шаблоны

`create_draft_from_markdown` принимает ровно один источник:

- `markdown` — текст в запросе;
- `markdown_file` — путь к локальному файлу `.md` или `.markdown`;
- `template_name` — имя шаблона из каталога [templates](templates).

Поддерживаются заголовки H1–H3, абзацы, жирный и курсивный текст, HTTP(S)-ссылки, списки, горизонтальные линии и таблицы Markdown. Это ограниченный набор Markdown; исходный HTML экранируется.

```json
{
  "to": ["user@example.com"],
  "subject": "Статус проекта {{project}}",
  "markdown": "# {{project}}\n\n{{name}}, добрый день.\n\nСтатус: **готово**.",
  "variables": {
    "project": "Внедрение",
    "name": "Анна"
  }
}
```

Переменные `{{name}}` подставляются в тему и текст. Отсутствие нужной переменной вызывает ошибку. Для одного получателя переменная `email` добавляется автоматически, если не задана явно.

В репозитории есть [test_personalized_email.md](templates/test_personalized_email.md) с переменными `project`, `first_name`, `status` и `deadline`.

### Персонализация по строкам CSV/XLSX

Пример CSV в UTF-8:

```csv
email,first_name,project,status,deadline
anna@example.com,Анна,Внедрение,В работе,25.09.2026
ivan@example.com,Иван,Миграция,На согласовании,28.09.2026
```

Вызов `create_bulk_drafts_from_template`:

```json
{
  "recipient_file": "C:\\Work\\mail\\projects.csv",
  "template_name": "test_personalized_email",
  "subject": "Статус проекта {{project}}"
}
```

Каждый столбец доступен шаблону как переменная. Служебные столбцы:

| Столбец | Назначение |
| --- | --- |
| `email` | Получатель; другое имя задаётся через `email_column` |
| `subject` или `Тема` | Тема строки с приоритетом над общим аргументом `subject` |
| `cc`, `bcc` | Дополнительные адресаты через запятую или точку с запятой |
| `from_email` | Отправитель строки с приоритетом над общим аргументом |

Для XLSX лист выбирается аргументом `sheet`, а не `recipient_file_sheet`. Параметр `subject_column` задаёт другое имя столбца темы. Вложения, переданные инструменту, общие для всех строк.

## Календарь

Передавайте местное время в ISO-формате без `Z` и смещения: `2026-09-21T15:30:00`. Конец интервала должен быть позже начала. Для OWA используется московское время.

### Чтение событий

`list_calendar_events`:

```json
{
  "start": "2026-09-21T00:00:00",
  "end": "2026-09-22T00:00:00",
  "limit": 100
}
```

Для дополнительного/общего хранилища добавьте `store_name` — его отображаемое имя в Outlook. Допускается точное или однозначное частичное совпадение. Проверить доступ можно через `diagnose_mailbox_calendar` с тем же именем.

`limit` — от 1 до 500, по умолчанию 100. Ответ содержит события с `entry_id`, временем, темой, местом, текстом и участниками, а также счётчики просмотра.

### Подготовка встречи для ручной отправки

`prepare_calendar_meeting`:

```json
{
  "subject": "Обсуждение проекта",
  "start": "2026-09-21T15:30:00",
  "end": "2026-09-21T16:00:00",
  "attendees": ["user1@example.com", "user2@example.com"],
  "location": "Переговорная 301",
  "body": "Обсудить текущий статус.",
  "reminder_minutes": 15
}
```

Нужен хотя бы один участник. Инструмент открывает окно встречи без программного сохранения и отправки. При блокировке `Recipients.Add` пробует `RequiredAttendees`. Проверьте участников в Outlook и отправьте приглашение вручную.

### Создание события

Те же аргументы можно передать в `create_calendar_event`; `attendees` здесь необязателен. Поддерживаются `all_day` и `reminder_minutes` (по умолчанию 15; `null` отключает напоминание).

Если Outlook блокирует сохранение встречи с участниками, сервер пытается открыть её окно. Проверяйте `status`, `event_saved`, `window_opened` и `manual_send_required`: открытое окно не означает сохранённое событие.

### Изменение и удаление

`update_calendar_event`:

```json
{
  "entry_id": "OUTLOOK_EVENT_ENTRY_ID",
  "location": "Переговорная 302",
  "start": "2026-09-21T16:00:00",
  "end": "2026-09-21T16:30:00",
  "disable_reminder": true
}
```

Можно также менять `subject`, `body`, `all_day` и `reminder_minutes`.

`delete_calendar_event`:

```json
{
  "entry_id": "OUTLOOK_EVENT_ENTRY_ID"
}
```

Используйте ID нужного события. Сервер не отправляет участникам обновления и отмены: изменение календаря организатора не означает уведомление участников. Параметр `store_name` предусмотрен только для чтения; инструменты изменения и удаления не принимают `store_id`.

## Занятость сотрудников

### Через Outlook COM

`get_employee_free_busy`:

```json
{
  "email": "user@example.com",
  "start": "2026-09-21T09:00:00",
  "end": "2026-09-21T18:00:00",
  "slot_minutes": 30
}
```

Ответ содержит `slots`, `free_slots` и объединённые `intervals`. Размер слота — от 5 до 1440 минут; COM-вариант ограничивает запрос 1440 слотами. Корпоративные политики могут блокировать разрешение адреса или получение занятости.

### Через прямой HTTP-запрос к OWA

`get_owa_free_busy`:

```json
{
  "emails": ["user1@example.com", "user2@example.com"],
  "start": "2026-09-21T09:00:00",
  "end": "2026-09-21T18:00:00",
  "slot_minutes": 30
}
```

Используется внутренний вызов OWA `GetUserAvailabilityInternal`. По умолчанию применяется Windows Integrated Authentication через SSPI.

Если обе переменные `OUTLOOK_MCP_OWA_COOKIE` и `OUTLOOK_MCP_OWA_CANARY` заданы в окружении процесса сервера, используются данные браузерной сессии. Cookie и canary — секреты сессии: не сохраняйте их в репозитории и не включайте в диагностические сообщения.

Проверка TLS использует системное хранилище доверенных сертификатов Windows через truststore. При привязке авторизации к браузеру прямой запрос может вернуть `authentication_required` или `authentication_redirect`.

### Через авторизованный браузер

1. Запустите Chromium-совместимый браузер с включённым CDP согласно настройкам корпоративной среды.
2. Откройте `https://mail.sberbank.ru` и выполните вход.
3. Проверьте подключение через `diagnose_browser_owa`.
4. Вызовите `get_browser_owa_free_busy` с теми же аргументами, что у HTTP-варианта; при необходимости добавьте `cdp_url`.

Приоритет адреса CDP: аргумент `cdp_url` → переменная `OUTLOOK_MCP_CDP_URL` → `http://127.0.0.1:9222`.

Пример настройки перед запуском:

```powershell
$env:OUTLOOK_MCP_CDP_URL = "http://127.0.0.1:9222"
.\.venv\Scripts\python.exe -m outlook_mcp.server
```

Переменная только указывает адрес: она не запускает браузер и не включает CDP. Playwright подключается к существующему браузеру. Сервер ищет вкладку OWA, получает canary и выполняет запрос внутри неё. Cookie и canary не возвращаются в обычном диагностическом результате.

**Оба браузерных инструмента перезагружают выбранную вкладку OWA для получения canary.** Перед вызовом завершите работу с несохранёнными формами. Не предоставляйте посторонним доступ к порту CDP.

Статусы занятости: `free`, `tentative`, `busy`, `out_of_office`, `working_elsewhere`; неизвестные коды отображаются как `unknown`. Пустой ответ не следует считать подтверждением свободного времени.

## Диагностика и текущие ограничения

Начните с `get_outlook_status` и `diagnose_outlook`, затем используйте диагностику нужного сценария. Для другого отправителя передавайте `diagnose_send_as_account` явный аргумент `email`, для общего календаря — `store_name`. В коде есть корпоративные значения по умолчанию, которые могут не соответствовать вашему профилю.

- Просмотр календаря начинается с ранних записей и ограничен 10 000 элементов. При большой истории ответ может быть неполным даже со статусом `ok`. Проверяйте `scanned`, `skipped` и `comparison_errors`; отдельного признака усечения пока нет.
- `delete_calendar_event` не проверяет тип найденного объекта. Ошибочный ID письма может привести к удалению письма.
- В нескольких операциях смещение часового пояса отбрасывается без преобразования. Используйте время без смещения в ожидаемом местном поясе; OWA зафиксирован на московском времени.
- Именованные шаблоны ищутся относительно дерева исходников. Их включение в устанавливаемый wheel пока не настроено.
- Работа зависит от профиля Outlook, прав на ящики, корпоративных политик и развёртывания OWA. Наличие инструмента не гарантирует доступность операции.
- В репозитории пока нет автоматического набора тестов и конфигурации CI.

## Структура проекта

| Файл или каталог | Назначение |
| --- | --- |
| [server.py](src/outlook_mcp/server.py) | MCP-инструменты и параметры запуска |
| [models.py](src/outlook_mcp/models.py) | Модели запросов |
| [outlook.py](src/outlook_mcp/outlook.py) | Черновики, получатели, таблицы и вложения |
| [markdown_email.py](src/outlook_mcp/markdown_email.py) | Преобразование Markdown в HTML |
| [markdown_draft.py](src/outlook_mcp/markdown_draft.py) | Шаблоны и Markdown-черновики |
| [bulk_template.py](src/outlook_mcp/bulk_template.py) | Персонализация по CSV/XLSX |
| [calendar.py](src/outlook_mcp/calendar.py) | Основной календарь и встречи |
| [mailbox_calendar.py](src/outlook_mcp/mailbox_calendar.py) | Чтение дополнительных календарей |
| [freebusy.py](src/outlook_mcp/freebusy.py) | Занятость через COM |
| [owa_freebusy.py](src/outlook_mcp/owa_freebusy.py) | Занятость через HTTP |
| [browser_owa_freebusy.py](src/outlook_mcp/browser_owa_freebusy.py) | Занятость через браузер и CDP |
| [templates](templates) | Именованные Markdown-шаблоны |

Диагностические функции также вынесены в `diagnostics.py`, `mailbox_calendar_diagnostics.py` и `send_as_diagnostics.py`.