diff --git a/calendar/list.js b/calendar/list.js index eac3058..e8b8f31 100644 --- a/calendar/list.js +++ b/calendar/list.js @@ -61,8 +61,17 @@ async function handleListEvents(args) { // Format results const eventList = response.value.map((event, index) => { - const startDate = formatDateTime(event.start.dateTime, event.start.timeZone); - const endDate = formatDateTime(event.end.dateTime, event.end.timeZone); + // 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`; @@ -93,4 +102,21 @@ async function handleListEvents(args) { } } +/** + * 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; diff --git a/config.js b/config.js index 1cdef92..a343891 100644 --- a/config.js +++ b/config.js @@ -34,15 +34,12 @@ module.exports = { GRAPH_API_ENDPOINT: 'https://graph.microsoft.com/v1.0/', // Calendar constants - CALENDAR_SELECT_FIELDS: 'id,subject,start,end,location,bodyPreview,isAllDay,recurrence,attendees', + CALENDAR_SELECT_FIELDS: 'id,subject,bodyPreview,start,end,location,organizer,attendees,isAllDay,isCancelled,recurrence', // Email constants EMAIL_SELECT_FIELDS: 'id,subject,from,toRecipients,ccRecipients,receivedDateTime,bodyPreview,hasAttachments,importance,isRead,conversationId', EMAIL_DETAIL_FIELDS: 'id,subject,from,toRecipients,ccRecipients,bccRecipients,receivedDateTime,bodyPreview,body,hasAttachments,importance,isRead,internetMessageHeaders', - // Calendar constants - CALENDAR_SELECT_FIELDS: 'id,subject,bodyPreview,start,end,location,organizer,attendees,isAllDay,isCancelled', - // Default timezone for calendar event creation and display (IANA tz name, e.g. 'America/New_York'). // Override via MS_TIMEZONE env var. Graph API accepts IANA timezone identifiers. DEFAULT_TIMEZONE: process.env.MS_TIMEZONE || 'America/New_York', diff --git a/utils/time-formatter.js b/utils/time-formatter.js index a8904e5..f9f3e08 100644 --- a/utils/time-formatter.js +++ b/utils/time-formatter.js @@ -2,7 +2,7 @@ * Timezone-aware date formatting utilities for Outlook MCP Server. * * All user-visible timestamps are formatted in the configured timezone - * (MS_TIMEZONE env var, default Eastern Standard Time) so that dates + * (MS_TIMEZONE env var, default America/New_York) so that dates * rendered inside the UTC container still match the user's local time. */ const config = require('../config');