Calendar MCP Server
Calendar MCP Server
A Model Context Protocol (MCP) server providing unified calendar management across Google Calendar, Microsoft 365, and Exchange On-Premises.
Table of Contents
Features
Multi-Provider Support: Connect to Google Calendar, Microsoft 365 (Graph API), and Exchange On-Premises (EWS)
Multiple Accounts: Support for multiple accounts per provider (e.g., 2+ Exchange accounts)
12 MCP Tools: Comprehensive calendar operations including CRUD, free/busy, conflict detection, and sync helpers
4 MCP Resources: Quick access to calendar summaries, today's events, weekly schedule, and upcoming events
4 MCP Prompts: Pre-built templates for scheduling meetings and daily briefings
NTLM Authentication: Full support for Exchange NTLM authentication via
@ewsjs/xhrUnified Data Model: Consistent event format across all providers
Timezone Support: Configurable timezone for all operations
Quick Start
# 1. Clone/navigate to the project
cd calendar-mcp
# 2. Install dependencies
npm install
# 3. Build the project
npm run build
# 4. Configure environment variables (see Configuration section)
cp .env.example .env
# Edit .env with your credentials
# 5. Test the server
node dist/index.jsInstallation
Prerequisites
Node.js 18+
npm or yarn
Access to at least one calendar provider (Google, Microsoft 365, or Exchange)
Install Dependencies
npm installBuild
npm run buildThis compiles TypeScript to JavaScript in the dist/ directory.
Configuration
The server uses environment variables for configuration. Create a .env file in the project root or pass environment variables directly.
Environment Variables
Server Settings
Variable | Description | Default |
| Server name for identification |
|
| Server version |
|
| Logging level: |
|
| IANA timezone for all operations |
|
| Working hours start (HH:MM) |
|
| Working hours end (HH:MM) |
|
| Working days (comma-separated) |
|
Request Settings
Variable | Description | Default |
| Request timeout in milliseconds |
|
| Maximum retry attempts |
|
| Delay between retries |
|
| Rate limiting |
|
Google Calendar Setup
Variable | Description | Required |
| Enable Google Calendar | Yes |
| Unique identifier for this account | Yes |
| Display name | Yes |
| Google account email | Yes |
| OAuth client ID from Google Cloud Console | Yes |
| OAuth client secret | Yes |
| OAuth redirect URI | Yes |
| Pre-authorized access token | Yes |
| Refresh token for token renewal | Yes |
| Token expiry (ISO 8601) | No |
Getting Google Credentials
Go to Google Cloud Console
Create a new project or select existing
Enable the Google Calendar API
Go to Credentials → Create Credentials → OAuth client ID
Configure consent screen if prompted
Select Desktop app or Web application
Copy Client ID and Client Secret
Use OAuth Playground to get tokens:
Select
https://www.googleapis.com/auth/calendarAuthorize and get access/refresh tokens
Example Google Configuration
GOOGLE_ENABLED=true
GOOGLE_PROVIDER_ID=google-personal
GOOGLE_PROVIDER_NAME=Personal Google Calendar
GOOGLE_EMAIL=yourname@gmail.com
GOOGLE_CLIENT_ID=123456789.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxx
GOOGLE_REDIRECT_URI=http://localhost:3000/oauth/google/callback
GOOGLE_ACCESS_TOKEN=ya29.xxxxx
GOOGLE_REFRESH_TOKEN=1//xxxxxMicrosoft 365 Setup
Variable | Description | Required |
| Enable Microsoft 365 | Yes |
| Unique identifier for this account | Yes |
| Display name | Yes |
| Microsoft account email | Yes |
| Azure AD app client ID | Yes |
| Azure AD app client secret | Yes |
| Azure tenant ID ( | Yes |
| OAuth redirect URI | Yes |
| Pre-authorized access token | Yes |
| Refresh token | Yes |
| Token expiry (ISO 8601) | No |
Getting Microsoft Credentials
Go to Azure Portal
Navigate to Azure Active Directory → App registrations
Click New registration
Add redirect URI
Go to Certificates & secrets → New client secret
Go to API permissions → Add:
Calendars.ReadWriteUser.Read
Grant admin consent if required
Use Graph Explorer to test
Example Microsoft Configuration
MICROSOFT_ENABLED=true
MICROSOFT_PROVIDER_ID=microsoft-work
MICROSOFT_PROVIDER_NAME=Work Microsoft 365
MICROSOFT_EMAIL=yourname@company.com
MICROSOFT_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
MICROSOFT_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxx
MICROSOFT_TENANT_ID=common
MICROSOFT_REDIRECT_URI=http://localhost:3000/oauth/microsoft/callback
MICROSOFT_ACCESS_TOKEN=eyJ0eXAiOiJKV1QiLCJ...
MICROSOFT_REFRESH_TOKEN=0.xxxxxExchange On-Premises Setup
Variable | Description | Required |
| Enable Exchange provider | Yes |
| Unique identifier for this account | Yes |
| Display name | Yes |
| Exchange email address | Yes |
| EWS endpoint URL | Yes |
| Authentication: | Yes |
For NTLM Authentication (most common for on-premises)
Variable | Description | Required |
| Username (without domain) | Yes |
| Password (quote if special chars) | Yes |
| Active Directory domain | Yes |
For Basic Authentication
Variable | Description | Required |
| Full username or email | Yes |
| Password | Yes |
For OAuth Authentication (Hybrid Exchange)
Variable | Description | Required |
| Azure AD app ID | Yes |
| Client secret | Yes |
| Azure tenant ID | Yes |
| Access token | Yes |
| Refresh token | No |
Finding Your EWS URL
Open Outlook on desktop
Hold Ctrl and right-click the Outlook icon in system tray
Select "Test E-mail AutoConfiguration"
Look for the EWS URL (typically
https://mail.company.com/EWS/Exchange.asmx)
Or ask your Exchange administrator.
Example Exchange Configuration (NTLM)
EXCHANGE_ENABLED=true
EXCHANGE_PROVIDER_ID=exchange-work
EXCHANGE_PROVIDER_NAME=Work Exchange
EXCHANGE_EMAIL=yourname@company.com
EXCHANGE_EWS_URL=https://mail.company.com/EWS/Exchange.asmx
EXCHANGE_AUTH_METHOD=ntlm
EXCHANGE_USERNAME="yourname"
EXCHANGE_PASSWORD="YourP@ssword!"
EXCHANGE_DOMAIN="COMPANYDOMAIN"Note: If your password contains special characters like
!,@,$, wrap it in quotes.
Multiple Exchange Accounts
To configure additional Exchange accounts, use the EXCHANGE_2_, EXCHANGE_3_, etc. prefixes:
# First Exchange Account
EXCHANGE_ENABLED=true
EXCHANGE_PROVIDER_ID=exchange-account1
EXCHANGE_PROVIDER_NAME=Account 1
EXCHANGE_EMAIL=user1@company.com
EXCHANGE_EWS_URL=https://mail.company.com/EWS/Exchange.asmx
EXCHANGE_AUTH_METHOD=ntlm
EXCHANGE_USERNAME="user1"
EXCHANGE_PASSWORD="password1"
EXCHANGE_DOMAIN="DOMAIN"
# Second Exchange Account
EXCHANGE_2_ENABLED=true
EXCHANGE_2_PROVIDER_ID=exchange-account2
EXCHANGE_2_PROVIDER_NAME=Account 2
EXCHANGE_2_EMAIL=user2@company.com
EXCHANGE_2_EWS_URL=https://mail.company.com/EWS/Exchange.asmx
EXCHANGE_2_AUTH_METHOD=ntlm
EXCHANGE_2_USERNAME="user2"
EXCHANGE_2_PASSWORD="password2"
EXCHANGE_2_DOMAIN="DOMAIN"
# Third Exchange Account (if needed)
EXCHANGE_3_ENABLED=true
# ... and so onClaude Code Integration
Method 1: Using /mcp Command (Recommended)
Open Claude Code in your project directory
Run
/mcpSelect "Add Server"
Configure with:
{
"type": "stdio",
"command": "node",
"args": ["/full/path/to/calendar-mcp/dist/index.js"],
"env": {
"DEFAULT_TIMEZONE": "Your/Timezone",
"EXCHANGE_ENABLED": "true",
"EXCHANGE_PROVIDER_ID": "your-exchange",
"EXCHANGE_PROVIDER_NAME": "Your Exchange",
"EXCHANGE_EMAIL": "your@email.com",
"EXCHANGE_EWS_URL": "https://mail.company.com/EWS/Exchange.asmx",
"EXCHANGE_AUTH_METHOD": "ntlm",
"EXCHANGE_USERNAME": "username",
"EXCHANGE_PASSWORD": "password",
"EXCHANGE_DOMAIN": "DOMAIN"
}
}Restart Claude Code or run
/mcpand reconnect
Method 2: Edit ~/.claude.json Directly
Add to the mcpServers object in your project's configuration:
{
"projects": {
"/path/to/your/project": {
"mcpServers": {
"calendar": {
"type": "stdio",
"command": "node",
"args": ["/full/path/to/calendar-mcp/dist/index.js"],
"env": {
"DEFAULT_TIMEZONE": "Asia/Baku",
"EXCHANGE_ENABLED": "true",
"EXCHANGE_PROVIDER_ID": "exchange-work",
"EXCHANGE_PROVIDER_NAME": "Work Calendar",
"EXCHANGE_EMAIL": "you@company.com",
"EXCHANGE_EWS_URL": "https://mail.company.com/EWS/Exchange.asmx",
"EXCHANGE_AUTH_METHOD": "ntlm",
"EXCHANGE_USERNAME": "username",
"EXCHANGE_PASSWORD": "password",
"EXCHANGE_DOMAIN": "DOMAIN"
}
}
}
}
}
}Verify Connection
After configuring, test with:
Ask Claude: "List my calendars"
Or: "What meetings do I have today?"
Tools Reference
Core Calendar Operations
list_calendars
List all available calendars across connected providers.
Parameter | Type | Description |
| string | Filter by provider: |
Example:
List all my calendarslist_events
List events within a time range with optional filters.
Parameter | Type | Required | Description |
| string | Yes | Start of time range (ISO 8601) |
| string | Yes | End of time range (ISO 8601) |
| string[] | No | Filter to specific providers |
| string[] | No | Filter to specific calendar IDs |
| string | No | Text search in subject/body |
| number | No | Maximum results (default: 100, max: 500) |
| string | No | Sort order: |
| boolean | No | Expand recurring events (default: true) |
Example:
Show me all my meetings for tomorrow
What events do I have this week?
Search my calendar for "standup" meetingsget_event
Get detailed information about a specific event.
Parameter | Type | Required | Description |
| string | Yes | The event ID |
| string | Yes | Provider: |
| string | No | Calendar ID (required for some providers) |
Example:
Get details for event ID AAMkADA3OWE...create_event
Create a new calendar event.
Parameter | Type | Required | Description |
| string | Yes | Provider to create event on |
| string | Yes | Event title |
| string | Yes | Start time (ISO 8601) |
| string | Yes | End time (ISO 8601) |
| string | No | Target calendar ID |
| string | No | Event description |
| string | No | Body type: |
| string | No | Physical location |
| array | No | List of attendees with |
| boolean | No | All-day event |
| object | No | Recurrence settings |
| number | No | Reminder before event |
| boolean | No | Create Teams/Meet link |
| string | No |
|
| string | No |
|
| string | No |
|
Example:
Create a meeting called "Project Review" tomorrow at 2pm for 1 hour
Schedule a lunch event next Monday from 12-1pm at "Cafe Downtown"
Create a weekly team standup every Monday at 9amupdate_event
Update an existing event.
Parameter | Type | Required | Description |
| string | Yes | Event ID to update |
| string | Yes | Provider the event belongs to |
| string | No | New title |
| string | No | New start time |
| string | No | New end time |
| string | No | New description |
| string | No | New location |
| array | No | Updated attendee list |
| string | No | For recurring: |
| boolean | No | Notify attendees (default: true) |
Example:
Move my 2pm meeting to 3pm
Change the location of event AAMk... to "Room 201"delete_event
Delete a calendar event.
Parameter | Type | Required | Description |
| string | Yes | Event ID to delete |
| string | Yes | Provider the event belongs to |
| string | No | Calendar ID |
| string | No | For recurring: |
| boolean | No | Send cancellation notices |
Example:
Delete my 3pm meeting
Cancel all instances of my recurring standupAvailability & Scheduling
get_free_busy
Get aggregated availability across all calendars.
Parameter | Type | Required | Description |
| string | Yes | Start of time range (ISO 8601) |
| string | Yes | End of time range (ISO 8601) |
| string[] | No | Filter to specific providers |
| string[] | No | Filter to specific calendars |
| number | No | Minimum free slot duration in minutes |
| boolean | No | Only consider working hours |
| object | No | Custom working hours config |
Example:
When am I free tomorrow?
Find me a 1-hour free slot this week
Show my availability for the next 3 days during working hourscheck_conflicts
Check if a proposed time slot conflicts with existing events.
Parameter | Type | Required | Description |
| string | Yes | Proposed start time (ISO 8601) |
| string | Yes | Proposed end time (ISO 8601) |
| string | No | Event ID to exclude (for rescheduling) |
| string | No | Provider of excluded event |
Example:
Do I have any conflicts tomorrow at 2pm?
Check if I can schedule a meeting on Friday from 10-11amrespond_to_invite
Respond to a meeting invitation.
Parameter | Type | Required | Description |
| string | Yes | Event ID |
| string | Yes | Provider the event belongs to |
| string | Yes | Response: |
| string | No | Calendar ID |
| string | No | Optional response message |
Example:
Accept the meeting invite for AAMk...
Decline the team lunch with message "I have a conflict"
Tentatively accept the project reviewSync Helpers
find_matching_events
Find matching events between two calendars.
Parameter | Type | Required | Description |
| string | Yes | Source calendar provider |
| string | Yes | Target calendar provider |
| string | Yes | Start of time range |
| string | Yes | End of time range |
| string | No | Source calendar ID |
| string | No | Target calendar ID |
| string | No | Match confidence: |
Example:
Find events that exist in both my Exchange calendars
Check for duplicates between Google and Microsoftcopy_event
Copy an event from one calendar to another.
Parameter | Type | Required | Description |
| string | Yes | Event ID to copy |
| string | Yes | Source provider |
| string | Yes | Target provider |
| string | No | Source calendar ID |
| string | No | Target calendar ID |
| boolean | No | Include attendees (default: false) |
| boolean | No | Include description (default: true) |
Example:
Copy event AAMk... from Exchange to Google Calendarcompare_calendars
Compare two calendars to find matching events, differences, and missing events.
Parameter | Type | Required | Description |
| string | Yes | Source calendar provider |
| string | Yes | Target calendar provider |
| string | Yes | Start of time range |
| string | Yes | End of time range |
| string | No | Source calendar ID |
| string | No | Target calendar ID |
Example:
Compare my two Exchange calendars for this week
What events are only in my first calendar but not the second?Resources Reference
MCP Resources provide quick access to calendar data.
URI | Description |
| Overview of all calendars with event counts |
| Today's events across all calendars |
| This week's events grouped by day |
| Next N upcoming events (e.g., |
Usage in Claude Code:
@calendar://today
Show me @calendar://summary
What's in @calendar://weekPrompts Reference
MCP Prompts are pre-built conversation templates.
schedule-meeting
Interactive prompt to schedule a new meeting.
Arguments:
title: Meeting titleduration: Duration (e.g., "30 minutes", "1 hour")attendees: Comma-separated email addressesnotes: Additional notes
daily-briefing
Generate a briefing of today's schedule.
Arguments:
timezone: Override default timezoneinclude_details: Include full event details (true/false)
find-meeting-time
Find available time slots for a meeting.
Arguments:
duration: Required meeting durationwithin_days: Number of days to searchattendees: Participants to check availability for
sync-calendars
Compare and sync events between calendars.
Arguments:
source_provider: Source calendar providertarget_provider: Target calendar provideraction:compare,copy_missing, orfull_sync
Usage Examples
Daily Workflow
# Morning check
"What's on my calendar today?"
# Quick view
"Show me my next 5 meetings"
# Check availability
"Am I free at 3pm today?"
# Schedule meeting
"Schedule a 30-minute call with john@company.com tomorrow at 2pm"Finding Time
"When am I free this week for a 1-hour meeting?"
"Find me an open slot tomorrow afternoon"
"Do I have any conflicts if I book 10-11am on Friday?"Managing Events
"Move my 2pm meeting to 3pm"
"Cancel tomorrow's standup"
"Add 'Conference Room A' as the location for my next meeting"Multi-Calendar
"Compare my two Exchange calendars for this week"
"List all calendars I have access to"
"Copy the project review from my work calendar to personal"Troubleshooting
Common Issues
"No calendars found"
Cause: Environment variables not loaded properly.
Solutions:
Verify
EXCHANGE_ENABLED=trueis setCheck that all required variables are present
For Claude Code: ensure
envobject in MCP config has all variablesRestart Claude Code after config changes
"401 Unauthorized" (Exchange NTLM)
Cause: Invalid credentials or wrong username format.
Solutions:
Use just the username, not
domain\userformatPut the domain in
EXCHANGE_DOMAINseparatelyQuote passwords with special characters:
EXCHANGE_PASSWORD="P@ss!word"Verify credentials work by visiting EWS URL in browser
"Failed to connect to Exchange"
Cause: Network or certificate issues.
Solutions:
Verify EWS URL is accessible from your network
Check if VPN is required
For self-signed certs, you may need to set
NODE_TLS_REJECT_UNAUTHORIZED=0
"Property not loaded" errors
Cause: EWS doesn't load all properties by default.
Solution: This is handled internally with safe property access. If you see this, update to latest version.
Google/Microsoft token expired
Cause: Access tokens have limited lifetime.
Solution:
Get new tokens using OAuth flow
Update
ACCESS_TOKENandTOKEN_EXPIRYin configImplement token refresh in your setup
Debug Mode
Run with debug logging:
LOG_LEVEL=debug node dist/index.jsTesting Connection
Test Exchange connection directly:
# Set environment variables
export EXCHANGE_ENABLED=true
export EXCHANGE_EWS_URL=https://mail.company.com/EWS/Exchange.asmx
export EXCHANGE_AUTH_METHOD=ntlm
export EXCHANGE_USERNAME=youruser
export EXCHANGE_PASSWORD='yourpassword'
export EXCHANGE_DOMAIN=YOURDOMAIN
# Run test
node -e "
const { loadConfig, hasConfiguredProviders } = require('./dist/utils/config.js');
console.log('Has providers:', hasConfiguredProviders());
console.log('Config:', JSON.stringify(loadConfig(), null, 2));
"Architecture
calendar-mcp/
├── src/
│ ├── index.ts # MCP server entry point
│ ├── types/
│ │ └── index.ts # TypeScript interfaces
│ ├── providers/
│ │ ├── base.ts # Base provider interface
│ │ ├── google/
│ │ │ ├── index.ts # Google Calendar provider
│ │ │ └── client.ts # Google API client
│ │ ├── microsoft/
│ │ │ ├── index.ts # Microsoft 365 provider
│ │ │ └── client.ts # Graph API client
│ │ └── exchange/
│ │ ├── index.ts # Exchange EWS provider
│ │ ├── client.ts # EWS client with NTLM support
│ │ └── auth.ts # Authentication manager
│ ├── services/
│ │ ├── calendar-service.ts # Multi-provider orchestration
│ │ └── sync-service.ts # Calendar sync logic
│ ├── tools/
│ │ ├── index.ts # Tool registration
│ │ ├── list-calendars.ts
│ │ ├── list-events.ts
│ │ ├── get-event.ts
│ │ ├── create-event.ts
│ │ ├── update-event.ts
│ │ ├── delete-event.ts
│ │ ├── get-free-busy.ts
│ │ ├── check-conflicts.ts
│ │ ├── respond-to-invite.ts
│ │ ├── find-matching-events.ts
│ │ ├── copy-event.ts
│ │ └── compare-calendars.ts
│ ├── resources/
│ │ └── index.ts # MCP resources
│ ├── prompts/
│ │ └── index.ts # MCP prompts
│ └── utils/
│ ├── config.ts # Configuration loader
│ ├── date.ts # Date utilities
│ └── formatting.ts # Output formatting
├── dist/ # Compiled JavaScript
├── .env # Environment configuration
├── .env.example # Example configuration
├── package.json
├── tsconfig.json
└── README.mdKey Dependencies
Package | Purpose |
| MCP server implementation |
| Exchange Web Services client |
| NTLM authentication for EWS |
| Microsoft Graph API |
| Google Calendar API |
| Environment variable loading |
| Date manipulation |
| Timezone handling |
License
MIT
Contributing
Fork the repository
Create a feature branch
Make your changes
Run tests:
npm testSubmit a pull request
Support
For issues and feature requests, please open a GitHub issue.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/rauf543/calendar-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server