Sofias-ai/mcp-outlook
If you are the rightful owner of mcp-outlook and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.
A lightweight MCP Server for seamless integration with Microsoft Outlook, enabling MCP clients to interact with emails for specific users.
MCP Outlook Server
An MCP (Model Context Protocol) server for interacting with Microsoft Outlook through Microsoft Graph API. This server provides advanced tools for searching, managing, and cleaning emails with HTML and text content processing capabilities.
Key Features
🔍 Advanced Email Search
- Dual search methods: OData filters + KQL (Keyword Query Language)
- Performance optimization: Body/No-Body variants for all search functions
- Unified search across multiple folders (Inbox, SentItems, Drafts)
- Natural language searches with KQL support ("from:john meeting today")
- Precise filtering with OData ("isRead eq false and importance eq 'high'")
- Automatic pagination for extensive results
- Structured result formatting
📧 Email Management
- Draft creation with local attachments
- Updating existing drafts
- Email deletion
- Support for categories and multiple recipients (TO, CC, BCC)
🧹 Advanced Content Cleaning
- Intelligent HTML to plain text conversion
- Automatic noise and disclaimer removal
- Character and format normalization
- Boilerplate detection and truncation
- Optional lightweight mode to skip deep HTML cleaning (
clean_body=False
)
📊 Structured Formatting
- Consistent dictionary format output
- Automatic metadata extraction (sender, date, subject, etc.)
- Automatic content summaries
- Robust error handling
Installation
Prerequisites
- Python 3.8+
- Azure AD application registration with Microsoft Graph permissions
- Configured environment variables
Dependencies
pip install mcp fastmcp office365-rest-python-client python-dotenv beautifulsoup4 html2text
Azure AD Configuration
-
Register a new application in Azure AD
-
Configure the following Microsoft Graph permissions:
Mail.Read
- Read user emailsMail.ReadWrite
- Create, update and delete emailsMail.Send
- Send emails (if needed)User.Read
- Read basic user profile
-
Generate a client secret
Environment Variables
Create a .env
file in the project root:
CLIENT_ID=your_azure_client_id
CLIENT_SECRET=your_azure_client_secret
TENANT_ID=your_azure_tenant_id
# Optional: comma separated folders to search by default
OUTLOOK_DEFAULT_FOLDERS=Inbox,SentItems,Drafts
Note: Make sure to use exactly these variable names (CLIENT_ID
, CLIENT_SECRET
, TENANT_ID
) as they are required by the server.
Usage
Starting the Server
python -m mcp_outlook_server.server
🎯 Function Selection Guide
Choose the right function for your use case:
Use Case | Recommended Function | Reason |
---|---|---|
Email listing/previews | Search_Outlook_Emails_No_Body or Search_Outlook_Emails_No_Body_By_Search_Query | Faster, reduced bandwidth, optimized for UI |
Reading full email content | Get_Outlook_Email | Complete email with formatted body |
Natural language search | Search_Outlook_Emails_By_Search_Query | KQL supports content-based searches |
Precise filtering | Search_Outlook_Emails | OData filters for exact criteria |
Performance-critical apps | Functions ending with _No_Body | Optimized for speed, uses native Graph bodyPreview |
Content analysis | Search_Outlook_Emails or Search_Outlook_Emails_By_Search_Query | Full body content included |
Building email clients | Search_Outlook_Emails_No_Body + Get_Outlook_Email | List view + detail view pattern |
Complex business logic | OData functions (Search_Outlook_Emails* ) | Precise boolean logic and date ranges |
User-friendly search | KQL functions (*_By_Search_Query ) | Natural language and keyword searches |
🔍 Search Method Comparison
Feature | OData ($filter) | KQL ($search) |
---|---|---|
Precision | Exact field matching | Semantic/fuzzy matching |
Syntax | SQL-like operators | Natural language keywords |
Performance | Highly optimized | Good for content search |
Use Cases | Business logic, exact filtering | User search, content discovery |
Date Ranges | ISO format required | Relative dates supported |
Boolean Logic | Full AND/OR/NOT support | Keyword combinations |
Recommendation: Use OData for precise business logic, KQL for user-facing search interfaces.
Available Tools
1. Get_Outlook_Email
Retrieves a specific email by its unique ID.
Parameters:
message_id
(str): Unique message IDuser_email
(str): Email of the mailbox owner
Example response:
{
"success": true,
"data": {
"sender": "John Doe <john@example.com>",
"date": "2024-01-15 10:30:00",
"cc": ["maria@example.com"],
"subject": "Project meeting",
"summary": "Meeting confirmation to discuss project progress...",
"body": "Complete email content clean and formatted",
"id": "AAMkAGE1M2IyNGNmLWI4MjktNDUyZi1iMzA4LTViNDI3NzhlOGM2NgBGAAAAAADUuTiuQqVlSKDGAz"
}
}
2. Search_Outlook_Emails
Advanced email search with OData filter support and optional field selection.
Parameters:
user_email
(str): User emailquery_filter
(str, optional): OData filtertop
(int, optional): Maximum number of results (default: 10)folders
(List[str], optional): Folders to search (default: ["Inbox", "SentItems", "Drafts"])fields
(List[str], optional): NEW - Specific fields to return (e.g.,['subject', 'sender', 'date', 'id']
)
🎯 Field Selection Examples:
// Get only essential metadata
fields: ['subject', 'sender', 'date', 'id']
// Get summary information without full body
fields: ['subject', 'sender', 'summary', 'date']
// Get recipients and categorization
fields: ['subject', 'sender', 'cc', 'date', 'id']
// All available fields
fields: ['subject', 'sender', 'date', 'id', 'body', 'summary', 'cc']
OData filter examples (Validated ✅):
// Unread emails
"isRead eq false"
// Emails from specific sender
"from/emailAddress/address eq 'user@example.com'"
// Emails containing keywords in subject
"contains(subject, 'invoice')"
// Emails starting with specific text
"startswith(subject, 'Newsletter')"
// Date range filtering
"receivedDateTime ge 2025-01-01T00:00:00Z and receivedDateTime le 2025-12-31T23:59:59Z"
// High importance unread emails
"isRead eq false and importance eq 'high'"
// Emails with attachments from last month
"hasAttachments eq true and receivedDateTime ge 2025-06-01T00:00:00Z"
// Complex OR conditions
"startswith(subject, 'Invoice') or startswith(subject, 'Receipt')"
// Domain-based filtering
"contains(from/emailAddress/address, 'gmail.com')"
Performance Notes:
- All OData operators validated:
eq
,ne
,gt
,ge
,lt
,le
,contains
,startswith
- Boolean logic fully supported:
and
,or
,not
- Date filtering requires ISO 8601 format with timezone
- Use
_No_Body
variants for 2-3x faster performance when full content isn't needed
2b. Search_Outlook_Emails_No_Body (Performance Optimized)
Performance-optimized email search that excludes email body content for faster processing and reduced data transfer.
Parameters:
user_email
(str): User emailquery_filter
(str, optional): OData filter (same syntax as Search_Outlook_Emails)top
(int, optional): Maximum number of results (default: 10)folders
(List[str], optional): Folders to search (default: ["Inbox", "SentItems", "Drafts"])fields
(List[str], optional): NEW - Specific fields to return (optimized for no-body searches)
Key Benefits:
- Faster performance: Excludes body content processing
- Reduced bandwidth: Smaller response payload
- Uses native Microsoft Graph
bodyPreview
field for the summary, which is more efficient and consistent than manual summary generation - Ideal for listings: Perfect for email previews and list views
- Same filtering: Full OData filter support maintained
Use Cases:
- Quick email listing and previews
- Performance-critical searches when body content is not needed
- Bandwidth-constrained environments
- Building email selection interfaces
3. Search_Outlook_Emails_By_Search_Query (KQL Search)
Advanced search using Microsoft Graph's KQL (Keyword Query Language) search parameter. More powerful for natural language and content-based searches.
Parameters:
user_email
(str): User emailsearch_query
(str): KQL search querytop
(int, optional): Maximum number of results (default: 10)folders
(List[str], optional): Folders to search (default: ["Inbox", "SentItems", "Drafts"])fields
(List[str], optional): NEW - Specific fields to return with intelligent KQL/OData mapping
KQL Search Examples (Validated ✅):
// Search by sender (multiple formats supported)
"from:john@example.com"
"from:John Doe"
// Content and subject searches
"meeting agenda"
"subject:urgent"
// Recipient-based searches
"to:maria@company.com"
// Date-based searches (natural language)
"received:today"
"received:last week"
"sent:2025-07-01..2025-07-07"
// Attachment searches
"hasattachment:true"
// Complex keyword combinations
"AI AND newsletter"
"project AND deadline NOT completed"
"invoice AND (Hetzner OR Google)"
// Mixed field and content searches
"from:boss@company.com urgent meeting"
KQL Advantages:
- Natural language date expressions
- Fuzzy matching and semantic search
- Content-based discovery across subject and body
- User-friendly syntax for end-user interfaces
- Excellent for exploratory searches
When to use KQL vs OData:
-
Use KQL (
Search_Outlook_Emails_By_Search_Query
) for:- Natural language searches
- Content-based searches (searching within email body)
- Complex keyword combinations
- Searches involving attachments
- Date-relative searches ("today", "last week")
-
Use OData (
Search_Outlook_Emails
) for:- Precise property-based filtering
- Boolean logic on specific fields
- Exact date/time ranges
- Flag-based searches (isRead, importance)
3b. Search_Outlook_Emails_No_Body_By_Search_Query (KQL + Performance)
Combines the power of KQL search with performance optimization by excluding email body content.
Parameters:
user_email
(str): User emailsearch_query
(str): KQL search query (same syntax as Search_Outlook_Emails_By_Search_Query)top
(int, optional): Maximum number of results (default: 10)folders
(List[str], optional): Folders to search (default: ["Inbox", "SentItems", "Drafts"])fields
(List[str], optional): NEW - Specific fields to return (optimized for KQL no-body searches)
Best for:
- Fast KQL-based searches when you only need email metadata
- Building search result previews with KQL capabilities
- Performance-critical applications using natural language search
Example response for all search functions:
{
"success": true,
"data": [
{
"sender": "John Doe <john@example.com>",
"date": "2024-01-15 10:30:00",
"cc": ["maria@example.com"],
"subject": "Project meeting",
"summary": "Hi team, I wanted to schedule our weekly project review for Thursday at 2pm. Please confirm your availability and we'll send out the meeting invite. The agenda will cover...",
"id": "AAMkAGE1M2IyNGNmLWI4MjktNDUyZi1iMzA4LTViNDI3NzhlOGM2NgBGAAAAAADUuTiuQqVlSKDGAz"
// Note: 'body' field only included in full search functions unless specifically requested in 'fields'
}
]
}
🎯 Field Selection Response Examples:
// With fields: ['subject', 'sender', 'id']
{
"data": [
{
"asunto": "Project meeting", // OData results use Spanish keys
"remitente": "John Doe <john@example.com>",
"id": "AAMkAGE1M2IyNGNmLWI4..."
}
]
}
// KQL search with fields: ['subject', 'sender', 'date']
{
"data": [
{
"subject": "Project meeting", // KQL results use English keys
"from": "John Doe <john@example.com>",
"receivedDateTime": "2024-01-15 10:30:00"
}
]
}
Field Mapping Reference:
Standard Field | OData Key (Spanish) | KQL Key (English) |
---|---|---|
subject | asunto | subject |
sender | remitente | from |
date | fecha | receivedDateTime |
cc | copiados | ccRecipients |
body | cuerpo | body |
summary | resumen | bodyPreview |
id | id | id |
4. Create_Outlook_Draft_Email
Creates a new draft email with support for attachments and categories.
Parameters:
subject
(str): Email subjectbody
(str): Message bodyto_recipients
(List[str]): List of primary recipientsuser_email
(str): User emailcc_recipients
(List[str], optional): CC recipientsbcc_recipients
(List[str], optional): BCC recipientsbody_type
(str, optional): Body type ("HTML" or "Text")category
(str, optional): Email categoryfile_paths
(List[str], optional): File paths to attach
Example:
create_draft_email_tool(
subject="Project report",
body="<p>Please find the requested report attached.</p>",
to_recipients=["client@example.com"],
cc_recipients=["supervisor@company.com"],
user_email="my.email@company.com",
category="Work",
file_paths=["C:/documents/report.pdf", "C:/documents/data.xlsx"]
)
5. Update_Outlook_Draft_Email
Updates an existing draft, including adding new attachments.
Parameters:
message_id
(str): ID of the draft to updateuser_email
(str): User email- All optional parameters from
Create_Outlook_Draft_Email
6. Delete_Outlook_Email
Deletes an email by its ID.
Parameters:
message_id
(str): ID of the message to deleteuser_email
(str): User email
🆕 Latest Updates (v0.1.15+)
Major Refactoring & Optimization
- Unified Resource Architecture: Complete refactoring of
resources.py
with unified search logic and pagination - Eliminated Code Duplication: Removed legacy functions and consolidated helpers for better maintainability
- Enhanced Performance: Optimized data fetching with configurable
include_body
parameter across all search functions - Improved Error Handling: Robust exception handling and fallback strategies
- Attachment Management: Streamlined attachment handling with unified API approach
Advanced Search Capabilities
- Comprehensive $filter Support: Full OData operator support validated (eq, ne, gt, ge, lt, le, contains, startswith)
- Complete KQL Integration: Natural language search with Microsoft's Keyword Query Language
- Combined Logic Operations: Complex AND/OR filtering with multiple conditions
- Performance Optimized: Smart body inclusion/exclusion based on use case
- Extensive Testing: Thoroughly validated against real mailbox data (sss@sofias.ai)
🎯 NEW: Field Selection Feature
- Selective Data Retrieval: All search tools now support a
fields
parameter for returning only specific email fields - Bandwidth Optimization: Reduce response size by selecting only needed fields (e.g.,
['subject', 'sender', 'date', 'id']
) - Dual Mapping Support: Intelligent field mapping for both OData (Spanish keys) and KQL (English keys) search results
- Autogen Compatible: Returns arrays optimized for AI agent frameworks and automated processing
- Flexible Field Names: Supports standardized field names (
subject
,sender
,date
,id
,body
,summary
,cc
) across all search methods
Reply Drafts with Preserved Conversation History
- The
create_draft_email_tool
function supports areply_to_id
parameter - When replying, drafts automatically include full conversation history with original formatting
- HTML and plain text preservation for maximum fidelity
- Seamless integration with Outlook's native reply behavior
Example usage
create_draft_email_tool(
subject="Re: Project Update",
body="Thank you for the update!",
to_recipients=["user@example.com"],
user_email="me@example.com",
reply_to_id="AAMkAGI2...",
category="Work" # Now supports categories in replies
)
Validated Search Examples
All search patterns have been extensively tested and validated:
# Advanced OData filtering
"receivedDateTime ge 2025-07-01T00:00:00Z and isRead eq false"
"contains(subject, 'invoice') and importance eq 'high'"
"startswith(subject, 'Newsletter') or endswith(from/emailAddress/address, 'gmail.com')"
# KQL natural language searches
"AI newsletter", "from:user@domain.com sent:2025-07-01..2025-07-07"
"subject:urgent AND hasattachment:true"
💡 Practical Examples
Example 1: Building an Email Client Interface
# Step 1: Get email list for preview (fast)
emails = search_emails_no_body_tool(
user_email="user@company.com",
query_filter="isRead eq false",
top=20
)
# Step 2: When user selects an email, get full content
full_email = get_email_tool(
message_id=selected_email_id,
user_email="user@company.com"
)
Example 2: Smart Content Search
# Natural language search using KQL
meetings = search_emails_by_search_query_tool(
user_email="user@company.com",
search_query="meeting AND (today OR tomorrow)",
top=10
)
# Precise filtering using OData
urgent_unread = search_emails_tool(
user_email="user@company.com",
query_filter="isRead eq false and importance eq 'high'",
top=5
)
Example 3: Performance-Optimized Searches with Field Selection
# Fast search for email previews without body processing
preview_results = search_emails_no_body_by_search_query_tool(
user_email="user@company.com",
search_query="from:boss@company.com project",
top=50
)
# Get only essential fields for UI display
essential_data = search_emails_tool(
user_email="user@company.com",
query_filter="isRead eq false",
fields=['subject', 'sender', 'date', 'id'],
top=100
)
# Bandwidth-optimized search for autogen/AI processing
ai_optimized = search_emails_by_search_query_tool(
user_email="user@company.com",
search_query="meeting AND urgent",
fields=['subject', 'sender', 'summary'],
top=20
)
Example 4: Draft Management
# Create draft with attachments
draft = create_draft_email_tool(
subject="Weekly Report",
body="<h1>Weekly Status</h1><p>Please find attached...</p>",
to_recipients=["team@company.com"],
user_email="user@company.com",
category="Work",
file_paths=["C:/reports/weekly.pdf"]
)
# Update the draft
update_draft_email_tool(
message_id=draft['id'],
user_email="user@company.com",
subject="Weekly Report - Updated",
cc_recipients=["manager@company.com"]
)
Technical Features
Field Selection System (NEW in v0.1.15+)
The server now supports selective field retrieval across all search functions:
- Standardized Field Names: Use consistent field names (
subject
,sender
,date
,id
,body
,summary
,cc
) across all search methods - Intelligent Mapping: Automatic detection and mapping between OData (Spanish keys) and KQL (English keys) result formats
- Bandwidth Optimization: Return only needed fields to reduce response size and improve performance
- Autogen Compatibility: Optimized array responses for AI agent frameworks and automated processing
- Flexible Implementation: Works with both full and no-body search variants
Unified Architecture (Latest)
The server features a completely refactored architecture with:
- Unified Resource Management: Single source of truth for all email operations in
resources.py
- Optimized Data Fetching: Configurable body inclusion with
include_body
parameter - Consolidated Pagination: Unified pagination logic across all search functions
- Eliminated Duplication: Removed legacy functions and consolidated helper methods
- Enhanced Error Handling: Robust exception handling with detailed logging
Content Cleaning System
The server includes an advanced cleaning system that:
- Converts HTML to plain text using html2text with optimized configuration
- Removes unwanted elements: scripts, styles, metadata, etc.
- Normalizes text: special characters, spaces, line breaks
- Detects and truncates disclaimers: identifies legal/boilerplate text patterns
- Generates automatic summaries: extracts first 150 relevant characters
- Smart Body Processing: Uses native Graph
bodyPreview
for_No_Body
functions for optimal performance
Robust Error Handling
- Exception handling decorators on all critical operations
- Fallback strategies for content processing
- Detailed logging for debugging
- Input parameter validation
- Graceful degradation for malformed content
Performance Optimizations
- Smart Body Inclusion:
include_body=False
for 2-3x performance improvement - Field Selection: NEW - Return only specific fields to reduce bandwidth and processing time
- Native Graph Features: Leverages
bodyPreview
for summaries when available - Unified Pagination: Consistent page limits to avoid timeouts
- Optimized Queries: Reduced API calls through intelligent batching
- Memory Efficient: Processes large result sets without memory bloat
- Dual Mapping System: Efficient field mapping for both OData and KQL result formats
Project Structure
mcp-outlook/
├── src/
│ └── mcp_outlook_server/
│ ├── __init__.py
│ ├── server.py # Server entry point
│ ├── common.py # Configuration and Graph client setup
│ ├── tools.py # MCP tool definitions (refactored & optimized)
│ ├── resources.py # Unified resource access logic (major refactor)
│ ├── format_utils.py # Email formatting and extraction utilities
│ ├── format_utils_search.py # Search result formatting
│ └── clean_utils.py # Advanced content cleaning system
├── .env # Environment variables (don't include in git)
├── pyproject.toml # Modern Python project configuration
└── README.md # This file
Recent Architecture Changes
resources.py
: Completely refactored with unified search logic, eliminated duplicate functions, added field selection systemtools.py
: Streamlined tool definitions, improved attachment handling, consolidated helpers, addedfields
parameter support- Performance: Added configurable
include_body
parameter and field selection across all search functions - Field Mapping: Implemented dual mapping system for OData (Spanish) and KQL (English) result compatibility
- Testing: Comprehensive validation documented in conversation history, including field selection scenarios
Logging
The server generates detailed logs in:
- File:
mcp_outlook.log
- Console: Standard output
Logging configuration in common.py
with INFO level by default.
Troubleshooting
Authentication Error
- Verify environment variables are correctly configured in
.env
file:CLIENT_ID
: Your Azure application (client) IDCLIENT_SECRET
: Your Azure application client secretTENANT_ID
: Your Azure tenant (directory) ID
- Confirm Azure AD application has necessary permissions
- Check that tenant ID is correct
- Ensure the
.env
file is in the project root directory
Common Environment Variable Issues
- Error: "Missing required environment variables": Make sure variable names match exactly (
CLIENT_ID
,CLIENT_SECRET
,TENANT_ID
) - Error: "Authentication failed": Verify the client secret hasn't expired and values are correct
Search Errors
- Validate OData filter syntax
- Verify user has access to specified folders
- Check logs for specific Graph API errors
Attachment Issues
- Confirm file paths exist and are accessible
- Verify files don't exceed Outlook size limits (25MB per attachment, 150MB total)
- Check file read permissions
- Ensure attachment functions use the unified API approach (implemented in latest version)
Performance Issues
- Use
_No_Body
functions for listings and previews (2-3x faster) - Use
fields
parameter to return only needed data (reduces bandwidth and processing time) - Limit
top
parameter to reasonable values (10-50 for UI, 100+ for batch processing) - Consider search scope - searching specific folders is faster than all folders
- For large datasets, implement pagination using multiple calls
- Field selection examples:
['subject', 'sender', 'id']
for lists,['subject', 'sender', 'summary', 'date']
for previews
Search Issues
- OData syntax: Ensure proper quoting and escaping of string values
- Date formats: Use ISO 8601 format with timezone (e.g.,
2025-07-01T00:00:00Z
) - Field names: Use exact field names (
from/emailAddress/address
, notfrom.email
) - KQL queries: Don't mix OData and KQL syntax in the same query
- Field selection: Use standardized field names (
subject
,sender
,date
,id
,body
,summary
,cc
) - Result format: OData returns Spanish keys, KQL returns English keys - field mapping handles this automatically
- Check the validation results in conversation history for confirmed working examples
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/new-functionality
) - Commit changes (
git commit -am 'Add new functionality'
) - Push to branch (
git push origin feature/new-functionality
) - Create a Pull Request
License
MIT License
Copyright (c) 2024
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Support
To report bugs or request new features, please open an issue in the repository.