Skip to content

Latest commit

 

History

History
388 lines (327 loc) · 32 KB

File metadata and controls

388 lines (327 loc) · 32 KB

outlook-ews-mcp

English · Русский

Python MCP Exchange Status License

outlook-ews-mcp — это MCP-сервер для доступа к локальному (on-prem) Microsoft Exchange через EWS (exchangelib). Он даёт MCP-совместимым клиентам (Claude Desktop, Claude Code и любым другим MCP-клиентам) доступ к почте, календарю, контактам, папкам, вложениям и данным о занятости — через единый, покрытый тестами Python-сервис, без прямых скриптов поверх почтового ящика.

Переименован из outlook-mcp. Это имя уже занято на PyPI другим, не связанным с проектом пакетом, поэтому имя дистрибутива и CLI теперь outlook-ews-mcp. Путь Python-импорта не изменился. До первого релиза в PyPI ставьте пакет прямо из этого репозитория, как показано ниже.

Содержание

Возможности

  • Почта — список, поиск (по подстроке или Advanced Query Syntax), чтение, отправка, ответ, пересылка, перемещение, копирование, удаление, отметка, категоризация, массовые операции, экспорт сырого MIME, добавление/удаление вложений
  • Система — правила входящих (Inbox Rules), автоответчик (Out-of-Office), список делегатов только на чтение
  • Календарь — список, создание, изменение, удаление, ответы на приглашения, поиск свободных слотов, просмотр календаря общего/делегированного ящика, Room Finder, массовые операции
  • Контакты — поиск, чтение, создание, изменение, удаление
  • Папки и вложения — CRUD для папок и скачивание вложений
  • АутентификацияNTLM и Basic для локального Exchange
  • Транспортstdio и SSE
  • Архитектура — централизованная обработка ошибок через единую абстракцию ExchangeClient (см. Заметки о проекте)
  • Безопасность — по умолчанию smoke-проверка не показывает реальные данные (см. Smoke-проверку)
  • Эксплуатация — Docker-образ, а также пайплайны GitHub и GitLab CI/CD "из коробки"

Каталог инструментов

Каждый инструмент ниже зарегистрирован в tool_specs.py — едином источнике правды для его имени, описания и схемы. Пометка только чтение — это инструменты, которые никогда не изменяют почтовый ящик: они получают больше параллелизма (см. Очередь запросов) и их безопасно вызывать "на всякий случай".

Система

Инструмент Описание Только чтение
ping_exchange Проверить соединение с Exchange
get_mailbox_info Получить метаданные почтового ящика
list_delegates Список делегатов ящика и уровней их прав на папки — только чтение, потому что exchangelib не поддерживает запись делегатов
list_inbox_rules Список серверных правил входящих
create_inbox_rule Создать серверное правило, например «от этого отправителя → переместить в папку»
update_inbox_rule Включить/выключить правило или изменить его приоритет (остальные поля здесь не редактируются)
delete_inbox_rule Удалить серверное правило по id
get_out_of_office Получить настройки автоответчика (Out-of-Office)
set_out_of_office Выключить, включить или запланировать автоответчик на период

⚠️ create_inbox_rule / update_inbox_rule / delete_inbox_rule управляют правилами через EWS, а это удаляет клиентский блок правил, который хранит настольный Outlook — из-за этого могут пропасть правила, созданные пользователем прямо в Outlook. Это задокументированное поведение EWS, а не баг сервера.

Почта

