WorkflowsMCP

Weekly personal review: Todoist + Calendar → Notion

Completed tasks plus meeting vs solo hours, filed as one Notion weekly review.

Once a week, pull Todoist completions and your meeting vs solo hours into one editable Notion review page. Todoist's Productivity view is charts you open — no document, no calendar cross-check. Google Calendar Time Insights is Workspace/school only, an in-app panel with no export or API, never beside task counts — this covers personal Gmail too. Optional Habit Tracker notes land as a read-only Habit notes property. Your own week, not a team capacity rollup: events.list on your primary calendar splits meetings (attendees ≥ 2) from solo blocks.

How it flows

  1. 01

    Weekly trigger fires

    Run once at a fixed point on the week boundary (Sunday evening or Monday morning). Use the primary calendar timezone for the window so Todoist, Calendar, and the Notion week label share one clock.

  2. 02

    Completed tasks pulled from Todoist

    GET /api/v1/tasks/completed/by_completion_date for this week's since/until. Sum item count and keep titles for the review page.

  3. 03

    Primary calendar split into meetings vs solo blocks

    events.list on primary with eventTypes=default and singleEvents=true. Default rule: attendees ≥ 2 → meeting hours; otherwise (no/0/1 attendees) → solo/focus hours. Sum both.

  4. 04

    Optional — Habit Tracker notes copied

    If enabled, read each tracked habit task's description and latest comment; hold them as a read-only subsection for the Notion page. Skip entirely when the extension is not in use.

  5. 05

    Last week's Notion page loaded for deltas

    Query the Weekly Review data source for the most recent page and compute week-over-week deltas on tasks completed and meeting hours.

  6. 06

    This week's review page instantiated

    POST /v1/pages with the template id and all computed properties (counts, hours, deltas, and optional Habit notes rich_text/Text if that module is on). One editable page per week — habit copy stays in properties, not a post-create block PATCH.

Set up each app

Work through these in order — later apps usually need a token or an id from an earlier one.

Todoist

This week's completed tasks (and optional habit streaks)

  1. 01

    Copy a read-only API token

    app.todoist.com → avatar → Settings → Integrations → Developer → copy your personal API token. This workflow only reads completed tasks (and, if you enable the optional habit section, task descriptions and comments), so request or rely on `data:read` — nothing writes back to Todoist.

  2. 02

    Pull completions by completion date, and know the free-plan window

    Use GET /api/v1/tasks/completed/by_completion_date with since/until set to this week's window — that filters on when you checked the task off, which is what a weekly review means by "what I finished." The sibling endpoint by_due_date filters on the original due date instead and will miss early finishes and include overdue catch-ups, so leave it alone here. Call /api/v1/ only: REST v2 and Sync v9 both return 410 Gone. On Beginner (free), Todoist's Activity history keeps only one week of history — if this job runs late, older completions can already be gone, so keep the weekly trigger on time or upgrade to Pro for full history.

    GET /api/v1/tasks/completed/by_completion_date
    curl -s -G "https://api.todoist.com/api/v1/tasks/completed/by_completion_date" \
      -H "Authorization: Bearer $TODOIST_TOKEN" \
      -d since=2026-08-03T00:00:00 \
      -d until=2026-08-10T00:00:00 \
      -d limit=50
  3. 03

    Optional — Habit Tracker read-only copy

    Skip this unless you already use Todoist's first-party Habit Tracker (Settings → Integrations → Browse; available on Beginner/Pro/Business). Habit Tracker already keeps streak progress in the task description and completion history in task comments — this module does not reinvent streaks; it only copies those existing fields into the Notion review as a read-only section. Note the task ids you track, and plan to read each task's description plus GET /api/v1/comments?task_id=… for the latest comment. Do not write your own notes into a tracked task's description — the extension overwrites it.

    GET /api/v1/comments?task_id=…
    curl -s -G "https://api.todoist.com/api/v1/comments" \
      -H "Authorization: Bearer $TODOIST_TOKEN" \
      -d task_id=6XR4GqQQCW6Gv9h4

Google Calendar

