notion-mcp-server 2.9.0 → 2.10.1
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 +300 -386
- package/build/operations/data-sources.js +28 -0
- package/build/operations/pages.js +21 -3
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,84 +1,69 @@
|
|
|
1
|
-
# Notion MCP Server — Connect Claude, Cursor &
|
|
1
|
+
# Notion MCP Server — Connect Claude, Cursor & VS Code to Notion
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/notion-mcp-server)
|
|
4
|
+

|
|
3
5
|

|
|
4
|
-

|
|
6
|
+

|
|
6
7
|
[](https://smithery.ai/server/@awkoy/notion-mcp-server)
|
|
7
|
-

|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
+
- **~90% less context overhead.** Two MCP tools (`notion_execute` + `notion_describe`) dispatch **43 operations**, instead of one tool schema per endpoint flooding your agent's context.
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
28
|
+
**Step 2 — add the server to your client.**
|
|
47
29
|
|
|
48
|
-
###
|
|
49
|
-
After setup, you can tell Claude things like:
|
|
30
|
+
### Claude Code
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
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
|
-
|
|
38
|
+
### Cursor
|
|
56
39
|
|
|
57
|
-
|
|
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
|
+
[](https://cursor.com/install-mcp?name=notion&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm5vdGlvbi1tY3Atc2VydmVyIl0sImVudiI6eyJOT1RJT05fVE9LRU4iOiJZT1VSX05PVElPTl9UT0tFTiJ9fQ==)
|
|
61
41
|
|
|
62
|
-
|
|
42
|
+
Click the badge (then replace `YOUR_NOTION_TOKEN` in the generated entry), or add to `~/.cursor/mcp.json` yourself:
|
|
63
43
|
|
|
64
|
-
|
|
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
|
+
[](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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
82
|
+
### Docker / Podman / OrbStack
|
|
108
83
|
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
+
**Step 3 — try it.** In a new chat:
|
|
114
93
|
|
|
115
|
-
|
|
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
|
-
##
|
|
98
|
+
## 💡 What your AI can do with it
|
|
120
99
|
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
<details>
|
|
122
|
+
<summary><b>Detailed comparison vs. the official open-source server</b></summary>
|
|
155
123
|
|
|
156
|
-
| |
|
|
124
|
+
| Capability | Official Notion MCP (open source) | **This server** |
|
|
157
125
|
| --- | --- | --- |
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
164
|
-
|
|
126
|
+
| **Tool surface** | ~24 tools (one per endpoint) loaded into context | **2 tools** — the LLM loads ~90% less schema |
|
|
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
|
-
|
|
141
|
+
**Real-world impact:**
|
|
167
142
|
|
|
168
|
-
|
|
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
|
-
|
|
147
|
+
</details>
|
|
171
148
|
|
|
172
|
-
|
|
149
|
+
## 🪄 Complete walkthrough (no coding required)
|
|
173
150
|
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
156
|
+
1. A Notion account.
|
|
157
|
+
2. The [Claude Desktop app](https://claude.ai/download).
|
|
158
|
+
3. About 5 minutes.
|
|
184
159
|
|
|
185
|
-
|
|
160
|
+
### Step 1 — Get your Notion token
|
|
186
161
|
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
170
|
+
4. **Copy the token now** — Notion shows it only once. It starts with `ntn_`. Treat it like a password.
|
|
196
171
|
|
|
197
|
-
|
|
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
|
-
|
|
174
|
+
### Step 2 — Tell Claude Desktop about the server
|
|
200
175
|
|
|
201
|
-
1. **
|
|
202
|
-
2.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
200
|
+
### Step 3 — Check and try
|
|
219
201
|
|
|
220
|
-
|
|
202
|
+
Type **`/`** in a new chat — you should see `notion_execute` and `notion_describe` in the list. Then ask:
|
|
221
203
|
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
+
## 🔧 Configuration
|
|
234
211
|
|
|
235
|
-
|
|
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
|
-
|
|
214
|
+
Both use the same `NOTION_TOKEN` env var — only where you get the token differs.
|
|
243
215
|
|
|
244
|
-
|
|
|
216
|
+
| | **Personal Access Token** (recommended) | **Internal Integration** (scoped) |
|
|
245
217
|
| --- | --- | --- |
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
292
|
-
(visible in your MCP client's server logs) summarizing what resolved, e.g.:
|
|
239
|
+
</details>
|
|
293
240
|
|
|
294
|
-
|
|
295
|
-
Operation access: 16/37 enabled (allow=read; block=(none))
|
|
296
|
-
```
|
|
241
|
+
### Environment variables
|
|
297
242
|
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
254
|
+
HTTP-transport variables (`MCP_TRANSPORT`, `PORT`, `HOST`, `MCP_AUTH_TOKEN`, …) are covered in [Remote / HTTP transport](#-remote--http-transport).
|
|
302
255
|
|
|
303
|
-
|
|
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
|
-
###
|
|
258
|
+
### Restricting operations
|
|
315
259
|
|
|
316
|
-
**
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
Everything except destructive ops:
|
|
341
276
|
|
|
342
|
-
```
|
|
343
|
-
|
|
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
|
-
|
|
281
|
+
Mix presets and individual ops:
|
|
353
282
|
|
|
354
|
-
```
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
295
|
+
<details>
|
|
296
|
+
<summary><b>Per-operation reference & limitations</b></summary>
|
|
377
297
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
309
|
+
† = also in the `destructive` group.
|
|
383
310
|
|
|
384
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
> ⚠️ **
|
|
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
|
-
|
|
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
|
|
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? }
|
|
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
|
|
442
|
-
- **Rate-limit + retry baked in** —
|
|
443
|
-
- **Self-healing validation errors** —
|
|
444
|
-
- **Markdown
|
|
445
|
-
- **
|
|
446
|
-
- **
|
|
447
|
-
- **
|
|
448
|
-
- **
|
|
449
|
-
- **
|
|
450
|
-
- **
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
## 📚 MCP tools
|
|
455
|
-
|
|
456
|
-
The
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
443
|
+
The authoritative list (with batchability) is served as an MCP resource at `notion://operations`.
|
|
525
444
|
|
|
526
445
|
### MCP resources
|
|
527
446
|
|
|
528
|
-
|
|
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>` |
|
|
534
|
-
| `notion://database/<data_source_id>` |
|
|
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
|
-
##
|
|
457
|
+
## ❓ Troubleshooting the Notion MCP server
|
|
558
458
|
|
|
559
|
-
-
|
|
560
|
-
- Notion
|
|
561
|
-
-
|
|
562
|
-
-
|
|
563
|
-
-
|
|
564
|
-
-
|
|
565
|
-
-
|
|
566
|
-
-
|
|
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
|
-
##
|
|
470
|
+
## 💬 FAQ: Notion MCP server
|
|
572
471
|
|
|
573
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
480
|
+
### What's the difference between this and Notion's official MCP?
|
|
590
481
|
|
|
591
|
-
|
|
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
|
-
|
|
484
|
+
### Can I use it with Cursor, VS Code, ChatGPT, or Cline?
|
|
594
485
|
|
|
595
|
-
|
|
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
|
-
|
|
488
|
+
### Is it safe to give an AI my Notion token?
|
|
598
489
|
|
|
599
|
-
|
|
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
|
-
|
|
492
|
+
### Does it work with self-hosted or local-only LLMs?
|
|
602
493
|
|
|
603
|
-
|
|
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
|
-
|
|
496
|
+
## 🛠 Development
|
|
606
497
|
|
|
607
|
-
|
|
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
|
-
|
|
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
|
-
|
|
509
|
+
Local build instead of npx:
|
|
612
510
|
|
|
613
|
-
|
|
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
|
-
|
|
517
|
+
<details>
|
|
518
|
+
<summary><b>Technical details: how it's built</b></summary>
|
|
616
519
|
|
|
617
|
-
|
|
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
|
|
@@ -53,6 +53,34 @@ register({
|
|
|
53
53
|
return { ok: true, data: slimDataSource(ds, verbose ?? false) };
|
|
54
54
|
}),
|
|
55
55
|
});
|
|
56
|
+
const ListDataSourceTemplatesParams = z.object({
|
|
57
|
+
data_source_id: z.string().describe("Data source ID to list templates for."),
|
|
58
|
+
name: z.string().optional().describe("Case-insensitive substring filter on template name."),
|
|
59
|
+
start_cursor: z.string().optional(),
|
|
60
|
+
page_size: z.number().int().min(1).max(100).optional(),
|
|
61
|
+
});
|
|
62
|
+
register({
|
|
63
|
+
name: "list_data_source_templates",
|
|
64
|
+
access: "read",
|
|
65
|
+
domain: "data_sources",
|
|
66
|
+
description: "List the page templates available for a data source. Returns {id, name, is_default} per template. Pass a returned id as template.template_id to create_page to apply it.",
|
|
67
|
+
batchable: false,
|
|
68
|
+
schema: ListDataSourceTemplatesParams,
|
|
69
|
+
example: { data_source_id: "<data-source-id>" },
|
|
70
|
+
handler: tryHandler(async ({ data_source_id, name, start_cursor, page_size }) => {
|
|
71
|
+
const notion = await getClient();
|
|
72
|
+
const res = await notion.dataSources.listTemplates({
|
|
73
|
+
data_source_id,
|
|
74
|
+
...(name !== undefined ? { name } : {}),
|
|
75
|
+
...(start_cursor !== undefined ? { start_cursor } : {}),
|
|
76
|
+
...(page_size !== undefined ? { page_size } : {}),
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
data: { data_source_id, templates: res.templates },
|
|
81
|
+
};
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
56
84
|
const UpdateDataSourceParams = z.object({
|
|
57
85
|
data_source_id: z.string(),
|
|
58
86
|
title: z.array(z.unknown()).optional().describe("Rich text array for the data source title."),
|
|
@@ -47,12 +47,26 @@ const CreatePageParams = z
|
|
|
47
47
|
properties: z.record(z.string(), PROPERTY_VALUE_SCHEMA).optional(),
|
|
48
48
|
markdown: z.string().optional().describe("Page body as markdown. Parsed server-side."),
|
|
49
49
|
children: z.array(z.unknown()).optional().describe("Structured Notion blocks. Mutually exclusive with markdown."),
|
|
50
|
+
template: z
|
|
51
|
+
.object({
|
|
52
|
+
type: z.enum(["default", "none", "template_id"]).describe("`template_id` to apply a specific template, `default` for the data source's default template, `none` for no template."),
|
|
53
|
+
template_id: z.string().optional().describe("Required when type is 'template_id'. Discover IDs via list_data_source_templates."),
|
|
54
|
+
timezone: z.string().optional().describe("IANA tz (e.g. 'Asia/Hong_Kong') controlling @now/@today resolution inside the template."),
|
|
55
|
+
})
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Create the page from a Notion template. The API forbids `markdown`/`children` alongside a template; only data_source_id parents support templates."),
|
|
50
58
|
icon: ICON_SCHEMA.nullable().optional(),
|
|
51
59
|
cover: FILE_SCHEMA.nullable().optional(),
|
|
52
60
|
verbose: VERBOSE,
|
|
53
61
|
})
|
|
54
62
|
.refine((v) => !(v.markdown && v.children), {
|
|
55
63
|
message: "Pass either `markdown` or `children`, not both.",
|
|
64
|
+
})
|
|
65
|
+
.refine((v) => !(v.template && (v.markdown || v.children)), {
|
|
66
|
+
message: "Pass either a `template` or body content (`markdown`/`children`), not both — the Notion API rejects children alongside a template.",
|
|
67
|
+
})
|
|
68
|
+
.refine((v) => !(v.template?.type === "template_id" && !v.template.template_id), {
|
|
69
|
+
message: "template.template_id is required when template.type is 'template_id'.",
|
|
56
70
|
});
|
|
57
71
|
register({
|
|
58
72
|
name: "create_page",
|
|
@@ -100,14 +114,18 @@ register({
|
|
|
100
114
|
title: [{ type: "text", text: { content: params.title } }],
|
|
101
115
|
};
|
|
102
116
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
// A template and explicit body content are mutually exclusive (API rule).
|
|
118
|
+
const children = params.template
|
|
119
|
+
? undefined
|
|
120
|
+
: params.markdown
|
|
121
|
+
? parseMarkdownToBlocks(params.markdown)
|
|
122
|
+
: params.children;
|
|
106
123
|
const notion = await getClient();
|
|
107
124
|
const body = {
|
|
108
125
|
parent,
|
|
109
126
|
properties,
|
|
110
127
|
...(children && children.length ? { children } : {}),
|
|
128
|
+
...(params.template ? { template: params.template } : {}),
|
|
111
129
|
...(params.icon !== undefined ? { icon: params.icon } : {}),
|
|
112
130
|
...(params.cover !== undefined ? { cover: params.cover } : {}),
|
|
113
131
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "notion-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
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": "^
|
|
56
|
+
"@types/node": "^26.0.0",
|
|
56
57
|
"shx": "^0.4.0",
|
|
57
58
|
"typescript": "^6.0.3",
|
|
58
59
|
"vitest": "^4.1.7"
|