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
- Quick Start
- Installation
- Configuration
- Claude Code Integration
- Tools Reference
- Resources Reference
- Prompts Reference
- Usage Examples
- Troubleshooting
- Architecture
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/xhr - Unified 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.js
Installation
Prerequisites
- Node.js 18+
- npm or yarn
- Access to at least one calendar provider (Google, Microsoft 365, or Exchange)
Install Dependencies
npm install
Build
npm run build
This 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 |
|---|---|---|
MCP_SERVER_NAME | Server name for identification | calendar-mcp |
MCP_SERVER_VERSION | Server version | 1.0.0 |
LOG_LEVEL | Logging level: debug, info, warn, error | info |
DEFAULT_TIMEZONE | IANA timezone for all operations | UTC |
DEFAULT_WORKING_HOURS_START | Working hours start (HH:MM) | 09:00 |
DEFAULT_WORKING_HOURS_END | Working hours end (HH:MM) | 18:00 |
DEFAULT_WORKING_DAYS | Working days (comma-separated) | monday,tuesday,wednesday,thursday,friday |
Request Settings
| Variable | Description | Default |
|---|---|---|
REQUEST_TIMEOUT | Request timeout in milliseconds | 30000 |
MAX_RETRIES | Maximum retry attempts | 3 |
RETRY_DELAY_MS | Delay between retries | 1000 |
RATE_LIMIT_REQUESTS_PER_MINUTE | Rate limiting | 60 |
Google Calendar Setup
| Variable | Description | Required |
|---|---|---|
GOOGLE_ENABLED | Enable Google Calendar | Yes |
GOOGLE_PROVIDER_ID | Unique identifier for this account | Yes |
GOOGLE_PROVIDER_NAME | Display name | Yes |
GOOGLE_EMAIL | Google account email | Yes |
GOOGLE_CLIENT_ID | OAuth client ID from Google Cloud Console | Yes |
GOOGLE_CLIENT_SECRET | OAuth client secret | Yes |
GOOGLE_REDIRECT_URI | OAuth redirect URI | Yes |
GOOGLE_ACCESS_TOKEN | Pre-authorized access token | Yes |
GOOGLE_REFRESH_TOKEN | Refresh token for token renewal | Yes |
GOOGLE_TOKEN_EXPIRY | 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/calendar - Authorize and get access/refresh tokens
- Select
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//xxxxx
Microsoft 365 Setup
| Variable | Description | Required |
|---|---|---|
MICROSOFT_ENABLED | Enable Microsoft 365 | Yes |
MICROSOFT_PROVIDER_ID | Unique identifier for this account | Yes |
MICROSOFT_PROVIDER_NAME | Display name | Yes |
MICROSOFT_EMAIL | Microsoft account email | Yes |
MICROSOFT_CLIENT_ID | Azure AD app client ID | Yes |
MICROSOFT_CLIENT_SECRET | Azure AD app client secret | Yes |
MICROSOFT_TENANT_ID | Azure tenant ID (common, organizations, or specific) | Yes |
MICROSOFT_REDIRECT_URI | OAuth redirect URI | Yes |
MICROSOFT_ACCESS_TOKEN | Pre-authorized access token | Yes |
MICROSOFT_REFRESH_TOKEN | Refresh token | Yes |
MICROSOFT_TOKEN_EXPIRY | 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.xxxxx
Exchange On-Premises Setup
| Variable | Description | Required |
|---|---|---|
EXCHANGE_ENABLED | Enable Exchange provider | Yes |
EXCHANGE_PROVIDER_ID | Unique identifier for this account | Yes |
EXCHANGE_PROVIDER_NAME | Display name | Yes |
EXCHANGE_EMAIL | Exchange email address | Yes |
EXCHANGE_EWS_URL | EWS endpoint URL | Yes |
EXCHANGE_AUTH_METHOD | Authentication: ntlm, basic, or oauth | Yes |
For NTLM Authentication (most common for on-premises)
| Variable | Description | Required |
|---|---|---|
EXCHANGE_USERNAME | Username (without domain) | Yes |
EXCHANGE_PASSWORD | Password (quote if special chars) | Yes |
EXCHANGE_DOMAIN | Active Directory domain | Yes |
For Basic Authentication
| Variable | Description | Required |
|---|---|---|
EXCHANGE_USERNAME | Full username or email | Yes |
EXCHANGE_PASSWORD | Password | Yes |
For OAuth Authentication (Hybrid Exchange)
| Variable | Description | Required |
|---|---|---|
EXCHANGE_OAUTH_CLIENT_ID | Azure AD app ID | Yes |
EXCHANGE_OAUTH_CLIENT_SECRET | Client secret | Yes |
EXCHANGE_OAUTH_TENANT_ID | Azure tenant ID | Yes |
EXCHANGE_OAUTH_ACCESS_TOKEN | Access token | Yes |
EXCHANGE_OAUTH_REFRESH_TOKEN | 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 on
Claude Code Integration
Method 1: Using /mcp Command (Recommended)
- Open Claude Code in your project directory
- Run
/mcp - Select "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 |
|---|---|---|
provider | string | Filter by provider: google, microsoft, exchange, or all (default: all) |
Example:
List all my calendars
list_events
List events within a time range with optional filters.
| Parameter | Type | Required | Description |
|---|---|---|---|
startTime | string | Yes | Start of time range (ISO 8601) |
endTime | string | Yes | End of time range (ISO 8601) |
providers | string[] | No | Filter to specific providers |
calendarIds | string[] | No | Filter to specific calendar IDs |
searchQuery | string | No | Text search in subject/body |
maxResults | number | No | Maximum results (default: 100, max: 500) |
orderBy | string | No | Sort order: start or updated |
expandRecurring | 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" meetings
get_event
Get detailed information about a specific event.
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId | string | Yes | The event ID |
provider | string | Yes | Provider: google, microsoft, or exchange |
calendarId | 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 |
|---|---|---|---|
provider | string | Yes | Provider to create event on |
subject | string | Yes | Event title |
startTime | string | Yes | Start time (ISO 8601) |
endTime | string | Yes | End time (ISO 8601) |
calendarId | string | No | Target calendar ID |
body | string | No | Event description |
bodyType | string | No | Body type: text or html |
location | string | No | Physical location |
attendees | array | No | List of attendees with email and type |
isAllDay | boolean | No | All-day event |
recurrence | object | No | Recurrence settings |
reminderMinutes | number | No | Reminder before event |
createOnlineMeeting | boolean | No | Create Teams/Meet link |
onlineMeetingProvider | string | No | teams or meet |
sensitivity | string | No | normal, personal, private, confidential |
showAs | string | No | free, busy, tentative, oof, workingElsewhere |
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 9am
update_event
Update an existing event.
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId | string | Yes | Event ID to update |
provider | string | Yes | Provider the event belongs to |
subject | string | No | New title |
startTime | string | No | New start time |
endTime | string | No | New end time |
body | string | No | New description |
location | string | No | New location |
attendees | array | No | Updated attendee list |
updateScope | string | No | For recurring: single, thisAndFuture, all |
sendUpdates | 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 |
|---|---|---|---|
eventId | string | Yes | Event ID to delete |
provider | string | Yes | Provider the event belongs to |
calendarId | string | No | Calendar ID |
deleteScope | string | No | For recurring: single, thisAndFuture, all |
sendCancellation | boolean | No | Send cancellation notices |
Example:
Delete my 3pm meeting
Cancel all instances of my recurring standup
Availability & Scheduling
get_free_busy
Get aggregated availability across all calendars.
| Parameter | Type | Required | Description |
|---|---|---|---|
startTime | string | Yes | Start of time range (ISO 8601) |
endTime | string | Yes | End of time range (ISO 8601) |
providers | string[] | No | Filter to specific providers |
calendarIds | string[] | No | Filter to specific calendars |
slotDuration | number | No | Minimum free slot duration in minutes |
workingHoursOnly | boolean | No | Only consider working hours |
workingHours | 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 hours
check_conflicts
Check if a proposed time slot conflicts with existing events.
| Parameter | Type | Required | Description |
|---|---|---|---|
startTime | string | Yes | Proposed start time (ISO 8601) |
endTime | string | Yes | Proposed end time (ISO 8601) |
excludeEventId | string | No | Event ID to exclude (for rescheduling) |
excludeProvider | 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-11am
respond_to_invite
Respond to a meeting invitation.
| Parameter | Type | Required | Description |
|---|---|---|---|
eventId | string | Yes | Event ID |
provider | string | Yes | Provider the event belongs to |
response | string | Yes | Response: accepted, declined, tentative |
calendarId | string | No | Calendar ID |
message | 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 review
Sync Helpers
find_matching_events
Find matching events between two calendars.
| Parameter | Type | Required | Description |
|---|---|---|---|
source_provider | string | Yes | Source calendar provider |
target_provider | string | Yes | Target calendar provider |
start_time | string | Yes | Start of time range |
end_time | string | Yes | End of time range |
source_calendar_id | string | No | Source calendar ID |
target_calendar_id | string | No | Target calendar ID |
min_confidence | string | No | Match confidence: high, medium, low |
Example:
Find events that exist in both my Exchange calendars
Check for duplicates between Google and Microsoft
copy_event
Copy an event from one calendar to another.
| Parameter | Type | Required | Description |
|---|---|---|---|
source_event_id | string | Yes | Event ID to copy |
source_provider | string | Yes | Source provider |
target_provider | string | Yes | Target provider |
source_calendar_id | string | No | Source calendar ID |
target_calendar_id | string | No | Target calendar ID |
include_attendees | boolean | No | Include attendees (default: false) |
include_body | boolean | No | Include description (default: true) |
Example:
Copy event AAMk... from Exchange to Google Calendar
compare_calendars
Compare two calendars to find matching events, differences, and missing events.
| Parameter | Type | Required | Description |
|---|---|---|---|
source_provider | string | Yes | Source calendar provider |
target_provider | string | Yes | Target calendar provider |
start_time | string | Yes | Start of time range |
end_time | string | Yes | End of time range |
source_calendar_id | string | No | Source calendar ID |
target_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 |
|---|---|
calendar://summary | Overview of all calendars with event counts |
calendar://today | Today's events across all calendars |
calendar://week | This week's events grouped by day |
calendar://next/{count} | Next N upcoming events (e.g., calendar://next/5) |
Usage in Claude Code:
@calendar://today
Show me @calendar://summary
What's in @calendar://week
Prompts 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 set - Check that all required variables are present
- For Claude Code: ensure
envobject in MCP config has all variables - Restart Claude Code after config changes
"401 Unauthorized" (Exchange NTLM)
Cause: Invalid credentials or wrong username format.
Solutions:
- Use just the username, not
domain\userformat - Put the domain in
EXCHANGE_DOMAINseparately - Quote 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 config - Implement token refresh in your setup
Debug Mode
Run with debug logging:
LOG_LEVEL=debug node dist/index.js
Testing 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.md
Key Dependencies
| Package | Purpose |
|---|---|
@modelcontextprotocol/sdk | MCP server implementation |
ews-javascript-api | Exchange Web Services client |
@ewsjs/xhr | NTLM authentication for EWS |
@microsoft/microsoft-graph-client | Microsoft Graph API |
googleapis | Google Calendar API |
dotenv | Environment variable loading |
date-fns | Date manipulation |
date-fns-tz | Timezone handling |
License
MIT
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
npm test - Submit a pull request
Support
For issues and feature requests, please open a GitHub issue.
