English · Русский
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 ставьте пакет прямо из этого репозитория, как показано ниже.
- Возможности
- Каталог инструментов
- Типичные сценарии использования
- Заметки по безопасности
- Быстрый старт
- Конфигурация
- Очередь запросов
- Пример для Claude Desktop
- Smoke-проверка
- Docker
- CI/CD
- Разработка
- Заметки о проекте
- Контрибьютинг
- Лицензия
- Почта — список, поиск (по подстроке или 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 itemdownloadable: 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затем сам повторяет только вызовы на чтение, ограниченный тем же бюджетом по времени. Записи никогда не повторяются автоматически. Превышения ожидаемого бюджета логируются.
{
"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"
}
}
}
}После заполнения .env запустите:
outlook-ews-mcp-smokeПо умолчанию вывод очищен от чувствительных данных для более безопасной проверки. Если осознанно нужны примеры реальных данных ящика/событий в выводе:
OUTLOOK_MCP_SMOKE_INCLUDE_DATA=true outlook-ews-mcp-smokedocker build -t outlook-ews-mcp .
docker run --rm --env-file .env outlook-ews-mcpИ 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.