Skip to main content
Glama

notion-plus-mcp-server

A local MCP server for Notion built for precise edits. It changes exactly the block or field you point at, checks every value against the database schema before writing, previews risky changes, and can undo anything it did.

Why this instead of the built-in Notion connector

Built-in connector

notion-plus

Editing page content

Rewrites content

Patches one block by id, inserts at an exact position, find/replace that keeps formatting and mentions

Setting properties

Values passed through as-is

Validated against the schema; forgiving name and option matching; all errors reported at once

Bulk changes

One call per row

Filtered bulk update with dry-run preview and rate limiting

Mistakes

Manual cleanup

Every write returns an undo_id; notion_undo reverts it

Stale overwrites

Not detected

Optional expected_last_edited_time refuses to write over newer edits

Schema changes

Limited

Add properties, add select options, rename properties

Related MCP server: Notion Weaver

Setup

1. Create a Notion integration. Go to https://www.notion.so/profile/integrations, create an internal integration, enable read content, update content, insert content, and (if you want automations to comment) insert comments, then copy the secret. To set people properties by name or email, also enable "Read user information including email addresses". Personal access tokens can't look up users at all; with one, pass user ids.

2. Share pages with it. In Notion, open each top-level page or database you want Claude to reach, click ••• → Connections, and add the integration. Everything under a shared page is included.

3. Install. Requires Node 20 or later.

git clone https://github.com/katekruger/notion-mcp.git
cd notion-mcp
npm ci
npm run build

To update later: git pull && npm ci && npm run build, then restart Claude (or start a new Claude Code session).

4. Connect it to Claude. Use the full path to your clone.

Claude Code:

claude mcp add notion-plus --env NOTION_TOKEN=ntn_your_secret_here -- node /ABSOLUTE/PATH/TO/notion-mcp/dist/index.js
claude mcp list   # notion-plus should show as connected

Claude Desktop: open Settings → Developer → Edit Config, and add this to claude_desktop_config.json, then restart Claude Desktop:

{
  "mcpServers": {
    "notion-plus": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/notion-mcp/dist/index.js"],
      "env": { "NOTION_TOKEN": "ntn_your_secret_here" }
    }
  }
}

Turn off the built-in Notion connector while using this one so Claude doesn't pick between two sets of Notion tools. Keep the token out of the repo: it belongs only in your Claude config (or a local .env, which is gitignored).

5. Check it works. Ask Claude "search Notion for ". You should see notion_search results with ids. To call tools by hand instead, run NOTION_TOKEN=ntn_... npm run inspect to open the MCP Inspector.

6. Optional: scheduled automations. See Automations and GitHub Actions below.

Tools

Read

  • notion_search: find pages and databases by title.

  • notion_get_page: properties plus a content outline with every block id.

  • notion_get_blocks: read one section of a large page.

  • notion_find_blocks: locate blocks by text or regex.

  • notion_get_schema: property types, options, status groups, relations.

  • notion_query: rows via simple where pairs or raw Notion filters.

Write content

  • notion_patch_block: edit one block's text, checkbox, code language, or color.

  • notion_insert_blocks: add markdown or structured blocks at the start, end, or after a specific block.

  • notion_replace_text: find and replace across a page, keeping formatting (dry run by default).

  • notion_delete_blocks: trash specific blocks.

Write data

  • notion_update_properties: set row fields with validation.

  • notion_create_page: new row or sub-page with content.

  • notion_bulk_update: change every matching row (dry run by default).

  • notion_trash_page: trash a page.

