import { McpError, ErrorCode, Tool } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import {
CreateEventSchema,
CreateReminderSchema,
FindFreeTimeSchema,
CreateEventInput,
CreateReminderInput,
FindFreeTimeInput,
CalendarEvent,
Calendar,
TimeSlot,
Reminder,
ApiResponse
} from '../types';
import { CalendarService } from '../services/CalendarService';
import { ReminderService } from '../services/ReminderService';
export class CalendarTools {
constructor(
private calendarService: CalendarService,
private reminderService: ReminderService
) {}
// Define all MCP tools
getTools(): Tool[] {
return [
// Calendar operations
{
name: 'get_calendars',
description: 'List all available calendars for the user',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
{
name: 'get_calendar_events',
description: 'Get events from a specific calendar within a date range, optionally filtered by search query',
inputSchema: {
type: 'object',
properties: {
calendar_id: {
type: 'string',
description: 'The ID of the calendar to query. Use "primary" for the user\'s main calendar'
},
start_date: {
type: 'string',
format: 'date-time',
description: 'Start date/time in ISO format (e.g., 2024-01-01T00:00:00Z)'
},
end_date: {
type: 'string',
format: 'date-time',
description: 'End date/time in ISO format (e.g., 2024-01-31T23:59:59Z)'
},
query: {
type: 'string',
description: 'Optional search query to filter events by title, description, or attendees'
}
},
required: ['calendar_id', 'start_date', 'end_date']
}
},
{
name: 'create_calendar_event',
description: 'Create a new calendar event',
inputSchema: {
type: 'object',
properties: {
calendar_id: {
type: 'string',
description: 'The ID of the calendar to create the event in'
},
title: {
type: 'string',
description: 'The title/summary of the event'
},
start_time: {
type: 'string',
format: 'date-time',
description: 'Start time in ISO format'
},
end_time: {
type: 'string',
format: 'date-time',
description: 'End time in ISO format'
},
description: {
type: 'string',
description: 'Optional detailed description of the event'
},
location: {
type: 'string',
description: 'Optional location of the event'
},
attendees: {
type: 'array',
description: 'Optional list of attendees',
items: {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
display_name: { type: 'string' },
optional: { type: 'boolean' }
},
required: ['email']
}
}
},
required: ['calendar_id', 'title', 'start_time', 'end_time']
}
},
{
name: 'update_calendar_event',
description: 'Update an existing calendar event',
inputSchema: {
type: 'object',
properties: {
calendar_id: { type: 'string' },
event_id: { type: 'string' },
title: { type: 'string' },
start_time: { type: 'string', format: 'date-time' },
end_time: { type: 'string', format: 'date-time' },
description: { type: 'string' },
location: { type: 'string' },
attendees: {
type: 'array',
items: {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
display_name: { type: 'string' },
optional: { type: 'boolean' }
},
required: ['email']
}
}
},
required: ['calendar_id', 'event_id']
}
},
{
name: 'delete_calendar_event',
description: 'Delete a calendar event',
inputSchema: {
type: 'object',
properties: {
calendar_id: { type: 'string' },
event_id: { type: 'string' }
},
required: ['calendar_id', 'event_id']
}
},
{
name: 'find_free_time',
description: 'Find available time slots across multiple calendars for scheduling',
inputSchema: {
type: 'object',
properties: {
calendar_ids: {
type: 'array',
items: { type: 'string' },
description: 'List of calendar IDs to check for availability'
},
start_date: {
type: 'string',
format: 'date-time',
description: 'Start of the search period'
},
end_date: {
type: 'string',
format: 'date-time',
description: 'End of the search period'
},
duration_minutes: {
type: 'number',
description: 'Required duration in minutes'
},
constraints: {
type: 'object',
description: 'Optional scheduling constraints',
properties: {
working_hours: {
type: 'object',
description: 'Working hours for each day of the week',
additionalProperties: {
type: 'object',
properties: {
start_time: { type: 'string', pattern: '^\\d{2}:\\d{2}$' },
end_time: { type: 'string', pattern: '^\\d{2}:\\d{2}$' }
}
}
},
exclude_weekends: { type: 'boolean' },
minimum_notice_period: { type: 'number', description: 'Minimum notice in minutes' },
buffer_time: { type: 'number', description: 'Buffer time between events in minutes' }
}
},
max_results: {
type: 'number',
description: 'Maximum number of time slots to return',
default: 10
}
},
required: ['calendar_ids', 'start_date', 'end_date', 'duration_minutes']
}
},
{
name: 'check_availability',
description: 'Check if specific people are available during a time period',
inputSchema: {
type: 'object',
properties: {
email_addresses: {
type: 'array',
items: { type: 'string', format: 'email' },
description: 'Email addresses to check availability for'
},
start_time: { type: 'string', format: 'date-time' },
end_time: { type: 'string', format: 'date-time' }
},
required: ['email_addresses', 'start_time', 'end_time']
}
},
// Reminder operations
{
name: 'create_reminder',
description: 'Create a new reminder/task',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Title of the reminder' },
description: { type: 'string', description: 'Optional detailed description' },
due_date_time: {
type: 'string',
format: 'date-time',
description: 'Optional due date/time in ISO format'
},
priority: {
type: 'string',
enum: ['low', 'medium', 'high', 'urgent'],
description: 'Priority level',
default: 'medium'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Optional tags for categorization'
}
},
required: ['title']
}
},
{
name: 'get_reminders',
description: 'Get reminders/tasks, optionally filtered by date range and status',
inputSchema: {
type: 'object',
properties: {
start_date: { type: 'string', format: 'date-time' },
end_date: { type: 'string', format: 'date-time' },
status: {
type: 'string',
enum: ['pending', 'completed', 'cancelled'],
description: 'Filter by status'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Filter by tags'
}
},
required: []
}
},
{
name: 'update_reminder',
description: 'Update an existing reminder',
inputSchema: {
type: 'object',
properties: {
reminder_id: { type: 'string' },
title: { type: 'string' },
description: { type: 'string' },
due_date_time: { type: 'string', format: 'date-time' },
priority: {
type: 'string',
enum: ['low', 'medium', 'high', 'urgent']
},
status: {
type: 'string',
enum: ['pending', 'completed', 'cancelled']
},
tags: {
type: 'array',
items: { type: 'string' }
}
},
required: ['reminder_id']
}
},
{
name: 'complete_reminder',
description: 'Mark a reminder as completed',
inputSchema: {
type: 'object',
properties: {
reminder_id: { type: 'string' }
},
required: ['reminder_id']
}
},
// Smart scheduling tools
{
name: 'suggest_meeting_times',
description: 'Suggest optimal meeting times considering attendee availability',
inputSchema: {
type: 'object',
properties: {
attendee_emails: {
type: 'array',
items: { type: 'string', format: 'email' },
description: 'Email addresses of meeting attendees'
},
duration_minutes: { type: 'number', description: 'Meeting duration in minutes' },
date_range_start: { type: 'string', format: 'date-time' },
date_range_end: { type: 'string', format: 'date-time' },
constraints: {
type: 'object',
properties: {
preferred_times: {
type: 'array',
items: {
type: 'object',
properties: {
start_time: { type: 'string', pattern: '^\\d{2}:\\d{2}$' },
end_time: { type: 'string', pattern: '^\\d{2}:\\d{2}$' },
preference: { type: 'string', enum: ['preferred', 'acceptable', 'avoid'] }
}
}
},
exclude_weekends: { type: 'boolean' },
minimum_notice_period: { type: 'number' }
}
},
max_suggestions: { type: 'number', default: 5 }
},
required: ['attendee_emails', 'duration_minutes', 'date_range_start', 'date_range_end']
}
},
// Analysis and insights
{
name: 'analyze_schedule_patterns',
description: 'Analyze scheduling patterns and provide insights',
inputSchema: {
type: 'object',
properties: {
calendar_id: { type: 'string' },
days_back: { type: 'number', description: 'Number of days to analyze', default: 30 }
},
required: ['calendar_id']
}
},
{
name: 'get_working_hours',
description: 'Get the user\'s working hours preferences',
inputSchema: {
type: 'object',
properties: {
calendar_id: { type: 'string', default: 'primary' }
},
required: []
}
},
// Context and suggestions
{
name: 'get_recent_similar_events',
description: 'Find recent events similar to a search query for context',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query to find similar events' },
limit: { type: 'number', description: 'Maximum number of events to return', default: 5 },
days_back: { type: 'number', description: 'How many days back to search', default: 90 }
},
required: ['query']
}
},
{
name: 'get_contact_suggestions',
description: 'Get contact suggestions based on partial name or email',
inputSchema: {
type: 'object',
properties: {
partial_name: { type: 'string', description: 'Partial name or email to search for' },
limit: { type: 'number', description: 'Maximum number of suggestions', default: 10 }
},
required: ['partial_name']
}
}
];
}
// Tool handlers
async handleToolCall(name: string, args: any, userId: string): Promise<any> {
try {
switch (name) {
case 'get_calendars':
return await this.calendarService.getCalendars(userId);
case 'get_calendar_events':
return await this.calendarService.getEvents(
userId,
args.calendar_id,
new Date(args.start_date),
new Date(args.end_date),
args.query
);
case 'create_calendar_event':
const createEventData = CreateEventSchema.parse({
calendarId: args.calendar_id,
title: args.title,
startTime: args.start_time,
endTime: args.end_time,
description: args.description,
location: args.location,
attendees: args.attendees?.map((a: any) => ({
email: a.email,
displayName: a.display_name,
optional: a.optional
}))
});
return await this.calendarService.createEvent(userId, createEventData);
case 'update_calendar_event':
const updates: Partial<CreateEventInput> = {};
if (args.title) updates.title = args.title;
if (args.start_time) updates.startTime = args.start_time;
if (args.end_time) updates.endTime = args.end_time;
if (args.description !== undefined) updates.description = args.description;
if (args.location !== undefined) updates.location = args.location;
if (args.attendees) {
updates.attendees = args.attendees.map((a: any) => ({
email: a.email,
displayName: a.display_name,
optional: a.optional
}));
}
return await this.calendarService.updateEvent(userId, args.calendar_id, args.event_id, updates);
case 'delete_calendar_event':
return await this.calendarService.deleteEvent(userId, args.calendar_id, args.event_id);
case 'find_free_time':
const freeTimeData = FindFreeTimeSchema.parse({
calendarIds: args.calendar_ids,
startDate: args.start_date,
endDate: args.end_date,
durationMinutes: args.duration_minutes,
constraints: args.constraints ? {
workingHours: args.constraints.working_hours,
excludeWeekends: args.constraints.exclude_weekends,
minimumNoticePeriod: args.constraints.minimum_notice_period,
bufferTime: args.constraints.buffer_time
} : undefined,
maxResults: args.max_results
});
return await this.calendarService.findFreeTime(userId, freeTimeData);
case 'check_availability':
return await this.calendarService.checkAvailability(
userId,
args.email_addresses,
new Date(args.start_time),
new Date(args.end_time)
);
case 'create_reminder':
const reminderData = CreateReminderSchema.parse({
title: args.title,
description: args.description,
dueDateTime: args.due_date_time,
priority: args.priority || 'medium',
tags: args.tags
});
return await this.reminderService.createReminder(userId, reminderData);
case 'get_reminders':
return await this.reminderService.getReminders(userId, {
startDate: args.start_date ? new Date(args.start_date) : undefined,
endDate: args.end_date ? new Date(args.end_date) : undefined,
status: args.status,
tags: args.tags
});
case 'update_reminder':
return await this.reminderService.updateReminder(userId, args.reminder_id, {
title: args.title,
description: args.description,
dueDateTime: args.due_date_time ? new Date(args.due_date_time) : undefined,
priority: args.priority,
status: args.status,
tags: args.tags
});
case 'complete_reminder':
return await this.reminderService.completeReminder(userId, args.reminder_id);
case 'suggest_meeting_times':
return await this.calendarService.suggestMeetingTimes(userId, {
attendeeEmails: args.attendee_emails,
durationMinutes: args.duration_minutes,
dateRangeStart: new Date(args.date_range_start),
dateRangeEnd: new Date(args.date_range_end),
constraints: args.constraints,
maxSuggestions: args.max_suggestions || 5
});
case 'analyze_schedule_patterns':
return await this.calendarService.analyzeSchedulePatterns(
userId,
args.calendar_id,
args.days_back || 30
);
case 'get_working_hours':
return await this.calendarService.getWorkingHours(userId, args.calendar_id || 'primary');
case 'get_recent_similar_events':
return await this.calendarService.getRecentSimilarEvents(
userId,
args.query,
args.limit || 5,
args.days_back || 90
);
case 'get_contact_suggestions':
return await this.calendarService.getContactSuggestions(
userId,
args.partial_name,
args.limit || 10
);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}`
);
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new McpError(
ErrorCode.InvalidParams,
`Invalid parameters: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`
);
}
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
}