Инструмент Описание Только чтение
list_emails Список писем в папке
get_email Получить письмо целиком по id
get_email_mime Экспортировать сырое содержимое письма (RFC 822) в base64
get_thread Получить все сообщения переписки по порядку, с телами писем
search_emails Поиск по подстроке (тема/тело/отправитель) или по серверному Advanced Query Syntax
send_email Отправить новое письмо
reply_email Ответить на письмо
forward_email Переслать письмо
move_email Переместить письмо в другую папку
copy_email Скопировать письмо в другую папку
move_emails Массовое перемещение с результатом по каждому элементу — одна плохая id не ломает остальные
copy_emails Массовое копирование с результатом по каждому элементу
delete_emails Массовое удаление с результатом по каждому элементу (мягкое удаление в «Удалённые», если не указан hard_delete)
delete_email Удалить письмо
mark_email Изменить статус прочтения, важность или флажок "к исполнению"
categorize_email Задать, добавить или убрать категории Outlook (цветные метки)
mark_emails Массовая версия mark_email, с результатом по каждому элементу
categorize_emails Массовая версия categorize_email, с результатом по каждому элементу
list_categories Список используемых категорий со счётчиками, на основе последних сообщений (не общий список категорий ящика)
list_folders Список папок ящика
create_folder Создать папку в ящике
rename_folder Переименовать папку — отказывает на встроенных папках (Входящие, Отправленные, Календарь и т.д.)
delete_folder Удалить папку со всем содержимым — отказывает на встроенных папках
create_draft Создать черновик письма
update_draft Изменить черновик; непереданные поля не трогаются, attachments (если передан) полностью заменяет набор вложений
send_draft Отправить существующий черновик
add_attachment Прикрепить локальный файл к сообщению, обычно к черновику — файл должен лежать внутри EXCHANGE_ATTACHMENT_ROOT
delete_attachment Удалить одно вложение из сообщения по id
get_attachment Сохранить вложение на диск

Календарь

Инструмент Описание Только чтение
list_events Список событий календаря за период; передайте mailbox, чтобы посмотреть календарь коллеги (нужны права делегата/имперсонации на сервере, нельзя сочетать с calendar_id)
get_event Получить событие календаря по id; передайте mailbox для календаря коллеги
create_event Создать событие календаря
update_event Изменить событие календаря
delete_event Удалить событие календаря
respond_to_invite Принять, отклонить или ответить "под вопросом" на приглашение
find_free_slots Найти свободные слоты для встречи
delete_events Массовое удаление событий, с результатом по каждому элементу
respond_to_invites Массовый ответ на приглашения, с результатом по каждому элементу
get_my_availability Получить свободные/занятые слоты; передайте mailbox для календаря коллеги
list_calendars Список календарей
list_room_lists Список групп переговорных комнат (Room Finder)
list_rooms Список переговорных комнат в группе Room Finder

Контакты

Инструмент Описание Только чтение
search_contacts Поиск контактов
get_contact Получить контакт по id
create_contact Создать личный контакт
update_contact Изменить личный контакт
delete_contact Удалить личный контакт

Типичные сценарии использования

  • Подключить Claude Desktop или другой MCP-клиент к локальному Exchange
  • Искать письма во входящих и получать полное содержимое письма
  • Отправлять письма или создавать черновики из AI-сценариев
  • Просматривать календари и создавать встречи
  • Проверять свободные/занятые окна для планирования
  • Искать в личных контактах или в глобальной адресной книге (GAL)
  • Дать доступ к операциям Exchange через контролируемую MCP-границу вместо прямых скриптов поверх ящика

Заметки по безопасности

Что уже делает текущий код:

Ограниченная связность Подключается только к тому EWS-адресу, что задан в EXCHANGE_SERVER
Никакой телеметрии Не содержит телеметрии, аналитики или логики экспорта данных на сторону
Секреты остаются локально Хранит секреты в переменных окружения / .env, которые игнорируются .gitignore (.env, .env.*, при этом .env.example остаётся в репозитории)
Чистые ответы об ошибках Структурированные ответы об ошибках MCP никогда не содержат сырой текст исключений Exchange, тела писем, содержимое вложений или пароли; успешные вызовы возвращают только те данные ящика, которые были запрошены
Чистые логи LOG_LEVEL управляет только логами самого приложения (outlook_mcp.*); SOAP XML-логи exchangelib, которые иначе печатали бы полный XML запроса/ответа даже на уровне ERROR при ошибках транспорта, всегда принудительно отключены
Чистая сборка Docker .dockerignore исключает .env, тесты, кэши и метаданные VCS из контекста сборки

