notion-mcp-server 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,84 +1,69 @@
1
- # Notion MCP Server — Connect Claude, Cursor & ChatGPT to Notion via Model Context Protocol
1
+ # Notion MCP Server — Connect Claude, Cursor & VS Code to Notion
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/notion-mcp-server)](https://www.npmjs.com/package/notion-mcp-server)
4
+ ![NPM Downloads](https://img.shields.io/npm/dw/notion-mcp-server)
3
5
  ![License](https://img.shields.io/badge/license-MIT-green)
4
- ![TypeScript](https://img.shields.io/badge/TypeScript-4.9+-blue)
5
- ![Model Context Protocol](https://img.shields.io/badge/MCP-Enabled-purple)
6
+ ![Model Context Protocol](https://img.shields.io/badge/MCP-Streamable_HTTP_+_stdio-purple)
6
7
  [![notion-mcp-server on Smithery](https://smithery.ai/badge/@awkoy/notion-mcp-server)](https://smithery.ai/server/@awkoy/notion-mcp-server)
7
- ![NPM Downloads](https://img.shields.io/npm/dw/notion-mcp-server)
8
8
  ![Stars](https://img.shields.io/github/stars/awkoy/notion-mcp-server)
9
9
 
10
- An agent-first **Notion MCP server** (Model Context Protocol) that connects Claude, Cursor, ChatGPT, Claude Desktop, Cline, Zed and other MCP-compatible AI clients to Notion. Sign in once with your Notion **Personal Access Token (PAT)** no per-page sharing dance, no extra integration to set up. Your AI sees the Notion pages you authorize the token for (typically your whole workspace) and can create pages, query databases, append blocks, leave comments, and upload files in natural language.
10
+ Give your AI full read/write access to Notion with **one token and one paste**. This is an agent-first **Notion MCP server**: your AI client (Claude Code, Claude Desktop, Cursor, VS Code, Cline, Zed anything that speaks MCP) can create pages, query databases, append blocks, apply templates, comment, and upload files in natural language.
11
11
 
12
- > **v2.4 built for AI agents, not REST clients.** Two MCP tools instead of 36 endpoints, batched mutations, idempotency keys, automatic retries on Notion rate limits, self-healing validation errors (now path-sliced to <1KB), slim token-efficient responses, and a markdown shortcut so the model can write a whole page in one call.
12
+ Three reasons it exists when Notion ships its own MCP:
13
+
14
+ - **Built for agents, not humans-in-the-loop.** Notion's hosted MCP is OAuth-only — it cannot run headless. This server authenticates with a token, so it works in **CI, cron jobs, background agents, and self-hosted deployments** where nobody can click "Authorize".
15
+ - **97% smaller tool footprint at connection.** Two MCP tools (**422 tokens**) instead of one schema per endpoint — the official open-source server loads **17,163 tokens** of tool schemas before you do anything. Operation schemas load on demand via `notion_describe`, so even a typical multi-operation task stays 85–95% lighter. [Measured, reproducible →](./benchmarks)
16
+ - **The operational stuff is built in.** Batched mutations with atomic rollback, idempotency keys, automatic retry on rate limits, slim token-efficient responses, full markdown round-trip, and self-healing validation errors that let the model fix its own bad payloads in one turn.
13
17
 
14
18
  <a href="https://glama.ai/mcp/servers/zrh07hteaa">
15
19
  <img width="380" height="200" src="https://glama.ai/mcp/servers/zrh07hteaa/badge" alt="Notion MCP Server on Glama" />
16
20
  </a>
17
21
 
18
- ## 📑 Table of Contents
19
-
20
- - [5-minute install (no coding required)](#-5-minute-install-no-coding-required)
21
- - [Why this server? (vs. the official Notion MCP)](#-why-this-server-vs-the-official-notion-mcp)
22
- - [Developer install](#-developer-install)
23
- - [Authentication: PAT (recommended) vs. Internal Integration](#authentication-pat-recommended-vs-internal-integration)
24
- - [Get a Personal Access Token — full walkthrough](#get-a-personal-access-token--full-walkthrough)
25
- - [Backward compatibility from v1.x](#backward-compatibility-from-v1x)
26
- - [Claude Code / Cursor / Claude Desktop](#claude-code--cursor--claude-desktop)
27
- - [Docker / Podman / OrbStack](#docker--podman--orbstack)
28
- - [Optional `NOTION_PAGE_ID`](#optional-notion_page_id)
29
- - [Remote / HTTP transport](#-remote--http-transport)
30
- - [Features: what this Notion MCP server does](#-features-what-this-notion-mcp-server-does)
31
- - [MCP tools for Notion (`notion_execute` & `notion_describe`)](#-mcp-tools-for-notion-notion_execute--notion_describe)
32
- - [`notion_execute`](#notion_execute)
33
- - [`notion_describe`](#notion_describe)
34
- - [Operations menu (35 ops, plus one alias)](#operations-menu-35-ops-plus-one-alias)
35
- - [Development](#-development)
36
- - [Technical details: how the Notion MCP server is built](#-technical-details-how-the-notion-mcp-server-is-built)
37
- - [Troubleshooting the Notion MCP server](#-troubleshooting-the-notion-mcp-server)
38
- - [FAQ: Notion MCP server](#-faq-notion-mcp-server)
39
- - [Contributing](#-contributing)
40
- - [License](#-license)
22
+ ## Quick start
41
23
 
42
- ---
24
+ **Step 1 — get a Notion token (1 minute).** Open **[app.notion.com/developers/tokens](https://app.notion.com/developers/tokens)** (the **Personal access tokens** page of Notion's developer portal) → **+ New token** → name it, pick your workspace → **Create token** → copy the `ntn_…` value. That's it — a PAT sees everything *you* can see, no per-page sharing required. (Page missing or empty? Your admin disabled PATs — see [auth alternatives](#authentication-pat-recommended-vs-internal-integration).)
43
25
 
44
- ## 🪄 5-minute install (no coding required)
26
+ <img src="https://raw.githubusercontent.com/awkoy/notion-mcp-server/main/assets/notion-pat-page.png" width="640" alt="Notion developer portal — the Personal access tokens page with the + New token button in the top right">
45
27
 
46
- You don't need to know what a terminal is. If you can copy text and paste it into two boxes, you can finish this.
28
+ **Step 2 add the server to your client.**
47
29
 
48
- ### What you'll get
49
- After setup, you can tell Claude things like:
30
+ ### Claude Code
50
31
 
51
- - *"Make a page in my Personal workspace called 'Q3 plan' and add a checklist of these five items."*
52
- - *"Find every page in my Tasks database where Status is 'Doing' and tell me which are overdue."*
53
- - *"Comment on yesterday's meeting notes with a one-paragraph summary."*
32
+ ```bash
33
+ claude mcp add notion -s user \
34
+ -e NOTION_TOKEN=ntn_paste_your_token_here \
35
+ -- npx -y notion-mcp-server
36
+ ```
54
37
 
55
- Claude reads and writes Notion directly — no copy/paste, no browser tabs.
38
+ ### Cursor
56
39
 
57
- ### What you'll need
58
- 1. A Notion account.
59
- 2. The [Claude Desktop app](https://claude.ai/download) installed. (Cursor and Claude Code work too — see [Developer install](#-developer-install).)
60
- 3. About 5 minutes.
40
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=notion&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm5vdGlvbi1tY3Atc2VydmVyIl0sImVudiI6eyJOT1RJT05fVE9LRU4iOiJZT1VSX05PVElPTl9UT0tFTiJ9fQ==)
61
41
 
62
- ### Step 1 Get your Notion Personal Access Token
42
+ Click the badge (then replace `YOUR_NOTION_TOKEN` in the generated entry), or add to `~/.cursor/mcp.json` yourself:
63
43
 
64
- A Personal Access Token (PAT) is like a key that lets the AI act as **you** inside Notion. It can see every page **you** can see — no per-page setup.
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "notion": {
48
+ "command": "npx",
49
+ "args": ["-y", "notion-mcp-server"],
50
+ "env": { "NOTION_TOKEN": "ntn_paste_your_token_here" }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### VS Code (Copilot agent mode)
57
+
58
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Notion_MCP-0098FF?logo=githubcopilot)](https://insiders.vscode.dev/redirect/mcp/install?name=notion&inputs=%5B%7B%22id%22%3A%22notion_token%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22Notion%20Personal%20Access%20Token%20(ntn_...)%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22notion-mcp-server%22%5D%2C%22env%22%3A%7B%22NOTION_TOKEN%22%3A%22%24%7Binput%3Anotion_token%7D%22%7D%7D)
65
59
 
66
- 1. Open the Notion developer portal: **[notion.so/profile/integrations](https://www.notion.so/profile/integrations)** (while logged into Notion). Same page if you go through the app: **Settings → Connections → Develop or manage integrations**.
67
- 2. Open the **Personal access tokens** tab → click **+ New personal access token**.
68
- 3. Give it a name like `Claude`, pick the **workspace** the token should act in, leave the default capabilities checked, and click **Create token**.
69
- 4. **Copy** the token — Notion shows the full value **only once**. It starts with `ntn_`. Treat it like a password.
60
+ VS Code prompts for the token on install and stores it as a secret input.
70
61
 
71
- > PATs **expire 1 year after creation**. Set a calendar reminder to rotate before then, or auth will start failing.
72
- >
73
- > Don't see a "Personal access tokens" tab? Your workspace admin may have disabled them — use the [Internal Integration alternative](#authentication-pat-recommended-vs-internal-integration).
74
- >
75
- > Need more detail (rotation, revocation, what a PAT can/can't do)? See the [full PAT walkthrough](#get-a-personal-access-token--full-walkthrough) further down. Official reference: [Notion PAT guide](https://developers.notion.com/guides/get-started/personal-access-tokens).
62
+ ### Claude Desktop
76
63
 
77
- ### Step 2 Tell Claude Desktop where the server lives
64
+ **Easiest: the one-click extension.** Download [`notion-mcp-server.mcpb` from the latest release](https://github.com/awkoy/notion-mcp-server/releases/latest/download/notion-mcp-server.mcpb), double-click it (or drag into Claude Desktop Settings Extensions), paste your Notion token when prompted — done. No config files, Node.js not required.
78
65
 
79
- 1. Open Claude Desktop → click **Claude** (top-left menu on Mac, or the hamburger menu on Windows) → **Settings****Developer****Edit Config**.
80
- 2. A file called `claude_desktop_config.json` opens in a text editor. **Don't panic at the curly braces** — it's just text. We're going to swap all of it out.
81
- 3. **Select all** the text in that file (`Cmd+A` on Mac, `Ctrl+A` on Windows), **delete it**, then paste the block below.
66
+ **Or via the config file:** Settings → Developer → Edit Config, then add:
82
67
 
83
68
  ```json
84
69
  {
@@ -86,391 +71,326 @@ A Personal Access Token (PAT) is like a key that lets the AI act as **you** insi
86
71
  "notion": {
87
72
  "command": "npx",
88
73
  "args": ["-y", "notion-mcp-server"],
89
- "env": {
90
- "NOTION_TOKEN": "ntn_paste_your_token_here"
91
- }
74
+ "env": { "NOTION_TOKEN": "ntn_paste_your_token_here" }
92
75
  }
93
76
  }
94
77
  }
95
78
  ```
96
79
 
97
- > **What is this block?** It tells Claude Desktop how to launch the Notion connector. `npx` is a small tool that downloads and runs the connector automatically the first time you don't install anything separately, it happens in the background (the first run may take a few seconds). `env` is where your Notion token goes. Leave every quote mark and bracket exactly as shown; the only thing you change is the token.
98
-
99
- 4. Replace `ntn_paste_your_token_here` with the token you copied in Step 1 — **leave the quotation marks around it**.
100
- 5. **Save** the file (`Cmd+S` / `Ctrl+S`).
101
- 6. **Quit Claude Desktop completely** (Mac: `Cmd+Q`, not just closing the window — Windows: right-click the tray icon → Quit) and reopen it.
102
-
103
- ### Step 2b — Did it work?
104
-
105
- After Claude Desktop reopens, start a new chat and type **`/`** in the message box. You should see `notion_execute` and `notion_describe` appear in the slash-command list. If they don't, the connection didn't take — go back to **Settings → Developer → Edit Config**, check there's no typo in the token (it must stay between the quotation marks), and confirm you fully quit and reopened Claude Desktop. Common pitfalls are also covered in [Troubleshooting](#-troubleshooting-the-notion-mcp-server).
80
+ Quit Claude Desktop fully (`Cmd+Q` / tray Quit) and reopen. **Never used a config file before?** Follow the [step-by-step walkthrough for non-developers](#-complete-walkthrough-no-coding-required) it assumes nothing.
106
81
 
107
- ### Step 3 Try it
82
+ ### Docker / Podman / OrbStack
108
83
 
109
- In a new Claude chat, type:
84
+ ```bash
85
+ claude mcp add notion -s user \
86
+ -e NOTION_TOKEN=ntn_paste_your_token_here \
87
+ -- docker run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latest
88
+ ```
110
89
 
111
- > *"Use Notion to make a new page called 'Hello from Claude' under my workspace and add a checklist of three things I want to try today."*
90
+ The `-i` flag is required (stdio transport). The image is OCI-compliant Podman, OrbStack, colima, Rancher Desktop, Finch, and nerdctl all work with the same flags.
112
91
 
113
- You should see Claude call the `notion_execute` tool and report back with a page link. Click it — your new page is live in Notion.
92
+ **Step 3 try it.** In a new chat:
114
93
 
115
- That's it. If something doesn't work, the most common fix is in [Troubleshooting](#-troubleshooting-the-notion-mcp-server) below — usually a token typo or Claude Desktop not being fully quit and reopened. The rest of this README covers Docker, Cursor, Claude Code, and self-hosting for developers.
94
+ > *"Use Notion to make a page called 'Hello from my agent' and add a checklist of three things to try today."*
116
95
 
117
- ---
96
+ Your AI calls `notion_execute` and replies with a live page link.
118
97
 
119
- ## Why this server? (vs. the official Notion MCP)
98
+ ## 💡 What your AI can do with it
120
99
 
121
- There's a [first-party Notion MCP server](https://github.com/makenotion/notion-mcp-server). It works for simple one-off calls. For agent workloads repeated queries, bulk mutations, long context windows it gets expensive fast: one MCP tool per endpoint, no batching, no idempotency, raw response shapes. Those choices add up to real token cost and real latency.
100
+ - *"Find every row in my Tasks database where Status is 'Doing' and tell me which are overdue."*typed `where` filters, flattened rows
101
+ - *"Rename these 50 pages to the new convention."* — one batched call, 10-way parallel, idempotent retry
102
+ - *"Create a page from my 'Weekly review' template and fill in this summary."* — Notion templates support
103
+ - *"Rewrite that spec page: fix the headings and add a code sample."* — full markdown round-trip (`get_page_markdown` → edit → `update_page_markdown`)
104
+ - *"Comment on yesterday's meeting notes with a one-paragraph summary."*
105
+ - *"Upload this diagram to the design page."* — single- and multi-part file uploads
122
106
 
123
- This server is designed from the agent's side of the protocol.
107
+ Full capability list in [Features](#-features-what-this-notion-mcp-server-does); the complete operation catalog (43 ops) is in the [Operations menu](#operations-menu-43-ops-plus-one-alias).
124
108
 
125
- | Capability | Official Notion MCP | **This server (designed for agents)** |
126
- | --- | --- | --- |
127
- | **Tool surface** | 22 tools (one per endpoint) loaded into context | **2 tools** (`notion_execute`, `notion_describe`) — the LLM loads ~90% less schema |
128
- | **Operations covered** | ~22 endpoints | **35 operations** (plus a `trash_page` alias of `archive_page`) across pages, blocks, databases, data sources, comments, users, files |
129
- | **Primary auth** | Internal Integration token + per-page "Connect" sharing | **Personal Access Token (PAT)** — uses the pages you've authorized for the token, zero per-page Connect step |
130
- | **Batch mutations** | Not documented | ✅ Universal `{ items: [...] }` envelope; runs up to **10 in parallel** |
131
- | **Atomic batches + rollback** | Not documented | ✅ `atomic: true` aborts on first failure, best-effort archives entities created earlier |
132
- | **Idempotency** | Not documented | ✅ `idempotency_key` — same key + same op returns the cached result for 5 minutes |
133
- | **Rate-limit handling** | Not documented (429s bubble up) | ✅ Shared token-bucket limiter (3 req/s default, configurable via `NOTION_RATE_LIMIT`) + exponential backoff on 429/5xx/timeouts, honors `Retry-After` |
134
- | **Response shapes** | Raw Notion SDK JSON | **Slim shapers by default** — drops `archived: false`, `created_time`, `last_edited_time`, `in_trash: false`, empty descriptions, etc. `verbose: true` to opt out |
135
- | **Database queries** | Raw `properties` bag per row | **Flattened** name → primitive map (title, rich_text, number, select, multi_select, status, date, people, files, checkbox, url, email, phone_number, formula, relation, rollup, unique_id, verification, created_by, last_edited_by, timestamps) |
136
- | **Wire format** | Default SDK serialization | **Compact (un-indented) JSON** — ~30% smaller payloads vs. indented output, identical to parse |
137
- | **Markdown input** | Page-level markdown editing supported | ✅ `markdown` shortcut on `create_page` / `append_blocks` / `update_block`, full markdown round-trip via `get_page_markdown` / `update_page_markdown`, plus markdown comment bodies — full GFM (paragraphs, headings 1–4, lists, to-dos with nested children, blockquotes, fenced code with language detection, images, dividers, inline bold/italic/strike/code/links) |
138
- | **File uploads** | Not in the documented tool surface | ✅ `upload_file` handles single-part and multi-part (5 MB chunks) transparently; MIME inferred from filename; rejects `application/octet-stream` |
139
- | **Validation errors** | Plain error string | **Self-healing**: `{ code, message, path, issues, schema, example, fix }` — agent corrects bad payloads in one round-trip without calling describe |
140
- | **Notion API version** | Not pinned in client config | Pinned to `2025-09-03` (the modern data-sources line) |
141
-
142
- ### Real-world impact
143
-
144
- - **Renaming 50 pages.** Without a batch envelope, the agent issues 50 separate `update-page` MCP calls — each one re-loading the tool schema and serialized through the agent's reasoning loop. With this server, the agent issues one `notion_execute` call with `{ items: [...], concurrency: 10 }`. Wall-clock improvement is roughly an order of magnitude on typical batch sizes; the bigger win is the tokens saved on prompt overhead.
145
- - **Loading the tool list into the agent's context.** Official server: 22 schema blobs every conversation. This server: 2 schema blobs — and only those 2 ever appear in the agent's tool list, regardless of which of the 35 operations the agent ends up calling.
146
- - **Reading a 100-row database.** Official server returns the raw Notion `properties` bag per row. This server flattens it; for a typical CRM table this is roughly **5–10× fewer tokens** without losing information.
109
+ ## 🧭 Which Notion MCP should you use?
147
110
 
148
- ---
111
+ Three options exist. Honest guidance:
149
112
 
150
- ## 🚀 Developer install
113
+ | | Best for | Auth | Headless / CI | Notes |
114
+ | --- | --- | --- | --- | --- |
115
+ | **[Notion hosted MCP](https://developers.notion.com/docs/get-started-with-mcp)** (`mcp.notion.com`) | Interactive chat in claude.ai, ChatGPT, Cursor | OAuth (human must click) | ❌ | First-party, 18 markdown tools, some plan-gated |
116
+ | **[Official open-source server](https://github.com/makenotion/notion-mcp-server)** | — | Token | ✅ | Notion has soft-deprecated it (“may sunset this repository… issues and PRs not actively monitored”) |
117
+ | **This server** | Agents, automation, CI, self-hosting, token-sensitive workloads | Token (PAT) | ✅ | Actively maintained, agent-first design below |
151
118
 
152
- ### Authentication: PAT (recommended) vs. Internal Integration
119
+ If you just want to chat with your Notion in claude.ai's web UI, use Notion's hosted connector — it's one click. Use **this** server when your agent runs unattended, when context/token cost matters, or when you want batch/idempotent semantics and self-hosting.
153
120
 
154
- There are two ways to authenticate. Both use the `NOTION_TOKEN` env var — only how you obtain the token differs.
121
+ <details>
122
+ <summary><b>Detailed comparison vs. the official open-source server</b></summary>
155
123
 
156
- | | **Personal Access Token** (recommended) | **Internal Integration** (legacy) |
124
+ | Capability | Official Notion MCP (open source) | **This server** |
157
125
  | --- | --- | --- |
158
- | Where you get it | [notion.so/profile/integrations](https://www.notion.so/profile/integrations) **Personal access tokens** tab **+ New personal access token** | [notion.so/profile/integrations/internal](https://www.notion.so/profile/integrations/internal) **+ New connection** |
159
- | Token prefix | `ntn_…` | `ntn_…` (new) or `secret_…` (older) |
160
- | Scope | Everything **you** can see | Only pages where you've clicked **• Connect \<integration\>** |
161
- | Setup friction | None works immediately | Per-page Connect dance for every page or database the agent should touch |
162
- | When to use | Default. Personal workspaces, team workspaces where you're authorized, prototyping. | When a workspace admin requires explicit per-resource scoping for compliance, or for shared production bots. |
163
-
164
- The rest of this README assumes PAT. Swap in an integration secret if you prefer the scoped model every command below is identical.
126
+ | **Tool surface** | 24 tools (one per endpoint), 17,163 tokens loaded into context | **2 tools**, 422 tokens [97% less schema at connection](./benchmarks) |
127
+ | **Operations covered** | ~24 endpoints | **43 operations** (plus a `trash_page` alias) across pages, blocks, databases, data sources, views, templates, comments, users, files |
128
+ | **Batch mutations** | Not documented | Universal `{ items: [...] }` envelope; up to **10 in parallel** |
129
+ | **Atomic batches + rollback** | Not documented | `atomic: true` aborts on first failure, best-effort archives entities created earlier |
130
+ | **Idempotency** | Not documented | `idempotency_key` same key + op returns the cached result for 5 minutes |
131
+ | **Rate-limit handling** | 429s bubble up | ✅ Token-bucket limiter (3 req/s default) + exponential backoff, honors `Retry-After` |
132
+ | **Response shapes** | Raw Notion SDK JSON | **Slim shapers** drop noise by default; `verbose: true` opts out |
133
+ | **Database queries** | Raw `properties` bag per row | **Flattened** name → primitive map (all 20+ property types) |
134
+ | **Wire format** | Default SDK serialization | **Compact JSON** — ~30% smaller payloads |
135
+ | **Markdown** | Page-level markdown tools | ✅ Markdown accepted by `create_page` / `append_blocks` / `update_block` / comments + full round-trip (`get_page_markdown` / `update_page_markdown`), full GFM |
136
+ | **Templates** | — | ✅ `create_page` from a Notion template + `list_data_source_templates` discovery |
137
+ | **File uploads** | Not in the documented tool surface | ✅ Single- and multi-part (5 MB chunks), MIME inferred |
138
+ | **Validation errors** | Plain error string | **Self-healing**: `{ code, message, path, issues, schema, example, fix }` — corrected in one round-trip |
139
+ | **Notion API version** | — | Pinned `2026-03-11` (data sources, views, templates) |
165
140
 
166
- > 💡 **Heads-up:** most "object_not_found" errors are a wrong auth choice, not a bug. If your agent reports "Could not find page" on pages you can see in Notion, you're almost certainly using an Internal Integration token that hasn't been Connected to those pages — switch to a PAT.
141
+ **Real-world impact:**
167
142
 
168
- ### Get a Personal Access Token full walkthrough
143
+ - **Renaming 50 pages** one `notion_execute` call with `{ items: [...], concurrency: 10 }` instead of 50 separate tool calls through the agent's reasoning loop: roughly an order of magnitude faster, and the prompt-token savings are the bigger win.
144
+ - **Tool list in context** — 2 schema blobs per conversation instead of ~24, no matter which of the 43 operations get called.
145
+ - **Reading a 100-row database** — flattened rows are typically **5–10× fewer tokens** than the raw `properties` bag, with no information loss.
169
146
 
170
- [Step 1 of the 5-minute install](#step-1--get-your-notion-personal-access-token) covers the happy path. This section covers what surrounds it: capabilities, expiry, revocation, and the admin-disabled fallback.
147
+ </details>
171
148
 
172
- > 📖 Official: [Notion PAT guide](https://developers.notion.com/guides/get-started/personal-access-tokens) · [Authorization overview](https://developers.notion.com/docs/authorization).
149
+ ## 🪄 Complete walkthrough (no coding required)
173
150
 
174
- #### What a PAT can and can't do
151
+ <details>
152
+ <summary><b>Set up Claude Desktop + Notion in 5 minutes — assumes you've never seen a config file</b></summary>
175
153
 
176
- | Can | Can't |
177
- | --- | --- |
178
- | Read every page you have access to | Access workspaces or pages you personally can't see |
179
- | Create / update pages and databases in workspaces where you have edit rights | Bypass workspace permission rules |
180
- | Add comments under your identity | Act as another user |
181
- | Upload files via the File Upload API | Modify workspace-level admin settings |
154
+ ### What you'll need
182
155
 
183
- A PAT is a **scope = your account**. If you lose edit access to a page, the PAT loses it too. Issue separate tokens to teammates — don't share one.
156
+ 1. A Notion account.
157
+ 2. The [Claude Desktop app](https://claude.ai/download).
158
+ 3. About 5 minutes.
184
159
 
185
- #### Expiry and rotation
160
+ ### Step 1 — Get your Notion token
186
161
 
187
- **PATs expire 1 year after creation** ([Notion docs](https://developers.notion.com/guides/get-started/personal-access-tokens)). After expiry, every API call returns an auth error until you replace the token. Set a calendar reminder for ~11 months out.
162
+ A Personal Access Token (PAT) is like a key that lets the AI act as **you** inside Notion it sees every page you can see, with no per-page setup.
188
163
 
189
- #### Revoking a PAT
164
+ 1. Open **[app.notion.com/developers/tokens](https://app.notion.com/developers/tokens)** while logged into Notion — that's the **Personal access tokens** page of Notion's developer portal (also reachable from the app via **Settings → Connections → Develop or manage integrations** → **Personal access tokens** in the sidebar).
165
+ 2. Click **+ New token**.
166
+ 3. Name it (e.g. `Claude`), pick the workspace, leave the default **Notion API** capability checked, click **Create token**.
190
167
 
191
- 1. Open **[notion.so/profile/integrations](https://www.notion.so/profile/integrations)** **Personal access tokens** tab.
192
- 2. Find the token by name → **• • • → Revoke**.
193
- 3. Update `NOTION_TOKEN` in your MCP client config and restart the client.
168
+ <img src="https://raw.githubusercontent.com/awkoy/notion-mcp-server/main/assets/notion-new-token-modal.png" width="460" alt="The New personal access token dialog: enter a token name, pick the workspace it has access to, keep the Notion API capability checked, then press Create token">
194
169
 
195
- Workspace admins can revoke any user's PATs from **Settings & members Connections All personal access tokens**. Revocation is immediate.
170
+ 4. **Copy the token now** Notion shows it only once. It starts with `ntn_`. Treat it like a password.
196
171
 
197
- #### Workspace admin disabled PATs?
172
+ > PATs **expire 1 year after creation** — set a reminder to rotate. No "Personal access tokens" tab? Your admin disabled them; use the [Internal Integration alternative](#authentication-pat-recommended-vs-internal-integration).
198
173
 
199
- Some enterprise workspaces only allow scoped Internal Integrations. Two options:
174
+ ### Step 2 Tell Claude Desktop about the server
200
175
 
201
- 1. **Ask your admin to enable PATs** for your account.
202
- 2. **Use the [Internal Integration](#authentication-pat-recommended-vs-internal-integration) path** — same `NOTION_TOKEN` env var; create it at **[notion.so/profile/integrations/internal](https://www.notion.so/profile/integrations/internal) + New connection**, then click **• • • → Connect** on every page or database you want the agent to touch.
176
+ 1. Open Claude Desktop → **Claude** menu (top-left on Mac, hamburger on Windows) → **Settings** **Developer** → **Edit Config**.
177
+ 2. A file named `claude_desktop_config.json` opens. Don't panic at the curly braces it's just text.
178
+ 3. Select all (`Cmd+A` / `Ctrl+A`), delete, and paste:
203
179
 
204
- ### Backward compatibility from v1.x
180
+ ```json
181
+ {
182
+ "mcpServers": {
183
+ "notion": {
184
+ "command": "npx",
185
+ "args": ["-y", "notion-mcp-server"],
186
+ "env": {
187
+ "NOTION_TOKEN": "ntn_paste_your_token_here"
188
+ }
189
+ }
190
+ }
191
+ }
192
+ ```
205
193
 
206
- If you ran a v1.x setup, **nothing in your environment needs to change**. Both env vars still work:
194
+ > This block tells Claude Desktop how to launch the connector. `npx` downloads and runs it automatically the first time. The only thing you change is the token.
207
195
 
208
- | Env var | Status in v2.4 | Notes |
209
- | --- | --- | --- |
210
- | `NOTION_TOKEN` | Required | Accepts **PATs** (`ntn_…`, recommended) and **Internal Integration secrets** (`secret_…` or `ntn_…`, legacy). Identical handling. |
211
- | `NOTION_PAGE_ID` | ✅ Optional | Still works as the default parent page for `create_page` / `create_database` when no `parent` is passed. v2 added a clean `missing_parent` validation error instead of v1's crash when neither is provided. |
212
- | `NOTION_RATE_LIMIT` | ✅ New, optional | Requests per second for the shared limiter. Defaults to `3` (Notion's documented per-integration limit). |
213
- | `NOTION_ALLOWED_OPERATIONS` | ✅ New, optional | Comma-separated allowlist of operations or group presets (`read`, `write`, `destructive`, plus per-domain groups `pages`, `blocks`, `databases`, `data_sources`, `comments`, `users`, `files`). Unset ⇒ all operations enabled. See [Restricting operations](#restricting-operations). |
214
- | `NOTION_BLOCKED_OPERATIONS` | ✅ New, optional | Comma-separated blocklist (same token vocabulary). Applied after the allowlist, so a blocked operation is always disabled. |
215
- | `NOTION_READ_ONLY` | ✅ New, optional | Set to `true`/`1`/`yes` to disable every write operation in one switch (equivalent to `NOTION_BLOCKED_OPERATIONS=write`). Composes with the allow/block lists. See [Restricting operations](#restricting-operations). |
216
- | `NOTION_DAILY_LOG_PAGE_ID` | ✅ Optional | Used only by the daily-log MCP prompt. Ignore if you don't call that prompt. |
196
+ 4. Replace `ntn_paste_your_token_here` with your token **keep the quotation marks**.
197
+ 5. Save (`Cmd+S` / `Ctrl+S`).
198
+ 6. **Quit Claude Desktop completely** (Mac: `Cmd+Q`; Windows: tray icon Quit) and reopen it.
217
199
 
218
- The only v2 break is the **tool surface itself** v1's `notion_pages`, `notion_blocks`, `notion_database`, `notion_comments`, `notion_users` are replaced by `notion_execute` and `notion_describe`. Modern MCP clients (Claude Code, Cursor, Claude Desktop) rediscover tools at startup, so they pick up the new surface automatically. If your client hard-codes the v1 tool names, see [MIGRATION.md](./MIGRATION.md) for the rename map.
200
+ ### Step 3Check and try
219
201
 
220
- A typical v1.x invocation continues to work unchanged:
202
+ Type **`/`** in a new chat you should see `notion_execute` and `notion_describe` in the list. Then ask:
221
203
 
222
- ```bash
223
- NOTION_TOKEN=secret_xxx NOTION_PAGE_ID=abc123... node build/index.js
224
- ```
204
+ > *"Use Notion to make a new page called 'Hello from Claude' and add a checklist of three things I want to try today."*
225
205
 
226
- ### Restricting operations
206
+ Claude calls the tool and replies with a page link. If something's off, it's almost always a token typo or Claude Desktop not fully quit — see [Troubleshooting](#-troubleshooting-the-notion-mcp-server).
227
207
 
228
- By default every operation is available. To limit what an agent can do, set
229
- `NOTION_ALLOWED_OPERATIONS` (an allowlist) and/or `NOTION_BLOCKED_OPERATIONS` (a
230
- blocklist). Each is a comma-separated list of **tokens**, where a token is either a
231
- **group preset** or an exact **operation name**.
208
+ </details>
232
209
 
233
- **Group presets** (one token expands to many operations):
210
+ ## 🔧 Configuration
234
211
 
235
- | Token | Expands to |
236
- | --- | --- |
237
- | `read` | every non-mutating operation |
238
- | `write` | every mutating operation |
239
- | `destructive` | operations whose purpose is removal (marked † below) |
240
- | `pages` `blocks` `databases` `data_sources` `comments` `users` `files` | every operation in that resource family (read **and** write) |
212
+ ### Authentication: PAT (recommended) vs. Internal Integration
241
213
 
242
- **All operations** — use any name directly for a precise allow/blocklist. († = also in the `destructive` group.)
214
+ Both use the same `NOTION_TOKEN` env var only where you get the token differs.
243
215
 
244
- | Domain | Read | Write |
216
+ | | **Personal Access Token** (recommended) | **Internal Integration** (scoped) |
245
217
  | --- | --- | --- |
246
- | `pages` | `search_pages` `get_page` `get_page_markdown` | `create_page` `set_page_title` `set_page_property` `set_page_properties` `update_page_markdown` `move_page` `restore_page` `archive_page`† `trash_page`† |
247
- | `blocks` | `get_block` `get_block_children` | `append_blocks` `update_block` `delete_block`† `batch_mixed_blocks`† |
248
- | `databases` | `query_database` | `create_database` `update_database` |
249
- | `data_sources` | `list_data_sources` `get_data_source` | `update_data_source` |
250
- | `views` | `list_views` `get_view` `query_view` | `create_view` `update_view` `delete_view`† |
251
- | `comments` | `list_comments` `get_comment` | `add_page_comment` `add_discussion_comment` `update_comment` `delete_comment`† |
252
- | `users` | `list_users` `get_user` `get_bot_user` `get_self` | — |
253
- | `files` | `list_file_uploads` `get_file_upload` | `upload_file` |
218
+ | Where | [app.notion.com/developers/tokens](https://app.notion.com/developers/tokens) **+ New token** | [app.notion.com/developers/connections](https://app.notion.com/developers/connections) **+ New connection** |
219
+ | Scope | Everything **you** can see | Only pages where you clicked **• • • → Connect → \<integration\>** |
220
+ | Friction | None | Per-page Connect step for every page/database |
221
+ | Use when | Default: personal + team workspaces, prototyping | Admin requires explicit per-resource scoping, or shared production bots |
254
222
 
255
- Read-only deployment (the most common case):
223
+ > 💡 Most `object_not_found` errors are a wrong auth choice, not a bug: an Internal Integration token that was never Connected to the page. Switch to a PAT.
256
224
 
257
- ```json
258
- {
259
- "mcpServers": {
260
- "notion": {
261
- "command": "npx",
262
- "args": ["-y", "notion-mcp-server"],
263
- "env": {
264
- "NOTION_TOKEN": "ntn_paste_your_token_here",
265
- "NOTION_ALLOWED_OPERATIONS": "read"
266
- }
267
- }
268
- }
269
- }
270
- ```
225
+ <details>
226
+ <summary><b>PAT details: capabilities, expiry, revocation, admin-disabled fallback</b></summary>
271
227
 
272
- Allow everything except destructive operations:
228
+ **Can:** read every page you have access to; create/update pages and databases where you have edit rights; comment as you; upload files.
229
+ **Can't:** access pages you can't see; bypass workspace permissions; act as another user; change admin settings. A PAT's scope = your account — if you lose access to a page, so does the PAT. Issue separate tokens per teammate.
273
230
 
274
- ```json
275
- { "env": { "NOTION_BLOCKED_OPERATIONS": "destructive" } }
276
- ```
231
+ **Expiry:** PATs expire **1 year after creation** ([Notion docs](https://developers.notion.com/guides/get-started/personal-access-tokens)); set a reminder for ~11 months.
277
232
 
278
- Mix presets and individual ops (read everything, plus append blocks and comments):
233
+ **Revoking:** [app.notion.com/developers/tokens](https://app.notion.com/developers/tokens) **Revoke** next to the token (immediate). Workspace admins can revoke anyone's from **Settings & members → Connections → All personal access tokens**.
279
234
 
280
- ```json
281
- { "env": { "NOTION_ALLOWED_OPERATIONS": "read,append_blocks,add_page_comment" } }
282
- ```
235
+ **Admin disabled PATs?** Ask them to enable, or create an Internal Integration at [app.notion.com/developers/connections](https://app.notion.com/developers/connections) (**+ New connection**) and **• • • → Connect** it to every page the agent should touch — same `NOTION_TOKEN` env var.
283
236
 
284
- **Rules:** tokens are case-insensitive; unknown tokens are ignored with a warning; the
285
- blocklist wins on conflict; and if the allowlist is set but resolves to no enabled
286
- operations (all tokens invalid, or every allowed op also blocked), **all** operations are
287
- disabled (fail-closed). Disabled operations are hidden from the `notion://operations`
288
- menu and from `notion_describe`, and `notion_execute` rejects them with
289
- `operation_not_allowed`.
237
+ Official reference: [PAT guide](https://developers.notion.com/guides/get-started/personal-access-tokens) · [Authorization overview](https://developers.notion.com/docs/authorization).
290
238
 
291
- **Verifying your configuration.** On startup the server prints one line to **stderr**
292
- (visible in your MCP client's server logs) summarizing what resolved, e.g.:
239
+ </details>
293
240
 
294
- ```text
295
- Operation access: 16/37 enabled (allow=read; block=(none))
296
- ```
241
+ ### Environment variables
297
242
 
298
- Unknown tokens and a fail-closed allowlist are logged there too. If the count or the
299
- `allow`/`block` values aren't what you expect, check that line first.
243
+ | Env var | Required | Default | Meaning |
244
+ | --- | --- | --- | --- |
245
+ | `NOTION_TOKEN` | ✅ | — | PAT (`ntn_…`, recommended) or Internal Integration secret (`secret_…` / `ntn_…`) |
246
+ | `NOTION_PAGE_ID` | — | — | Default parent for `create_page` / `create_database` when no `parent` is passed (page → Share → Copy link; ID = last 32 chars) |
247
+ | `NOTION_RATE_LIMIT` | — | `3` | Requests/second for the shared limiter (Notion's documented per-integration limit) |
248
+ | `NOTION_READ_ONLY` | — | — | `true`/`1`/`yes` disables every write operation in one switch |
249
+ | `NOTION_ALLOWED_OPERATIONS` | — | all | Comma-separated allowlist of operations or group presets — see [Restricting operations](#restricting-operations) |
250
+ | `NOTION_BLOCKED_OPERATIONS` | — | — | Comma-separated blocklist (same vocabulary); wins over the allowlist |
251
+ | `HTTPS_PROXY` / `HTTP_PROXY` | — | — | Route Notion API traffic through an HTTP(S) proxy (standard env vars, lowercase also accepted) |
252
+ | `NOTION_DAILY_LOG_PAGE_ID` | — | — | Only used by the daily-log MCP prompt |
300
253
 
301
- **Limitations** (control is per-operation, not per-parameter):
254
+ HTTP-transport variables (`MCP_TRANSPORT`, `PORT`, `HOST`, `MCP_AUTH_TOKEN`, …) are covered in [Remote / HTTP transport](#-remote--http-transport).
302
255
 
303
- - The `destructive` group covers operations whose *purpose* is removal (`trash_page`,
304
- `archive_page`, `delete_block`, `delete_comment`, `batch_mixed_blocks`). A few *write*
305
- operations can also remove content via a parameter — e.g. `update_database` /
306
- `update_data_source` accept `in_trash`, and `update_page_markdown` can replace a page
307
- body. Blocking `destructive` does **not** disable those write ops. **For a guaranteed
308
- no-mutation deployment, use the allowlist** (`NOTION_ALLOWED_OPERATIONS=read`) or the
309
- shorthand `NOTION_READ_ONLY=true` — both leave only read operations enabled.
310
- - MCP *prompts* (e.g. the daily-log prompt) may still reference operations you have
311
- disabled. The prompt text is unaffected by the allowlist; the underlying operation is
312
- still rejected at execution time.
256
+ > **Upgrading from v1.x?** Your env vars all still work unchanged. The only break is the tool surface (v1's five tools became `notion_execute` + `notion_describe`); modern clients rediscover tools automatically. Details: [MIGRATION.md](./MIGRATION.md).
313
257
 
314
- ### Claude Code / Cursor / Claude Desktop
258
+ ### Restricting operations
315
259
 
316
- **Claude Code:**
260
+ Limit what an agent can do with `NOTION_ALLOWED_OPERATIONS` (allowlist) and/or `NOTION_BLOCKED_OPERATIONS` (blocklist) — each a comma-separated list of **group presets** or exact **operation names**.
317
261
 
318
- ```bash
319
- claude mcp add notion -s user \
320
- -e NOTION_TOKEN=ntn_paste_your_token_here \
321
- -- npx -y notion-mcp-server
322
- ```
262
+ | Preset | Expands to |
263
+ | --- | --- |
264
+ | `read` | every non-mutating operation |
265
+ | `write` | every mutating operation |
266
+ | `destructive` | operations whose purpose is removal (`archive_page`/`trash_page`, `delete_block`, `batch_mixed_blocks`, `delete_comment`, `delete_view`) |
267
+ | `pages` `blocks` `databases` `data_sources` `views` `comments` `users` `files` | every operation in that family (read **and** write) |
323
268
 
324
- **Cursor** (`~/.cursor/mcp.json`) **or Claude Desktop** (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows: `%APPDATA%\Claude\claude_desktop_config.json`):
269
+ Read-only deployment (most common):
325
270
 
326
271
  ```json
327
- {
328
- "mcpServers": {
329
- "notion": {
330
- "command": "npx",
331
- "args": ["-y", "notion-mcp-server"],
332
- "env": {
333
- "NOTION_TOKEN": "ntn_paste_your_token_here"
334
- }
335
- }
336
- }
337
- }
272
+ { "env": { "NOTION_TOKEN": "ntn_xxx", "NOTION_ALLOWED_OPERATIONS": "read" } }
338
273
  ```
339
274
 
340
- **Local build (no npx):**
275
+ Everything except destructive ops:
341
276
 
342
- ```bash
343
- git clone https://github.com/awkoy/notion-mcp-server.git
344
- cd notion-mcp-server
345
- npm install && npm run build
346
-
347
- claude mcp add notion -s user \
348
- -e NOTION_TOKEN=ntn_paste_your_token_here \
349
- -- node "$(pwd)/build/index.js"
277
+ ```json
278
+ { "env": { "NOTION_BLOCKED_OPERATIONS": "destructive" } }
350
279
  ```
351
280
 
352
- ### Docker / Podman / OrbStack
281
+ Mix presets and individual ops:
353
282
 
354
- ```bash
355
- claude mcp add notion -s user \
356
- -e NOTION_TOKEN=ntn_paste_your_token_here \
357
- -- docker run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latest
283
+ ```json
284
+ { "env": { "NOTION_ALLOWED_OPERATIONS": "read,append_blocks,add_page_comment" } }
358
285
  ```
359
286
 
360
- The `-i` flag is required (stdio transport). `-e NOTION_TOKEN` (no `=value`) forwards the env var from the parent process.
287
+ **Rules:** case-insensitive; unknown tokens ignored with a warning; blocklist wins; an allowlist that resolves to zero operations disables **everything** (fail-closed). Disabled operations disappear from `notion_describe` and the `notion://operations` menu, and `notion_execute` rejects them with `operation_not_allowed`.
361
288
 
362
- For Cursor / Claude Desktop:
289
+ On startup the server logs one line to stderr summarizing what resolved — check it first if the config doesn't behave as expected:
363
290
 
364
- ```json
365
- {
366
- "mcpServers": {
367
- "notion": {
368
- "command": "docker",
369
- "args": ["run", "--rm", "-i", "-e", "NOTION_TOKEN", "ghcr.io/awkoy/notion-mcp-server:latest"],
370
- "env": { "NOTION_TOKEN": "ntn_paste_your_token_here" }
371
- }
372
- }
373
- }
291
+ ```text
292
+ Operation access: 20/44 enabled (allow=read; block=(none))
374
293
  ```
375
294
 
376
- The published image is OCI-compliant — **Podman**, **OrbStack**, **colima**, **Rancher Desktop**, **Finch**, and **nerdctl** all work with the same flags (substitute the runtime's CLI for `docker`). Docker Desktop is not required.
295
+ <details>
296
+ <summary><b>Per-operation reference & limitations</b></summary>
377
297
 
378
- ### Optional `NOTION_PAGE_ID`
379
-
380
- A default parent page for `create_page` / `create_database` when the caller doesn't pass one. Operations that need a parent and don't get one return a clear validation error instead of crashing.
298
+ | Domain | Read | Write |
299
+ | --- | --- | --- |
300
+ | `pages` | `search_pages` `get_page` `get_page_markdown` | `create_page` `set_page_title` `set_page_property` `set_page_properties` `update_page_markdown` `move_page` `restore_page` `archive_page`† `trash_page`† |
301
+ | `blocks` | `get_block` `get_block_children` | `append_blocks` `update_block` `delete_block`† `batch_mixed_blocks`† |
302
+ | `databases` | `query_database` | `create_database` `update_database` |
303
+ | `data_sources` | `list_data_sources` `get_data_source` `list_data_source_templates` | `update_data_source` |
304
+ | `views` | `list_views` `get_view` `query_view` | `create_view` `update_view` `delete_view`† |
305
+ | `comments` | `list_comments` `get_comment` | `add_page_comment` `add_discussion_comment` `update_comment` `delete_comment`† |
306
+ | `users` | `list_users` `get_user` `get_bot_user` `get_self` | — |
307
+ | `files` | `list_file_uploads` `get_file_upload` | `upload_file` |
381
308
 
382
- To find a page ID: open the page in Notion → **Share → Copy link**. The ID is the last 32 characters of the URL.
309
+ = also in the `destructive` group.
383
310
 
384
- ```bash
385
- claude mcp add notion -s user \
386
- -e NOTION_TOKEN=ntn_xxx \
387
- -e NOTION_PAGE_ID=abc123... \
388
- -- npx -y notion-mcp-server
389
- ```
311
+ **Limitations** (control is per-operation, not per-parameter): a few *write* ops can remove content via a parameter — `update_database` / `update_data_source` accept `in_trash`, and `update_page_markdown` can replace a page body. Blocking `destructive` does **not** disable those. For a guaranteed no-mutation deployment use `NOTION_ALLOWED_OPERATIONS=read` or `NOTION_READ_ONLY=true`. MCP *prompts* may still mention disabled operations, but execution is rejected.
390
312
 
391
- ---
313
+ </details>
392
314
 
393
315
  ## 🌐 Remote / HTTP transport
394
316
 
395
- By default the server speaks **stdio** (the local connector path above). To run it as a remote/hosted endpoint — for web clients, networked agents, or a shared deployment — set `MCP_TRANSPORT=http`:
317
+ By default the server speaks **stdio** (the local path above). To run it as a remote/hosted endpoint — web clients, networked agents, shared deployments — set `MCP_TRANSPORT=http`:
396
318
 
397
319
  ```bash
398
320
  MCP_TRANSPORT=http PORT=3000 NOTION_TOKEN=ntn_xxx npx -y notion-mcp-server
399
321
  # -> notion-mcp-server vX.Y.Z running on http://127.0.0.1:3000/mcp
400
322
  ```
401
323
 
402
- It serves the MCP **Streamable HTTP** protocol at `POST/GET/DELETE /mcp` (stateful sessions via the `mcp-session-id` header) plus an unauthenticated `GET /health`. It's **single-tenant** — every request uses the one `NOTION_TOKEN` the process was started with.
403
-
404
- ### Configuration
324
+ It serves MCP **Streamable HTTP** at `POST/GET/DELETE /mcp` (stateful sessions via the `mcp-session-id` header) plus an unauthenticated `GET /health`. It's **single-tenant** — every request acts as the one `NOTION_TOKEN` the process started with.
405
325
 
406
326
  | env | default | meaning |
407
327
  | --- | --- | --- |
408
- | `MCP_TRANSPORT` | `stdio` | set to `http` to enable the HTTP transport |
328
+ | `MCP_TRANSPORT` | `stdio` | set to `http` to enable HTTP |
409
329
  | `PORT` | `3000` | listen port (`0` = OS-assigned) |
410
- | `HOST` | `127.0.0.1` | bind address. Loopback by default; set `0.0.0.0` to expose externally (do this only with `MCP_AUTH_TOKEN`) |
330
+ | `HOST` | `127.0.0.1` | bind address; set `0.0.0.0` to expose externally (**only with `MCP_AUTH_TOKEN`**) |
411
331
  | `MCP_AUTH_TOKEN` | — | when set, every `/mcp` request must send `Authorization: Bearer <token>` |
412
332
  | `MCP_ALLOWED_HOSTS` | localhost + bound host | comma-list for DNS-rebinding `Host` allowlist |
413
333
  | `MCP_ALLOWED_ORIGINS` | localhost origins | comma-list for browser `Origin` allowlist |
414
334
 
415
- > ⚠️ **Single-tenant means whoever reaches `/mcp` acts as your `NOTION_TOKEN`.** On loopback (the default) that's just local processes. Before binding a non-loopback `HOST`, set `MCP_AUTH_TOKEN` (the server logs a warning if you don't) and/or put it behind an authenticating reverse proxy.
335
+ > ⚠️ **Whoever reaches `/mcp` acts as your `NOTION_TOKEN`.** On loopback (the default) that's just local processes. Before binding a non-loopback `HOST`, set `MCP_AUTH_TOKEN` (the server warns if you don't) and/or front it with an authenticating reverse proxy.
336
+
337
+ Connect from clients that support headers (Claude Code, Cursor, VS Code):
338
+
339
+ ```bash
340
+ claude mcp add --transport http notion https://your-host/mcp \
341
+ --header "Authorization: Bearer <MCP_AUTH_TOKEN>"
342
+ ```
416
343
 
417
- ### Try it
344
+ Try it locally:
418
345
 
419
346
  ```bash
420
- # health check
421
347
  curl http://127.0.0.1:3000/health
422
348
  # -> {"status":"healthy","transport":"http","port":3000}
423
-
424
- # point the MCP Inspector at it
425
349
  npx @modelcontextprotocol/inspector --transport http --server-url http://127.0.0.1:3000/mcp
426
350
  ```
427
351
 
428
- In Docker, set the env and publish the port:
352
+ In Docker:
429
353
 
430
354
  ```bash
431
355
  docker run --rm -e NOTION_TOKEN=ntn_xxx -e MCP_TRANSPORT=http -p 3000:3000 ghcr.io/awkoy/notion-mcp-server
432
356
  ```
433
357
 
434
- ---
435
-
436
358
  ## 🌟 Features: what this Notion MCP server does
437
359
 
438
360
  - **Two-tool surface** — `notion_execute` (do it) + `notion_describe` (learn the shape). The whole API is one schema deep.
439
- - **Universal batch envelope** — every mutating op accepts `{ items: [...], atomic?, idempotency_key?, concurrency? }`. Per-item validation, per-item results, summary counts.
361
+ - **Universal batch envelope** — every mutating op accepts `{ items: [...], atomic?, idempotency_key?, concurrency? }` with per-item validation and results.
440
362
  - **Atomic batches with best-effort rollback** — `atomic: true` aborts on first failure and archives anything created earlier in the batch.
441
- - **Idempotency keys** — same `(operation, idempotency_key)` returns the cached batch result for 5 minutes (max 512 entries). Safe to retry on flaky networks.
442
- - **Rate-limit + retry baked in** — shared token-bucket limiter (3 req/s default, configurable via `NOTION_RATE_LIMIT`); transient SDK failures (429, 5xx, timeouts) auto-retry with exponential backoff and honor `Retry-After`.
443
- - **Self-healing validation errors** — every failure returns `{ schema, example, fix }`. The model corrects bad payloads in one round-trip — no extra `notion_describe` call needed.
444
- - **Markdown shortcut** — `create_page` / `append_blocks` / `update_block` / `update_page_markdown` / comment bodies accept a `markdown` string (full GFM: paragraphs, headings 1–4, lists, to-dos with nested children, blockquotes, fenced code with language normalization, dividers, images, inline bold/italic/strike/code/links).
445
- - **Slim responses + flattened rows** — defaults drop noisy fields and the `query_database` rows flatten each property to a name primitive map. `verbose: true` per call to get the raw SDK shape. Compact JSON wire format (~30% smaller payloads).
446
- - **File uploads** — `upload_file` handles single-part and multi-part (5 MB chunks) transparently; auto-detects MIME from filename; rejects `application/octet-stream`.
447
- - **Opt-in auto-pagination** — pass `paginate: true` on `search_pages`, `list_comments`, or `query_database` and the server walks `next_cursor` for you (capped by `page_limit`, default 10 pages 1000 items at `page_size: 100`). Other list ops return a single Notion page with `has_more` / `next_cursor`.
448
- - **Typed `where` filter shorthand** — `query_database` accepts a `where` clause like `{Status: {equals: "Done"}, AND: [...]}` with operator objects (`eq`, `ne`, `gte`, `lte`, `contains`, `starts_with`, etc.); the server compiles it to Notion filter JSON. Pass raw Notion `filter` JSON for edge cases the shorthand can't express (the two fields are mutually exclusive).
449
- - **Database views** — `list_views` / `get_view` / `query_view` plus `create_view` / `update_view` / `delete_view`. `query_view` runs a view's stored filters/sorts and returns hydrated rows by default (`hydrate: false` for ids only); `create_view` / `update_view` reuse the same `where` shorthand for filters and take a raw `configuration` for type-specific layout (calendar/board/timeline/chart/map require it).
450
- - **Universal MCP compatibility** — Cursor, Claude Desktop, Claude Code, Cline, Zed, Continue, anything that speaks MCP stdio.
451
-
452
- ---
453
-
454
- ## 📚 MCP tools for Notion (`notion_execute` & `notion_describe`)
455
-
456
- The v2 server exposes exactly **two** MCP tools — your AI client only ever loads these two schemas, regardless of which of the 35 Notion operations you call.
363
+ - **Idempotency keys** — same `(operation, idempotency_key)` returns the cached result for 5 minutes. Safe to retry on flaky networks.
364
+ - **Rate-limit + retry baked in** — token-bucket limiter (3 req/s default, `NOTION_RATE_LIMIT` to change) with exponential backoff on 429/5xx/timeouts, honoring `Retry-After`.
365
+ - **Self-healing validation errors** — failures return `{ schema, example, fix }` so the model corrects bad payloads in one round-trip.
366
+ - **Markdown everywhere** — `create_page` / `append_blocks` / `update_block` / comment bodies accept a `markdown` string (full GFM: headings 1–4, lists, nested to-dos, blockquotes, fenced code with language detection, images, dividers, inline formatting), plus full round-trip via `get_page_markdown` / `update_page_markdown`.
367
+ - **Notion templates** — `create_page` can apply a data source's template (`template: { type: "template_id" | "default" }`), with `list_data_source_templates` to discover template IDs.
368
+ - **Database views** — list/get/query/create/update/delete views; `query_view` runs a view's stored filters/sorts and returns hydrated rows.
369
+ - **Typed `where` filter shorthand** — `query_database` takes `{Status: {equals: "Done"}, AND: [...]}` and compiles it to Notion filter JSON (raw `filter` still accepted for edge cases).
370
+ - **Slim responses + flattened rows** — noisy fields dropped by default, `query_database` rows flattened to name primitive maps, compact JSON wire format (~30% smaller). `verbose: true` opts out per call.
371
+ - **File uploads** — single-part and multi-part (5 MB chunks) transparently; MIME inferred from filename.
372
+ - **Opt-in auto-pagination** — `paginate: true` on `search_pages` / `list_comments` / `query_database` walks `next_cursor` for you (default cap ≈ 1000 items).
373
+ - **HTTP(S) proxy support** — standard `HTTPS_PROXY` / `HTTP_PROXY` env vars for corporate networks.
374
+ - **Access control** — `NOTION_READ_ONLY` one-switch read-only mode plus per-operation allow/block lists.
375
+
376
+ ## 📚 MCP tools (`notion_execute` & `notion_describe`)
377
+
378
+ The server exposes exactly **two** MCP tools — your client loads two schemas regardless of which of the 43 operations gets called.
457
379
 
458
380
  ### `notion_execute`
459
381
 
460
- Run any Notion operation. Pass `{ operation, payload }` payload is either a single object, or `{ items: [...] }` for batch mode.
461
-
462
- **Single call:**
382
+ Run any operation: `{ operation, payload }`, where payload is a single object or `{ items: [...] }` for batch mode.
463
383
 
464
384
  ```jsonc
385
+ // single call
465
386
  {
466
387
  "operation": "set_page_title",
467
388
  "payload": { "page_id": "<page-id>", "title": "Q3 plan" }
468
389
  }
469
390
  ```
470
391
 
471
- **Batch:**
472
-
473
392
  ```jsonc
393
+ // batch
474
394
  {
475
395
  "operation": "set_page_title",
476
396
  "payload": {
@@ -480,14 +400,13 @@ Run any Notion operation. Pass `{ operation, payload }` — payload is either a
480
400
  ],
481
401
  "atomic": false,
482
402
  "concurrency": 3,
483
- "idempotency_key": "rename-pass-2025-05-26"
403
+ "idempotency_key": "rename-pass-2026-07-02"
484
404
  }
485
405
  }
486
406
  ```
487
407
 
488
- **Markdown shortcut** (works in `create_page`, `append_blocks`, `update_block`, `update_page_markdown`):
489
-
490
408
  ```jsonc
409
+ // markdown shortcut (create_page, append_blocks, update_block, update_page_markdown)
491
410
  {
492
411
  "operation": "create_page",
493
412
  "payload": {
@@ -498,125 +417,116 @@ Run any Notion operation. Pass `{ operation, payload }` — payload is either a
498
417
  }
499
418
  ```
500
419
 
501
- **Self-healing errors:** if the payload doesn't validate, the response includes the full JSON Schema for that operation plus a working example, so the next call can be corrected without round-tripping through `notion_describe`.
420
+ If a payload doesn't validate, the error response includes the operation's full JSON Schema, a working example, and a `fix` hint the next call can be corrected without a `notion_describe` round-trip.
502
421
 
503
422
  ### `notion_describe`
504
423
 
505
- Return the JSON Schema + working example for a single operation. Use this when you want to see the shape of a complex op (filter expressions, mixed block batches, full database property definitions) before calling `notion_execute`.
424
+ Returns the JSON Schema + working example for one operation useful before complex calls (filter expressions, mixed block batches, database property definitions).
506
425
 
507
426
  ```jsonc
508
427
  { "operation": "query_database" }
509
428
  ```
510
429
 
511
- ### Operations menu (41 ops, plus one alias)
430
+ ### Operations menu (43 ops, plus one alias)
512
431
 
513
432
  | Area | Operations |
514
433
  | --- | --- |
515
434
  | **Pages** | `create_page`, `get_page`, `set_page_title`, `set_page_property`, `set_page_properties`, `archive_page` (alias: `trash_page`), `restore_page`, `search_pages`, `move_page`, `get_page_markdown`, `update_page_markdown` |
516
435
  | **Blocks** | `append_blocks`, `get_block`, `get_block_children`, `update_block`, `delete_block`, `batch_mixed_blocks` |
517
436
  | **Databases** | `create_database`, `query_database`, `update_database` |
518
- | **Data sources** | `list_data_sources`, `get_data_source`, `update_data_source` |
437
+ | **Data sources** | `list_data_sources`, `get_data_source`, `update_data_source`, `list_data_source_templates` |
519
438
  | **Views** | `list_views`, `get_view`, `query_view`, `create_view`, `update_view`, `delete_view` |
520
439
  | **Comments** | `list_comments`, `add_page_comment`, `add_discussion_comment`, `get_comment`, `update_comment`, `delete_comment` |
521
- | **Users** | `list_users`, `get_user`, `get_bot_user` |
440
+ | **Users** | `list_users`, `get_user`, `get_bot_user`, `get_self` |
522
441
  | **Files** | `upload_file`, `list_file_uploads`, `get_file_upload` |
523
442
 
524
- The authoritative list (with batchability) is also served as an MCP resource at `notion://operations` — useful as a one-shot cheat sheet for the LLM.
443
+ The authoritative list (with batchability) is served as an MCP resource at `notion://operations`.
525
444
 
526
445
  ### MCP resources
527
446
 
528
- The server exposes three resources, so clients that support resource attachment (`@`-mention) can pull Notion content into context without a tool call:
447
+ Clients that support resource attachment (`@`-mention) can pull Notion content into context without a tool call:
529
448
 
530
449
  | Resource URI | Returns |
531
450
  | --- | --- |
532
- | `notion://operations` | Markdown cheat sheet of every enabled operation. |
533
- | `notion://page/<page_id>` | The page body as markdown. |
534
- | `notion://database/<data_source_id>` | The data source's schema as JSON. |
535
-
536
- The dynamic page/database resources route through the same auth, rate limiting, and access gating as tool calls — a page disabled by your allow/block config (or `NOTION_READ_ONLY`) returns an error envelope, not content.
537
-
538
- ---
539
-
540
- ## 🛠 Development
541
-
542
- ```bash
543
- git clone https://github.com/awkoy/notion-mcp-server.git
544
- cd notion-mcp-server
545
- npm install
546
-
547
- # Set NOTION_TOKEN (and optionally NOTION_PAGE_ID) in a .env file.
548
- echo "NOTION_TOKEN=ntn_xxx" > .env
549
-
550
- npm run build # tsc -> build/
551
- npm test # vitest smoke suite
552
- npm run inspector # MCP inspector against the built binary
553
- ```
451
+ | `notion://operations` | Markdown cheat sheet of every enabled operation |
452
+ | `notion://page/<page_id>` | Page body as markdown |
453
+ | `notion://database/<data_source_id>` | Data source schema as JSON |
554
454
 
555
- ---
455
+ Dynamic resources route through the same auth, rate limiting, and access gating as tool calls.
556
456
 
557
- ## 🔧 Technical details: how the Notion MCP server is built
457
+ ## Troubleshooting the Notion MCP server
558
458
 
559
- - TypeScript + MCP SDK (`^1.29.0`)
560
- - Notion SDK `@notionhq/client@^5.22.0`, pinned `Notion-Version: 2025-09-03`
561
- - Zod 4 payload validation; emits draft-7 JSON Schema with `$defs` deduplication for error envelopes
562
- - Markdown Notion blocks via the `remark` / `remark-gfm` pipeline
563
- - Bounded-concurrency batch worker (default 3, max 10)
564
- - Shared token-bucket rate limiter; `withRetry` wraps every dispatched call with exponential backoff on transient failures
565
- - In-memory idempotency cache (5-minute TTL, 512 entries)
566
- - Slim shapers per entity type (`slimPage`, `slimBlock`, `slimDatabase`, `slimDataSource`, `slimUser`, `slimComment`, `slimFileUpload`) with `verbose: true` opt-out
567
- - Vitest smoke harness covering the markdown parser, slim shapers, schema emitter, dispatcher, batch partial success / atomic rollback / idempotency dedupe (`npm test`)
459
+ - **`object_not_found` / "Could not find …"** — an Internal Integration token only sees pages explicitly Connected to it. Switch to a PAT to skip per-page sharing.
460
+ - **"Notion auth failed" on every call** — token missing, revoked, or expired (PATs expire after 1 year). Check `NOTION_TOKEN` in your client config, then confirm the token is still listed as Active at [app.notion.com/developers/tokens](https://app.notion.com/developers/tokens).
461
+ - **"No parent page configured"** pass `parent` in the call, or set `NOTION_PAGE_ID`.
462
+ - **`multi_source_database` from `query_database`** the database has multiple data sources. Call `list_data_sources`, then pass `data_source_id` instead of `database_id`.
463
+ - **Tools don't appear in Claude Desktop** — token typo (must stay inside the quotes) or the app wasn't fully quit (`Cmd+Q`, not window close) before reopening.
464
+ - **Startup logs "Notion auth check failed" but tools work** the startup check is best-effort; ignore if calls succeed.
465
+ - **Docker exits immediately / "Connection closed"** — the `-i` flag is required: `docker run --rm -i …`.
466
+ - **Docker: "NOTION_TOKEN is not set" despite `-e`** use `-e NOTION_TOKEN` (forwards from parent env) or `-e NOTION_TOKEN=ntn_xxx`, not `-e NOTION_TOKEN ntn_xxx`.
568
467
 
569
- ---
468
+ Still stuck? [GitHub Issues](https://github.com/awkoy/notion-mcp-server/issues) · [Notion API reference](https://developers.notion.com/reference/intro) · [MCP spec](https://modelcontextprotocol.io)
570
469
 
571
- ## Troubleshooting the Notion MCP server
470
+ ## 💬 FAQ: Notion MCP server
572
471
 
573
- - **"object_not_found" / "Could not find ..."** — the integration token can only see pages explicitly shared with it. Switch to a PAT to skip per-page sharing.
574
- - **"Notion auth failed" on every call** — the token was missing, revoked, or expired (PATs expire 1 year after creation). Check `NOTION_TOKEN` is set in your MCP client config, then open [notion.so/profile/integrations](https://www.notion.so/profile/integrations) → **Personal access tokens** and confirm yours is still listed and not past its expiry. If it expired, create a new one and update the env var.
575
- - **"No parent page configured"** — pass `parent` in the call, or set `NOTION_PAGE_ID` to a default.
576
- - **"multi_source_database" error from `query_database`** — your database has more than one data source. Call `list_data_sources` to get the IDs, then pass `data_source_id` instead of `database_id`.
577
- - **Server logs "Notion auth check failed" on startup but tools still work** — the startup check is best-effort. If subsequent tool calls succeed, ignore the warning (Claude Code suppresses MCP stderr anyway).
578
- - **Docker container exits immediately / "Connection closed"** — the `-i` flag is required so Docker keeps stdin open for the MCP stdio transport. `docker run --rm -i ...`, not `docker run --rm ...`.
579
- - **Docker: "NOTION_TOKEN is not set" despite passing `-e`** — make sure the form is `-e NOTION_TOKEN` (forwards from parent env) or `-e NOTION_TOKEN=ntn_xxx` (inline value), not `-e NOTION_TOKEN ntn_xxx` (treated as two separate args).
472
+ ### What is the Notion MCP server and how does it work?
580
473
 
581
- ### Getting help
474
+ A Model Context Protocol server that connects AI assistants — Claude, Cursor, VS Code Copilot, Cline, Zed, Continue, anything that speaks MCP — to your Notion workspace. It runs locally (or in Docker, or as an HTTP endpoint) and exposes two MCP tools the AI calls to read and write Notion. You authenticate once with a Notion token; everything else is natural language.
582
475
 
583
- - [GitHub Issues](https://github.com/awkoy/notion-mcp-server/issues)
584
- - [Notion API reference](https://developers.notion.com/reference/intro)
585
- - [Model Context Protocol spec](https://modelcontextprotocol.io)
476
+ ### How do I connect Claude to Notion using MCP?
586
477
 
587
- ---
478
+ See the [Quick start](#-quick-start): get a PAT at [app.notion.com/developers/tokens](https://app.notion.com/developers/tokens), then one `claude mcp add` command (Claude Code) or one JSON paste (Claude Desktop). Non-developers: the [complete walkthrough](#-complete-walkthrough-no-coding-required) assumes nothing.
588
479
 
589
- ## 💬 FAQ: Notion MCP server
480
+ ### What's the difference between this and Notion's official MCP?
590
481
 
591
- ### What is the Notion MCP server and how does it work?
482
+ Notion's **hosted** MCP (`mcp.notion.com`) is OAuth-only and built for interactive chat — it can't run headless. Their **open-source** server is soft-deprecated and exposes one tool per endpoint. This server authenticates with a token (works in CI/automation), exposes 2 tools dispatching 43 operations, batches mutations with idempotency and retries, and slims responses to cut token cost. See [Which Notion MCP should you use?](#-which-notion-mcp-should-you-use).
592
483
 
593
- The Notion MCP server is a Model Context Protocol (MCP) server that connects AI assistants — Claude, Cursor, ChatGPT, Claude Desktop, Cline, Zed, Continue, anything that speaks MCP — to your Notion workspace. It runs locally (or in Docker) and exposes two MCP tools (`notion_execute`, `notion_describe`) that the AI calls to read and write Notion. You authenticate once with a Notion Personal Access Token; everything else is natural language.
484
+ ### Can I use it with Cursor, VS Code, ChatGPT, or Cline?
594
485
 
595
- ### How do I connect Claude to Notion using MCP?
486
+ Cursor, VS Code (Copilot agent mode), Cline, Zed, Continue: yes — install badges and config blocks are in the [Quick start](#-quick-start). ChatGPT's built-in connectors require OAuth-hosted servers, so use Notion's hosted MCP there; developers can still reach this server from the OpenAI API's `mcp` tool by pointing it at a self-hosted [HTTP endpoint](#-remote--http-transport) with a bearer token.
596
487
 
597
- Follow the [5-minute install](#-5-minute-install-no-coding-required) above. The short version: get a Notion Personal Access Token at [notion.so/profile/integrations](https://www.notion.so/profile/integrations) → **Personal access tokens** tab → **+ New personal access token**, then paste it into Claude Desktop's `claude_desktop_config.json` (Settings → Developer → Edit Config). Quit and reopen Claude Desktop and you can ask it to create or read Notion pages directly.
488
+ ### Is it safe to give an AI my Notion token?
598
489
 
599
- ### What is a Notion Personal Access Token and how do I get one?
490
+ The token lives in your MCP client's local config and is only sent to `api.notion.com` over HTTPS. The server is open source — read every line. A PAT has the same access you do, so don't paste it into untrusted clients, and revoke it at [app.notion.com/developers/tokens](https://app.notion.com/developers/tokens) if a laptop is lost. For agents that should never write, set `NOTION_READ_ONLY=true`.
600
491
 
601
- A Personal Access Token (PAT) is a key that lets an app act as **you** inside Notion. It can see every page you can see — no per-page "Connect" step required. Generate one at **[notion.so/profile/integrations](https://www.notion.so/profile/integrations) → Personal access tokens → + New personal access token**. The token starts with `ntn_…` and expires 1 year after creation. Treat it like a password; don't commit it to git or share it publicly. See the [full walkthrough](#get-a-personal-access-token--full-walkthrough) for capabilities, rotation, and admin restrictions, or the [official Notion guide](https://developers.notion.com/guides/get-started/personal-access-tokens).
492
+ ### Does it work with self-hosted or local-only LLMs?
602
493
 
603
- ### What's the difference between this Notion MCP server and the official Notion MCP?
494
+ Yes anything that speaks MCP stdio (or Streamable HTTP) works. The server doesn't care what's on the other side of the protocol.
604
495
 
605
- The official Notion MCP server exposes one MCP tool per REST endpoint (22 tools), uses an Internal Integration token (which requires per-page sharing in Notion's UI), and returns raw Notion JSON. This server exposes two tools that dispatch 36 named operations, defaults to a Personal Access Token (no per-page setup), batches mutations, retries on rate limits, and slims responses to cut token usage. See the [full comparison table](#-why-this-server-vs-the-official-notion-mcp).
496
+ ## 🛠 Development
606
497
 
607
- ### Can I use this Notion MCP server with Cursor, ChatGPT, or Cline?
498
+ ```bash
499
+ git clone https://github.com/awkoy/notion-mcp-server.git
500
+ cd notion-mcp-server
501
+ npm install
502
+ echo "NOTION_TOKEN=ntn_xxx" > .env
608
503
 
609
- Yes. Anything that speaks the MCP stdio protocol works: Claude Desktop, Claude Code, Cursor, Cline, Zed, Continue, and self-hosted clients. Cursor uses `~/.cursor/mcp.json`; the config block is in the [Developer install](#-developer-install) section. ChatGPT support depends on the client you're using — any wrapper that supports MCP servers will work.
504
+ npm run build # tsc -> build/
505
+ npm test # vitest suite
506
+ npm run inspector # MCP inspector against the built binary
507
+ ```
610
508
 
611
- ### Is it safe to give an AI my Notion token?
509
+ Local build instead of npx:
612
510
 
613
- The token is stored locally in your MCP client's config file and only sent to the Notion API (over HTTPS). It never leaves your machine except to talk to `api.notion.com`. The server itself is open source — you can read every line. That said, a PAT has the same access your account does, so don't paste it into untrusted clients, and revoke it at [notion.so/profile/integrations](https://www.notion.so/profile/integrations) → Personal access tokens if a laptop is lost.
511
+ ```bash
512
+ claude mcp add notion -s user \
513
+ -e NOTION_TOKEN=ntn_xxx \
514
+ -- node "$(pwd)/build/index.js"
515
+ ```
614
516
 
615
- ### Does this work with self-hosted or local-only LLMs?
517
+ <details>
518
+ <summary><b>Technical details: how it's built</b></summary>
616
519
 
617
- Yes, as long as the LLM client supports MCP stdio (or you run a wrapper that bridges it). The server doesn't care what's on the other side of the protocol.
520
+ - TypeScript + MCP SDK (`^1.29.0`); stdio + Streamable HTTP transports
521
+ - Notion SDK `@notionhq/client@^5.22.0`, pinned `Notion-Version: 2026-03-11`
522
+ - Zod 4 payload validation; emits draft-7 JSON Schema with `$defs` deduplication for error envelopes
523
+ - Markdown → Notion blocks via `remark` / `remark-gfm`
524
+ - Bounded-concurrency batch worker (default 3, max 10); shared token-bucket rate limiter; `withRetry` with exponential backoff around every dispatched call
525
+ - In-memory idempotency cache (5-minute TTL, 512 entries)
526
+ - Slim shapers per entity type with `verbose: true` opt-out
527
+ - Vitest suite covering the markdown parser, shapers, schema emitter, dispatcher, batch semantics (partial success / atomic rollback / idempotency), access control, and HTTP transport
618
528
 
619
- ---
529
+ </details>
620
530
 
621
531
  ## 🤝 Contributing
622
532
 
@@ -625,3 +535,7 @@ PRs welcome. Fork → branch → commit → push → PR. Run `npm test` before s
625
535
  ## 📄 License
626
536
 
627
537
  MIT — see [LICENSE](./LICENSE).
538
+
539
+ ---
540
+
541
+ mcp-name: io.github.awkoy/notion-mcp-server
@@ -125,9 +125,9 @@ export const RELATION_DB_PROPERTY_SCHEMA = z.object({
125
125
  type: z.literal("relation").describe("Relation property type"),
126
126
  relation: z
127
127
  .object({
128
- database_id: z
128
+ data_source_id: z
129
129
  .string()
130
- .describe("The ID of the database this relation refers to"),
130
+ .describe("The ID of the data source this relation refers to"),
131
131
  synced_property_name: z
132
132
  .string()
133
133
  .optional()
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "notion-mcp-server",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
+ "mcpName": "io.github.awkoy/notion-mcp-server",
4
5
  "type": "module",
5
6
  "bin": {
6
7
  "notion-mcp-server": "build/index.js"
@@ -52,7 +53,7 @@
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/mdast": "^4.0.4",
55
- "@types/node": "^25.9.3",
56
+ "@types/node": "^26.0.0",
56
57
  "shx": "^0.4.0",
57
58
  "typescript": "^6.0.3",
58
59
  "vitest": "^4.1.7"