UNPKG

msexchange-mcp

Version:

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

213 lines 9.99 kB
import { getGraphClient, executeWithTokenRetry } from '../graph/graphClient.js'; export class MailService { userEmail; constructor(userEmail) { this.userEmail = userEmail; } async retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000) { let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; // Check if it's a rate limiting error if (error.code === 'TooManyRequests' || error.status === 429) { const retryAfter = error.headers?.['retry-after']; const delay = retryAfter ? parseInt(retryAfter) * 1000 : baseDelay * Math.pow(2, attempt); console.error(`🚨 Rate limited on attempt ${attempt + 1}/${maxRetries + 1}, retrying after ${delay}ms`); if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, delay)); continue; } } // If it's not a rate limiting error or we've exhausted retries, throw throw error; } } throw lastError; } async queryEmails(options) { return this.retryWithBackoff(async () => { return executeWithTokenRetry(this.userEmail, async (client) => { let query = client.api(`/users/${this.userEmail}/messages`); // Important: Microsoft Graph API restrictions: // 1. Cannot use $search with $orderBy // 2. Complex $filter expressions may not work with $orderBy if (options.search) { query = query.search(options.search); // Log warning if orderBy is also specified if (options.orderBy) { console.error('⚠️ Warning: $orderBy is not supported with $search queries. Ignoring orderBy parameter.'); } } if (options.filter) { query = query.filter(options.filter); } if (options.top) { // Ensure we don't exceed Graph API limits const safeTop = Math.min(options.top, 1000); query = query.top(safeTop); } if (options.skip) { query = query.skip(options.skip); } // Only apply orderBy if search is not being used if (options.orderBy && !options.search) { try { query = query.orderby(options.orderBy); } catch (error) { console.error(`⚠️ Warning: orderBy "${options.orderBy}" failed, possibly due to complex filter. Proceeding without sorting.`); } } if (options.select && options.select.length > 0) { query = query.select(options.select); } try { const response = await query.get(); return response.value; } catch (error) { // Handle specific Graph API errors if (error.message?.includes('restriction or sort order is too complex')) { console.error('⚠️ Graph API error: restriction or sort order is too complex. Retrying without orderBy...'); // Retry without orderBy query = client.api(`/users/${this.userEmail}/messages`); if (options.search) { query = query.search(options.search); } if (options.filter) { query = query.filter(options.filter); } if (options.top) { const safeTop = Math.min(options.top, 1000); query = query.top(safeTop); } if (options.skip) { query = query.skip(options.skip); } if (options.select && options.select.length > 0) { query = query.select(options.select); } const response = await query.get(); return response.value; } // Re-throw other errors throw error; } }); }); } async getEmailById(messageId) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); return await client.api(`/users/${this.userEmail}/messages/${messageId}`).get(); }); } async moveEmail(messageId, destinationFolderId) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); return await client.api(`/users/${this.userEmail}/messages/${messageId}/move`) .post({ destinationId: destinationFolderId }); }); } async updateEmail(messageId, updates) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); return await client.api(`/users/${this.userEmail}/messages/${messageId}`) .patch(updates); }); } async listFolders() { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); const response = await client.api(`/users/${this.userEmail}/mailFolders`) .select(['id', 'displayName', 'parentFolderId', 'childFolderCount', 'unreadItemCount', 'totalItemCount']) .get(); return response.value; }); } async createFolder(folderData) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); // If no parent folder specified, create in the root mailFolders const endpoint = folderData.parentFolderId ? `/users/${this.userEmail}/mailFolders/${folderData.parentFolderId}/childFolders` : `/users/${this.userEmail}/mailFolders`; return await client.api(endpoint).post({ displayName: folderData.displayName, }); }); } async createDraft(draft) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); return await client.api(`/users/${this.userEmail}/messages`).post(draft); }); } async addAttachmentToDraft(messageId, attachment) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); const attachmentData = { '@odata.type': '#microsoft.graph.fileAttachment', name: attachment.name, contentType: attachment.contentType || 'application/octet-stream', contentBytes: attachment.contentBytes, }; // For large attachments (>4MB), we would need to use upload sessions // For now, we'll handle smaller attachments directly if (attachment.size && attachment.size > 4 * 1024 * 1024) { throw new Error('Attachments larger than 4MB require upload sessions (not yet implemented)'); } return await client.api(`/users/${this.userEmail}/messages/${messageId}/attachments`) .post(attachmentData); }); } async sendMail(message, saveToSentItems = true) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); await client.api(`/users/${this.userEmail}/sendMail`) .post({ message, saveToSentItems }); }); } async deleteEmail(messageId) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); await client.api(`/users/${this.userEmail}/messages/${messageId}`).delete(); }); } async listMailRules() { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); const response = await client.api(`/users/${this.userEmail}/mailFolders/inbox/messageRules`).get(); return response.value; }); } async createMailRule(ruleData) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); return await client.api(`/users/${this.userEmail}/mailFolders/inbox/messageRules`).post(ruleData); }); } async getMailRule(ruleId) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); return await client.api(`/users/${this.userEmail}/mailFolders/inbox/messageRules/${ruleId}`).get(); }); } async updateMailRule(ruleId, updateData) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); return await client.api(`/users/${this.userEmail}/mailFolders/inbox/messageRules/${ruleId}`).patch(updateData); }); } async deleteMailRule(ruleId) { return this.retryWithBackoff(async () => { const client = await getGraphClient(this.userEmail); await client.api(`/users/${this.userEmail}/mailFolders/inbox/messageRules/${ruleId}`).delete(); }); } } //# sourceMappingURL=mailService.js.map