Skip to main content
Glama
Schedule MCP Server

Schedule MCP Server

schedule-mcp

Personal schedule management MCP server. Connects Google Calendar, Notion Appointments, and Notion Tasks so Claude can help you plan your week, detect conflicts, schedule task work blocks, and keep your calendar and Notion in sync.

Architecture

Google Calendar  ←→  Claude (sync layer)  ←→  Notion Appointments
                                ↓
                         Notion Tasks
                    (linked via GCal event description)

Key design decisions:

  • Google Calendar is the source of truth for time

  • Notion Appointments is the source of truth for context (type, notes, rich metadata)

  • Notion Tasks are linked to calendar events via Notion URLs in the event description (no bidirectional sync needed)

  • A GCal Event ID field on each Notion Appointment prevents duplicate sync

  • A GCal Series ID field for linking Notion Appointments with recurring GCal events

Related MCP server: Google Calendar MCP Server

Tools

Tool

Source

What it does

gcal_list_calendars

GCal

List all your calendars

gcal_get_events

GCal

Fetch events in a date range

gcal_create_event

GCal

Create a calendar event

gcal_update_event

GCal

Update an existing event

gcal_delete_event

GCal

Delete an event

gcal_find_free_slots

GCal

Find available time blocks

notion_get_appointments

Notion

Query appointments with filters

notion_create_appointment

Notion

Add an appointment to Notion

notion_update_appointment

Notion

Update an appointment

notion_get_appointment_by_gcal_id

Notion

Check if a GCal event is already in Notion

notion_get_tasks

Notion

Query tasks with filters

notion_get_overdue_tasks

Notion

Get all overdue tasks

notion_create_task

Notion

Add a new task

notion_update_task

Notion

Update a task

schedule_week_overview

All

Unified week view: events, appointments, tasks due, overdue tasks

schedule_task_block

GCal + Notion

Schedule a work block for a task

schedule_find_conflicts

GCal

Detect overlaps and tight transitions

Notion Database Schema

Appointments database — expected fields:

Field

Type

Notes

Appointment

Title

Appointment name

Start

Date

Start datetime

End

Date

End datetime

Type

Select

Medical, Personal, Work, Other

Status

Status

Scheduled, In progress, Completed

Canceled

Select

Canceled, Not canceled

Recurring

Select

One-Time, Limited Recurring, Recurring

Notes

Rich text

Free-form notes

GCal Event ID

Rich text

Linked GCal event ID (prevents duplicate sync)

GCal Series ID

Rich text

Linked GCal series ID (for recurring events)

Tasks database — expected fields:

Field

Type

Notes

Task name

Title

Task name

Task Status

Select

Not started, In progress, Done, Archived, Overdue

Due

Date

Due date

Setup

1. Prerequisites

  • Python 3.11+

  • uv (recommended) or pip

  • A Google Cloud project with the Calendar API enabled

  • A Notion integration with access to your Appointments and Tasks databases

2. Google Calendar API

  1. Go to Google Cloud Console

  2. Create a project (or use an existing one)

  3. Enable the Google Calendar API

  4. Go to APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID

  5. Application type: Desktop app

  6. Download the JSON file → save as ~/.schedule_mcp/google_credentials.json (Windows: %USERPROFILE%\.schedule_mcp\google_credentials.json)

3. Notion Integration

  1. Go to https://www.notion.so/my-integrations

  2. Create a new integration (Internal, Read + Write content)

  3. Copy the Internal Integration Token

  4. In Notion, open your Appointments database → ... menu → Connections → add your integration

  5. Do the same for your Tasks database

4. Configure Environment

# Linux/macOS
cp .env.example .env

# Windows (PowerShell)
Copy-Item .env.example .env

Then edit .env with your credentials.

5. Install and Run

# Using uv (recommended)
uv sync
uv run schedule-mcp

# Or with pip
pip install -e .
schedule-mcp

The first run will open a browser window for Google OAuth consent. After that, the token is saved and auto-refreshed.

6. Add to Claude MCP Config

Add this to %APPDATA%\Claude\claude_desktop_config.json (Windows) or ~/Library/Application Support/Claude/claude_desktop_config.json (macOS), merged into the top-level JSON object alongside any existing keys:

{
  "mcpServers": {
    "schedule": {
      "command": "uv",
      "args": ["run", "--project", "C:/Users/YOU/source/repos/schedule_mcp", "schedule-mcp"],
      "env": {
        "GOOGLE_TOKEN_FILE": "C:/Users/YOU/.schedule_mcp/google_token.json",
        "GOOGLE_CREDENTIALS_FILE": "C:/Users/YOU/.schedule_mcp/google_credentials.json",
        "NOTION_TOKEN": "your_token_here",
        "NOTION_APPOINTMENTS_DB_ID": "your_notion_appointments_db_id_here",
        "NOTION_TASKS_DB_ID": "your_notion_tasks_db_id_here",
        "LOCAL_TIMEZONE": "America/Los_Angeles"
      }
    }
  }
}

Replace YOU with your Windows username. On macOS, use ~ paths instead.

