msexchange-mcp
Version:
MCP server for Microsoft Exchange/Outlook email operations via Microsoft Graph API
133 lines (129 loc) • 6.9 kB
JavaScript
import { z } from 'zod';
import { SmartQueryBuilder } from '../mail/queryBuilder.js';
import accountsConfig from '../config/accountsLoader.js';
export const queryEmailsTool = {
name: 'query_emails',
description: `Query emails with intelligent query handling and automatic optimization.
Features:
- Natural language search: "emails from John about the project"
- OData filters: "from eq 'john@example.com' and hasAttachments eq true"
- Structured queries with from, to, subject filters
- Automatic query simplification for complex filters
- Intelligent fallback strategies (AND → OR → filter-only)
Search parameters:
- search: Natural language query (searches subject, body, from)
- filter: OData filter expression
- from/to/subject: Quick filters for common searches
- folder: Search within specific folder (e.g., "Inbox", "Sent Items")
- include_body: Include full email content (default: false)
- limit: Max results (default: 10, max: 100)
- order_by: Sort order (not available with search parameter)
Examples:
- Recent emails: {"order_by": "receivedDateTime DESC", "limit": 20}
- Unread with attachments: {"filter": "isRead eq false and hasAttachments eq true"}
- From specific sender: {"from": "client@company.com", "folder": "Inbox"}
- Natural search: {"search": "invoice payment december"}
Best practices:
- Use 'search' for natural language queries
- Use 'filter' for precise OData queries
- Combine quick filters (from/to/subject) for simple searches
- Set include_body=false for better performance
- Use query_emails_batch for large result sets`,
parameters: z.object({
user_id: z.string().describe('The user ID or email address'),
search: z.string().optional().describe('Natural language search (searches subject, body, from). Cannot be combined with orderBy.'),
filter: z.string().optional().describe('OData filter expression. Complex filters may be automatically simplified.'),
from: z.string().optional().describe('Filter by sender email address'),
to: z.string().optional().describe('Filter by recipient email address'),
subject: z.string().optional().describe('Filter by subject (partial match)'),
folder: z.string().optional().describe('Folder name to search in (e.g., Inbox, Sent Items)'),
limit: z.number().max(100).optional().default(10).describe('Maximum number of emails to return (max 100 for token efficiency)'),
skip: z.number().optional().default(0).describe('Number of emails to skip'),
order_by: z.string().optional().default('receivedDateTime DESC').describe('Sort order (not supported when using search parameter)'),
include_body: z.boolean().optional().default(false).describe('Include full email body'),
}),
execute: async (params) => {
try {
const account = accountsConfig.accounts.find(acc => acc.id === params.user_id || acc.email === params.user_id);
if (!account) {
throw new Error(`User not found: ${params.user_id}`);
}
// Validate and cap the limit to prevent token overflow issues
// When including body, limit to 5 emails to stay under 25k token limit
// Without body, allow up to 100 emails
const maxLimit = params.include_body ? 5 : 100;
const safeLimit = Math.min(params.limit || 10, maxLimit);
if (params.limit && params.limit > maxLimit) {
console.error(`⚠️ Requested limit ${params.limit} exceeds maximum of ${maxLimit} ${params.include_body ? '(with body)' : '(without body)'}, capping to ${maxLimit}`);
}
const queryBuilder = new SmartQueryBuilder(account.email);
// Build query options
const queryOptions = {
search: params.search,
filter: params.filter,
from: params.from,
to: params.to,
subject: params.subject,
folder: params.folder,
limit: safeLimit,
skip: params.skip,
orderBy: params.order_by,
includeBody: params.include_body,
};
// Execute query with smart handling
const result = await queryBuilder.executeQuery(queryOptions);
const emails = result.emails;
// Log warnings if any
if (result.warnings) {
result.warnings.forEach(warning => console.error(`⚠️ ${warning}`));
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
queryStrategy: result.strategy,
warnings: result.warnings,
emails: emails.map(email => ({
id: email.id,
subject: email.subject,
from: email.from?.emailAddress?.address,
fromName: email.from?.emailAddress?.name,
to: email.toRecipients?.map(r => r.emailAddress.address),
receivedDateTime: email.receivedDateTime,
hasAttachments: email.hasAttachments,
isRead: email.isRead,
preview: email.bodyPreview,
categories: email.categories,
body: params.include_body ? email.body : undefined,
})),
count: emails.length,
hasMore: emails.length === safeLimit,
requestedLimit: params.limit,
actualLimit: safeLimit,
tokenOptimized: true,
maxLimitWithBody: 5,
maxLimitWithoutBody: 100,
paginationAdvice: params.include_body && (params.limit || 10) > 5
? "For large queries with email bodies, use multiple smaller requests with pagination"
: emails.length === safeLimit
? "Use skip parameter for pagination: skip=" + (params.skip || 0) + safeLimit
: undefined,
}, null, 2),
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error querying emails: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
};
}
},
};
//# sourceMappingURL=queryEmails.js.map