Meeting hours vs solo/focus blocks on your primary calendar

  1. 01

    Create an OAuth client with the owned-events scope

    Google Cloud Console → APIs & Services → Credentials → create an OAuth client. Request only `https://www.googleapis.com/auth/calendar.events.owned.readonly` — it reads events on calendars you own, which is enough for primary and tighter than the broader `calendar.events.readonly` that also covers calendars others shared with you. The freebusy-only scope is not enough here: freeBusy returns busy intervals with no titles or attendees, so it cannot split meetings from solo blocks.

  2. 02

    Fix the week window to your primary calendar timezone

    Derive since/until (and Calendar timeMin/timeMax) from the timezone of your primary calendar — one timezone for Todoist completions, Calendar events, and the Notion week label, so the three numbers line up. Default classification (configurable): attendees.length ≥ 2 (you usually appear in attendees, so two means at least one other person) → meeting; otherwise (missing attendees, length 0, or length 1 = only you) → solo/focus block. Tune the threshold later if you want to exclude declined guests via responseStatus.

  3. 03

    Verify events.list on primary for this week

    GET calendars/primary/events with singleEvents=true (required so recurring series expand into this week's instances), orderBy=startTime, and eventTypes=default to exclude focusTime, outOfOffice, birthday, and workingLocation. If you also want a separate Focus Time column, make a second call with eventTypes=focusTime rather than mixing types in one response. Sum durations after classifying by the attendee rule above.

    GET /calendar/v3/calendars/primary/events
    curl -s -G "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
      -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN" \
      -d timeMin=2026-08-03T00:00:00-04:00 \
      -d timeMax=2026-08-10T00:00:00-04:00 \
      -d singleEvents=true \
      -d orderBy=startTime \
      -d eventTypes=default

Notion

Weekly Review database — editable page per week

  1. 01

    Create an internal integration

    app.notion.com/my-integrations → New integration. Capabilities: Insert content and Read content — you write a new weekly page and may read last week's page for week-over-week deltas. Leave user information at "No user information"; this is a single-player workflow with no @-mentions. Copy the token.

  2. 02

    Build the Weekly Review database and template

    Create a "Weekly Review" database with properties the API will write by name, for example: Name (Title), Week of (Date), Tasks completed (Number), Meeting hours (Number), Solo/focus hours (Number), vs last week (Text), and optionally Habit notes (Text) if you enable the Habit Tracker module — put the copied description/latest-comment text there on create so you never race the template's async body fill-in. In the database, New → dropdown arrow → New template, and sketch fixed reflection prompts (What shipped / Where time went / Next week). The template only produces that skeleton — it cannot pull Todoist or Calendar — so every computed number must go in properties on create.

  3. 03

    Share the database and record the data source id

    Open the database → ••• → Add connections → pick the integration. Then GET /v1/databases/{database_id} with Notion-Version: 2026-03-11 and store data_sources[0].id — that is not the id in the URL. Look up the template id with GET /v1/data_sources/{data_source_id}/templates?name=… rather than hardcoding it.

    GET data source templates
    GET https://api.notion.com/v1/data_sources/{data_source_id}/templates?name=Weekly%20Review
    Authorization: Bearer $NOTION_INTEGRATION_TOKEN
    Notion-Version: 2026-03-11
  4. 04

    Test page create and last-week query

    POST /v1/pages with parent.data_source_id, the computed properties, and template.template_id. Put numbers in properties in that same call — template body content is applied asynchronously, so do not PATCH blocks immediately after create or you race Notion's fill-in. For week-over-week copy, POST /v1/data_sources/{data_source_id}/query sorted by your Week of (or equivalent) date descending with page_size 1, then subtract last week's Tasks completed / Meeting hours from this week's.

    POST /v1/pages with template
    {
      "parent": { "data_source_id": "<WEEKLY_REVIEW_DATA_SOURCE_ID>" },
      "properties": {
        "Name": { "title": [{ "text": { "content": "Week of Aug 3" } }] },
        "Week of": { "date": { "start": "2026-08-03" } },
        "Tasks completed": { "number": 18 },
        "Meeting hours": { "number": 6.5 },
        "Solo/focus hours": { "number": 4 },
        "vs last week": { "rich_text": [{ "text": { "content": "+3 tasks, −1.0 meeting hours" } }] }
      },
      "template": { "type": "template_id", "template_id": "<WEEKLY_REVIEW_TEMPLATE_ID>" }
    }