Example Claude Prompts

What does my week look like?

Do I have any conflicts or back-to-backs this week?

Find me 90 minutes of free time tomorrow morning for SeattleCouncilmatic work.

Schedule 2 hours for "Add transit data support to SeattleCouncilmatic" 
on Thursday afternoon.

I have a dentist appointment Tuesday at 2pm — add it to both my calendar and Notion.

Show me all my medical appointments this month.

What tasks do I have due this week?

Development

# Verify syntax
python -m py_compile schedule_mcp/server.py

# Run with MCP Inspector for interactive testing
npx @modelcontextprotocol/inspector uv run schedule-mcp

Project Structure

schedule_mcp/
├── pyproject.toml
├── uv.lock
├── .env.example
├── README.md
└── schedule_mcp/
    ├── __init__.py
    ├── server.py              # MCP server entry point
    ├── auth/
    │   ├── __init__.py
    │   └── google_auth.py     # Google OAuth2 flow
    ├── clients/
    │   ├── __init__.py
    │   ├── gcal.py            # Google Calendar API client
    │   └── notion.py          # Notion API client
    └── tools/
        ├── __init__.py        # Exports register_* functions
        ├── calendar.py        # gcal_* MCP tools
        ├── appointments.py    # notion_*appointment* MCP tools
        ├── tasks.py           # notion_*task* MCP tools
        └── schedule.py        # schedule_* cross-source tools

Available Tools

17 tools
gcal_create_eventA

Create a new Google Calendar event.

When creating work blocks for Notion tasks, include the task's Notion URL in the description so there's a direct link from the calendar event to the task.

Args: params.title: Event title. params.start: ISO datetime string for start time. params.end: ISO datetime string for end time. params.description: Optional description (include Notion task URL here for task blocks). params.location: Optional location. params.calendar_id: Target calendar (default: primary).

Returns: str: JSON dict of the created event including its 'id'.

Inputs: params (required). Returns: result

gcal_delete_eventA
DestructiveIdempotent

Permanently delete a Google Calendar event.

This action cannot be undone. Confirm the event ID with gcal_get_events before deleting.

Args: params.event_id: ID of the event to delete. params.calendar_id: Calendar containing the event.

Returns: str: Confirmation message.

Inputs: params (required). Returns: result

gcal_find_free_slotsA
Read-only

Find available time slots of a given duration on a specific date.

Useful for scheduling work blocks, appointments, or any time-bound task. Returns all slots with at least the requested duration available.

Args: params.date: The date to check (ISO date string). params.duration_minutes: How long the slot needs to be (in minutes). params.earliest_hour: Don't suggest slots starting before this hour. params.latest_hour: Don't suggest slots ending after this hour. params.calendar_id: Calendar to check for conflicts.

Returns: str: JSON array of available slots with 'start' and 'end' ISO datetimes.

Inputs: params (required). Returns: result

gcal_get_eventsA
Read-only

Fetch Google Calendar events within a date range.

Returns a list of events with their IDs, titles, start/end times, descriptions, and locations. Use the event ID with other tools to update or delete specific events.

Args: params.start_date: Start of the date range (ISO date string). params.end_date: End of the date range (ISO date string). params.calendar_id: Calendar to query (default: primary). params.max_results: Max events to return (default: 50).

Returns: str: JSON array of event dicts.

Inputs: params (required). Returns: result

gcal_list_calendarsA
Read-only

List all Google Calendars the user has access to.

Returns: str: JSON array of calendars with id, summary, and primary flag.

Inputs: no named parameters. Returns: result

gcal_update_eventA
Idempotent

Update one or more fields on an existing Google Calendar event. Only provided fields are changed; omitted fields remain as-is.

Args: params.event_id: ID of the event to update. params.calendar_id: Calendar containing the event. params.title/start/end/description/location: Fields to update.

Returns: str: JSON dict of the updated event.

Inputs: params (required). Returns: result

notion_create_appointmentA

Create a new appointment in the Notion Appointments database.

When syncing from Google Calendar, always pass the gcal_event_id to prevent duplicates. When creating a new appointment from scratch, omit gcal_event_id and use gcal_create_event separately to sync it to Google Calendar.

Args: params.title: Appointment name. params.start/end: ISO datetime strings. params.appointment_type: Medical, Personal, Work, or Other. params.notes: Optional notes. params.gcal_event_id: Google Calendar event ID (if syncing from GCal). params.gcal_series_id: Google Calendar series ID (for recurring appointments). params.recurring: One-Time, Limited Recurring, or Recurring.

Returns: str: JSON dict of the created appointment including Notion ID.

Inputs: params (required). Returns: result

notion_create_taskA

Create a new task in the Notion Tasks database.

Args: params.name: Task name. params.due_date: Optional due date (ISO date string). params.status: Initial status (default: Not started).

Returns: str: JSON dict of the created task including its Notion ID and URL.

Inputs: params (required). Returns: result

notion_get_appointment_by_gcal_idA
Read-only

Find a Notion appointment by its linked Google Calendar event ID.

