msexchange-mcp
Version:
MCP server for Microsoft Exchange/Outlook email operations via Microsoft Graph API
148 lines • 6.05 kB
JavaScript
/**
* 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