UNPKG

msexchange-mcp

Version:

MCP server for Microsoft Exchange/Outlook email operations via Microsoft Graph API

178 lines (168 loc) • 7.7 kB
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