269 lines
7.9 KiB
JavaScript
269 lines
7.9 KiB
JavaScript
/**
|
|
* List rules functionality
|
|
*/
|
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
const { ensureAuthenticated } = require('../auth');
|
|
|
|
/**
|
|
* List rules handler
|
|
* @param {object} args - Tool arguments
|
|
* @returns {object} - MCP response
|
|
*/
|
|
async function handleListRules(args) {
|
|
const includeDetails = args.includeDetails === true;
|
|
|
|
try {
|
|
// Get access token
|
|
const accessToken = await ensureAuthenticated();
|
|
|
|
// Get all inbox rules
|
|
const rules = await getInboxRules(accessToken);
|
|
|
|
// Build an id -> displayName map from all mail folders so rule actions can
|
|
// show friendly folder names instead of raw Graph folder IDs.
|
|
const folderMap = await getFolderDisplayNameMap(accessToken);
|
|
|
|
// Format the rules based on detail level
|
|
const formattedRules = formatRulesList(rules, includeDetails, folderMap);
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: formattedRules
|
|
}]
|
|
};
|
|
} 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 listing rules: ${error.message}`
|
|
}]
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all inbox rules
|
|
* @param {string} accessToken - Access token
|
|
* @returns {Promise<Array>} - Array of rule objects
|
|
*/
|
|
async function getInboxRules(accessToken) {
|
|
try {
|
|
const response = await callGraphAPI(
|
|
accessToken,
|
|
'GET',
|
|
'me/mailFolders/inbox/messageRules',
|
|
null
|
|
);
|
|
|
|
return response.value || [];
|
|
} catch (error) {
|
|
console.error(`Error getting inbox rules: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a mapping from folder ID to displayName for all mail folders.
|
|
* Recurses into child folders so folders nested under Recoverable Items
|
|
* (and other parents) are resolved.
|
|
* @param {string} accessToken - Access token
|
|
* @returns {Promise<Map<string, string>>} - Map of folder id -> displayName
|
|
*/
|
|
async function getFolderDisplayNameMap(accessToken) {
|
|
const map = new Map();
|
|
|
|
try {
|
|
await collectFolders(accessToken, map, 'me/mailFolders');
|
|
} catch (error) {
|
|
console.error(`Error building folder display name map: ${error.message}`);
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Recursively collect id -> displayName mappings for a folder endpoint.
|
|
* @param {string} accessToken - Access token
|
|
* @param {Map<string, string>} map - Accumulator map
|
|
* @param {string} endpoint - Graph API folder endpoint
|
|
*/
|
|
async function collectFolders(accessToken, map, endpoint) {
|
|
const response = await callGraphAPI(
|
|
accessToken,
|
|
'GET',
|
|
endpoint,
|
|
null,
|
|
{
|
|
$top: 100,
|
|
$select: 'id,displayName,childFolderCount'
|
|
}
|
|
);
|
|
|
|
const folders = response.value || [];
|
|
const childPromises = [];
|
|
|
|
for (const folder of folders) {
|
|
if (folder.id && folder.displayName) {
|
|
map.set(folder.id, folder.displayName);
|
|
}
|
|
|
|
if (folder.childFolderCount > 0) {
|
|
childPromises.push(collectFolders(accessToken, map, `me/mailFolders/${folder.id}/childFolders`));
|
|
}
|
|
}
|
|
|
|
await Promise.all(childPromises);
|
|
}
|
|
|
|
/**
|
|
* Format rules list for display
|
|
* @param {Array} rules - Array of rule objects
|
|
* @param {boolean} includeDetails - Whether to include detailed conditions and actions
|
|
* @param {Map<string, string>} folderMap - Optional folder id -> displayName map
|
|
* @returns {string} - Formatted rules list
|
|
*/
|
|
function formatRulesList(rules, includeDetails, folderMap = new Map()) {
|
|
if (!rules || rules.length === 0) {
|
|
return "No inbox rules found.\n\nTip: You can create rules using the 'create-rule' tool. Rules are processed in order of their sequence number (lower numbers are processed first).";
|
|
}
|
|
|
|
// Sort rules by sequence to show execution order
|
|
const sortedRules = [...rules].sort((a, b) => {
|
|
return (a.sequence || 9999) - (b.sequence || 9999);
|
|
});
|
|
|
|
// Format rules based on detail level
|
|
if (includeDetails) {
|
|
// Detailed format
|
|
const detailedRules = sortedRules.map((rule, index) => {
|
|
// Format rule header with sequence
|
|
let ruleText = `${index + 1}. ${rule.displayName}${rule.isEnabled ? '' : ' (Disabled)'} - Sequence: ${rule.sequence || 'N/A'}`;
|
|
|
|
// Format conditions
|
|
const conditions = formatRuleConditions(rule);
|
|
if (conditions) {
|
|
ruleText += `\n Conditions: ${conditions}`;
|
|
}
|
|
|
|
// Format actions
|
|
const actions = formatRuleActions(rule, folderMap);
|
|
if (actions) {
|
|
ruleText += `\n Actions: ${actions}`;
|
|
}
|
|
|
|
return ruleText;
|
|
});
|
|
|
|
return `Found ${rules.length} inbox rules (sorted by execution order):\n\n${detailedRules.join('\n\n')}\n\nRules are processed in order of their sequence number. You can change rule order using the 'edit-rule-sequence' tool.`;
|
|
} else {
|
|
// Simple format
|
|
const simpleRules = sortedRules.map((rule, index) => {
|
|
return `${index + 1}. ${rule.displayName}${rule.isEnabled ? '' : ' (Disabled)'} - Sequence: ${rule.sequence || 'N/A'}`;
|
|
});
|
|
|
|
return `Found ${rules.length} inbox rules (sorted by execution order):\n\n${simpleRules.join('\n')}\n\nTip: Use 'list-rules with includeDetails=true' to see more information about each rule.`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format rule conditions for display
|
|
* @param {object} rule - Rule object
|
|
* @returns {string} - Formatted conditions
|
|
*/
|
|
function formatRuleConditions(rule) {
|
|
const conditions = [];
|
|
|
|
// From addresses
|
|
if (rule.conditions?.fromAddresses?.length > 0) {
|
|
const senders = rule.conditions.fromAddresses.map(addr => addr.emailAddress.address).join(', ');
|
|
conditions.push(`From: ${senders}`);
|
|
}
|
|
|
|
// Subject contains
|
|
if (rule.conditions?.subjectContains?.length > 0) {
|
|
conditions.push(`Subject contains: "${rule.conditions.subjectContains.join(', ')}"`);
|
|
}
|
|
|
|
// Contains body text
|
|
if (rule.conditions?.bodyContains?.length > 0) {
|
|
conditions.push(`Body contains: "${rule.conditions.bodyContains.join(', ')}"`);
|
|
}
|
|
|
|
// Has attachment
|
|
if (rule.conditions?.hasAttachment === true) {
|
|
conditions.push('Has attachment');
|
|
}
|
|
|
|
// Importance
|
|
if (rule.conditions?.importance) {
|
|
conditions.push(`Importance: ${rule.conditions.importance}`);
|
|
}
|
|
|
|
return conditions.join('; ');
|
|
}
|
|
|
|
/**
|
|
* Format rule actions for display
|
|
* @param {object} rule - Rule object
|
|
* @param {Map<string, string>} folderMap - Optional folder id -> displayName map
|
|
* @returns {string} - Formatted actions
|
|
*/
|
|
function formatRuleActions(rule, folderMap = new Map()) {
|
|
const actions = [];
|
|
|
|
// Resolve a folder ID to a displayName; fall back to the raw ID if unknown.
|
|
const resolveFolder = (folderId) => {
|
|
if (!folderId) return folderId;
|
|
return folderMap.get(folderId) || folderId;
|
|
};
|
|
|
|
// Move to folder
|
|
if (rule.actions?.moveToFolder) {
|
|
actions.push(`Move to folder: ${resolveFolder(rule.actions.moveToFolder)}`);
|
|
}
|
|
|
|
// Copy to folder
|
|
if (rule.actions?.copyToFolder) {
|
|
actions.push(`Copy to folder: ${resolveFolder(rule.actions.copyToFolder)}`);
|
|
}
|
|
|
|
// Mark as read
|
|
if (rule.actions?.markAsRead === true) {
|
|
actions.push('Mark as read');
|
|
}
|
|
|
|
// Mark importance
|
|
if (rule.actions?.markImportance) {
|
|
actions.push(`Mark importance: ${rule.actions.markImportance}`);
|
|
}
|
|
|
|
// Forward
|
|
if (rule.actions?.forwardTo?.length > 0) {
|
|
const recipients = rule.actions.forwardTo.map(r => r.emailAddress.address).join(', ');
|
|
actions.push(`Forward to: ${recipients}`);
|
|
}
|
|
|
|
// Delete
|
|
if (rule.actions?.delete === true) {
|
|
actions.push('Delete');
|
|
}
|
|
|
|
return actions.join('; ');
|
|
}
|
|
|
|
module.exports = {
|
|
handleListRules,
|
|
getInboxRules,
|
|
formatRulesList,
|
|
formatRuleActions
|
|
};
|