На что всё же стоит обратить внимание:

  • EXCHANGE_VERIFY_SSL=false отключает проверку TLS-сертификата — используйте только для доверенных внутренних окружений или с самоподписанными сертификатами.
  • EXCHANGE_AUTH_TYPE=Basic передаёт учётные данные в открытом виде, поэтому сервер отказывается запускаться с EXCHANGE_SERVER по http://; переопределить это можно только через EXCHANGE_ALLOW_INSECURE_BASIC_AUTH=true — и только для локального/ тестового сервера, который вы контролируете.
  • get_attachment пишет файлы на диск, а send_email/reply_email/forward_email/ create_draft читают локальные файлы (через attachments) и прикрепляют их содержимое к исходящей почте. В сочетании с недоверенным содержимым писем это правдоподобный путь для эксфильтрации файла через prompt injection — любого файла, доступного процессу на чтение. Доступ к локальным файлам запрещён по умолчанию и начинает работать только после того, как EXCHANGE_ATTACHMENT_ROOT указывает на абсолютный путь к директории — тогда и пути в attachments, и save_path у get_attachment ограничиваются этим деревом директорий (при пустом save_path файл всё равно попадёт во временную директорию системы).
  • outlook-ews-mcp-smoke по умолчанию безопасен для приватности и печатает только замаскированную информацию о ящике и счётчики; ставьте OUTLOOK_MCP_SMOKE_INCLUDE_DATA=true, только если осознанно хотите увидеть реальные данные писем/событий в stdout.
  • Если включаете файловое логирование через LOG_FILE, защитите этот файл правами ОС.
  • Если публикуете Docker-образы из CI, защитите доступ к проекту GitLab/GitHub и права на registry.

Быстрый старт

uv venv
source .venv/bin/activate
uv pip install -e .[dev]
cp .env.example .env
outlook-ews-mcp

По умолчанию сервер работает в режиме stdio. Установите MCP_TRANSPORT=sse, чтобы поднять HTTP-сервер.

Конфигурация

Минимальный .env для старта — у всего остального ниже есть рабочее значение по умолчанию:

EXCHANGE_SERVER=https://mail.company.com/EWS/Exchange.asmx
EXCHANGE_USERNAME=DOMAIN\username
EXCHANGE_PASSWORD=secret
EXCHANGE_EMAIL_ADDRESS=user@company.com
EXCHANGE_AUTH_TYPE=NTLM

Файл со всеми переменными и комментариями к каждой лежит в .env.example.

