notion-calendar-mcp
Provides tools for interacting with Notion Calendar, including managing events, calendars, user preferences, free/busy availability, contacts, and scheduling holds.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@notion-calendar-mcp What meetings do I have tomorrow?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
notion-calendar-mcp
Stdio MCP server for Notion Calendar. It talks to the private calendar-api.notion.so HTTP API (no public docs; reverse-engineered).
This project is not affiliated with, endorsed by, or supported by Notion Labs, Inc.
Warning
Use this at your own risk.
The Calendar HTTP API is undocumented and reverse-engineered. Endpoints, payloads, and auth can change or disappear without notice.
Auth is a browser session JWT that typically lasts about 5 hours. After that, calls return 401/403 until you refresh the token from a signed-in calendar.notion.so session.
Using an unofficial API may violate Notion’s Terms of Service. You are responsible for how you use this software.
Never commit session tokens,
credentials.json, or.envfiles. Never paste JWTs into chat, issues, or pull requests.
Licensed under the MIT License.
Related MCP server: Notion MCP
Requirements
Python 3.10+
A Notion Calendar account signed in at calendar.notion.so
The MCP SDK is pinned to mcp==1.29.1 so Server.list_tools remains available (later 2.x releases removed that registration API).
Install
git clone https://github.com/felipeorlando/notion-calendar-mcp.git
cd notion-calendar-mcp
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtAuthentication
Provide a session JWT in one of these ways (file wins for base_url / extra headers; env can supply or override the token).
1. Credentials file (recommended)
mkdir -p ~/.config/notion-calendar
cp credentials.json.example ~/.config/notion-calendar/credentials.json
chmod 600 ~/.config/notion-calendar/credentials.jsonEdit ~/.config/notion-calendar/credentials.json:
{
"authorization": "Bearer YOUR_SESSION_JWT",
"base_url": "https://calendar-api.notion.so",
"headers": {
"x-timezone": "America/New_York",
"x-client-type": "web",
"x-client-platform": "web",
"x-client-os": "linux"
}
}headers is optional. Set x-timezone to your IANA timezone. See credentials.json.example for the full placeholder shape.
How to copy the JWT from the browser:
Sign in at calendar.notion.so.
Open DevTools → Network.
Trigger any calendar load and inspect a request to
calendar-api.notion.so.Copy the
Authorizationvalue (Bearer …).
2. Environment variables
export NOTION_CALENDAR_AUTHORIZATION="Bearer YOUR_SESSION_JWT"
# or a raw token / Bearer value:
# export NOTION_CALENDAR_TOKEN="YOUR_SESSION_JWT"
# optional:
# export NOTION_CALENDAR_BASE_URL="https://calendar-api.notion.so"Do not put real tokens in shell history files you share, or in MCP config that lives inside a git repo.
On 401/403, refresh the JWT. list_tools works without a token; tool calls fail with a clear error if it is missing or expired.
Run
.venv/bin/python server.pyThe process speaks MCP over stdio (no HTTP port).
Claude Desktop
Add the server to the Claude Desktop MCP config:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonLinux:
~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"notion-calendar": {
"command": "/ABSOLUTE/PATH/TO/notion-calendar-mcp/.venv/bin/python",
"args": ["/ABSOLUTE/PATH/TO/notion-calendar-mcp/server.py"]
}
}
}Replace /ABSOLUTE/PATH/TO/notion-calendar-mcp with the real clone path. Prefer the credentials file above instead of embedding the JWT in this JSON.
Restart Claude Desktop after saving.
Cursor
User config: ~/.cursor/mcp.json. Project config: .cursor/mcp.json (do not commit tokens).
{
"mcpServers": {
"notion-calendar": {
"command": "/ABSOLUTE/PATH/TO/notion-calendar-mcp/.venv/bin/python",
"args": ["server.py"],
"cwd": "/ABSOLUTE/PATH/TO/notion-calendar-mcp"
}
}
}Restart Cursor after saving.
An agent-plugin layout (plugin.json, mcp.json, skills/notion-calendar/SKILL.md) is also included for hosts that load that format.
Tools
Call get_user first, then get_calendar_lists (or get_calendars) before event reads/writes. Prefer title/summary over raw ids when presenting results.
Tool | Kind | Purpose |
| read | Signed-in profile and linked calendar accounts |
| read | Sidebar calendars / |
| read | Calendar metadata (color, role, time zone) |
| read | Events in a time window ( |
| read | One event by id |
| read | Keyword search across accounts/calendars |
| write | Create an event ( |
| write | Patch one or more events |
| destructive | Delete events (confirm first) |
| write |
|
| read | Free/busy blocks before scheduling |
| read | Working hours and defaults (includes |
| write | Update prefs (send |
| read | Paginated directory / attendees |
| read | Contact lookup (prefer |
| read | Color palette |
| read | Mirrored/synced calendars |
| read | Room/resource calendars |
| read | Expand Google group invitees |
| read | Normalize events for export |
| read | Advanced sync tokens; prefer |
| read | Scheduling Holds (booking pages) |
| write | Create or update a Hold |
| destructive | Delete a Hold |
| read | Notion identities tied to this Calendar session |
| write | Rename/recolor calendars |
| write | Subscribe to a calendar |
| destructive | Unsubscribe from the sidebar list |
create_event eventData needs id (uuid hex is fine), summary, start, end, status, provider, accountId, calendarId, and reminders: { "useDefault": true }. Default send_updates to none unless you intend to email guests (all / externalOnly).
Smoke test
With valid credentials:
.venv/bin/python smoke_test.pyCreates a 30-minute probe event two weeks out, then deletes it. It does not print tokens.
Security
See SECURITY.md. Short version: session JWT, ~5 hour TTL, never commit secrets, refresh on 401/403.
Contributing
See CONTRIBUTING.md.
License
MIT © Felipe Orlando
Available Tools
30 toolscreate_eventA
Create a calendar event. Call after confirming calendar + time with the user. POST /v2/createEvent. eventData must include id (uuid hex ok), summary, start, end, status, provider, accountId, calendarId, reminders:{useDefault:true}. Use start/end only (never startTime/endTime). start_json/end_json: {dateTime,timeZone} or {date}. send_updates: all|none|externalOnly. Summary first.
Inputs: summary (required), end_json (required), provider (required), account_id (required), +10 more.
create_holdA
Create a Hold scheduling page. POST /v2/createHold. hold_json must include id, timeRanges, userPrimaryTimeZone (and usual Hold fields). Call when the user wants a new booking link. Alias/title first.
Inputs: hold_json (required).
delete_calendar_listADestructive
Unsubscribe/remove a calendar from the calendar list. POST /v2/deleteCalendarList {mutation:{provider, accountId, calendarId}}. mutation_json: JSON object. Destructive for the sidebar entry (not the calendar itself).
Inputs: mutation_json (required).
delete_eventsADestructive
Delete one or more events. Call only after confirming which event(s). POST /v2/deleteEvents {mutations:[{provider, accountId, calendarId, eventId, sendUpdates?}]}. mutations_json: JSON array. Destructive. sendUpdates: all|none|externalOnly.
Inputs: mutations_json (required).
delete_holdADestructive
Delete a Hold by holdId. POST /v2/deleteHold {holdId}. Destructive — confirm with the user first.
Inputs: hold_id (required).
export_eventsARead-only
Normalize/export partial event objects (ICS-oriented shape). POST /v2/exportEvents {events:[...]}. events_json: array of partial events with id/start/end/summary/…. Call when packaging events for export.
Inputs: events_json (required).
get_calendar_free_busyARead-only
Get free/busy blocks for calendars in a time range. Call when checking availability before scheduling. POST /v2/getCalendarFreeBusy. time_min/time_max ms epoch. calendars_json: [{accountId, calendarId}].
Inputs: time_max (required), time_min (required), calendars_json (required).
get_calendar_listsARead-only
List calendars for one or more accounts (sidebar calendar list). Call after get_user when you need calendarId values. POST /v2/getCalendarLists {queries:[{provider, accountId}]}. queries_json: JSON array. Calendar summary/name first.
Inputs: queries_json (required).
get_calendar_resourcesARead-only
List room/resource calendars for accounts. POST /v2/getCalendarResources {accounts:[accountId,...]}. accounts_json: JSON string array. Call when booking rooms.
Inputs: accounts_json (required).
get_calendarsARead-only
Fetch calendar metadata (colors, access role, timeZone) for accounts. POST /v2/getCalendars {queries:[{provider, accountId, query?}]}. queries_json: JSON array. Prefer get_calendar_lists for the sidebar list. Summary first.
Inputs: queries_json (required).
get_colorsARead-only
Fetch calendar color palette. Call when rendering/assigning calendar colors. POST /v2/getColors {}.
Inputs: no named parameters.
get_contactsARead-only
Fetch contacts via POST /v2/getContacts (Spectral-proven; may be alias). Call for quick contact lookup; prefer get_people for paginated directory. Optional body_json object. Display name/email first.
Inputs: body_json.
get_eventARead-only
Fetch one event by id. Call when you already have provider/accountId/calendarId/eventId. POST /v2/getEvent {query:{...}}. Optional userTimeZone. Summary first in any presentation.
Inputs: event_id (required), provider (required), account_id (required), calendar_id (required), +1 more.
get_eventsARead-only
Fetch events in a time window for specific calendars. Call after you know provider/accountId/calendarId. POST /v2/getEvents {queries:[...]}. timeMin/timeMax are ms epoch. queries_json: JSON array; provider defaults to google. Return order: summary/title first, never lead with event id.
Inputs: queries_json (required).
get_group_membersARead-only
Resolve Google group members for attendee expansion. POST /v2/getGroupMembers {accountId, emails:[...]}. emails_json: JSON array. Call when an invitee is a group address.
Inputs: account_id (required), emails_json (required).
get_holdARead-only
Fetch one Hold (scheduling page) by username, alias, or holdShortId. POST /v2/getHold. Optional time_min/time_max (ms). Call after get_holds when inspecting availability slots. Alias/name first.
Inputs: alias, time_max, time_min, username, +1 more.
get_holdsARead-only
List scheduling Holds (booking pages) for the signed-in user. POST /v2/getHolds {}. Call when you need hold aliases/ids before get_hold / create_hold. Name/alias first, never lead with id.
Inputs: no named parameters.
get_notion_session_usersARead-only
List Notion session users tied to this Calendar session. POST /v2/getNotionSessionUsers {}. Call when bridging Calendar ↔ Notion identity. Display name/email first.
Inputs: no named parameters.
get_peopleARead-only
List people/contacts from connected accounts (paginated). POST /v2/getPeople {accountIds?, cursor?, limit?}. Call when searching attendees by directory. Display name/email first.
Inputs: limit, cursor, account_ids_json.
get_synchronized_calendarsARead-only
List synchronized/mirrored calendars. Call when inspecting Calendar sync links between accounts. POST /v2/getSynchronizedCalendars {}. Name first.
Inputs: no named parameters.
get_userARead-only
Call first to learn who is signed in and which calendar accounts exist. POST /v2/getUser {}. Returns profile + linked accounts (title/email first). Use before get_calendar_lists / get_events. Session JWT required.
Inputs: no named parameters.
get_user_preferencesARead-only
Read Calendar user preferences (working hours, defaults, etag). Call before update_user_preferences (etag required for force check). POST /v2/getUserPreferences {}.
Inputs: no named parameters.
incremental_syncARead-only
Drive incremental calendar sync with sync tokens. POST /v2/incrementalSync {syncTokens, metadata:{caller}}. Often 400 without valid tokens from a prior sync — expose for advanced use; prefer get_events for normal reads. sync_tokens_json + optional metadata_json.
Inputs: metadata_json, sync_tokens_json.
insert_calendar_listA
Subscribe/add a calendar to the user's calendar list. POST /v2/insertCalendarList {mutation:{provider, accountId, calendarId, calendarData?}}. mutation_json: JSON object. Call when adding a shared calendar.
Inputs: mutation_json (required).
rsvp_eventA
RSVP to an event invite (accepted|declined|tentative). Call when the user wants to respond to an invitation. POST /v2/rsvpEvent. response_status must NOT be needsAction. Optional comment and counter_proposal_json {start,end}.
Inputs: event_id (required), account_id (required), calendar_id (required), response_status (required), +3 more.
search_eventsARead-only
Full-text search events across accounts/calendars in a time range. Call when the user asks to find meetings by keyword. POST /v2/searchEvents. time_min/time_max are ms epoch. accounts_json: [{accountId, calendarIds:[]}]. Title/summary first.
Inputs: query (required), time_max (required), time_min (required), accounts_json (required), +2 more.
update_calendarsA
Patch calendar metadata (summary, color, selected, …). POST /v2/updateCalendars {mutations:[{provider, accountId, calendarId, calendarData, colorRgbFormat?, variant?}]}. mutations_json: JSON array. Call when renaming/recoloring calendars. Summary first.
Inputs: mutations_json (required).
update_eventsA
Update one or more existing events. Call when changing time/title/details. POST /v2/updateEvents {mutations:[{provider, accountId, eventId, calendarId, eventData, userTimeZone?, sendUpdates?}]}. mutations_json: JSON array. Patch eventData with fields to change; keep summary visible. sendUpdates: all|none|externalOnly.
Inputs: mutations_json (required).
update_holdA
Update an existing Hold. POST /v2/updateHold. hold_json should include id plus fields to change (alias, timeRanges, …). Call after get_hold / get_holds.
Inputs: hold_json (required).
update_user_preferencesA
Update Calendar user preferences. Call only with an etag from get_user_preferences (412 if stale). POST /v2/updateUserPreferences {userPreferences:{...}}. user_preferences_json: full/partial prefs object including etag.
Inputs: user_preferences_json (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 30 tools
Most tools target a distinct resource and action, and descriptions generally clarify intent. However, get_calendar_lists vs get_calendars and get_contacts vs get_people are close enough that an agent could misselect without reading carefully.
The dominant verb_noun pattern (get_, create_, update_, delete_) is consistent and readable. Minor deviations like insert_calendar_list, rsvp_event, and incremental_sync break the pattern slightly but are not chaotic.
At 30 tools, this exceeds the 25+ threshold and feels heavy even for a calendar/scheduling domain. Several calendar-listing and contact-lookup tools could reasonably be consolidated.
The surface covers events, calendars, holds, preferences, contacts, and sync well, with create/read/update/delete coverage for the main resources. Minor gaps exist, such as no dedicated create_calendar or synchronization-management tools, but core workflows are supported.
Related MCP Connectors
Calendar API for AI agents: events, availability, Google/Microsoft setup, scheduling, and iCal.
Connects ChatGPT to your Apple Calendar via a local Mac agent + Vercel relay
Scheduling infrastructure for AI agents across Google and Microsoft calendars.
Email inboxes and calendars for AI agents: send, receive, search, draft and schedule.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with Notion workspaces through the Notion API, allowing them to search, read, create, update pages and databases, and manage comments using natural language commands.260,037 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables interaction with Notion via natural language, allowing page creation, calendar management, expense tracking, and custom tool integration.1-
- AlicenseAqualityCmaintenanceCalendar API purpose-built for AI agents. Exposes tools to manage agents, calendars, and events, find meeting times, run scheduling proposals, set availability rules, manage webhooks, and subscribe to iCal feeds.5490 npmApache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to interact with Google Calendar through natural language, supporting listing, creating, and searching events across accessible calendars.151 npmMIT