import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { AuthManager } from "./auth.js";
import { GraphClient } from "./graph-client.js";
import {
listCalendarsTool,
handleListCalendars,
listEventsTool,
listEventsSchema,
handleListEvents,
getEventTool,
getEventSchema,
handleGetEvent,
createEventTool,
createEventSchema,
handleCreateEvent,
updateEventTool,
updateEventSchema,
handleUpdateEvent,
deleteEventTool,
deleteEventSchema,
handleDeleteEvent,
respondEventTool,
respondEventSchema,
handleRespondEvent,
findMeetingTimesTool,
findMeetingTimesSchema,
handleFindMeetingTimes,
} from "./tools/index.js";
export class CalendarMcpServer {
private server: McpServer;
private auth: AuthManager;
private graph: GraphClient;
constructor(auth: AuthManager) {
this.auth = auth;
this.graph = new GraphClient(auth);
this.server = new McpServer({
name: "m365calendar-mcp",
version: "1.0.0",
});
this.registerTools();
}
private registerTools(): void {
// List Calendars
this.server.tool(
listCalendarsTool.name,
listCalendarsTool.description,
{},
async () => {
try {
const result = await handleListCalendars(this.graph);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
// List Events
this.server.tool(
listEventsTool.name,
listEventsTool.description,
{
calendarId: listEventsSchema.shape.calendarId,
startDateTime: listEventsSchema.shape.startDateTime,
endDateTime: listEventsSchema.shape.endDateTime,
top: listEventsSchema.shape.top,
},
async (args) => {
try {
const result = await handleListEvents(this.graph, args);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
// Get Event
this.server.tool(
getEventTool.name,
getEventTool.description,
{
eventId: getEventSchema.shape.eventId,
calendarId: getEventSchema.shape.calendarId,
},
async (args) => {
try {
const result = await handleGetEvent(this.graph, args);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
// Create Event
this.server.tool(
createEventTool.name,
createEventTool.description,
{
subject: createEventSchema.shape.subject,
body: createEventSchema.shape.body,
startDateTime: createEventSchema.shape.startDateTime,
startTimeZone: createEventSchema.shape.startTimeZone,
endDateTime: createEventSchema.shape.endDateTime,
endTimeZone: createEventSchema.shape.endTimeZone,
location: createEventSchema.shape.location,
attendees: createEventSchema.shape.attendees,
isAllDay: createEventSchema.shape.isAllDay,
isOnlineMeeting: createEventSchema.shape.isOnlineMeeting,
showAs: createEventSchema.shape.showAs,
importance: createEventSchema.shape.importance,
sensitivity: createEventSchema.shape.sensitivity,
categories: createEventSchema.shape.categories,
reminderMinutesBeforeStart: createEventSchema.shape.reminderMinutesBeforeStart,
calendarId: createEventSchema.shape.calendarId,
recurrence: createEventSchema.shape.recurrence,
},
async (args) => {
try {
const result = await handleCreateEvent(this.graph, args);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
// Update Event
this.server.tool(
updateEventTool.name,
updateEventTool.description,
{
eventId: updateEventSchema.shape.eventId,
calendarId: updateEventSchema.shape.calendarId,
subject: updateEventSchema.shape.subject,
body: updateEventSchema.shape.body,
startDateTime: updateEventSchema.shape.startDateTime,
startTimeZone: updateEventSchema.shape.startTimeZone,
endDateTime: updateEventSchema.shape.endDateTime,
endTimeZone: updateEventSchema.shape.endTimeZone,
location: updateEventSchema.shape.location,
attendees: updateEventSchema.shape.attendees,
isAllDay: updateEventSchema.shape.isAllDay,
isOnlineMeeting: updateEventSchema.shape.isOnlineMeeting,
showAs: updateEventSchema.shape.showAs,
importance: updateEventSchema.shape.importance,
sensitivity: updateEventSchema.shape.sensitivity,
categories: updateEventSchema.shape.categories,
reminderMinutesBeforeStart: updateEventSchema.shape.reminderMinutesBeforeStart,
},
async (args) => {
try {
const result = await handleUpdateEvent(this.graph, args);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
// Delete Event
this.server.tool(
deleteEventTool.name,
deleteEventTool.description,
{
eventId: deleteEventSchema.shape.eventId,
calendarId: deleteEventSchema.shape.calendarId,
},
async (args) => {
try {
const result = await handleDeleteEvent(this.graph, args);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
// Respond to Event
this.server.tool(
respondEventTool.name,
respondEventTool.description,
{
eventId: respondEventSchema.shape.eventId,
response: respondEventSchema.shape.response,
comment: respondEventSchema.shape.comment,
sendResponse: respondEventSchema.shape.sendResponse,
},
async (args) => {
try {
const result = await handleRespondEvent(this.graph, args);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
// Find Meeting Times
this.server.tool(
findMeetingTimesTool.name,
findMeetingTimesTool.description,
{
attendees: findMeetingTimesSchema.shape.attendees,
startDateTime: findMeetingTimesSchema.shape.startDateTime,
endDateTime: findMeetingTimesSchema.shape.endDateTime,
durationMinutes: findMeetingTimesSchema.shape.durationMinutes,
maxCandidates: findMeetingTimesSchema.shape.maxCandidates,
isOrganizerOptional: findMeetingTimesSchema.shape.isOrganizerOptional,
meetingTimeZone: findMeetingTimesSchema.shape.meetingTimeZone,
},
async (args) => {
try {
const result = await handleFindMeetingTimes(this.graph, args);
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error}` }],
isError: true,
};
}
}
);
}
async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("M365 Calendar MCP server running on stdio");
}
}