Переменная По умолчанию Описание
EXCHANGE_SERVER (обязательна) Адрес EWS, например https://mail.company.com/EWS/Exchange.asmx
EXCHANGE_USERNAME (обязательна) DOMAIN\username или UPN. Ровно один обратный слэш — dotenv не обрабатывает escape-последовательности
EXCHANGE_PASSWORD (обязательна) Пароль учётной записи
EXCHANGE_EMAIL_ADDRESS не задано SMTP-адрес; задайте, если EXCHANGE_USERNAME не является таким адресом
EXCHANGE_AUTH_TYPE NTLM NTLM или Basic
EXCHANGE_ALLOW_INSECURE_BASIC_AUTH false Разрешить Basic-аутентификацию по http:// — только для локальных/тестовых серверов
EXCHANGE_VERIFY_SSL true Проверять TLS-сертификат сервера; false только для доверенных внутренних/самоподписанных окружений
EXCHANGE_VERSION не задано (автоопределение) Версия сервера Exchange, например EXCHANGE_2016
EXCHANGE_TIMEZONE_FALLBACK Europe/Moscow Используется, только если Exchange вернул нераспознаваемый GUID часового пояса; в обычных операциях используется часовой пояс самого ящика
EXCHANGE_TIMEOUT 30 Таймаут на один запрос, в секундах (1–300)
EXCHANGE_MAX_RETRY_WAIT_SECONDS 90 Общий бюджет по времени (не количество попыток) на повтор только для чтения, когда Exchange сообщает о занятости; 0 отключает повторы. Записи никогда не повторяются автоматически
EXCHANGE_IMPERSONATE_AS не задано Ящик для имперсонации (требует прав имперсонации в Exchange)
EXCHANGE_ATTACHMENT_MAX_SIZE_MB 10 Макс. размер одного вложения — и при отправке, и при скачивании через get_attachment (1–100)
EXCHANGE_ATTACHMENT_MAX_COUNT 10 Макс. число вложений в одном вызове send/reply/forward/create_draft (1–100)
EXCHANGE_ATTACHMENT_MAX_TOTAL_SIZE_MB 25 Макс. суммарный размер вложений в одном вызове (1–500)
EXCHANGE_ATTACHMENT_ROOT не задано (отключено) Директория, к которой привязаны пути вложений. Без значения доступ к локальным файлам для attachments/save_path запрещён; укажите абсолютный путь, чтобы разрешить файлы внутри него
EXCHANGE_EMAIL_BODY_MAX_CHARS 200000 Лимит на body_text/body_html в get_email (1 000–5 000 000); более длинные тела обрезаются, и в ответе выставляется truncated: true
EXCHANGE_EMAIL_MIME_MAX_SIZE_MB 25 Лимит на размер сырого MIME-экспорта до расширения в base64 (1–100)
EXCHANGE_SIGNATURE_TEXT не задано Добавляется к исходящим текстовым телам писем, ответам и пересылкам. У EWS нет API подписи, так что это настройка сервера, а не подпись из Outlook пользователя
EXCHANGE_SIGNATURE_HTML не задано Добавляется к исходящим HTML-телам. Та же оговорка, что и выше; автоматической конвертации между форматами нет. Любую из подписей можно отключить на конкретный вызов через include_signature: false
MCP_TRANSPORT stdio stdio или sse
MCP_SSE_HOST 127.0.0.1 Хост для привязки при MCP_TRANSPORT=sse
MCP_SSE_PORT 8080 Порт для привязки при MCP_TRANSPORT=sse
MCP_MAX_CONCURRENCY 4 Сколько вызовов "только для чтения" выполняются одновременно (1–8); операции записи всегда выполняются монопольно. См. Очередь запросов
MCP_MAX_QUEUE_SIZE 20 Сколько вызовов может быть принято одновременно, включая выполняющиеся и ожидающие (1–1000); сверх лимита — сразу ошибка server_busy
LOG_LEVEL INFO DEBUG, INFO, WARNING или ERROR
LOG_FILE не задано (stderr) Путь к файлу логов; если задан, защитите его правами ОС

Поведение, не привязанное к одной переменной:

  • list_events и find_free_slots принимают ограниченный limit (по умолчанию 200, максимум 1000); диапазон событий ограничен 366 днями, а диапазон поиска свободных слотов — 31 днём, чтобы широкие запросы не приводили к неограниченным ответам EWS или MCP.
  • Списки специально сделаны компактными: сводки писем содержат отправителя, но не список получателей (он есть в get_email), list_events возвращает события без тел (они есть в get_event), а get_email возвращает заголовки RFC-822 только при include_headers: true.
  • Операции отправки возвращают id: null, если EWS не даёт устойчивый id для отправленной копии (в первую очередь это касается ответов, пересылок и отправки черновиков).
  • Метаданные вложения включают downloadable; у встроенных вложений типа Exchange item downloadable: false, и get_attachment не может их сохранить.

Очередь запросов

