const { formatDateTime } = require('../utils/time-formatter'); /** * List events functionality */ const config = require('../config'); const { callGraphAPI } = require('../utils/graph-api'); const { ensureAuthenticated } = require('../auth'); /** * List events handler * @param {object} args - Tool arguments * @returns {object} - MCP response */ async function handleListEvents(args) { const count = Math.min(parseInt(args.count, 10) || 10, config.MAX_RESULT_COUNT); // Resolve date range — accept plain YYYY-MM-DD or full ISO datetime const toISO = (dateStr, endOfDay = false) => { if (!dateStr) return null; // If just a date (no T), append time component if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { return endOfDay ? `${dateStr}T23:59:59Z` : `${dateStr}T00:00:00Z`; } return new Date(dateStr).toISOString(); }; const startISO = toISO(args.startDate) || new Date().toISOString(); const endISO = toISO(args.endDate, true); try { // Get access token const accessToken = await ensureAuthenticated(); // Build API endpoint let endpoint = 'me/events'; // Build date filter const filterParts = [`start/dateTime ge '${startISO}'`]; if (endISO) filterParts.push(`end/dateTime le '${endISO}'`); // Add query parameters const queryParams = { $top: count, $orderby: 'start/dateTime', $filter: filterParts.join(' and '), $select: config.CALENDAR_SELECT_FIELDS }; // Make API call const response = await callGraphAPI(accessToken, 'GET', endpoint, null, queryParams); if (!response.value || response.value.length === 0) { return { content: [{ type: "text", text: "No calendar events found." }] }; } // Format results const eventList = response.value.map((event, index) => { // Graph stores timed events in UTC and returns dateTime without a 'Z' suffix. // Normalize to UTC so the display matches the stored instant regardless of // the container's default timezone. const startDate = formatDateTime( normalizeGraphDateTime(event.start.dateTime, event.start.timeZone), config.DEFAULT_TIMEZONE ); const endDate = formatDateTime( normalizeGraphDateTime(event.end.dateTime, event.end.timeZone), config.DEFAULT_TIMEZONE ); const location = event.location.displayName || 'No location'; return `${index + 1}. ${event.subject} - Location: ${location}\nStart: ${startDate}\nEnd: ${endDate}\nSubject: ${event.subject}\nSummary: ${event.bodyPreview}\nID: ${event.id}\n`; }).join("\n"); return { content: [{ type: "text", text: `Found ${response.value.length} events:\n\n${eventList}` }] }; } 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 events: ${error.message}` }] }; } } /** * Ensure a Graph dateTime string is parsed as the intended instant. * Graph returns timed events in UTC with no Z suffix; append Z so Node parses * it as UTC rather than as the container's local timezone. * @param {string} dateTime - Graph dateTime value * @param {string} timeZone - Graph timeZone value * @returns {string} - Date string safe for new Date()/toLocaleString */ function normalizeGraphDateTime(dateTime, timeZone) { if (!dateTime) return dateTime; const hasOffset = /Z|[+-]\d{2}:\d{2}$|[+-]\d{4}$/.test(dateTime); if (!hasOffset && timeZone === 'UTC') { return `${dateTime}Z`; } return dateTime; } module.exports = handleListEvents;