msexchange-mcp
Version:
MCP server for Microsoft Exchange/Outlook email operations via Microsoft Graph API
139 lines • 6.69 kB
JavaScript
import { z } from 'zod';
import { MailService } from '../mail/mailService.js';
import accountsConfig from '../config/accountsLoader.js';
export const queryEmailsBatchTool = {
name: 'query_emails_batch',
description: 'Query emails in batches to handle large requests efficiently while respecting token limits. Note: When using search, orderBy is not supported due to Graph API limitations.',
parameters: z.object({
user_id: z.string().describe('The user ID or email address'),
search: z.string().optional().describe('Search query (searches subject, body, from)'),
filter: z.string().optional().describe('OData filter expression'),
folder: z.string().optional().describe('Folder name to search in (e.g., Inbox, Sent Items)'),
total_emails: z.number().max(500).optional().default(50).describe('Total number of emails to retrieve (max 500)'),
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 (automatically batches smaller)'),
batch_size: z.number().optional().describe('Override automatic batch sizing'),
}),
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}`);
}
const totalEmails = params.total_emails || 50;
// Smart batch sizing based on content
let batchSize = params.batch_size;
if (!batchSize) {
if (params.include_body) {
batchSize = 3; // Very small batches when including body to stay under token limit
}
else {
batchSize = 50; // Larger batches for metadata only
}
}
const mailService = new MailService(account.email);
// Calculate number of batches needed
const numBatches = Math.ceil(totalEmails / batchSize);
const allEmails = [];
let totalProcessed = 0;
console.error(`📊 Batch query: ${totalEmails} emails in ${numBatches} batches of ${batchSize}`);
// Execute batches with delay to avoid rate limiting
for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
const skip = batchIndex * batchSize;
const take = Math.min(batchSize, totalEmails - totalProcessed);
if (take <= 0)
break;
console.error(`📦 Processing batch ${batchIndex + 1}/${numBatches} (skip: ${skip}, take: ${take})`);
// Select fields
const selectFields = [
'id',
'subject',
'from',
'toRecipients',
'receivedDateTime',
'sentDateTime',
'hasAttachments',
'importance',
'isRead',
'isDraft',
'bodyPreview',
'categories',
'conversationId',
];
if (params.include_body) {
selectFields.push('body');
}
try {
const emails = await mailService.queryEmails({
search: params.search,
filter: params.filter,
top: take,
skip: skip,
orderBy: params.order_by,
select: selectFields,
});
allEmails.push(...emails);
totalProcessed += emails.length;
// If we got fewer emails than requested, we've hit the end
if (emails.length < take) {
console.error(`📋 Reached end of results at batch ${batchIndex + 1}`);
break;
}
// Add delay between batches to be nice to the API
if (batchIndex < numBatches - 1) {
console.error(`⏰ Waiting 500ms before next batch...`);
await new Promise(resolve => setTimeout(resolve, 500));
}
}
catch (error) {
console.error(`❌ Batch ${batchIndex + 1} failed: ${error}`);
// Continue with partial results
break;
}
}
// Create summary
const summary = {
totalRequested: totalEmails,
totalRetrieved: allEmails.length,
batchesProcessed: Math.floor(totalProcessed / batchSize) + (totalProcessed % batchSize > 0 ? 1 : 0),
batchSize: batchSize,
includesBody: params.include_body,
tokenOptimized: true,
};
return {
content: [
{
type: 'text',
text: JSON.stringify({
summary,
emails: allEmails.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,
})),
}, null, 2),
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error in batch query: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
};
}
},
};
//# sourceMappingURL=queryEmailsBatch.js.map