MCP server providing Microsoft Outlook integration via Graph API: - Email: list, search, read, send, thread reconstruction - Calendar: list, create, decline, cancel, delete events - Folders: list, create, move emails - Inbox rules: list, create, reorder - MSAL device code flow auth with persistent token cache - Test mode with mock data - Comprehensive README with setup, config, and tool reference 20 MCP tools across 5 modules. Node.js >= 14. MIT license.
221 lines
5.9 KiB
JavaScript
221 lines
5.9 KiB
JavaScript
/**
|
|
* OData helper functions for Microsoft Graph API
|
|
*/
|
|
|
|
/**
|
|
* Escapes a string for use in OData queries
|
|
* @param {string} str - The string to escape
|
|
* @returns {string} - The escaped string
|
|
*/
|
|
function escapeODataString(str) {
|
|
if (!str) return str;
|
|
|
|
// Replace single quotes with double single quotes (OData escaping)
|
|
// And remove any special characters that could cause OData syntax errors
|
|
str = str.replace(/'/g, "''");
|
|
|
|
// Escape other potentially problematic characters
|
|
str = str.replace(/[\(\)\{\}\[\]\:\;\,\/\?\&\=\+\*\%\$\#\@\!\^]/g, '');
|
|
|
|
console.error(`Escaped OData string: '${str}'`);
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Builds an OData filter from filter conditions
|
|
* @param {Array<string>} conditions - Array of filter conditions
|
|
* @returns {string} - Combined OData filter expression
|
|
*/
|
|
function buildODataFilter(conditions) {
|
|
if (!conditions || conditions.length === 0) {
|
|
return '';
|
|
}
|
|
|
|
return conditions.join(' and ');
|
|
}
|
|
|
|
/**
|
|
* Gets start of day for a given date
|
|
* @param {Date} date - The date
|
|
* @returns {Date} - Start of day
|
|
*/
|
|
function startOfDay(date) {
|
|
const start = new Date(date);
|
|
start.setHours(0, 0, 0, 0);
|
|
return start;
|
|
}
|
|
|
|
/**
|
|
* Gets end of day for a given date
|
|
* @param {Date} date - The date
|
|
* @returns {Date} - End of day
|
|
*/
|
|
function endOfDay(date) {
|
|
const end = new Date(date);
|
|
end.setHours(23, 59, 59, 999);
|
|
return end;
|
|
}
|
|
|
|
/**
|
|
* Parses date input (ISO string or relative date)
|
|
* @param {string} dateInput - Date string
|
|
* @returns {Date} - Parsed date
|
|
*/
|
|
function parseDate(dateInput) {
|
|
if (!dateInput) return null;
|
|
|
|
const now = new Date();
|
|
const today = new Date(now);
|
|
|
|
// Handle relative dates
|
|
const relativeMap = {
|
|
'today': today,
|
|
'yesterday': new Date(now.getTime() - 24*60*60*1000),
|
|
'tomorrow': new Date(now.getTime() + 24*60*60*1000),
|
|
'last7days': new Date(now.getTime() - 7*24*60*60*1000),
|
|
'last30days': new Date(now.getTime() - 30*24*60*60*1000),
|
|
'last90days': new Date(now.getTime() - 90*24*60*60*1000)
|
|
};
|
|
|
|
if (relativeMap[dateInput.toLowerCase()]) {
|
|
return relativeMap[dateInput.toLowerCase()];
|
|
}
|
|
|
|
// Handle ISO dates
|
|
const parsed = new Date(dateInput);
|
|
if (isNaN(parsed.getTime())) {
|
|
throw new Error(`Invalid date format: ${dateInput}`);
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
/**
|
|
* Processes predefined date ranges
|
|
* @param {string} dateRange - Predefined range
|
|
* @returns {Object} - Object with from and to dates
|
|
*/
|
|
function processDateRange(dateRange) {
|
|
if (!dateRange) return null;
|
|
|
|
const now = new Date();
|
|
const today = new Date(now);
|
|
|
|
switch (dateRange.toLowerCase()) {
|
|
case 'today':
|
|
return {
|
|
from: startOfDay(today),
|
|
to: endOfDay(today)
|
|
};
|
|
|
|
case 'yesterday': {
|
|
const yesterday = new Date(now.getTime() - 24*60*60*1000);
|
|
return {
|
|
from: startOfDay(yesterday),
|
|
to: endOfDay(yesterday)
|
|
};
|
|
}
|
|
|
|
case 'last7days':
|
|
return {
|
|
from: new Date(now.getTime() - 7*24*60*60*1000),
|
|
to: now
|
|
};
|
|
|
|
case 'last30days':
|
|
return {
|
|
from: new Date(now.getTime() - 30*24*60*60*1000),
|
|
to: now
|
|
};
|
|
|
|
case 'thisweek': {
|
|
const startOfWeek = new Date(today);
|
|
startOfWeek.setDate(today.getDate() - today.getDay());
|
|
return {
|
|
from: startOfDay(startOfWeek),
|
|
to: endOfDay(today)
|
|
};
|
|
}
|
|
|
|
case 'lastweek': {
|
|
const startOfLastWeek = new Date(today);
|
|
startOfLastWeek.setDate(today.getDate() - today.getDay() - 7);
|
|
const endOfLastWeek = new Date(startOfLastWeek);
|
|
endOfLastWeek.setDate(startOfLastWeek.getDate() + 6);
|
|
return {
|
|
from: startOfDay(startOfLastWeek),
|
|
to: endOfDay(endOfLastWeek)
|
|
};
|
|
}
|
|
|
|
case 'thismonth': {
|
|
const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
return {
|
|
from: startOfDay(startOfMonth),
|
|
to: endOfDay(today)
|
|
};
|
|
}
|
|
|
|
case 'lastmonth': {
|
|
const startOfLastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
|
const endOfLastMonth = new Date(today.getFullYear(), today.getMonth(), 0);
|
|
return {
|
|
from: startOfDay(startOfLastMonth),
|
|
to: endOfDay(endOfLastMonth)
|
|
};
|
|
}
|
|
|
|
default:
|
|
throw new Error(`Unknown date range: ${dateRange}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds date filter conditions for OData queries
|
|
* @param {string} dateFrom - Start date
|
|
* @param {string} dateTo - End date
|
|
* @param {string} dateRange - Predefined range
|
|
* @returns {Array<string>} - Array of filter conditions
|
|
*/
|
|
function buildDateFilter(dateFrom, dateTo, dateRange) {
|
|
const conditions = [];
|
|
|
|
try {
|
|
if (dateRange) {
|
|
const range = processDateRange(dateRange);
|
|
if (range) {
|
|
conditions.push(`receivedDateTime ge ${range.from.toISOString()}`);
|
|
conditions.push(`receivedDateTime le ${range.to.toISOString()}`);
|
|
}
|
|
} else {
|
|
if (dateFrom) {
|
|
const fromDate = parseDate(dateFrom);
|
|
conditions.push(`receivedDateTime ge ${fromDate.toISOString()}`);
|
|
}
|
|
if (dateTo) {
|
|
const toDate = parseDate(dateTo);
|
|
// If only date provided (no time), set to end of day
|
|
if (dateTo.length === 10) { // YYYY-MM-DD format
|
|
conditions.push(`receivedDateTime le ${endOfDay(toDate).toISOString()}`);
|
|
} else {
|
|
conditions.push(`receivedDateTime le ${toDate.toISOString()}`);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Date filter error: ${error.message}`);
|
|
// Return empty conditions on error to avoid breaking the query
|
|
}
|
|
|
|
return conditions;
|
|
}
|
|
|
|
module.exports = {
|
|
escapeODataString,
|
|
buildODataFilter,
|
|
parseDate,
|
|
processDateRange,
|
|
buildDateFilter,
|
|
startOfDay,
|
|
endOfDay
|
|
};
|