outlook-mcp/folder/move.js
Seton Carmichael e70840552d feat(outlook-mcp): shared mailbox targeting, discovery, and scopes (v1.1.0)
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.
2026-08-24 08:41:05 -04:00

155 lines
4 KiB
JavaScript

/**
* Move emails functionality
*/
const { callGraphAPI } = require('../utils/graph-api');
const { ensureAuthenticated } = require('../auth');
const { getFolderIdByName } = require('../email/folder-utils');
const { normalizeMailbox, buildPath, withMailboxHeaders } = require('../utils/mailbox');
/**
* Move emails handler
* @param {object} args - Tool arguments
* @returns {object} - MCP response
*/
async function handleMoveEmails(args) {
const emailIds = args.emailIds || '';
const targetFolder = args.targetFolder || '';
const sourceFolder = args.sourceFolder || '';
const mb = normalizeMailbox(args.mailbox);
if (!emailIds) {
return {
content: [{
type: "text",
text: "Email IDs are required. Please provide a comma-separated list of email IDs to move."
}]
};
}
if (!targetFolder) {
return {
content: [{
type: "text",
text: "Target folder name is required."
}]
};
}
try {
const accessToken = await ensureAuthenticated();
const ids = emailIds.split(',').map(id => id.trim()).filter(id => id);
if (ids.length === 0) {
return {
content: [{
type: "text",
text: "No valid email IDs provided."
}]
};
}
const result = await moveEmailsToFolder(accessToken, ids, targetFolder, sourceFolder, mb);
return {
content: [{
type: "text",
text: result.message
}]
};
} 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 moving emails: ${error.message}`
}]
};
}
}
/**
* Move emails to a folder
*/
async function moveEmailsToFolder(accessToken, emailIds, targetFolderName, sourceFolderName, mb) {
const opts = { headers: withMailboxHeaders(mb) };
try {
const targetFolderId = await getFolderIdByName(accessToken, targetFolderName, mb);
if (!targetFolderId) {
return {
success: false,
message: `Target folder "${targetFolderName}" not found${mb.kind === 'user' ? ` in ${mb.smtpOrUpn}` : ''}. Please specify a valid folder name.`
};
}
const results = {
successful: [],
failed: []
};
for (const emailId of emailIds) {
try {
await callGraphAPI(
accessToken,
'POST',
buildPath(mb, `messages/${emailId}/move`),
{
destinationId: targetFolderId
},
null,
opts
);
results.successful.push(emailId);
} catch (error) {
console.error(`Error moving email ${emailId}: ${error.message}`);
results.failed.push({
id: emailId,
error: error.message
});
}
}
let message = '';
if (results.successful.length > 0) {
message += `Successfully moved ${results.successful.length} email(s) to "${targetFolderName}"`;
if (mb.kind === 'user') message += ` (${mb.smtpOrUpn})`;
message += '.';
}
if (results.failed.length > 0) {
if (message) message += '\n\n';
message += `Failed to move ${results.failed.length} email(s). Errors:`;
const maxErrors = Math.min(results.failed.length, 3);
for (let i = 0; i < maxErrors; i++) {
const failure = results.failed[i];
message += `\n- Email ${i + 1}: ${failure.error}`;
}
if (results.failed.length > maxErrors) {
message += `\n...and ${results.failed.length - maxErrors} more.`;
}
}
return {
success: results.successful.length > 0,
message,
results
};
} catch (error) {
console.error(`Error in moveEmailsToFolder: ${error.message}`);
throw error;
}
}
module.exports = handleMoveEmails;