OWA Exchange MCP Server
Click on "Deploy 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., "@OWA Exchange MCP Server Find a meeting time with Alice and Bob next Tuesday afternoon"
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.
OWA Exchange MCP Server
MCP (Model Context Protocol) server for any Microsoft Exchange / OWA (Outlook Web Access) deployment. Gives LLM agents access to email, calendar, directory search, folders, availability, and meeting analytics via 30 tools.
Works with any on-premise or hosted Exchange server that exposes OWA.
Quick Start
# Copy and edit the MCP config with your OWA URL
cp .mcp.json.example .mcp.json
# One-time: set up encrypted credentials
python3 login.py --setup
# Login (opens headless browser, 2FA approval required)
python3 login.py
# Install the MCP server
pip install -e .Related MCP server: outlook-mcp
Install
Add to your MCP client config. Replace https://owa.example.com with your OWA URL.
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"exchange": {
"command": "uvx",
"args": ["exchange-mcp-server"],
"env": {
"EXCHANGE_OWA_URL": "https://owa.example.com"
}
}
}
}Cursor (.cursor/mcp.json):
{
"mcpServers": {
"exchange": {
"command": "uvx",
"args": ["exchange-mcp-server"],
"env": {
"EXCHANGE_OWA_URL": "https://owa.example.com"
}
}
}
}Claude Code (.mcp.json):
{
"mcpServers": {
"exchange": {
"command": "uvx",
"args": ["exchange-mcp-server"],
"env": {
"EXCHANGE_OWA_URL": "https://owa.example.com"
}
}
}
}Configuration
Variable | Required | Description |
| Yes | Base URL of your OWA instance |
| No | Path to session cookies file (default: |
Login
Option A: Via MCP tool (recommended)
The login tool handles credential setup and authentication within the MCP session — no separate terminal needed.
First time (setup + login):
login(master_password="...", username="user@example.com", password="...")Subsequent logins (decrypts stored credentials):
login(master_password="...")Option B: Via CLI
python3 login.py --setup # First time: save encrypted credentials
python3 login.py # Login with 2FABoth methods:
Open a headless browser to your OWA URL
Submit credentials
Wait for 2FA approval (up to 90 seconds)
Save encrypted session cookies to
session-cookies.txt
Credentials and session cookies are encrypted at rest with AES-256 (PBKDF2 key derivation, 480k iterations).
Tools (30)
Email (10)
Tool | Description |
| List emails from a folder with filtering |
| Get full email content by ID |
| Send a new email |
| Reply to an email |
| Forward an email |
| Delete an email |
| Move email to another folder |
| Mark email as read/unread |
| Download file attachments from an email |
| Extract hyperlinks from an email body |
Calendar (7)
Tool | Description |
| Get events in a date range (supports recurring expansion) |
| Create a meeting with attendees |
| Update an existing meeting |
| Cancel a meeting and notify attendees |
| Accept, decline, or tentatively accept |
| Download file attachments from a calendar event |
| Extract hyperlinks from an event description |
Directory (1)
Tool | Description |
| Search people in Active Directory |
Folders (7)
Tool | Description |
| List mail folders with unread counts |
| Create a new mail folder |
| Rename an existing folder |
| Empty all items from a folder |
| Delete a mail folder |
| Move a folder to a different parent |
| Check if the OWA session is authenticated |
Availability (2)
Tool | Description |
| Find free slots in your calendar |
| Find common free slots for multiple people |
Analytics (2)
Tool | Description |
| Meeting count statistics for multiple people |
| Connection matrix — who you meet with most |
Auth (1)
Tool | Description |
| Authenticate to OWA (credential setup + 2FA login) |
Files
login.py # Browser-based 2FA login (standalone CLI)
exchange_mcp/
server.py # FastMCP server entry point
owa_client.py # OWA HTTP client
auth.py # Async login logic (shared by MCP tool)
tools/
email.py # Email tools
calendar.py # Calendar tools
people.py # Directory search
folders.py # Folder management & session check
availability.py # Free time / meeting time
analytics.py # Meeting stats & contacts
auth.py # Login tool
pyproject.toml # Package configWarning
Every Exchange / OWA deployment has its own authentication setup — some require 2FA (push notifications, TOTP, SMS), others use single-factor login or SSO. The login logic in this project (login.py and exchange_mcp/auth.py) is written for a specific 2FA flow (mobile push approval). If your OWA server uses a different 2FA method or no 2FA at all, you will need to modify or remove the login logic to match your environment.
Security
Credentials and session cookies encrypted with AES-256-Fernet
Master password never stored
PBKDF2 with 480,000 iterations for key derivation
Credential and cookie files have
0600permissionsCookies decrypted into memory only — never written to disk as plaintext (via MCP tool)
Session cookies never transmitted except to your OWA server
Available Tools
30 toolscancel_meetingA
Cancel (delete) a calendar meeting and notify attendees.
Args: item_id: The ItemId of the meeting to cancel. message: Optional cancellation message to attendees.
Returns: JSON object with cancellation result.
Inputs: item_id (required), message. Returns: result
check_sessionA
Check whether the current OWA session is authenticated.
Makes a lightweight GetFolder call on the inbox. Returns session status, the mailbox display name, and cookie file path.
Returns: JSON object with authenticated (bool), mailbox name, and details.
Inputs: no named parameters. Returns: result
create_folderA
Create a new mail folder.
Args: name: Display name for the new folder. parent_folder_id: Parent folder to create under. Defaults to "msgfolderroot" (top-level). Can be a distinguished folder name or a raw folder ID.
Returns: JSON object with the created folder's id and name.
Inputs: name (required), parent_folder_id. Returns: result
create_meetingA
Create a new calendar meeting.
Args: subject: Meeting subject/topic. date: Meeting date in YYYY-MM-DD format. start_time: Start time in HH:MM format. duration_minutes: Duration in minutes (default 30). required_attendees: List of email addresses for required attendees. optional_attendees: List of email addresses for optional attendees. location: Location or video link. description: Meeting description/body text. is_all_day: Whether this is an all-day event. reminder_minutes: Minutes before start for reminder (default 15). importance: Importance level: Low, Normal, or High. sensitivity: Sensitivity: Normal, Personal, Private, or Confidential.
Returns: JSON object with creation result including item_id on success.
Inputs: subject (required), date (required), start_time (required), duration_minutes, +8 more. Returns: result
delete_emailA
Delete one or more emails.
Args: item_ids: List of Exchange ItemIds to delete. permanent: If True, permanently delete (HardDelete). Otherwise move to Deleted Items.
Inputs: item_ids (required), permanent. Returns: result
delete_folderA
Delete a mail folder.
Args: folder_id: The Exchange folder ID to delete (from get_folders). permanent: If True, permanently delete (HardDelete). Otherwise move to Deleted Items. Default False.
Returns: JSON object with success status.
Inputs: folder_id (required), permanent. Returns: result
download_attachmentsA
Download all file attachments from an email to disk.
Args: item_id: The Exchange ItemId of the email to download attachments from. target_folder: Local directory to save files (default /tmp/attachments).
Inputs: item_id (required), target_folder. Returns: result
download_event_attachmentsA
Download all file attachments from a calendar event to disk.
Args: item_id: The Exchange ItemId of the calendar event. target_folder: Local directory to save files (default /tmp/attachments).
Inputs: item_id (required), target_folder. Returns: result
empty_folderB
Empty all items from a mail folder.
Args: folder_id: The Exchange folder ID to empty (from get_folders). delete_sub_folders: If True, also delete sub-folders. Default False. permanent: If True, permanently delete items (HardDelete). Otherwise move to Deleted Items. Default False.
Returns: JSON object with success status.
Inputs: folder_id (required), delete_sub_folders, permanent. Returns: result
find_free_timeA
Find free time slots in your own calendar.
Analyzes your calendar events and returns available time slots within working hours for each weekday in the range.
Args: start_date: Start date in YYYY-MM-DD format. end_date: End date in YYYY-MM-DD format. Defaults to start_date if not provided (single-day search). duration_minutes: Minimum slot duration in minutes. Default 30. start_hour: Working day start hour (0-23). Default 9. end_hour: Working day end hour (0-23). Default 18.
Returns: JSON object with free_slots keyed by date, each containing an array of {start, end, duration_minutes} objects.
Inputs: start_date (required), end_date, duration_minutes, start_hour, +1 more. Returns: result
find_meeting_timeA
Find meeting times that work for multiple people.
Uses the OWA GetUserAvailability API to check cross-mailbox availability and find common free slots for all attendees. Supports multi-day ranges — searches each weekday in the range.
Args: emails: Comma-separated email addresses or names of attendees. start_date: Start date in YYYY-MM-DD format. end_date: End date in YYYY-MM-DD format. Defaults to start_date if not provided (single-day search). duration_minutes: Minimum slot duration in minutes. Default 30. start_hour: Working day start hour (0-23). Default 9. end_hour: Working day end hour (0-23). Default 18.
Returns: JSON object with attendee info and free_slots keyed by date, each containing an array of {start, end, duration_minutes}.
Inputs: emails (required), start_date (required), end_date, duration_minutes, +2 more. Returns: result
find_personA
Search for people in the corporate directory.
Looks up employees by name, email, department, or keyword using the Exchange ResolveNames API against Active Directory.
Args: query: Name, email address, or keyword to search for.
Returns: JSON array of matching people with contact details (name, email, job_title, department, company, office, phones, address, manager, direct_reports, alias).
Inputs: query (required). Returns: result
forward_emailB
Forward an email to other recipients.
Args: item_id: The Exchange ItemId of the email to forward. to: Comma-separated list of recipient email addresses. body: Optional message to include above the forwarded content.
Inputs: item_id (required), to (required), body. Returns: result
get_calendar_eventsA
Get calendar events within a date range.
Args: start_date: Start date in YYYY-MM-DD format. end_date: End date in YYYY-MM-DD format. include_body: If True, fetch full event details (organizer, attendees, body) via GetItem for each event. Slower but more complete. Ignored when expand_recurring=True. expand_recurring: If True, show every individual occurrence of recurring meetings (via GetUserAvailability). This gives an accurate count of all events but returns fewer fields per event (no item_id, attendees, or body). Default False.
Returns: JSON array of event objects with subject, start, end, location, attendees, etc.
Inputs: start_date (required), end_date (required), include_body, expand_recurring. Returns: result
get_emailB
Get a single email with full body and details.
Args: item_id: The Exchange ItemId of the email to retrieve.
Inputs: item_id (required). Returns: result
get_email_linksA
Extract all hyperlinks from an email's HTML body.
Args: item_id: The Exchange ItemId of the email to extract links from.
Inputs: item_id (required). Returns: result
get_emailsA
Get emails from a mailbox folder.
Args: folder: Folder name (Inbox, Sent, Drafts, Deleted, Junk, or custom name). limit: Maximum number of emails to return (default 10, max 50). offset: Number of emails to skip for pagination. include_body: If True, fetch full body for each email (slower). unread_only: If True, only return unread emails. ids_only: If True, return only item IDs and dates (compact, for bulk ops). Max limit raised to 500 in this mode.
Inputs: folder, limit, offset, include_body, +2 more. Returns: result
get_event_linksA
Extract all hyperlinks from a calendar event's HTML description.
Args: item_id: The Exchange ItemId of the calendar event to extract links from.
Inputs: item_id (required). Returns: result
get_foldersA
List mail folders from the Exchange mailbox.
Args: parent_folder_id: Parent folder to list children of. Defaults to "msgfolderroot" (top-level). Can be a distinguished folder name or a raw folder ID. recursive: If True, traverse all subfolders recursively (Deep). If False, only list immediate children (Shallow).
Returns: JSON array of folder objects with: name, id, total_count, unread_count, child_folder_count.
Inputs: parent_folder_id, recursive. Returns: result
get_meeting_contactsA
Build a connection matrix: who you meet with most often.
Analyzes your calendar over a date range, accounting for recurring meetings. For each contact found in your meetings, returns the weighted count of shared meetings.
Args: start_date: Start date in YYYY-MM-DD format. end_date: End date in YYYY-MM-DD format. top_n: Number of top contacts to return. Default 30.
Returns: JSON object with total_meetings, unique_contacts, and a ranked contacts array of {name, email, meetings} objects.
Inputs: start_date (required), end_date (required), top_n. Returns: result
get_meeting_statsA
Get meeting count statistics for one or more people over a date range.
Uses GetUserAvailability to count calendar events (including expanded recurring instances) for each person.
Args: people: Comma-separated names or email addresses to analyze. start_date: Start date in YYYY-MM-DD format. end_date: End date in YYYY-MM-DD format.
Returns: JSON object with per-person stats sorted by meeting count: total_meetings, meetings_per_workday, days_with_meetings.
Inputs: people (required), start_date (required), end_date (required). Returns: result
loginA
Authenticate to Exchange OWA (handles credential setup and 2FA login).
Call this tool when the session has expired or before first use. It performs browser-based SSO login with 2FA mobile push approval. Session cookies are encrypted at rest with the master password.
Two-call 2FA flow: The first call starts the browser login in the background and returns immediately asking you to tell the user to approve 2FA on their phone. Call login again with the same master_password after the user approves — the second call picks up the result.
Args:
master_password: Decrypts stored credentials (and cookies), or encrypts
new ones if username/password are also provided.
username: Email address. Provide together with password for first-time
credential setup (replaces login.py --setup).
password: Account password. Required together with username for setup.
Returns: JSON result with success status and any error details.
Inputs: master_password (required), username, password. Returns: result
mark_email_readA
Mark one or more emails as read or unread.
Args: item_ids: List of Exchange ItemIds to update. is_read: True to mark as read, False to mark as unread (default True).
Inputs: item_ids (required), is_read. Returns: result
move_emailA
Move one or more emails to a different folder.
Args: item_ids: List of Exchange ItemIds to move. target_folder: Destination folder name (e.g. Inbox, Sent, Deleted, or custom).
Inputs: item_ids (required), target_folder (required). Returns: result
move_folderA
Move a mail folder to a different parent folder.
Args: folder_id: The Exchange folder ID to move (from get_folders). target_parent_folder_id: Destination parent folder. Defaults to "msgfolderroot" (top-level). Can be a distinguished folder name or a raw folder ID.
Returns: JSON object with success status and new folder ID.
Inputs: folder_id (required), target_parent_folder_id. Returns: result
rename_folderA
Rename an existing mail folder.
Args: folder_id: The Exchange folder ID to rename (from get_folders). new_name: New display name for the folder.
Returns: JSON object with success status and new folder id.
Inputs: folder_id (required), new_name (required). Returns: result
reply_emailA
Reply to an email.
Args: item_id: The Exchange ItemId of the email to reply to. body: Reply body text. reply_all: If True, reply to all recipients. Otherwise reply to sender only.
Inputs: item_id (required), body (required), reply_all. Returns: result
respond_to_meetingA
Respond to a meeting invitation (accept, decline, or tentative).
Args: item_id: The ItemId of the meeting to respond to. response: Response type: "Accept", "Decline", or "Tentative". message: Optional message to include with the response.
Returns: JSON object with response result.
Inputs: item_id (required), response (required), message. Returns: result
send_emailA
Send a new email.
Args: to: Comma-separated list of recipient email addresses. subject: Email subject line. body: Email body text. cc: Comma-separated CC recipients (optional). bcc: Comma-separated BCC recipients (optional). importance: Email importance: Low, Normal, or High (default Normal). is_html: If True, body is treated as HTML. Otherwise plain text.
Inputs: to (required), subject (required), body (required), cc, +3 more. Returns: result
update_meetingA
Update an existing calendar meeting.
Internally cancels the old meeting and creates a new one with updated fields, because OWA's JSON API does not support UpdateItem for calendar items reliably. Unchanged fields are preserved from the original meeting.
Args: item_id: The ItemId of the meeting to update (from get_calendar_events). subject: New subject (omit to keep original). date: New date in YYYY-MM-DD format (omit to keep original). start_time: New start time in HH:MM format (omit to keep original). duration_minutes: New duration in minutes (omit to keep original). location: New location (omit to keep original). description: New description/body text (omit to keep original). required_attendees: Email addresses for required attendees. Replaces existing list. Omit to keep original attendees. optional_attendees: Email addresses for optional attendees. Replaces existing list. Omit to keep original attendees. change_key: Ignored (kept for backward compatibility).
Returns: JSON object with update result including new item_id.
Inputs: item_id (required), subject, date, start_time, +6 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 30 tools
Every tool targets a distinct action or resource, with clear differentiation between email, calendar, folder, and people operations. No overlapping functionality is evident.
Tool names follow a consistent verb_noun pattern in snake_case, with only minor deviations like 'login' (single verb) and 'mark_email_read' (adjective after noun). Overall predictable naming.
30 tools cover email, calendar, folder, and contact management comprehensively. While slightly on the higher end, each tool serves a distinct purpose for a full-featured Exchange MCP server.
The tool surface covers core CRUD and lifecycle operations for emails, folders, and calendar events, plus supporting features like attachment download, link extraction, and scheduling analytics. Minor gaps like email search are absent but not critical.
Maintenance
Related MCP Connectors
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Connect any AI agent to 1,000+ apps and 27,000+ actions through one remote MCP server (OAuth).
Unified MCP Server is a remote MCP connector for AI agents and vertical AI products that provides access to 22,000+ authorized SaaS tools across 400+ integrations and 24 categories directly inside LLMs (Claude, GPT, Gemini, Cohere). Tools operate only on explicitly authorized customer connections, enabling agents to safely read and write against live third-party systems.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA production-ready MCP server that provides secure, delegated access to Microsoft 365 services including Email, SharePoint, OneDrive, and Calendar. It enables AI models to search messages, browse files, manage calendar events, and parse document contents using OAuth 2.1 authentication.MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for Outlook — enables AI assistants to access email, contacts, and calendars via Windows COM interface.39 PyPI1MIT
- FlicenseBqualityCmaintenanceMCP server providing AI assistants with full access to Microsoft Outlook email and calendar via the Microsoft Graph API, featuring 26 tools for mail, calendar, contacts, and scheduling with delegated authentication.29-
- FlicenseAqualityDmaintenanceMCP 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.181-