122 lines
3.9 KiB
JavaScript
122 lines
3.9 KiB
JavaScript
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;
|