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
JavaScript
/**
* 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;