Use this before syncing to check whether a calendar event already has a corresponding Notion record, preventing duplicate entries.

Args: params.gcal_event_id: Google Calendar event ID to search for.

Returns: str: JSON dict of the appointment if found, or a not-found message.

Inputs: params (required). Returns: result

notion_get_appointmentsA
Read-only

Query the Notion Appointments database with optional filters.

Returns appointments with Notion ID, title, start/end times, type, status, notes, and linked Google Calendar event ID (if synced).

Args: params.start_date: Only return appointments from this date onwards. params.end_date: Only return appointments up to this date. params.appointment_type: Filter by Medical, Personal, Work, or Other. params.status: Filter by Scheduled, In progress, or Completed.

Returns: str: JSON array of appointment dicts.

Inputs: params (required). Returns: result

notion_get_overdue_tasksA
Read-only

Return all overdue tasks — either explicitly marked Overdue or with a due date in the past that isn't marked Done, Archived, or Overdue.

Returns: str: JSON array of overdue task dicts.

Inputs: no named parameters. Returns: result

notion_get_tasksA
Read-only

Query the Notion Tasks database with optional filters.

Returns tasks with their Notion IDs, names, statuses, due dates, and Notion URLs (for linking in calendar event descriptions).

Args: params.status: Filter by Not started, In progress, Done, Archived, or Overdue. params.due_before: Only return tasks due on or before this date. params.due_after: Only return tasks due on or after this date.

Returns: str: JSON array of task dicts.

Inputs: params (required). Returns: result

notion_update_appointmentA
Idempotent

Update fields on an existing Notion appointment. Only provided fields change.

Args: params.notion_id: The Notion page ID (from notion_get_appointments). params.*: Any fields to update.

Returns: str: JSON dict of the updated appointment.

Inputs: params (required). Returns: result

notion_update_taskA
Idempotent

Update fields on an existing Notion task. Only provided fields change.

Args: params.notion_id: Notion page ID (from notion_get_tasks). params.name/status/due_date: Fields to update.

Returns: str: JSON dict of the updated task.

Inputs: params (required). Returns: result

schedule_find_conflictsA
Read-only

Detect overlapping events or tight transitions in a date range.

Checks Google Calendar events for: exact overlaps, events with fewer than 10 minutes between them (tight transitions), and events longer than 4 hours (marathon sessions worth flagging).

Args: params.start_date: Start of the range to scan. params.end_date: End of the range to scan. params.calendar_id: Calendar to check.

Returns: str: JSON with lists of overlaps, tight transitions, and long events.

Inputs: params (required). Returns: result

schedule_task_blockA

Schedule a focused work block for a Notion task on Google Calendar.

Creates a Google Calendar event with the task name as the title and a link to the Notion task in the description. The task itself is NOT modified — this just blocks time and creates a visible pointer.

Typical workflow: 1. notion_get_tasks → find the task and get its ID and URL 2. gcal_find_free_slots → find available time 3. schedule_task_block → create the calendar event with Notion link

Args: params.task_notion_id: Notion page ID of the task. params.task_name: Task name (used as event title). params.task_url: Notion URL (embedded in event description). params.start/end: ISO datetime strings for the work block. params.calendar_id: Target calendar.

Returns: str: JSON with the created calendar event and a confirmation.

Inputs: params (required). Returns: result

schedule_week_overviewA
Read-only

Get a unified overview of a week: calendar events, Notion appointments, and tasks due that week.

This is the primary 'how does my week look?' tool. It pulls from all three sources and returns a combined view so you can see everything in context — scheduled appointments, work blocks, and upcoming deadlines.

Args: params.date: Any date in the target week (defaults to current week).

Returns: str: JSON with 'week_range', 'calendar_events', 'appointments', 'tasks_due', and 'overdue_tasks' keys.

Inputs: params (required). Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 17 tools

    Disambiguation4/5

    Tools are grouped by clear domain prefixes and resource/action pairs, making most purposes obvious. The main overlaps are notion_get_overdue_tasks versus notion_get_tasks with a status filter, and schedule_task_block acting as a convenience wrapper around gcal_create_event, but the descriptions explain these well enough to prevent serious misselection.

    Naming Consistency4/5

    Most tools follow a consistent verb_noun pattern with domain prefixes like gcal_create_event and notion_update_task. The schedule_* tools break the pattern, using noun phrases like schedule_week_overview and schedule_task_block rather than verb_noun names, and gcal_find_free_slots adds an adjective into the mix.

    Tool Count4/5

    17 tools is on the higher end, but the count is reasonably justified by covering two external systems plus cross-service scheduling helpers. The set could be slightly tighter by removing redundant conveniences like notion_get_overdue_tasks, but no tool feels entirely out of place.

    Completeness3/5

    The server covers calendar CRUD, Notion task query/create/update, Notion appointment query/create/update, and useful cross-service views. However, there are no delete operations for Notion tasks or appointments, so cleaning up a Notion record after deleting its linked calendar event is impossible, leaving a notable lifecycle gap.

    Maintenance

    ActivityInactive
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers