# M365 Calendar MCP Server
A Model Context Protocol (MCP) server for Microsoft 365 Calendar integration. Enables AI assistants like Claude to manage your Outlook/Microsoft 365 calendar through the Microsoft Graph API.
## Features
- **List Calendars** - View all your calendars with permissions and ownership info
- **List Events** - Browse events in a date range with full details (supports recurring events)
- **Get Event** - Retrieve complete event details including body, recurrence, and attachments info
- **Create Event** - Create events with attendees, Teams meetings, recurrence, reminders, and more
- **Update Event** - Modify any event property (partial updates supported)
- **Delete Event** - Remove events with automatic cancellation notices
- **Respond to Events** - Accept, tentatively accept, or decline meeting invitations
- **Find Meeting Times** - Find available meeting slots across multiple attendees
## Prerequisites
- Node.js >= 18
- A Microsoft 365 account (work, school, or personal)
- An Azure AD app registration
## Azure AD App Setup
1. Go to the [Azure Portal](https://portal.azure.com) > **Azure Active Directory** > **App registrations**
2. Click **New registration**
- Name: `M365 Calendar MCP`
- Supported account types: Choose based on your needs (single tenant or multi-tenant)
- Redirect URI: Leave blank (we use device code flow)
3. After creation, note the **Application (client) ID** and **Directory (tenant) ID**
4. Go to **API permissions** > **Add a permission** > **Microsoft Graph** > **Delegated permissions**
5. Add the following permissions:
- `Calendars.ReadWrite`
- `Calendars.Read`
- `User.Read`
6. Go to **Authentication** > Enable **Allow public client flows** (required for device code flow)
## Installation
```bash
npm install
npm run build
```
## Configuration
Set the following environment variables:
```bash
# Required: Your Azure AD app client ID
export M365_CLIENT_ID="your-client-id-here"
# Optional: Your Azure AD tenant ID (defaults to "common" for multi-tenant)
export M365_TENANT_ID="your-tenant-id-here"
# Optional: Custom token cache path
export M365_CALENDAR_TOKEN_CACHE_PATH="/path/to/token-cache.json"
```
## Authentication
Before using the MCP server, authenticate with your Microsoft account:
```bash
# Login via device code flow
npm run login
# or
node dist/index.js --login
```
This will display a URL and code. Open the URL in your browser, enter the code, and sign in with your Microsoft account.
Other auth commands:
```bash
# Check authentication status
node dist/index.js --check-auth
# Logout and clear cached tokens
npm run logout
```
## Usage with Claude Desktop
Add to your Claude Desktop configuration (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"m365calendar": {
"command": "node",
"args": ["/path/to/m365calender-mcp/dist/index.js"],
"env": {
"M365_CLIENT_ID": "your-client-id-here",
"M365_TENANT_ID": "your-tenant-id-here"
}
}
}
}
```
## Usage with Claude Code
```bash
claude mcp add m365calendar -- node /path/to/m365calender-mcp/dist/index.js
```
Make sure `M365_CLIENT_ID` is set in your environment.
## Available Tools
### `list-calendars`
Lists all calendars accessible to the authenticated user, including shared calendars. Returns each calendar's name, ID, color, default status, edit/share permissions, and owner information.
This is a good starting point to discover calendar IDs. The `id` returned here can be passed as the `calendarId` parameter to any other tool. If you omit `calendarId` in other tools, they default to the user's primary calendar.
**Parameters:** None
**Returns:** Array of calendars with `id`, `name`, `color`, `isDefault`, `canEdit`, `canShare`, and `owner`.
---
### `list-events`
Lists calendar events within a date/time range. Uses Microsoft's `calendarView` endpoint, which automatically expands recurring event series into their individual instances within the range. Results are sorted by start time.
Use this tool to answer questions like "What's on my calendar this week?" or "Do I have any meetings tomorrow afternoon?" Follow up with `get-event` on any event ID to retrieve full details.
**Parameters:**
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `startDateTime` | Yes | string | Start of the time range in ISO 8601 format (e.g., `2025-03-01T00:00:00Z`) |
| `endDateTime` | Yes | string | End of the time range in ISO 8601 format (e.g., `2025-03-31T23:59:59Z`) |
| `calendarId` | No | string | Calendar ID from `list-calendars`. Defaults to the primary calendar. |
| `top` | No | number | Maximum number of events to return (default: 25, max: 100) |
**Returns:** Array of event summaries including `subject`, `start`/`end` times with time zones, `location`, `organizer`, `attendees` with response status, `isAllDay`, `isCancelled`, `isOnlineMeeting`, `onlineMeetingUrl`, `webLink`, `showAs`, `importance`, `isRecurring`, and your own `myResponse`.
**Tip:** All date/time values should include a timezone offset or use UTC (`Z` suffix). The response includes time zone information for each event so you can display times correctly.
---
### `get-event`
Retrieves the full details of a single calendar event by its ID. This returns significantly more data than `list-events`, including the full HTML body, all locations, online meeting join details, recurrence patterns, categories, attachment indicators, and audit timestamps.
Use this after `list-events` when you need the complete body content, the Teams join URL, or the recurrence configuration.
**Parameters:**
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `eventId` | Yes | string | The event ID (obtained from `list-events` or `create-event`) |
| `calendarId` | No | string | Calendar ID. Defaults to the primary calendar. |
**Returns:** Full event object including `body` (HTML), `locations[]`, `onlineMeeting` (join URL, conference ID, toll numbers), `recurrence` (pattern + range), `categories[]`, `hasAttachments`, `seriesMasterId`, `type` (singleInstance/occurrence/exception/seriesMaster), `createdDateTime`, and `lastModifiedDateTime`.
---
### `create-event`
Creates a new calendar event with full control over all properties. Supports plain events, all-day events, events with attendees (which automatically send invitations), Teams online meetings, recurring events, and custom reminders.
After creating an event, the full created event object is returned, including the server-assigned `id` and any auto-generated fields like the Teams meeting URL.
**Parameters:**
| Param | Required | Type | Default | Description |
|-------|----------|------|---------|-------------|
| `subject` | Yes | string | - | Event title |
| `startDateTime` | Yes | string | - | Start in ISO 8601 (e.g., `2025-03-15T09:00:00`) |
| `endDateTime` | Yes | string | - | End in ISO 8601 (e.g., `2025-03-15T10:00:00`) |
| `body` | No | string | - | Event description (HTML supported) |
| `startTimeZone` | No | string | `UTC` | IANA time zone (e.g., `America/New_York`, `Europe/London`) |
| `endTimeZone` | No | string | `UTC` | IANA time zone for end |
| `location` | No | string | - | Location display name (e.g., "Conference Room A") |
| `attendees` | No | array | - | List of `{ email, name?, type? }` (type: `required`/`optional`/`resource`) |
| `isAllDay` | No | boolean | `false` | All-day event (start/end should be date-only, e.g., `2025-03-15T00:00:00`) |
| `isOnlineMeeting` | No | boolean | `false` | Set `true` to auto-create a Teams meeting with join link |
| `showAs` | No | enum | `busy` | `free`, `tentative`, `busy`, `oof`, `workingElsewhere`, `unknown` |
| `importance` | No | enum | `normal` | `low`, `normal`, `high` |
| `sensitivity` | No | enum | `normal` | `normal`, `personal`, `private`, `confidential` |
| `categories` | No | string[] | - | Color category labels |
| `reminderMinutesBeforeStart` | No | number | `15` | Reminder timing in minutes |
| `calendarId` | No | string | - | Target calendar ID. Defaults to the primary calendar. |
| `recurrence` | No | object | - | Recurrence pattern and range (see below) |
**Recurrence object structure:**
```json
{
"pattern": {
"type": "weekly",
"interval": 1,
"daysOfWeek": ["monday", "wednesday", "friday"]
},
"range": {
"type": "endDate",
"startDate": "2025-03-15",
"endDate": "2025-06-15"
}
}
```
- **Pattern types:** `daily`, `weekly`, `absoluteMonthly`, `relativeMonthly`, `absoluteYearly`, `relativeYearly`
- **Range types:** `endDate` (with `endDate`), `noEnd` (runs forever), `numbered` (with `numberOfOccurrences`)
- For `weekly`: use `daysOfWeek`. For `absoluteMonthly`: use `dayOfMonth`. For `relativeMonthly`: use `daysOfWeek` + `index` (`first`, `second`, `third`, `fourth`, `last`).
**Tip:** When adding attendees, Microsoft automatically sends invitation emails. Set `isOnlineMeeting: true` to include a Teams join link in the invitation.
---
### `update-event`
Updates an existing calendar event using partial update (PATCH) semantics. Only the fields you provide are modified; all other fields remain unchanged. This is safe to use when you only need to change one property (e.g., updating just the subject or moving the time).
If the event has attendees, Microsoft sends update notifications automatically when relevant fields change (time, location, etc.).
**Parameters:**
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `eventId` | Yes | string | The ID of the event to update |
| `calendarId` | No | string | Calendar ID. Defaults to the primary calendar. |
All other parameters are the same as `create-event` (subject, body, startDateTime, endDateTime, location, attendees, etc.) but all are optional. Only include the fields you want to change.
**Returns:** The full updated event object.
**Tip:** To reschedule, provide both `startDateTime` and `endDateTime` together. To update attendees, provide the complete attendee list (it replaces the existing list, not appends).
---
### `delete-event`
Permanently deletes a calendar event. If the authenticated user is the organizer and the event has attendees, Microsoft automatically sends cancellation notices to all attendees.
This action cannot be undone. The event is moved to the Deleted Items folder.
**Parameters:**
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `eventId` | Yes | string | The ID of the event to delete |
| `calendarId` | No | string | Calendar ID. Defaults to the primary calendar. |
**Returns:** Confirmation message.
---
### `respond-event`
Responds to a calendar event invitation that someone else organized. You can accept, tentatively accept, or decline. Optionally include a message visible to the organizer, and control whether a response email is actually sent.
Use this after finding an event via `list-events` where your `myResponse` is `none` or `notResponded`.
**Parameters:**
| Param | Required | Type | Default | Description |
|-------|----------|------|---------|-------------|
| `eventId` | Yes | string | - | The ID of the event to respond to |
| `response` | Yes | enum | - | `accept`, `tentativelyAccept`, or `decline` |
| `comment` | No | string | - | Message to include with the response (visible to the organizer) |
| `sendResponse` | No | boolean | `true` | Set `false` to update your status silently without notifying the organizer |
**Returns:** Confirmation message.
---
### `find-meeting-times`
Finds available meeting times for a group of attendees using Microsoft's scheduling intelligence. This queries each attendee's calendar availability and suggests optimal time slots where everyone (or the most people) can meet.
This is particularly useful for scheduling meetings with multiple people without manually checking each person's calendar. Use the suggested time slots with `create-event` to book the meeting.
**Parameters:**
| Param | Required | Type | Default | Description |
|-------|----------|------|---------|-------------|
| `attendees` | Yes | array | - | List of `{ email, name?, type? }` to check availability for |
| `startDateTime` | Yes | string | - | Start of the search window in ISO 8601 |
| `endDateTime` | Yes | string | - | End of the search window in ISO 8601 |
| `durationMinutes` | No | number | `30` | Desired meeting length in minutes |
| `maxCandidates` | No | number | `5` | Maximum number of time suggestions to return |
| `isOrganizerOptional` | No | boolean | `false` | If `true`, suggestions may include times when the organizer is busy |
| `meetingTimeZone` | No | string | `UTC` | Time zone for the suggestions (e.g., `America/Chicago`) |
**Returns:** Meeting time suggestions from Microsoft's scheduling engine, including the proposed time slots, attendee availability for each slot, and a confidence score.
**Tip:** The search window should be at least a few days wide to get good results. The attendees must be in the same Microsoft 365 organization (or federated) for availability lookup to work.
---
### Typical Workflow
1. **`list-calendars`** - Discover available calendars and their IDs
2. **`list-events`** - Browse events in a date range on a specific calendar
3. **`get-event`** - Drill into a specific event for full details (body, Teams link, recurrence)
4. **`find-meeting-times`** - Find a slot that works for everyone, then...
5. **`create-event`** - Book the meeting with attendees and a Teams link
6. **`update-event`** - Reschedule or modify the event later
7. **`respond-event`** - Accept or decline meetings others have invited you to
8. **`delete-event`** - Cancel a meeting you organized
## Development
```bash
# Install dependencies
npm install
# Build
npm run build
# Run in development mode
npm run dev
```
## License
MIT