msexchange-mcp
Version:
MCP server for Microsoft Exchange/Outlook email operations via Microsoft Graph API
178 lines (168 loc) ⢠7.7 kB
JavaScript
import { z } from 'zod';
import { MailService } from '../services/mail.js';
import { UnifiedSearchService } from '../services/unifiedSearch.js';
import { AliasManager } from '../services/aliasManager.js';
import { formatEmailForMCP } from '../utils/formatters.js';
import { logger } from '../utils/logger.js';
// Input schema combining query and options
const inputSchema = z.object({
query: z.string().describe('Natural language search query (e.g., "emails from Florian about insurance")'),
accountId: z.string().optional().default('default').describe('Account ID to search in'),
limit: z.number().optional().default(20).describe('Maximum number of results'),
offset: z.number().optional().default(0).describe('Number of results to skip'),
includeBody: z.boolean().optional().default(false).describe('Include email body content'),
orderBy: z.enum(['receivedDateTime', 'subject', 'from', 'importance']).optional().default('receivedDateTime'),
orderDirection: z.enum(['asc', 'desc']).optional().default('desc'),
});
export const unifiedEmailSearchTool = {
name: 'unified_email_search',
description: `Search emails using natural language with intelligent query understanding.
Features:
- Natural language queries: "emails from Florian about Mads insurance"
- Smart name recognition: "Florian" ā finds emails from florian.semrau@allianz.de
- Alias understanding: "Doss" ā knows this refers to Mads Schwartz
- AND logic by default: Searches for emails containing ALL terms
- Intelligent fallbacks: Automatically tries different strategies if no results
- Helpful suggestions: Provides alternatives when searches fail
Examples:
- "emails from Florian about insurance"
- "Justinas new company incorporation"
- "from:noewe subject:invoice"
- "messages with attachments from last week"
- "unread emails about tax obligations"
Filters:
- from:name/email - Filter by sender
- to:name/email - Filter by recipient
- subject:text - Filter by subject
- has:attachment - Only emails with attachments
- is:unread - Only unread emails
- before:date - Emails before a specific date (e.g., before:2024-12-01)
- after:date - Emails after a specific date (e.g., after:2024-01-01)
This tool replaces all other search tools with a unified, intelligent interface.`,
parameters: inputSchema,
execute: async ({ query, accountId, limit, offset, includeBody, orderBy, orderDirection }) => {
try {
const mailService = MailService.getInstance();
const searchService = new UnifiedSearchService(mailService);
const aliasManager = AliasManager.getInstance();
// Learn from recent emails to improve alias resolution
await aliasManager.learnFromEmails(mailService, accountId);
// Convert string query to SearchQuery
const searchQuery = {
text: query,
requireAllTerms: true,
includeAliases: true,
semanticSearch: true,
limit,
};
logger.info('Unified email search', { query: searchQuery, accountId });
// Perform the search
const result = await searchService.search(searchQuery, {
accountId,
limit,
offset,
orderBy,
orderDirection,
includeBody,
});
// Format results
const formattedEmails = result.emails.map(email => formatEmailForMCP(email, {
includeBody,
includeHeaders: false,
maxBodyLength: includeBody ? 1000 : 0,
}));
// Build response with helpful information
let response = '';
if (result.emails.length === 0) {
response = 'ā No emails found matching your search.\n\n';
if (result.suggestions && result.suggestions.length > 0) {
response += 'š” Suggestions:\n';
result.suggestions.forEach(suggestion => {
response += `⢠${suggestion}\n`;
});
response += '\n';
}
response += 'Tips for better results:\n';
response += '⢠Use fewer search terms\n';
response += '⢠Try searching by sender: from:john\n';
response += '⢠Use quotes for exact phrases: "project update"\n';
response += '⢠Check spelling of names\n';
}
else {
response = `ā
Found ${result.totalCount} email${result.totalCount !== 1 ? 's' : ''} matching your search`;
if (result.searchStrategy) {
switch (result.searchStrategy) {
case 'or_fallback':
response += ' (showing emails with ANY of your search terms)';
break;
case 'filter_only_fallback':
response += ' (showing results based on filters only)';
break;
}
}
response += '\n\n';
if (result.expandedQuery) {
response += `š Expanded search: ${result.expandedQuery}\n\n`;
}
response += formattedEmails.join('\n' + 'ā'.repeat(50) + '\n');
if (result.hasMore) {
response += `\n\nš Showing ${limit} of ${result.totalCount}+ results. Use offset parameter to see more.`;
}
if (result.suggestions && result.suggestions.length > 0) {
response += '\n\nš” Additional suggestions:\n';
result.suggestions.forEach(suggestion => {
response += `⢠${suggestion}\n`;
});
}
}
return {
content: [
{
type: 'text',
text: response,
},
],
};
}
catch (error) {
logger.error('Unified email search error', { error });
const errorMessage = error instanceof Error ? error.message : String(error);
// Provide helpful error messages
if (errorMessage.includes('filter clause is incompatible')) {
return `ā Search too complex for Microsoft Graph API.
The Graph API cannot combine certain filters with search. Try one of these approaches:
1. Use simpler search terms without special operators
2. Search by sender only: "from:florian"
3. Search by keywords only: "insurance policy"
4. Remove date or attachment filters
Example working searches:
⢠"emails from Florian"
⢠"insurance" (searches all email content)
⢠"from:florian.semrau@allianz.de"`;
}
if (errorMessage.includes('Invalid filter clause')) {
return `ā Invalid search syntax.
Examples of valid searches:
⢠"emails from John about project"
⢠"from:john@example.com"
⢠"subject:invoice"
⢠"has:attachment"
Avoid special characters in search terms unless using quotes.`;
}
return {
content: [
{
type: 'text',
text: `ā Search failed: ${errorMessage}
Try:
⢠Simpler search terms
⢠Searching by sender: "from:name"
⢠Checking your spelling
⢠Using fewer filters`,
},
],
};
}
},
};
//# sourceMappingURL=unifiedEmailSearch.js.map