UNPKG

outlook-mcp

Version:

Comprehensive MCP server for Claude to access Microsoft Outlook and Teams via Microsoft Graph API - including Email, Calendar, Contacts, Tasks, Teams, Chats, and Online Meetings

145 lines (122 loc) 4.67 kB
/** * Calendar search functionality */ const { callGraphAPI } = require('../utils/graph-api'); const { ensureAuthenticated } = require('../auth'); const config = require('../config'); /** * Search events handler * @param {object} args - Tool arguments * @returns {object} - MCP response */ async function handleSearchEvents(args) { try { const { query = '', startDate, endDate, subject = '', attendee = '', location = '', organizer = '', calendarId, count = config.DEFAULT_PAGE_SIZE, showRecurring = true } = args; // Ensure user is authenticated const accessToken = await ensureAuthenticated(); // Validate count parameter const validCount = Math.min(Math.max(1, count), config.MAX_RESULT_COUNT); // Build the API path let apiPath = 'me/events'; if (calendarId) { apiPath = `me/calendars/${calendarId}/events`; } // Build filter conditions const filterConditions = []; if (query) { filterConditions.push(`(contains(subject,'${query}') or contains(bodyPreview,'${query}') or contains(location/displayName,'${query}'))`); } if (subject) { filterConditions.push(`contains(subject,'${subject}')`); } if (location) { filterConditions.push(`contains(location/displayName,'${location}')`); } if (organizer) { filterConditions.push(`contains(organizer/emailAddress/name,'${organizer}')`); } if (attendee) { filterConditions.push(`attendees/any(a: contains(a/emailAddress/name,'${attendee}') or contains(a/emailAddress/address,'${attendee}'))`); } // Add date range filter if (startDate || endDate) { const now = new Date(); const start = startDate ? new Date(startDate) : new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago const end = endDate ? new Date(endDate) : new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000); // 1 year ahead filterConditions.push(`start/dateTime ge '${start.toISOString()}' and end/dateTime le '${end.toISOString()}'`); } // Filter out recurring series if requested if (!showRecurring) { filterConditions.push(`type ne 'seriesMaster'`); } // Build query parameters const queryParams = { '$select': config.CALENDAR_SELECT_FIELDS, '$top': validCount, '$orderby': 'start/dateTime asc' }; if (filterConditions.length > 0) { queryParams['$filter'] = filterConditions.join(' and '); } console.error(`Searching events with filters: ${queryParams['$filter'] || 'none'}`); // Make API call const response = await callGraphAPI(accessToken, 'GET', apiPath, null, queryParams); const events = response.value || []; // Format the response const formatDateTime = (dateObj) => { if (!dateObj) return 'Not set'; return new Date(dateObj.dateTime).toLocaleString(); }; const formatAttendees = (attendees) => { if (!attendees || attendees.length === 0) return 'None'; return attendees.map(a => a.emailAddress.name || a.emailAddress.address).join(', '); }; return { content: [ { type: "text", text: `Found ${events.length} events matching your search:\n\n${events.map(event => { const subject = event.subject || 'No subject'; const start = formatDateTime(event.start); const end = formatDateTime(event.end); const location = event.location && event.location.displayName ? event.location.displayName : 'No location'; const organizer = event.organizer && event.organizer.emailAddress ? (event.organizer.emailAddress.name || event.organizer.emailAddress.address) : 'Unknown'; const attendees = formatAttendees(event.attendees); const isRecurring = event.recurrence ? ' (Recurring)' : ''; const isCancelled = event.isCancelled ? ' (Cancelled)' : ''; return `📅 ${subject}${isRecurring}${isCancelled} ID: ${event.id} Start: ${start} End: ${end} Location: ${location} Organizer: ${organizer} Attendees: ${attendees} ${event.bodyPreview ? `Preview: ${event.bodyPreview.substring(0, 100)}...` : ''}`; }).join('\n\n')}` } ] }; } catch (error) { console.error('Error in handleSearchEvents:', error); return { error: { code: -32603, message: `Failed to search events: ${error.message}` } }; } } module.exports = handleSearchEvents;