Add optional mailbox (UPN/SMTP) routing on email/folder/thread tools via
users/{upn}/... and X-AnchorMailbox. New list-mailboxes probes primary,
OUTLOOK_SHARED_MAILBOXES seeds, cache, and candidates. Send supports
mailbox-rooted sendMail and onBehalfOf. MSAL requests Mail.*.Shared;
check-auth-status reports token scp gaps. Docs, env example, tests.
152 lines
4.8 KiB
JavaScript
152 lines
4.8 KiB
JavaScript
/**
|
|
* Read multiple emails functionality
|
|
*/
|
|
const config = require('../config');
|
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
const { ensureAuthenticated } = require('../auth');
|
|
const { cleanBody } = require('../utils/bodyParser');
|
|
const { formatDateTime } = require('../utils/time-formatter');
|
|
const { normalizeMailbox, buildPath, withMailboxHeaders } = require('../utils/mailbox');
|
|
|
|
/**
|
|
* Format a single email for display
|
|
* @param {object} email - Email object from Graph API
|
|
* @param {string} emailId - Email ID for error context
|
|
* @returns {string} - Formatted email text
|
|
*/
|
|
function formatEmail(email, emailId) {
|
|
if (!email) {
|
|
return `Email ID ${emailId}: Not found or inaccessible`;
|
|
}
|
|
|
|
try {
|
|
const sender = email.from ? `${email.from.emailAddress.name} (${email.from.emailAddress.address})` : 'Unknown';
|
|
const to = email.toRecipients ? email.toRecipients.map(r => `${r.emailAddress.name} (${r.emailAddress.address})`).join(", ") : 'None';
|
|
const cc = email.ccRecipients && email.ccRecipients.length > 0 ? email.ccRecipients.map(r => `${r.emailAddress.name} (${r.emailAddress.address})`).join(", ") : 'None';
|
|
const bcc = email.bccRecipients && email.bccRecipients.length > 0 ? email.bccRecipients.map(r => `${r.emailAddress.name} (${r.emailAddress.address})`).join(", ") : 'None';
|
|
const date = formatDateTime(email.receivedDateTime);
|
|
|
|
let body = '';
|
|
if (email.body) {
|
|
body = cleanBody(email.body.content);
|
|
} else {
|
|
body = cleanBody(email.bodyPreview) || 'No content';
|
|
}
|
|
|
|
return `From: ${sender}
|
|
To: ${to}
|
|
${cc !== 'None' ? `CC: ${cc}\n` : ''}${bcc !== 'None' ? `BCC: ${bcc}\n` : ''}Subject: ${email.subject}
|
|
Date: ${date}
|
|
Importance: ${email.importance || 'normal'}
|
|
Has Attachments: ${email.hasAttachments ? 'Yes' : 'No'}
|
|
|
|
${body}`;
|
|
} catch (error) {
|
|
return `Email ID ${emailId}: Error formatting email - ${error.message}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read multiple emails handler
|
|
* @param {object} args - Tool arguments
|
|
* @returns {object} - MCP response
|
|
*/
|
|
async function handleReadMultipleEmails(args) {
|
|
const emailIds = args.ids;
|
|
const mb = normalizeMailbox(args.mailbox);
|
|
const opts = { headers: withMailboxHeaders(mb) };
|
|
|
|
if (!emailIds || !Array.isArray(emailIds) || emailIds.length === 0) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: "Email IDs array is required and must contain at least one ID."
|
|
}]
|
|
};
|
|
}
|
|
|
|
const maxEmails = 10;
|
|
if (emailIds.length > maxEmails) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Too many email IDs provided. Maximum allowed is ${maxEmails}, but ${emailIds.length} were provided.`
|
|
}]
|
|
};
|
|
}
|
|
|
|
try {
|
|
const accessToken = await ensureAuthenticated();
|
|
|
|
const emailPromises = emailIds.map(async (emailId) => {
|
|
try {
|
|
const endpoint = buildPath(mb, `messages/${emailId}`);
|
|
const queryParams = {
|
|
$select: config.EMAIL_DETAIL_FIELDS
|
|
};
|
|
|
|
const email = await callGraphAPI(accessToken, 'GET', endpoint, null, queryParams, opts);
|
|
return { emailId, email, error: null };
|
|
} catch (error) {
|
|
console.error(`Error reading email ${emailId}: ${error.message}`);
|
|
return { emailId, email: null, error: error.message };
|
|
}
|
|
});
|
|
|
|
const results = await Promise.all(emailPromises);
|
|
|
|
const mailboxNote = mb.kind === 'user' ? ` Mailbox: ${mb.smtpOrUpn}.` : '';
|
|
|
|
const formattedEmails = results.map((result, index) => {
|
|
const emailNumber = index + 1;
|
|
const separator = "=".repeat(80);
|
|
|
|
if (result.error) {
|
|
return `${separator}
|
|
EMAIL ${emailNumber} (ID: ${result.emailId})
|
|
${separator}
|
|
Error: ${result.error}`;
|
|
} else {
|
|
const formattedEmail = formatEmail(result.email, result.emailId);
|
|
return `${separator}
|
|
EMAIL ${emailNumber} (ID: ${result.emailId})
|
|
${separator}
|
|
${formattedEmail}`;
|
|
}
|
|
});
|
|
|
|
const successCount = results.filter(r => !r.error && r.email).length;
|
|
const errorCount = results.filter(r => r.error || !r.email).length;
|
|
|
|
const summary = `Retrieved ${successCount} email(s) successfully${errorCount > 0 ? `, ${errorCount} failed` : ''}.${mailboxNote}
|
|
|
|
`;
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: summary + formattedEmails.join('\n\n')
|
|
}
|
|
]
|
|
};
|
|
} catch (error) {
|
|
if (error.message === 'Authentication required') {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
}]
|
|
};
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Error accessing emails: ${error.message}`
|
|
}]
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = handleReadMultipleEmails;
|