Skip to main content
Glama
ItayElizur

mcp-outlook

by ItayElizur

mcp-outlook

A self-hostable MCP server for on-prem Microsoft Exchange over EWS (Exchange Web Services). Built for air-gapped / no-cloud environments — it talks directly to your internal Exchange server and never touches Microsoft Graph, Azure AD, or a desktop Outlook client.

  • Backend: exchangelib (EWS client)

  • Framework: FastMCP

  • Transport: streamable-http (a standalone HTTP server other internal hosts connect to)

  • Auth to Exchange: basic or NTLM (selectable via config)

Two modes

Mode

Who it serves

How it authenticates

static (default)

one mailbox

connects as that account (your own creds) — great for local beta testing

jwt

many users

validates each caller's user JWT, then acts on that user's mailbox via a service account + EWS Impersonation

How jwt mode bridges identity. Exchange can't consume your company JWTs (Outlook is PKINIT/Kerberos-based). So the MCP does two separate authentications that never mix:

User --(JWT)--> MCP validates the token, reads the user's email
                MCP --(service account, NTLM)--> Exchange
                MCP --(impersonation header = user's email)--> acts on the user's mailbox

The user's JWT is never sent to Exchange, and no user password/smart-card ever touches the MCP — only the single service credential does. See TODO.md for what admins must set up for jwt mode.

Related MCP server: OWA Exchange MCP Server

Tools

Tool

Purpose

list_emails(folder="inbox", limit=20)

Recent messages, newest first

search_emails(query, folder, start_date, end_date, sender, recipient, limit)

Text + date-range + sender/recipient search

get_email(message_id, folder="inbox")

Full message: body, recipients, attachment names

draft_email(to, subject, body)

Open the interactive compose widget (MCP Apps); user edits and sends

reply_email(message_id, folder, reply_all, body)

Open the compose widget pre-filled as a reply

forward_email(message_id, folder, to, body)

Open the compose widget pre-filled as a forward

send_email(to, subject, body, cc, bcc, html, attachments)

Send — callable only from the widget (app-only visibility)

search_contacts(query, limit)

Search contacts — callable only from the widget (app-only)

mark_email_read(message_id, folder) / mark_email_unread(...)

Toggle read status

delete_email(message_id, folder, permanent=False)

Move to Deleted Items, or permanently delete

flag_email_important(message_id, folder, important=True)

Set Outlook's High/Normal importance

move_email(message_id, destination, folder)

Move a message to another folder

list_folders()

Available mail folder names, including subfolders

list_events(start_date, end_date, limit)

Calendar events in a date range

get_event(event_id)

Full event: body, attendees, location

find_meeting_slots(attendees, duration_minutes, ...)

Scheduling assistant — ranks slots by attendee availability

draft_event(subject, start, end, location, body, required_attendees, optional_attendees)

Open the interactive event-draft widget

create_event(subject, start, end, required_attendees, ...)

Create the event — callable only from the widget (app-only)

accept_meeting(event_id) / decline_meeting(event_id)

Respond to a meeting invite

list_emails and search_emails also take unread_only=true to return only unread messages.

Setup

Requires Python 3.11+ and uv.

uv sync                 # create venv + install deps
cp .env.example .env    # then edit .env with your Exchange details
uv run python -m mcp_outlook

The server binds to MCP_HOST:MCP_PORT (default 127.0.0.1:8000) and serves the streamable-http MCP endpoint at /mcp.

Local beta testing (no admin setup needed)

Run against your own mailbox with your own username/password — no JWT, no impersonation, no service account, no smart card:

# in .env:
OUTLOOK_AUTH_MODE=static          # the default
OUTLOOK_EWS_ENDPOINT=https://mail.corp.local/EWS/Exchange.asmx
OUTLOOK_USERNAME=CORP\you
OUTLOOK_PASSWORD=...
uv run python -m mcp_outlook

Configuration

All settings come from environment variables (or a .env file). See .env.example for the full list. The essentials:

Var

Notes

OUTLOOK_AUTH_MODE

static (default, one mailbox) or jwt (multi-user tunnel)

OUTLOOK_EWS_ENDPOINT

Full asmx URL, e.g. https://mail.corp.local/EWS/Exchange.asmx. Preferred.

OUTLOOK_SERVER

Host-only alternative (endpoint assumed at /EWS/Exchange.asmx)

OUTLOOK_USERNAME

The connecting account — your own (static) or the service account (jwt). DOMAIN\user for NTLM, or email for basic. Not used with sspi

OUTLOOK_EMAIL

Mailbox to open (static mode). Optional — defaults to OUTLOOK_USERNAME when it's an email; ignored in jwt mode; required with sspi (no username to default from)

OUTLOOK_PASSWORD

account password. Not used with sspi

OUTLOOK_AUTH_TYPE

ntlm (default), basic, or sspi (Windows Integrated Auth — authenticates as this process's own AD identity, no username/password; Windows-only, needs uv sync --extra sspi)

OUTLOOK_JWT_ISSUER / _AUDIENCE

Required in jwt mode — token issuer and audience to require

OUTLOOK_JWT_JWKS_URI / _PUBLIC_KEY

jwt mode — signing keys (JWKS URI, or a static PEM for air-gapped)

OUTLOOK_JWT_EMAIL_CLAIM

jwt mode — claim holding the user's SMTP address (default email)

OUTLOOK_CA_BUNDLE

Path to internal CA .pem (for self-signed / internal CA)

OUTLOOK_VERIFY_SSL

true (default); false disables TLS verify (dev only)

MCP_HOST / MCP_PORT

HTTP bind (default 127.0.0.1:8000)

Finding your EWS endpoint

The EWS URL is not the OWA (webmail) URL. On the Exchange server:

Get-WebServicesVirtualDirectory | fl Name,InternalUrl,ExternalUrl

In an air-gapped setup you almost always want the InternalUrl.

Testing

Unit tests need no Exchange server (config parsing + serialization only):

uv run pytest

Live smoke test (with a real .env): start the server, connect an MCP client or the MCP Inspector, then call list_folderslist_emailssend_email (to yourself) and confirm receipt. Flip OUTLOOK_AUTH_TYPE between ntlm and basic to confirm whichever your Exchange admin has enabled.

Compose UI (MCP Apps)

draft_email opens an interactive MCP Apps widget — a React composer built into a single self-contained HTML file (src/mcp_outlook/widgets/compose.html). Any MCP Apps-capable host renders it inline in the chat thread.

Widget capabilities:

  • To field with inline contact search — type after the last comma to search contacts; pick a result to replace the query with a chip; valid addresses render as labelled chips.

  • Send / Discard — Send fires send_email directly from the widget (app-only — the model cannot call it); Discard collapses the card.

  • Supersession — opening a new draft greys out any older open draft widget.

  • Signature — every draft is pre-seeded with "Written with Airchat" (editable).

Air-gap guarantee: the built HTML (React + bridge JS inlined) ships with the Python package. No external asset requests are made at runtime; Node.js is only needed to rebuild the widget.

Rebuild the widget (dev only)

cd frontend
npm ci
npm run build     # tsc + vite build + artifact copy → src/mcp_outlook/widgets/compose.html

The build script validates no external URLs ended up in the HTML before copying.

Try it visually (standalone dev preview)

cd frontend && npm run dev
# Opens http://localhost:5173 with a mock host — no Exchange needed.
# Type in To, see chips form, contact results appear, Send/Discard collapse the card.

Gotchas

  • Basic auth is often disabled on modern Exchange — NTLM is the safer default.

  • Internal/self-signed certs require OUTLOOK_CA_BUNDLE, or the connection fails on TLS verification.

  • Multi-user (jwt) mode needs one Exchange grant — the service account must hold the ApplicationImpersonation RBAC role. See TODO.md. static mode needs no such grant.

  • .env holds a plaintext password. It is git-ignored; also lock down file permissions (chmod 600 .env) on the host.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    MCP server for any Microsoft Exchange / OWA deployment. Gives LLM agents access to email, calendar, directory search, folders, availability, and meeting analytics via 30 tools.
    30
    25 PyPI
    8
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables reading, sending, and managing Microsoft 365/Outlook emails through MCP tools with OAuth 2.1 authentication.
    119 npm
    MIT