outlook-mcp
This MCP server provides comprehensive access to on-premises Microsoft Exchange via EWS for managing email, calendar, contacts, folders, and availability data.
Email: List, search, read, send, reply, forward, move, copy, delete, and mark emails; create and send drafts; download attachments.
Folders: List and create mailbox folders.
Calendar: List, get, create, update, and delete events; respond to meeting invites; find free time slots; get personal availability; list calendars.
Contacts: Search (personal and GAL), get, create, update, and delete contacts.
System: Test connectivity (ping) and retrieve mailbox metadata.
The server is distributed as a PyPI package named outlook-ews-mcp.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@outlook-mcpfind my unread emails from last week"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
outlook-ews-mcp
outlook-ews-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.
Note: this project was previously referred to as
outlook-mcp. It was renamed because that name is already taken on PyPI by an unrelated project. The distribution and CLI name isoutlook-ews-mcp; until the first tagged PyPI release, install it from this repository as shown below.
Short description
Secure MCP server for on-prem Microsoft Exchange (EWS) with tools for email, calendar, contacts, folders, attachments, and free/busy availability.
Related MCP server: m365-mcp
Suggested repository topics / tags
mcp, model-context-protocol, exchange, microsoft-exchange, ews, outlook, email, calendar, contacts, python, automation, exchangelib
Highlights
email operations: list, search (substring or AQS), read, send, reply, forward, move, copy, delete, mark, categorize, bulk actions, raw MIME export, attachment add/delete
Inbox Rules, Out-of-Office (automatic replies), and read-only delegate listing
calendar operations: list, create, update, delete, respond to invites, find free slots, view a shared/delegate mailbox's calendar, Room Finder, bulk actions
contacts operations: search, read, create, update, delete
folder operations and attachment download
Exchange auth via
NTLMandBasicMCP transport via
stdioandSSEcentralized error mapping and a single
ExchangeClientabstractionprivacy-safer smoke check output by default
Docker support plus GitHub and GitLab CI/CD pipelines included
Tool catalog
System
ping_exchangeget_mailbox_infoget_out_of_office/set_out_of_officelist_delegates(read-only)list_inbox_rules/create_inbox_rule/update_inbox_rule/delete_inbox_rule— managing rules over EWS can wipe client-side rules created in desktop Outlook (documented EWS behaviour; see tool descriptions)
list_emailsget_emailget_email_mime— raw RFC 822 message content, base64-encodedget_threadsearch_emailssend_emailreply_emailforward_emailmove_emailcopy_emaildelete_emailmove_emails/copy_emails/delete_emails— bulk variants with per-item resultsmark_emailcategorize_emailmark_emails/categorize_emails— bulk variants with per-item resultslist_categorieslist_folderscreate_folderrename_folderdelete_foldercreate_draftupdate_draft— partial update;attachments, if given, replaces the whole setsend_draftget_attachmentadd_attachment/delete_attachment
Calendar
list_events/get_event/get_my_availability— passmailboxto view a colleague's default calendar instead of your own (requires delegate/impersonation access already granted on the server; not combinable withcalendar_id)create_eventupdate_eventdelete_eventrespond_to_invitedelete_events/respond_to_invites— bulk variants with per-item resultsfind_free_slotslist_calendarslist_room_lists/list_rooms— Room Finder
Contacts
search_contactsget_contactcreate_contactupdate_contactdelete_contact
Typical use cases
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
Security notes
What the current code does:
connects only to the Exchange/EWS endpoint configured in
EXCHANGE_SERVERdoes not contain telemetry, analytics, or third-party data export logic
keeps secrets in environment variables /
.envignores local secret files via
.gitignore(.env,.env.*, while keeping.env.example)structured MCP error responses do not include raw Exchange exception text, message bodies, attachment contents, or passwords; successful tools return the mailbox data they were asked for
server logs do not include message bodies or attachment contents either:
LOG_LEVELonly controls verbosity of the app's ownoutlook_mcp.*loggers, andexchangelib's SOAP XML loggers (which would otherwise dump full request/response XML, including atERRORlevel on unexpected transport errors) are always force-silenced regardless ofLOG_LEVELexcludes
.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 environmentsEXCHANGE_AUTH_TYPE=Basicsends credentials in the clear, so the server refuses to start against anhttp://EXCHANGE_SERVER; only override withEXCHANGE_ALLOW_INSECURE_BASIC_AUTH=truefor a local/test server you controlget_attachmentwrites files to disk, andsend_email/reply_email/forward_email/create_draftread local files (viaattachments) and attach their contents to outgoing mail — combined with untrusted email content, this is a plausible path for a prompt-injected exfiltration of any file readable by the process; local file access is refused by default and only works onceEXCHANGE_ATTACHMENT_ROOTis set to an absolute directory, which then confines bothattachmentspaths andget_attachment'ssave_pathto that directory tree (an unsetsave_pathstill falls back to the system temp directory)outlook-ews-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 stdoutif you enable file logging with
LOG_FILE, protect that file with OS permissionsif you publish Docker images from CI, protect GitLab/GitHub project access and registry permissions
Quick start
uv venv
source .venv/bin/activate
uv pip install -e .[dev]
cp .env.example .env
outlook-ews-mcpBy default the server runs in stdio mode. Set MCP_TRANSPORT=sse to start an HTTP server.
Configuration
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_ALLOW_INSECURE_BASIC_AUTH=false
EXCHANGE_VERSION=EXCHANGE_2016
EXCHANGE_TIMEOUT=30
EXCHANGE_MAX_RETRY_WAIT_SECONDS=90
EXCHANGE_TIMEZONE_FALLBACK=Europe/Moscow
EXCHANGE_IMPERSONATE_AS=
EXCHANGE_ATTACHMENT_MAX_SIZE_MB=10
EXCHANGE_ATTACHMENT_MAX_COUNT=10
EXCHANGE_ATTACHMENT_MAX_TOTAL_SIZE_MB=25
EXCHANGE_ATTACHMENT_ROOT=
EXCHANGE_EMAIL_BODY_MAX_CHARS=200000
EXCHANGE_EMAIL_MIME_MAX_SIZE_MB=25
EXCHANGE_SIGNATURE_TEXT=
EXCHANGE_SIGNATURE_HTML=
MCP_TRANSPORT=stdio
MCP_SSE_HOST=127.0.0.1
MCP_SSE_PORT=8080
MCP_MAX_CONCURRENCY=4
MCP_MAX_QUEUE_SIZE=20
LOG_LEVEL=INFO
LOG_FILE=Notes:
MCP_MAX_CONCURRENCYis how many read-only tool calls execute at once; mutating calls always run exclusively. More calls than fit queue to wait their turn, up toMCP_MAX_QUEUE_SIZE. See Request queue.MCP_MAX_QUEUE_SIZEcaps how many tool calls can be admitted at once (running + waiting); once full, further calls are rejected immediately with aserver_busyerrorEXCHANGE_SIGNATURE_TEXT/EXCHANGE_SIGNATURE_HTMLare appended to outgoing bodies (text signature for text bodies and replies/forwards, html for html bodies; no cross-conversion). Per-call opt-out viainclude_signature: false. EWS has no server-side signature API, so this is configuration, not the mailbox's Outlook signature.set
EXCHANGE_EMAIL_ADDRESSwhenEXCHANGE_USERNAMEis not an SMTP addressEXCHANGE_ALLOW_INSECURE_BASIC_AUTHonly matters withEXCHANGE_AUTH_TYPE=Basicand anhttp://EXCHANGE_SERVER; startup fails otherwise unless it's settrueEXCHANGE_IMPERSONATE_ASenables mailbox impersonation when Exchange permissions are configured accordinglyEXCHANGE_TIMEZONE_FALLBACKis only used when Exchange reports a timezone as an unresolvable GUID id; normal operations (naive datetimes, all-day event math) use the mailbox's own default timezone insteadEXCHANGE_ATTACHMENT_MAX_SIZE_MBis enforced both on local files attached to outgoing email and on attachments downloaded viaget_attachmentEXCHANGE_ATTACHMENT_MAX_COUNTandEXCHANGE_ATTACHMENT_MAX_TOTAL_SIZE_MBcap the number and combined size of attachments on a singlesend_email/reply_email/forward_email/create_draftcallEXCHANGE_ATTACHMENT_ROOTis unset by default, which refuses rather than allows local file access: any non-emptyattachmentslist (send/reply/forward/create_draft) or explicitget_attachmentsave_pathis rejected until it's set to an absolute directory, which then confines those paths to that directory tree.get_attachmentwith nosave_pathstill works unset, falling back to the system temp directoryEXCHANGE_MAX_RETRY_WAIT_SECONDSis a total wall-clock budget for retrying read-only calls when Exchange reports itself busy, not a retry count; set to0to disable retries and fail fast on the first error. Writes (send_email,create_event,delete_contact, ...) are never auto-retried, since an ambiguous failure could mean the operation already happened on the server before the error came back. See Request queue.EXCHANGE_EMAIL_BODY_MAX_CHARScapsget_email'sbody_text/body_html; a message beyond the cap is truncated andtruncated: trueis set on the response instead of returning an unbounded MCP payloadEXCHANGE_EMAIL_MIME_MAX_SIZE_MBrejects oversized raw MIME exports before base64 expansion; use attachment download or a narrower server-side export path for larger messageslist_eventsandfind_free_slotsaccept a boundedlimit(default 200, maximum 1000); event ranges are capped at 366 days and free-slot ranges at 31 days so broad queries cannot produce unbounded EWS or MCP responseslistings stay lean by design: email summaries carry the sender but not the recipient lists (
get_emailhas them),list_eventsreturns events without bodies (get_eventhas them), andget_emailreturns RFC-822 headers only withinclude_headers: truesend operations return
id: nullwhen EWS does not provide a durable ID for the sent copy (notably replies, forwards, and sent drafts)attachment metadata includes
downloadable; embedded Exchange item attachments havedownloadable: falseand cannot be saved byget_attachment
Request queue
Clients issue several tool calls in parallel. Exchange work is blocking, so the server runs it in worker threads and admits calls through one shared FIFO queue.
MCP_MAX_CONCURRENCY(default4) sets how many read-only calls run at once, so an agent asking for an email, the folder list and the calendar pays the slowest round trip instead of the sum. Mutating calls always run exclusively -- one at a time, never overlapping a read -- so read/write races on shared account state cannot happen. Callers beyond the limit wait their turn, served in the order they arrived; a waiting mutation blocks later reads from overtaking it.MCP_MAX_QUEUE_SIZE(default20) caps how many calls can be admitted at once, running or waiting. Once that many are already in, further calls get an immediateserver_busyerror instead of joining an unbounded queue.The transport stays responsive while work is in flight. Tools are awaited rather than run on the event loop thread, so finished responses go out immediately and pings are answered while a long call is still running.
There is no per-call timeout, deliberately. A thread blocked on a socket read cannot be killed from outside; the runtime can only stop waiting for it, which abandons the thread along with the EWS session it holds. exchangelib's session pool has a hard maximum and hands out sessions in a loop with no give-up path, so leaked sessions eventually starve it and every later call blocks forever. A slow call is waited out instead, bounded by
EXCHANGE_TIMEOUTplusEXCHANGE_MAX_RETRY_WAIT_SECONDS: the account's retry policy is fail-fast, so every EWS call raises on its first transient error rather than exchangelib retrying it forever internally, andExchangeClientretries only read-only calls itself, bounded by that wall-clock budget. Writes are never auto-retried. Overruns past the expected budget are logged.
Claude Desktop example
{
"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 check
After filling .env, run:
outlook-ews-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-ews-mcp-smokeDocker
docker build -t outlook-ews-mcp .
docker run --rm --env-file .env outlook-ews-mcpCI/CD
GitHub Actions and GitLab CI both run lint, formatting, type checks, tests, dependency audit, and package builds. Both use the uv version pinned in pyproject.toml.
GitHub additionally publishes tagged releases (v*) to PyPI with OIDC trusted publishing. Before the first release, configure a PyPI pending publisher for repository viartemev/outlook-ews-mcp, workflow ci.yml, and environment pypi; no long-lived PyPI token is stored in GitHub.
GitLab additionally 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:latestgit tag: pushes
:$CI_COMMIT_TAG
GitLab built-in registry variables are used:
CI_REGISTRYCI_REGISTRY_USERCI_REGISTRY_PASSWORDCI_REGISTRY_IMAGE
Development
uv run --python 3.12 --with '.[dev]' ruff check .
uv run --python 3.12 --with '.[dev]' pytest -qProject notes
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.
Contributing
Bug reports and PRs are welcome — see CONTRIBUTING.md for how to set up a dev environment and run the test suite without a real Exchange server. For vulnerability reports, see SECURITY.md.
License
MIT — see LICENSE.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server for any Microsoft Exchange / OWA deployment. Gives LLM agents access to email, calendar, directory search, folders, availability, and meeting analytics via 30 tools.307MIT
- AlicenseAqualityBmaintenanceProduction-grade MCP server for Microsoft 365, providing tools to manage Email, Calendar, Contacts, OneDrive, Teams, Tasks, and Users via delegated OAuth.4442MIT
- FlicenseAqualityBmaintenanceMCP server for corporate Exchange that provides access to email, calendar, and people directory via OWA JSON API.20
- FlicenseAqualityBmaintenanceMCP server for Claude to access on-premises Outlook/Exchange mailboxes via EWS with NTLM authentication, providing tools for email, calendar, and contact management without relying on Microsoft 365 or Graph API.18
Related MCP Connectors
Read, search, send, organize, draft and schedule email across your inboxes from any MCP client.
Fully-managed email as MCP tools - register domains, real mailboxes, send and receive mail.
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/viartemev/outlook-ews-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server