Клиенты выпускают несколько вызовов инструментов параллельно. Работа с Exchange блокирующая, поэтому сервер выполняет её в рабочих потоках и пропускает вызовы через одну общую FIFO-очередь.

  • MCP_MAX_CONCURRENCY (по умолчанию 4) задаёт, сколько вызовов только для чтения выполняются одновременно — так агент, запрашивающий письмо, список папок и календарь, платит временем самого медленного запроса, а не суммой всех. Операции записи всегда выполняются монопольно — по одной, никогда не пересекаясь с чтением — поэтому гонок чтения/записи над общим состоянием ящика не бывает. Вызовы сверх лимита ждут своей очереди в порядке поступления; ожидающая операция записи не даёт более поздним чтениям обогнать её.
  • MCP_MAX_QUEUE_SIZE (по умолчанию 20) ограничивает, сколько вызовов может быть принято одновременно — выполняющихся и ожидающих. Когда лимит достигнут, дальнейшие вызовы сразу получают ошибку server_busy вместо попадания в неограниченную очередь.
  • Транспорт остаётся отзывчивым, пока идёт работа. Инструменты выполняются через await, а не в потоке event loop, поэтому готовые ответы отправляются немедленно, а пинги обрабатываются даже во время выполнения долгого вызова.
  • Таймаута на отдельный вызов намеренно нет. Поток, заблокированный на чтении сокета, нельзя убить снаружи — рантайм может только перестать ждать его, что оставляет поток работать дальше вместе с EWS-сессией, которую он держит. Пул сессий exchangelib имеет жёсткий максимум и раздаёт сессии в цикле без возможности отказаться от ожидания, поэтому "утёкшие" сессии рано или поздно исчерпывают пул, и все последующие вызовы блокируются навсегда. Вместо этого медленный вызов просто дожидается, ограниченный суммой EXCHANGE_TIMEOUT и EXCHANGE_MAX_RETRY_WAIT_SECONDS: политика повторов у аккаунта — fail-fast, поэтому каждый вызов EWS завершается с ошибкой при первой же временной проблеме, вместо того чтобы exchangelib бесконечно повторял его сам; ExchangeClient затем сам повторяет только вызовы на чтение, ограниченный тем же бюджетом по времени. Записи никогда не повторяются автоматически. Превышения ожидаемого бюджета логируются.

Пример для Claude Desktop

{
  "mcpServers": {
    "outlook": {
      "command": "outlook-ews-mcp",
      "env": {
        "EXCHANGE_SERVER": "https://mail.company.com/EWS/Exchange.asmx",
        "EXCHANGE_USERNAME": "DOMAIN\\username",
        "EXCHANGE_PASSWORD": "secret",
        "EXCHANGE_EMAIL_ADDRESS": "user@company.com",
        "EXCHANGE_AUTH_TYPE": "NTLM"
      }
    }
  }
}

Smoke-проверка

После заполнения .env запустите:

outlook-ews-mcp-smoke

По умолчанию вывод очищен от чувствительных данных для более безопасной проверки. Если осознанно нужны примеры реальных данных ящика/событий в выводе:

OUTLOOK_MCP_SMOKE_INCLUDE_DATA=true outlook-ews-mcp-smoke

Docker

docker build -t outlook-ews-mcp .
docker run --rm --env-file .env outlook-ews-mcp

CI/CD

И GitHub Actions, и GitLab CI запускают линтер, проверку форматирования, проверку типов, тесты, аудит зависимостей и сборку пакета — обе системы используют версию uv, зафиксированную в pyproject.toml.

GitHub Дополнительно публикует релизы по тегам (v*) в PyPI через OIDC trusted publishing. Перед первым релизом настройте PyPI pending publisher для репозитория viartemev/outlook-ews-mcp, workflow ci.yml и окружения pypi — долгоживущий PyPI-токен в GitHub не хранится.
GitLab Дополнительно собирает и пушит Docker-образ в GitLab Container Registry на default-ветке и по тегам, используя встроенные переменные CI_REGISTRY / CI_REGISTRY_USER / CI_REGISTRY_PASSWORD / CI_REGISTRY_IMAGE.

Тегирование образов по умолчанию:

Триггер Какие теги пушатся
Default-ветка :$CI_COMMIT_SHORT_SHA и :latest
Git-тег :$CI_COMMIT_TAG

Разработка

uv run --python 3.12 --with '.[dev]' ruff check .
uv run --python 3.12 --with '.[dev]' pytest -q

Заметки о проекте

  • Реализация строится вокруг единой абстракции ExchangeClient, чтобы аутентификация, транспорт, повторы и обработка ошибок оставались в одном месте.
  • Ошибки возвращаются в структурированном JSON-формате, подходящем для обработки MCP как isError=true.

Контрибьютинг

Баг-репорты и PR приветствуются — как настроить окружение разработки и запустить тесты без реального сервера Exchange, смотрите в CONTRIBUTING.md. Об уязвимостях — в SECURITY.md.

Лицензия

MIT — см. LICENSE.