Schema

  • notion_add_property: add a column.

  • notion_update_options: add select or multi-select options. (Renaming options isn't possible through the API; see limits.)

  • notion_rename_property: rename a column.

Safety

  • notion_history: recent changes and their undo ids.

  • notion_undo: revert a change.

Automations (see below)

  • notion_automation_list: show the rules.

  • notion_automation_add: check a rule against the live schema, save it, and preview what it would do.

  • notion_automation_dry_run: show what each rule would change right now. Never writes.

The undo journal is stored in ~/.notion-plus/journal.json (last 500 changes; set NOTION_PLUS_HOME to move it). Undo restores the snapshot taken at write time, so it will also overwrite later edits to the same fields. Undoing notion_update_options deletes the added options, which also clears them from any rows that used them since.

Known Notion API limits

  • A block's type can't be changed in place; insert a new block and delete the old one.

  • Blocks can't be moved; insert a copy where you want it and delete the original.

  • Status options and Notion's built-in database automations can't be created or edited through the API.

  • Synced blocks, AI blocks, and some embeds are read-only.

  • Last-edited times are rounded to the minute, so the freshness check catches edits made in an earlier minute.

  • Select option names and colors can't be changed through the API. Renames are accepted and silently ignored; color changes are rejected. Rename options in Notion, or add a new option, move rows with notion_bulk_update, and delete the old option in Notion.

  • Leaving an option out of a schema update deletes it and clears it from every row.

  • One write can carry at most 100 relations or people, and at most 100 rich text segments per block or property (each segment up to 2000 characters). This server splits long text into segments and returns a clear error past those limits instead of truncating.

  • blocks.children.append accepts 2 levels of nesting per request; deeper content is added in follow-up requests automatically.

  • Page reads include at most 25 items of a title, rich text, relation, or people value. Undo snapshots and before/after previews re-read the full value, so nothing is lost; notion_get_page and bulk dry-run previews may still show only the first 25.

  • New databases take a few seconds to appear in notion_search.

  • Notion merges adjacent text segments with identical formatting when you read them back, and normalizes link URLs (for example adding a trailing /).

  • Undoing a large insert trashes blocks one request at a time (about 3 per second).

Automations

Rules in automations/rules.json run on a schedule. Each rule is a database query; every row it matches gets the rule's actions. There are no webhooks: a GitHub Actions workflow checks hourly.

{
  "version": 1,
  "timezone": "America/New_York",
  "rules": [
    {
      "id": "stamp-completed",
      "database": "https://www.notion.so/…",
      "when": { "where": { "Status": "Done", "Completed Date": null } },
      "actions": [{ "set": { "Completed Date": "{{today}}" } }]
    },
    {
      "id": "archive-stale",
      "database": "https://www.notion.so/…",
      "when": { "where": { "Status": "Done" }, "relative": [{ "property": "$last_edited", "older_than_days": 30 }] },
      "actions": [{ "trash": true }],
      "limit": 20
    },
    {
      "id": "welcome-high-priority",
      "database": "https://www.notion.so/…",
      "when": { "where": { "Priority": "High" } },
      "actions": [{ "comment": "Flagged high priority: {{page.Name}}" }, { "append": "- [ ] Triage by {{today}}" }],
      "marker": "Automated"
    }
  ]
}

Conditions (when): where and filter work exactly like notion_query. relative takes {property, older_than_days} or {property, newer_than_days} for a date, created time, or last edited time property, or "$created" / "$last_edited".

Actions: {"set": {...}} (validated like notion_update_properties), {"append": "markdown"} or blocks, {"comment": "text"}, {"trash": true}. Strings can use {{today}} (in the file's timezone), {{now}}, {{page.<Property>}}, {{page.url}}, and {{page.id}}.

Each rule acts once per row. Because rules are re-checked every hour, a rule must stop matching a row after acting on it, or it would act again on every run. The runner refuses a rule unless it changes a property its condition checks to a different value, trashes the row, or names a marker: a checkbox property the runner requires to be unchecked and then checks.

Other options: enabled (default true), limit (rows per run, default 50; the rest wait for the next run), allow_new_options, data_source_name.

Running

npm run automations -- --dry-run            # preview every enabled rule
npm run automations -- --rule stamp-completed
npm run automations                         # apply

One run acts on at most 200 rows across all rules (--max-writes or AUTOMATIONS_MAX_WRITES). A failing rule is reported and the others still run. Each rule's run is one journal entry, so notion_undo <undo_id> reverts it. Comments can't be deleted through the API; undo leaves them.

From Claude, add rules with notion_automation_add (it saves to the local rules file); commit automations/rules.json so the scheduled workflow picks them up.

GitHub Actions

.github/workflows/automations.yml runs the rules hourly (at minute 17) from the committed automations/rules.json, and has a manual Run workflow button with dry_run (on by default for manual runs) and rule inputs.

One-time setup

  1. Make a token for the job. Create a separate internal integration (step 1 of Setup) and share only the databases your rules touch with it. Enable insert comments if rules comment. This limits what the scheduled job can reach.

  2. Add it as a repository secret. Open the repo on GitHub, click the repo's Settings tab (the one after Insights, not the Settings in your profile menu), then Secrets and variables → Actions → New repository secret. Name it NOTION_TOKEN and paste the token. Direct link: https://github.com/<owner>/notion-mcp/settings/secrets/actions.

  3. Test the workflow. Go to Actions → Notion automations → Run workflow, leave "dry run" checked, and run it. A green run with "No enabled rules." in the summary means the token and workflow are working.

Adding a rule

  1. In Claude, ask for it in plain words, for example: "Add an automation to the Projects database that stamps Completed Date with today when Status is Done and Completed Date is empty." Claude uses notion_automation_add, which checks the rule against the live database, shows the rows it would act on, and saves it to your local automations/rules.json.

  2. Commit and push automations/rules.json.

  3. Run the workflow once by hand with "dry run" checked and read the summary. After that, the hourly runs apply it.

To pause a rule, set "enabled": false and push. To stop everything, disable the workflow under Actions → Notion automations → ••• → Disable workflow.

Undoing a scheduled run

Each run writes its summary (with an undo id per rule) to the run page and uploads the undo journal as an artifact named notion-plus-journal-<run id>, kept 90 days. To revert:

  1. Download the artifact from the run page and unzip it into a folder.

  2. Run NOTION_TOKEN=ntn_... NOTION_PLUS_HOME=/path/to/that/folder npm run inspect, or point your Claude config's NOTION_PLUS_HOME at it.

  3. Call notion_undo with the undo id from the run summary.

GitHub may start scheduled runs a few minutes late, and turns off schedules in repos with no activity for 60 days.

Development

npm test            # offline unit tests (no network)
npm run typecheck   # src, scripts, and tests
npm run smoke       # live test against a real workspace

npm run smoke needs NOTION_TOKEN and NOTION_TEST_PAGE (a page shared with the integration), from the environment or a local .env file (gitignored). It creates two throwaway databases under that page, runs every tool including dry runs and notion_undo for each write type, then moves them to the trash. It never writes outside the test page, and its undo journal goes to a temp folder. Set SMOKE_KEEP=1 to keep the databases for inspection.

Roadmap

  • Page templates, comments, and move/copy helpers.

Related MCP Connectors

Related MCP Servers