outlook-mcp is an MCP server for on-prem Microsoft Exchange via EWS (exchangelib).
It gives MCP-compatible clients access to email, calendar, contacts, folders, attachments, and availability data through a single, testable Python service.
Secure MCP server for on-prem Microsoft Exchange (EWS) with tools for email, calendar, contacts, folders, attachments, and free/busy availability.
mcp, model-context-protocol, exchange, microsoft-exchange, ews, outlook, email, calendar, contacts, python, automation, exchangelib
- email operations: list, search, read, send, reply, forward, move, copy, delete, mark
- calendar operations: list, create, update, delete, respond to invites, find free slots
- contacts operations: search, read, create, update, delete
- folder operations and attachment download
- Exchange auth via
NTLMandBasic - MCP transport via
stdioandSSE - centralized error mapping and a single
ExchangeClientabstraction - privacy-safer smoke check output by default
- Docker support and GitLab CI/CD pipeline included
ping_exchangeget_mailbox_info
list_emailsget_emailsearch_emailssend_emailreply_emailforward_emailmove_emailcopy_emaildelete_emailmark_emaillist_folderscreate_foldercreate_draftsend_draftget_attachment
list_eventsget_eventcreate_eventupdate_eventdelete_eventrespond_to_invitefind_free_slotsget_my_availabilitylist_calendars
search_contactsget_contactcreate_contactupdate_contactdelete_contact
- connect Claude Desktop or another MCP client to on-prem Exchange
- search inbox messages and fetch full email content
- send or draft emails from AI workflows
- inspect calendars and create meetings
- check free/busy windows for scheduling
- search personal contacts or the GAL
- expose Exchange operations through a controlled MCP boundary instead of direct mailbox scripting
What the current code does:
- connects only to the Exchange/EWS endpoint configured in
EXCHANGE_SERVER - does not contain telemetry, analytics, or third-party data export logic
- keeps secrets in environment variables /
.env - ignores local secret files via
.gitignore(.env,.env.*, while keeping.env.example) - logs only tool name, status, duration, and error code; it does not log message bodies, attachment contents, or passwords
- excludes
.env, tests, caches, and VCS metadata from Docker build context via.dockerignore
What you should still be careful with:
EXCHANGE_VERIFY_SSL=falsedisables TLS certificate verification and should be used only for trusted internal/self-signed environmentsget_attachmentwrites files to disk, so choose a safe destination directoryoutlook-mcp-smokeis privacy-safe by default and prints only masked mailbox info plus counts; setOUTLOOK_MCP_SMOKE_INCLUDE_DATA=trueonly if you explicitly want real inbox/event data in stdout- if you enable file logging with
LOG_FILE, protect that file with OS permissions - if you publish Docker images from CI, protect GitLab/GitHub project access and registry permissions
uv venv
source .venv/bin/activate
uv pip install -e .[dev]
cp .env.example .env
outlook-mcpBy default the server runs in stdio mode. Set MCP_TRANSPORT=sse to start an HTTP server.
Example .env:
EXCHANGE_SERVER=https://mail.company.com/EWS/Exchange.asmx
EXCHANGE_USERNAME=DOMAIN\\username
EXCHANGE_PASSWORD=secret
EXCHANGE_EMAIL_ADDRESS=user@company.com
EXCHANGE_VERIFY_SSL=true
EXCHANGE_AUTH_TYPE=NTLM
EXCHANGE_VERSION=EXCHANGE_2016
EXCHANGE_TIMEOUT=30
EXCHANGE_MAX_RETRIES=3
EXCHANGE_TIMEZONE=Europe/Moscow
EXCHANGE_IMPERSONATE_AS=
ATTACHMENT_MAX_SIZE_MB=10
MCP_TRANSPORT=stdio
MCP_SSE_HOST=127.0.0.1
MCP_SSE_PORT=8080
LOG_LEVEL=INFO
LOG_FILE=Notes:
- set
EXCHANGE_EMAIL_ADDRESSwhenEXCHANGE_USERNAMEis not an SMTP address OAuth2is reserved in config, but this build currently supports live auth withNTLMorBasicEXCHANGE_IMPERSONATE_ASenables mailbox impersonation when Exchange permissions are configured accordinglyATTACHMENT_MAX_SIZE_MBis enforced before sending/replying/forwarding/drafting with local attachments
{
"mcpServers": {
"outlook": {
"command": "outlook-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"
}
}
}
}After filling .env, run:
outlook-mcp-smokeDefault output is sanitized for safer verification. If you intentionally want sample mailbox/event data in the output:
OUTLOOK_MCP_SMOKE_INCLUDE_DATA=true outlook-mcp-smokedocker build -t outlook-mcp .
docker run --rm --env-file .env outlook-mcpThe repository includes .gitlab-ci.yml with these stages:
lint— runsrufftest— runspytestbuild— builds Python package artifacts intodist/release— builds and pushes a Docker image to the GitLab Container Registry on the default branch and on tags
Default image tagging behavior:
- default branch: pushes
:$CI_COMMIT_SHORT_SHAand:latest - git tag: pushes
:$CI_COMMIT_TAG
GitLab built-in registry variables are used:
CI_REGISTRYCI_REGISTRY_USERCI_REGISTRY_PASSWORDCI_REGISTRY_IMAGE
uv run --python 3.12 --with '.[dev]' ruff check .
uv run --python 3.12 --with '.[dev]' pytest -q- The implementation is centered around a single
ExchangeClientabstraction so auth, transport, retries, and error mapping stay centralized. - Errors are returned in a structured JSON form suitable for MCP
isError=truehandling. RTK.mdis not present in this repository, so the implementation followsPLAN.md.