UNPKG

msexchange-mcp

Version:

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

148 lines 6.05 kB
/** * Sanitizes and optimizes search queries for Microsoft Graph API * Handles character restrictions and converts problematic queries */ export class SearchQuerySanitizer { /** * Detects if a search query contains problematic characters * Microsoft Graph search has issues with numbers and some special characters */ static hasProblematicCharacters(query) { // Check for numbers which cause "character 'X' is not valid" errors return /\d/.test(query); } /** * Sanitizes a search query by removing or replacing problematic characters * @param query Raw search query * @returns Sanitized query safe for Graph API search */ static sanitizeForSearch(query) { // Remove numbers but keep the rest of the query let sanitized = query.replace(/\d+/g, ' ').trim(); // Clean up multiple spaces sanitized = sanitized.replace(/\s+/g, ' '); // Remove empty parentheses that might be left after number removal sanitized = sanitized.replace(/\(\s*\)/g, ''); return sanitized.trim(); } /** * Extracts important tokens from a query for alternative search strategies */ static extractSearchTokens(query) { const years = query.match(/\b(19|20)\d{2}\b/g) || []; const emails = query.match(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g) || []; // Extract keywords (remove common words, numbers, and emails) const commonWords = new Set(['the', 'and', 'or', 'for', 'with', 'from', 'to', 'in', 'of', 'a', 'an']); const cleanQuery = query .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '') // Remove emails .replace(/\b(19|20)\d{2}\b/g, ''); // Remove years const keywords = cleanQuery .toLowerCase() .split(/\s+/) .filter(word => word.length > 2 && !commonWords.has(word)); return { years, emails, keywords, hasNumbers: /\d/.test(query) }; } /** * Converts a search query with numbers into a filter expression * This is more reliable than search for queries containing years, dates, etc. */ static convertToFilterExpression(query) { const tokens = this.extractSearchTokens(query); const filters = []; // Handle email addresses if (tokens.emails.length > 0) { tokens.emails.forEach(email => { filters.push(`from/emailAddress/address eq '${email}'`); }); } // Handle years in subject or body if (tokens.years.length > 0) { const yearFilters = tokens.years.map(year => `(contains(subject, '${year}') or contains(body/content, '${year}'))`); filters.push(...yearFilters); } // Handle keywords if (tokens.keywords.length > 0) { // Take top 3 most relevant keywords to avoid complexity const topKeywords = tokens.keywords.slice(0, 3); const keywordFilters = topKeywords.map(keyword => `(contains(subject, '${keyword}') or contains(body/content, '${keyword}'))`); filters.push(...keywordFilters); } // Combine with OR if we have multiple filters if (filters.length === 0) { return ''; } else if (filters.length === 1) { return filters[0]; } else { // Use OR for email filters, AND for content filters const emailFilters = filters.filter(f => f.includes('from/emailAddress')); const contentFilters = filters.filter(f => !f.includes('from/emailAddress')); if (emailFilters.length > 0 && contentFilters.length > 0) { return `(${emailFilters.join(' or ')}) and (${contentFilters.join(' or ')})`; } else if (emailFilters.length > 0) { return emailFilters.join(' or '); } else { return contentFilters.join(' or '); } } } /** * Suggests alternative search strategies based on the query */ static suggestSearchStrategy(query) { const tokens = this.extractSearchTokens(query); const warnings = []; const suggestions = []; if (tokens.hasNumbers) { warnings.push('Search queries with numbers often fail in Exchange. Using alternative strategy.'); // If we have years, suggest filter if (tokens.years.length > 0) { suggestions.push(`Search for emails from ${tokens.years.join(', ')}`); return { strategy: 'filter', filterExpression: this.convertToFilterExpression(query), warnings, suggestions }; } // Otherwise, try sanitized search with fallback const sanitized = this.sanitizeForSearch(query); if (sanitized.length > 3) { suggestions.push('Removed numbers from search query'); return { strategy: 'hybrid', sanitizedQuery: sanitized, filterExpression: this.convertToFilterExpression(query), warnings, suggestions }; } } // If we have email addresses, prefer filter if (tokens.emails.length > 0) { suggestions.push('Using email-based filtering for better accuracy'); return { strategy: 'filter', filterExpression: this.convertToFilterExpression(query), warnings, suggestions }; } // Default to search for simple queries return { strategy: 'search', sanitizedQuery: query, warnings, suggestions }; } } //# sourceMappingURL=searchQuerySanitizer.js.map