notion-mcp-server 1.0.1 → 2.4.2
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 +383 -192
- package/build/config/index.js +3 -1
- package/build/dispatch/concurrency.js +15 -0
- package/build/dispatch/idempotency.js +38 -0
- package/build/dispatch/index.js +175 -0
- package/build/dispatch/rate-limit.js +56 -0
- package/build/dispatch/retry.js +97 -0
- package/build/index.js +1 -1
- package/build/markdown/parse.js +265 -0
- package/build/operations/blocks.js +331 -0
- package/build/operations/comments.js +191 -0
- package/build/operations/data-sources.js +85 -0
- package/build/operations/databases.js +345 -0
- package/build/operations/files.js +239 -0
- package/build/operations/index.js +19 -0
- package/build/operations/pages.js +486 -0
- package/build/operations/registry.js +16 -0
- package/build/operations/users.js +101 -0
- package/build/prompts/index.js +105 -0
- package/build/schema/blocks.js +19 -138
- package/build/schema/database.js +27 -111
- package/build/schema/emit.js +68 -0
- package/build/schema/file.js +1 -1
- package/build/schema/filter-dsl.js +333 -0
- package/build/schema/icon.js +1 -1
- package/build/schema/page-properties.js +17 -3
- package/build/schema/page.js +12 -125
- package/build/schema/refs.js +16 -0
- package/build/schema/rich-text.js +1 -1
- package/build/server/index.js +16 -3
- package/build/services/auth.js +19 -0
- package/build/services/notion.js +14 -17
- package/build/tools/index.js +119 -21
- package/build/utils/error.js +125 -86
- package/build/utils/handler.js +11 -0
- package/build/utils/learning-error.js +40 -0
- package/build/utils/notion-types.js +16 -0
- package/build/utils/paginate.js +35 -0
- package/build/utils/schema-slice.js +156 -0
- package/build/utils/slim.js +269 -0
- package/package.json +13 -7
- package/build/resources/imageList.js +0 -62
- package/build/resources/index.js +0 -1
- package/build/resources/predictionList.js +0 -43
- package/build/resources/svgList.js +0 -69
- package/build/schema/comments.js +0 -60
- package/build/schema/notion.js +0 -57
- package/build/schema/richText.js +0 -757
- package/build/schema/tools.js +0 -17
- package/build/schema/users.js +0 -39
- package/build/services/loggs.js +0 -13
- package/build/services/replicate.js +0 -23
- package/build/tools/appendBlockChildren.js +0 -25
- package/build/tools/batchAppendBlockChildren.js +0 -33
- package/build/tools/batchDeleteBlocks.js +0 -32
- package/build/tools/batchMixedOperations.js +0 -58
- package/build/tools/batchUpdateBlocks.js +0 -33
- package/build/tools/blocks.js +0 -34
- package/build/tools/comments.js +0 -81
- package/build/tools/createDatabase.js +0 -18
- package/build/tools/createPage.js +0 -18
- package/build/tools/createPrediction.js +0 -28
- package/build/tools/database.js +0 -16
- package/build/tools/deleteBlock.js +0 -24
- package/build/tools/formatRichText.js +0 -83
- package/build/tools/generateImage.js +0 -48
- package/build/tools/generateImageVariants.js +0 -105
- package/build/tools/generateMultipleImages.js +0 -60
- package/build/tools/generateSVG.js +0 -43
- package/build/tools/getPrediction.js +0 -22
- package/build/tools/pages.js +0 -22
- package/build/tools/predictionList.js +0 -30
- package/build/tools/queryDatabase.js +0 -22
- package/build/tools/retrieveBlock.js +0 -24
- package/build/tools/retrieveBlockChildren.js +0 -32
- package/build/tools/searchPage.js +0 -24
- package/build/tools/updateBlock.js +0 -25
- package/build/tools/updateDatabase.js +0 -18
- package/build/tools/updatePage.js +0 -40
- package/build/tools/updatePageProperties.js +0 -21
- package/build/tools/users.js +0 -75
- package/build/types/blocks.js +0 -12
- package/build/types/comments.js +0 -7
- package/build/types/database.js +0 -6
- package/build/types/notion.js +0 -1
- package/build/types/page.js +0 -8
- package/build/types/richText.js +0 -1
- package/build/types/tools.js +0 -1
- package/build/types/users.js +0 -6
- package/build/utils/blob.js +0 -5
- package/build/utils/image.js +0 -34
- package/build/utils/index.js +0 -1
- package/build/utils/richText.js +0 -174
- package/build/validation/blocks.js +0 -568
- package/build/validation/notion.js +0 -51
- package/build/validation/page.js +0 -262
- package/build/validation/richText.js +0 -744
- package/build/validation/tools.js +0 -16
- /package/build/{types/index.js → operations/types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,289 +1,480 @@
|
|
|
1
|
-
# Notion MCP Server
|
|
1
|
+
# Notion MCP Server — Connect Claude, Cursor & ChatGPT to Notion via Model Context Protocol
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|

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

|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
**Notion MCP
|
|
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.
|
|
11
11
|
|
|
12
|
-
>
|
|
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.
|
|
13
13
|
|
|
14
14
|
<a href="https://glama.ai/mcp/servers/zrh07hteaa">
|
|
15
|
-
<img width="380" height="200" src="https://glama.ai/mcp/servers/zrh07hteaa/badge" />
|
|
15
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/zrh07hteaa/badge" alt="Notion MCP Server on Glama" />
|
|
16
16
|
</a>
|
|
17
17
|
|
|
18
18
|
## 📑 Table of Contents
|
|
19
19
|
|
|
20
|
-
- [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
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
|
+
- [Features: what this Notion MCP server does](#-features-what-this-notion-mcp-server-does)
|
|
30
|
+
- [MCP tools for Notion (`notion_execute` & `notion_describe`)](#-mcp-tools-for-notion-notion_execute--notion_describe)
|
|
31
|
+
- [`notion_execute`](#notion_execute)
|
|
32
|
+
- [`notion_describe`](#notion_describe)
|
|
33
|
+
- [Operations menu (35 ops, plus one alias)](#operations-menu-35-ops-plus-one-alias)
|
|
28
34
|
- [Development](#-development)
|
|
29
|
-
- [Technical
|
|
30
|
-
- [Troubleshooting](#-troubleshooting)
|
|
35
|
+
- [Technical details: how the Notion MCP server is built](#-technical-details-how-the-notion-mcp-server-is-built)
|
|
36
|
+
- [Troubleshooting the Notion MCP server](#-troubleshooting-the-notion-mcp-server)
|
|
37
|
+
- [FAQ: Notion MCP server](#-faq-notion-mcp-server)
|
|
31
38
|
- [Contributing](#-contributing)
|
|
32
39
|
- [License](#-license)
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
---
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
## 🪄 5-minute install (no coding required)
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
- Create an integration at [Notion Developers](https://www.notion.so/my-integrations)
|
|
40
|
-
- Copy your API key
|
|
41
|
-
|
|
42
|
-
2. **Enable Integration for Your Pages**
|
|
43
|
-
- Select an existing page or create a new one in Notion
|
|
44
|
-
- Click the "..." menu in the top right corner
|
|
45
|
-
- Go to "Connections"
|
|
46
|
-
- Find and enable your integration from the list
|
|
47
|
-
|
|
48
|
-

|
|
45
|
+
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.
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
### What you'll get
|
|
48
|
+
After setup, you can tell Claude things like:
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- "Add bullet points to my meeting notes page"
|
|
57
|
-
- "Create a new database for tracking projects"
|
|
58
|
-
- "Add new entries to my task database"
|
|
59
|
-
- "Add a comment to my project page"
|
|
60
|
-
- "Show me all comments on this document"
|
|
61
|
-
- "List all users in my workspace"
|
|
62
|
-
- "Get information about a specific user"
|
|
50
|
+
- *"Make a page in my Personal workspace called 'Q3 plan' and add a checklist of these five items."*
|
|
51
|
+
- *"Find every page in my Tasks database where Status is 'Doing' and tell me which are overdue."*
|
|
52
|
+
- *"Comment on yesterday's meeting notes with a one-paragraph summary."*
|
|
63
53
|
|
|
64
|
-
|
|
54
|
+
Claude reads and writes Notion directly — no copy/paste, no browser tabs.
|
|
65
55
|
|
|
66
|
-
|
|
56
|
+
### What you'll need
|
|
57
|
+
1. A Notion account.
|
|
58
|
+
2. The [Claude Desktop app](https://claude.ai/download) installed. (Cursor and Claude Code work too — see [Developer install](#-developer-install).)
|
|
59
|
+
3. About 5 minutes.
|
|
67
60
|
|
|
68
|
-
|
|
61
|
+
### Step 1 — Get your Notion Personal Access Token
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
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**.
|
|
66
|
+
2. Open the **Personal access tokens** tab → click **+ New personal access token**.
|
|
67
|
+
3. Give it a name like `Claude`, pick the **workspace** the token should act in, leave the default capabilities checked, and click **Create token**.
|
|
68
|
+
4. **Copy** the token — Notion shows the full value **only once**. It starts with `ntn_`. Treat it like a password.
|
|
69
|
+
|
|
70
|
+
> PATs **expire 1 year after creation**. Set a calendar reminder to rotate before then, or auth will start failing.
|
|
71
|
+
>
|
|
72
|
+
> 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).
|
|
73
|
+
>
|
|
74
|
+
> 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).
|
|
75
|
+
|
|
76
|
+
### Step 2 — Tell Claude Desktop where the server lives
|
|
77
|
+
|
|
78
|
+
1. Open Claude Desktop → click **Claude** (top-left menu on Mac, or the hamburger menu on Windows) → **Settings** → **Developer** → **Edit Config**.
|
|
79
|
+
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.
|
|
80
|
+
3. **Select all** the text in that file (`Cmd+A` on Mac, `Ctrl+A` on Windows), **delete it**, then paste the block below.
|
|
69
81
|
|
|
70
82
|
```json
|
|
71
83
|
{
|
|
72
84
|
"mcpServers": {
|
|
73
|
-
"notion
|
|
74
|
-
"command": "
|
|
75
|
-
"args": ["-y", "notion-mcp-server"]
|
|
85
|
+
"notion": {
|
|
86
|
+
"command": "npx",
|
|
87
|
+
"args": ["-y", "github:awkoy/notion-mcp-server"],
|
|
88
|
+
"env": {
|
|
89
|
+
"NOTION_TOKEN": "ntn_paste_your_token_here"
|
|
90
|
+
}
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
```
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
3. Restart Cursor to apply the changes
|
|
96
|
+
> **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 30–60 seconds while it builds). `env` is where your Notion token goes. Leave every quote mark and bracket exactly as shown; the only thing you change is the token.
|
|
83
97
|
|
|
84
|
-
|
|
98
|
+
4. Replace `ntn_paste_your_token_here` with the token you copied in Step 1 — **leave the quotation marks around it**.
|
|
99
|
+
5. **Save** the file (`Cmd+S` / `Ctrl+S`).
|
|
100
|
+
6. **Quit Claude Desktop completely** (Mac: `Cmd+Q`, not just closing the window — Windows: right-click the tray icon → Quit) and reopen it.
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
2. Navigate to the "MCP" or "Model Context Protocol" section
|
|
88
|
-
3. Click "Add Server" or equivalent
|
|
89
|
-
4. Enter the following command in the appropriate field:
|
|
102
|
+
### Step 2b — Did it work?
|
|
90
103
|
|
|
104
|
+
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).
|
|
105
|
+
|
|
106
|
+
### Step 3 — Try it
|
|
107
|
+
|
|
108
|
+
In a new Claude chat, type:
|
|
109
|
+
|
|
110
|
+
> *"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."*
|
|
111
|
+
|
|
112
|
+
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.
|
|
113
|
+
|
|
114
|
+
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.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## ⚡ Why this server? (vs. the official Notion MCP)
|
|
119
|
+
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
This server is designed from the agent's side of the protocol.
|
|
123
|
+
|
|
124
|
+
| Capability | Official Notion MCP | **This server (designed for agents)** |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| **Tool surface** | 22 tools (one per endpoint) loaded into context | **2 tools** (`notion_execute`, `notion_describe`) — the LLM loads ~90% less schema |
|
|
127
|
+
| **Operations covered** | ~22 endpoints | **35 operations** (plus a `trash_page` alias of `archive_page`) across pages, blocks, databases, data sources, comments, users, files |
|
|
128
|
+
| **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 |
|
|
129
|
+
| **Batch mutations** | Not documented | ✅ Universal `{ items: [...] }` envelope; runs up to **10 in parallel** |
|
|
130
|
+
| **Atomic batches + rollback** | Not documented | ✅ `atomic: true` aborts on first failure, best-effort archives entities created earlier |
|
|
131
|
+
| **Idempotency** | Not documented | ✅ `idempotency_key` — same key + same op returns the cached result for 5 minutes |
|
|
132
|
+
| **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` |
|
|
133
|
+
| **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 |
|
|
134
|
+
| **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) |
|
|
135
|
+
| **Wire format** | Default SDK serialization | **Compact (un-indented) JSON** — ~30% smaller payloads vs. indented output, identical to parse |
|
|
136
|
+
| **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) |
|
|
137
|
+
| **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` |
|
|
138
|
+
| **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 |
|
|
139
|
+
| **Notion API version** | Not pinned in client config | Pinned to `2025-09-03` (the modern data-sources line) |
|
|
140
|
+
|
|
141
|
+
### Real-world impact
|
|
142
|
+
|
|
143
|
+
- **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.
|
|
144
|
+
- **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.
|
|
145
|
+
- **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.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 🚀 Developer install
|
|
150
|
+
|
|
151
|
+
### Authentication: PAT (recommended) vs. Internal Integration
|
|
152
|
+
|
|
153
|
+
There are two ways to authenticate. Both use the `NOTION_TOKEN` env var — only how you obtain the token differs.
|
|
154
|
+
|
|
155
|
+
| | **Personal Access Token** (recommended) | **Internal Integration** (legacy) |
|
|
156
|
+
| --- | --- | --- |
|
|
157
|
+
| 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** |
|
|
158
|
+
| Token prefix | `ntn_…` | `ntn_…` (new) or `secret_…` (older) |
|
|
159
|
+
| Scope | Everything **you** can see | Only pages where you've clicked **• • • → Connect → \<integration\>** |
|
|
160
|
+
| Setup friction | None — works immediately | Per-page Connect dance for every page or database the agent should touch |
|
|
161
|
+
| 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. |
|
|
162
|
+
|
|
163
|
+
The rest of this README assumes PAT. Swap in an integration secret if you prefer the scoped model — every command below is identical.
|
|
164
|
+
|
|
165
|
+
> 💡 **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.
|
|
166
|
+
|
|
167
|
+
### Get a Personal Access Token — full walkthrough
|
|
168
|
+
|
|
169
|
+
[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.
|
|
170
|
+
|
|
171
|
+
> 📖 Official: [Notion PAT guide](https://developers.notion.com/guides/get-started/personal-access-tokens) · [Authorization overview](https://developers.notion.com/docs/authorization).
|
|
172
|
+
|
|
173
|
+
#### What a PAT can and can't do
|
|
174
|
+
|
|
175
|
+
| Can | Can't |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| Read every page you have access to | Access workspaces or pages you personally can't see |
|
|
178
|
+
| Create / update pages and databases in workspaces where you have edit rights | Bypass workspace permission rules |
|
|
179
|
+
| Add comments under your identity | Act as another user |
|
|
180
|
+
| Upload files via the File Upload API | Modify workspace-level admin settings |
|
|
181
|
+
|
|
182
|
+
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.
|
|
183
|
+
|
|
184
|
+
#### Expiry and rotation
|
|
185
|
+
|
|
186
|
+
**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.
|
|
187
|
+
|
|
188
|
+
#### Revoking a PAT
|
|
189
|
+
|
|
190
|
+
1. Open **[notion.so/profile/integrations](https://www.notion.so/profile/integrations)** → **Personal access tokens** tab.
|
|
191
|
+
2. Find the token by name → **• • • → Revoke**.
|
|
192
|
+
3. Update `NOTION_TOKEN` in your MCP client config and restart the client.
|
|
193
|
+
|
|
194
|
+
Workspace admins can revoke any user's PATs from **Settings & members → Connections → All personal access tokens**. Revocation is immediate.
|
|
195
|
+
|
|
196
|
+
#### Workspace admin disabled PATs?
|
|
197
|
+
|
|
198
|
+
Some enterprise workspaces only allow scoped Internal Integrations. Two options:
|
|
199
|
+
|
|
200
|
+
1. **Ask your admin to enable PATs** for your account.
|
|
201
|
+
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.
|
|
202
|
+
|
|
203
|
+
### Backward compatibility from v1.x
|
|
204
|
+
|
|
205
|
+
If you ran a v1.x setup, **nothing in your environment needs to change**. Both env vars still work:
|
|
206
|
+
|
|
207
|
+
| Env var | Status in v2.4 | Notes |
|
|
208
|
+
| --- | --- | --- |
|
|
209
|
+
| `NOTION_TOKEN` | ✅ Required | Accepts **PATs** (`ntn_…`, recommended) and **Internal Integration secrets** (`secret_…` or `ntn_…`, legacy). Identical handling. |
|
|
210
|
+
| `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. |
|
|
211
|
+
| `NOTION_RATE_LIMIT` | ✅ New, optional | Requests per second for the shared limiter. Defaults to `3` (Notion's documented per-integration limit). |
|
|
212
|
+
| `NOTION_DAILY_LOG_PAGE_ID` | ✅ Optional | Used only by the daily-log MCP prompt. Ignore if you don't call that prompt. |
|
|
213
|
+
|
|
214
|
+
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.
|
|
215
|
+
|
|
216
|
+
A typical v1.x invocation continues to work unchanged:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
NOTION_TOKEN=secret_xxx NOTION_PAGE_ID=abc123... node build/index.js
|
|
91
220
|
```
|
|
92
|
-
env NOTION_TOKEN=YOUR_KEY NOTION_PAGE_ID=YOUR_PAGE_ID npx -y notion-mcp-server
|
|
93
|
-
```
|
|
94
221
|
|
|
95
|
-
|
|
96
|
-
|
|
222
|
+
### Claude Code / Cursor / Claude Desktop
|
|
223
|
+
|
|
224
|
+
> ⚠️ **Heads-up while the v2 line stabilizes on npm.** The latest published `notion-mcp-server` on npm is **v1.x**; this repo is **v2.4**. Until v2 is published, the install snippets below pull from GitHub via `npx -y github:awkoy/notion-mcp-server` (npm builds from source on first run). Once v2 is on npm, you can swap that for plain `notion-mcp-server@^2`.
|
|
97
225
|
|
|
98
|
-
|
|
226
|
+
**Claude Code:**
|
|
99
227
|
|
|
100
|
-
|
|
228
|
+
```bash
|
|
229
|
+
claude mcp add notion -s user \
|
|
230
|
+
-e NOTION_TOKEN=ntn_paste_your_token_here \
|
|
231
|
+
-- npx -y github:awkoy/notion-mcp-server
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Cursor** (`~/.cursor/mcp.json`) **or Claude Desktop** (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows: `%APPDATA%\Claude\claude_desktop_config.json`):
|
|
101
235
|
|
|
102
236
|
```json
|
|
103
237
|
{
|
|
104
238
|
"mcpServers": {
|
|
105
|
-
"notion
|
|
239
|
+
"notion": {
|
|
106
240
|
"command": "npx",
|
|
107
|
-
"args": ["-y", "notion-mcp-server"],
|
|
241
|
+
"args": ["-y", "github:awkoy/notion-mcp-server"],
|
|
108
242
|
"env": {
|
|
109
|
-
"NOTION_TOKEN": "
|
|
110
|
-
"NOTION_PAGE_ID": "YOUR_PAGE_ID"
|
|
243
|
+
"NOTION_TOKEN": "ntn_paste_your_token_here"
|
|
111
244
|
}
|
|
112
245
|
}
|
|
113
246
|
}
|
|
114
247
|
}
|
|
115
248
|
```
|
|
116
249
|
|
|
117
|
-
|
|
118
|
-
3. Restart Claude Desktop to apply the changes
|
|
250
|
+
**Local build (no npx):**
|
|
119
251
|
|
|
120
|
-
|
|
252
|
+
```bash
|
|
253
|
+
git clone https://github.com/awkoy/notion-mcp-server.git
|
|
254
|
+
cd notion-mcp-server
|
|
255
|
+
npm install && npm run build
|
|
121
256
|
|
|
122
|
-
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
- **📊 Block Management** - Append, update, and delete blocks within Notion pages
|
|
127
|
-
- **💾 Database Operations** - Create, query, and update databases
|
|
128
|
-
- **🔄 Batch Operations** - Perform multiple operations in a single request
|
|
129
|
-
- **🗑️ Archive & Restore** - Archive and restore Notion pages
|
|
130
|
-
- **🔎 Search Functionality** - Search Notion pages and databases by title
|
|
131
|
-
- **💬 Comments Management** - Get, create, and reply to comments on pages and discussions
|
|
132
|
-
- **👥 User Management** - Retrieve workspace users and user information
|
|
257
|
+
claude mcp add notion -s user \
|
|
258
|
+
-e NOTION_TOKEN=ntn_paste_your_token_here \
|
|
259
|
+
-- node "$(pwd)/build/index.js"
|
|
260
|
+
```
|
|
133
261
|
|
|
134
|
-
|
|
262
|
+
### Docker / Podman / OrbStack
|
|
135
263
|
|
|
136
|
-
|
|
264
|
+
```bash
|
|
265
|
+
claude mcp add notion -s user \
|
|
266
|
+
-e NOTION_TOKEN=ntn_paste_your_token_here \
|
|
267
|
+
-- docker run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latest
|
|
268
|
+
```
|
|
137
269
|
|
|
138
|
-
The
|
|
270
|
+
The `-i` flag is required (stdio transport). `-e NOTION_TOKEN` (no `=value`) forwards the env var from the parent process.
|
|
139
271
|
|
|
140
|
-
|
|
272
|
+
For Cursor / Claude Desktop:
|
|
141
273
|
|
|
142
|
-
|
|
143
|
-
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"mcpServers": {
|
|
277
|
+
"notion": {
|
|
278
|
+
"command": "docker",
|
|
279
|
+
"args": ["run", "--rm", "-i", "-e", "NOTION_TOKEN", "ghcr.io/awkoy/notion-mcp-server:latest"],
|
|
280
|
+
"env": { "NOTION_TOKEN": "ntn_paste_your_token_here" }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
144
285
|
|
|
145
|
-
|
|
146
|
-
Update the properties of an existing Notion page
|
|
286
|
+
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.
|
|
147
287
|
|
|
148
|
-
|
|
149
|
-
Archive (move to trash) a Notion page by ID
|
|
288
|
+
### Optional `NOTION_PAGE_ID`
|
|
150
289
|
|
|
151
|
-
|
|
152
|
-
Restore a previously archived Notion page by ID
|
|
290
|
+
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.
|
|
153
291
|
|
|
154
|
-
|
|
155
|
-
Search for pages and databases in Notion by title
|
|
292
|
+
To find a page ID: open the page in Notion → **Share → Copy link**. The ID is the last 32 characters of the URL.
|
|
156
293
|
|
|
157
|
-
|
|
294
|
+
```bash
|
|
295
|
+
claude mcp add notion -s user \
|
|
296
|
+
-e NOTION_TOKEN=ntn_xxx \
|
|
297
|
+
-e NOTION_PAGE_ID=abc123... \
|
|
298
|
+
-- npx -y github:awkoy/notion-mcp-server
|
|
299
|
+
```
|
|
158
300
|
|
|
159
|
-
|
|
160
|
-
Create a new database in Notion with specified properties
|
|
301
|
+
---
|
|
161
302
|
|
|
162
|
-
|
|
163
|
-
Query a database in Notion with filters, sorts, and pagination
|
|
303
|
+
## 🌟 Features: what this Notion MCP server does
|
|
164
304
|
|
|
165
|
-
|
|
166
|
-
|
|
305
|
+
- **Two-tool surface** — `notion_execute` (do it) + `notion_describe` (learn the shape). The whole API is one schema deep.
|
|
306
|
+
- **Universal batch envelope** — every mutating op accepts `{ items: [...], atomic?, idempotency_key?, concurrency? }`. Per-item validation, per-item results, summary counts.
|
|
307
|
+
- **Atomic batches with best-effort rollback** — `atomic: true` aborts on first failure and archives anything created earlier in the batch.
|
|
308
|
+
- **Idempotency keys** — same `(operation, idempotency_key)` returns the cached batch result for 5 minutes (max 512 entries). Safe to retry on flaky networks.
|
|
309
|
+
- **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`.
|
|
310
|
+
- **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.
|
|
311
|
+
- **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).
|
|
312
|
+
- **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).
|
|
313
|
+
- **File uploads** — `upload_file` handles single-part and multi-part (5 MB chunks) transparently; auto-detects MIME from filename; rejects `application/octet-stream`.
|
|
314
|
+
- **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`.
|
|
315
|
+
- **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).
|
|
316
|
+
- **Universal MCP compatibility** — Cursor, Claude Desktop, Claude Code, Cline, Zed, Continue, anything that speaks MCP stdio.
|
|
167
317
|
|
|
168
|
-
|
|
318
|
+
---
|
|
169
319
|
|
|
170
|
-
|
|
171
|
-
Retrieve a block from Notion by ID
|
|
320
|
+
## 📚 MCP tools for Notion (`notion_execute` & `notion_describe`)
|
|
172
321
|
|
|
173
|
-
|
|
174
|
-
Retrieve the children of a block from Notion
|
|
322
|
+
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.
|
|
175
323
|
|
|
176
|
-
|
|
177
|
-
Append child blocks to a parent block in Notion
|
|
324
|
+
### `notion_execute`
|
|
178
325
|
|
|
179
|
-
|
|
180
|
-
Update a block's content in Notion
|
|
326
|
+
Run any Notion operation. Pass `{ operation, payload }` — payload is either a single object, or `{ items: [...] }` for batch mode.
|
|
181
327
|
|
|
182
|
-
|
|
183
|
-
Delete (move to trash) a block in Notion
|
|
328
|
+
**Single call:**
|
|
184
329
|
|
|
185
|
-
|
|
330
|
+
```jsonc
|
|
331
|
+
{
|
|
332
|
+
"operation": "set_page_title",
|
|
333
|
+
"payload": { "page_id": "<page-id>", "title": "Q3 plan" }
|
|
334
|
+
}
|
|
335
|
+
```
|
|
186
336
|
|
|
187
|
-
|
|
188
|
-
Append children to multiple blocks in a single operation
|
|
337
|
+
**Batch:**
|
|
189
338
|
|
|
190
|
-
|
|
191
|
-
|
|
339
|
+
```jsonc
|
|
340
|
+
{
|
|
341
|
+
"operation": "set_page_title",
|
|
342
|
+
"payload": {
|
|
343
|
+
"items": [
|
|
344
|
+
{ "page_id": "<p1>", "title": "First" },
|
|
345
|
+
{ "page_id": "<p2>", "title": "Second" }
|
|
346
|
+
],
|
|
347
|
+
"atomic": false,
|
|
348
|
+
"concurrency": 3,
|
|
349
|
+
"idempotency_key": "rename-pass-2025-05-26"
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
192
353
|
|
|
193
|
-
|
|
194
|
-
Delete multiple blocks in a single operation
|
|
354
|
+
**Markdown shortcut** (works in `create_page`, `append_blocks`, `update_block`, `update_page_markdown`):
|
|
195
355
|
|
|
196
|
-
|
|
197
|
-
|
|
356
|
+
```jsonc
|
|
357
|
+
{
|
|
358
|
+
"operation": "create_page",
|
|
359
|
+
"payload": {
|
|
360
|
+
"parent": { "type": "page_id", "page_id": "<parent>" },
|
|
361
|
+
"title": "Notes",
|
|
362
|
+
"markdown": "# Heading\n\n- [ ] todo\n- [x] done\n\n```ts\nconst x = 1;\n```"
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
198
366
|
|
|
199
|
-
|
|
367
|
+
**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`.
|
|
200
368
|
|
|
201
|
-
|
|
202
|
-
Retrieve comments from a page or block with pagination support
|
|
369
|
+
### `notion_describe`
|
|
203
370
|
|
|
204
|
-
|
|
205
|
-
Add a new comment to a Notion page
|
|
371
|
+
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`.
|
|
206
372
|
|
|
207
|
-
|
|
208
|
-
|
|
373
|
+
```jsonc
|
|
374
|
+
{ "operation": "query_database" }
|
|
375
|
+
```
|
|
209
376
|
|
|
210
|
-
|
|
377
|
+
### Operations menu (35 ops, plus one alias)
|
|
211
378
|
|
|
212
|
-
|
|
213
|
-
|
|
379
|
+
| Area | Operations |
|
|
380
|
+
| --- | --- |
|
|
381
|
+
| **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` |
|
|
382
|
+
| **Blocks** | `append_blocks`, `get_block`, `get_block_children`, `update_block`, `delete_block`, `batch_mixed_blocks` |
|
|
383
|
+
| **Databases** | `create_database`, `query_database`, `update_database` |
|
|
384
|
+
| **Data sources** | `list_data_sources`, `get_data_source`, `update_data_source` |
|
|
385
|
+
| **Comments** | `list_comments`, `add_page_comment`, `add_discussion_comment`, `get_comment`, `update_comment`, `delete_comment` |
|
|
386
|
+
| **Users** | `list_users`, `get_user`, `get_bot_user` |
|
|
387
|
+
| **Files** | `upload_file`, `list_file_uploads`, `get_file_upload` |
|
|
214
388
|
|
|
215
|
-
|
|
216
|
-
Get detailed information about a specific user by ID
|
|
389
|
+
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.
|
|
217
390
|
|
|
218
|
-
|
|
219
|
-
Retrieve the current bot user associated with the API token
|
|
391
|
+
---
|
|
220
392
|
|
|
221
|
-
|
|
393
|
+
## 🛠 Development
|
|
222
394
|
|
|
223
|
-
|
|
395
|
+
```bash
|
|
396
|
+
git clone https://github.com/awkoy/notion-mcp-server.git
|
|
397
|
+
cd notion-mcp-server
|
|
398
|
+
npm install
|
|
224
399
|
|
|
225
|
-
|
|
400
|
+
# Set NOTION_TOKEN (and optionally NOTION_PAGE_ID) in a .env file.
|
|
401
|
+
echo "NOTION_TOKEN=ntn_xxx" > .env
|
|
226
402
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
2. **Install Dependencies**
|
|
234
|
-
```
|
|
235
|
-
npm install
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
3. **Set Up Environment Variables**
|
|
239
|
-
- Create a `.env` file with:
|
|
240
|
-
```
|
|
241
|
-
NOTION_TOKEN=your_notion_api_key
|
|
242
|
-
NOTION_PAGE_ID=your_notion_page_id
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
4. **Build the Project**
|
|
246
|
-
```
|
|
247
|
-
npm run build
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
5. **Run the Inspector**
|
|
251
|
-
```
|
|
252
|
-
npm run inspector
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## 🔧 Technical Details
|
|
256
|
-
|
|
257
|
-
- Built using TypeScript and the MCP SDK (version 1.7.0+)
|
|
258
|
-
- Uses the official Notion API client (@notionhq/client v2.3.0+)
|
|
259
|
-
- Follows the Model Context Protocol specification
|
|
260
|
-
- Implements tools for CRUD operations on Notion pages, blocks, and databases
|
|
261
|
-
- Supports efficient batch operations for performance optimization
|
|
262
|
-
- Validates input/output with Zod schemas
|
|
263
|
-
|
|
264
|
-
## ❓ Troubleshooting
|
|
265
|
-
|
|
266
|
-
- **Common Issues**
|
|
267
|
-
- **Authentication Errors**: Ensure your Notion token has the correct permissions and integration is enabled for your pages/databases
|
|
268
|
-
- **Page Access Issues**: Make sure your integration has been added to the pages you're attempting to access
|
|
269
|
-
- **Rate Limiting**: Notion API has rate limits - use batch operations to optimize requests
|
|
270
|
-
|
|
271
|
-
- **Getting Help**
|
|
272
|
-
- Create an issue on the [GitHub repository](https://github.com/awkoy/notion-mcp-server/issues)
|
|
273
|
-
- Check the [Notion API documentation](https://developers.notion.com/reference/intro)
|
|
274
|
-
- Visit the MCP community channels for assistance
|
|
403
|
+
npm run build # tsc -> build/
|
|
404
|
+
npm test # vitest smoke suite
|
|
405
|
+
npm run inspector # MCP inspector against the built binary
|
|
406
|
+
```
|
|
275
407
|
|
|
276
|
-
|
|
408
|
+
---
|
|
277
409
|
|
|
278
|
-
|
|
410
|
+
## 🔧 Technical details: how the Notion MCP server is built
|
|
279
411
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
412
|
+
- TypeScript + MCP SDK (`^1.29.0`)
|
|
413
|
+
- Notion SDK `@notionhq/client@^5.22.0`, pinned `Notion-Version: 2025-09-03`
|
|
414
|
+
- Zod 4 payload validation; emits draft-7 JSON Schema with `$defs` deduplication for error envelopes
|
|
415
|
+
- Markdown → Notion blocks via the `remark` / `remark-gfm` pipeline
|
|
416
|
+
- Bounded-concurrency batch worker (default 3, max 10)
|
|
417
|
+
- Shared token-bucket rate limiter; `withRetry` wraps every dispatched call with exponential backoff on transient failures
|
|
418
|
+
- In-memory idempotency cache (5-minute TTL, 512 entries)
|
|
419
|
+
- Slim shapers per entity type (`slimPage`, `slimBlock`, `slimDatabase`, `slimDataSource`, `slimUser`, `slimComment`, `slimFileUpload`) with `verbose: true` opt-out
|
|
420
|
+
- Vitest smoke harness covering the markdown parser, slim shapers, schema emitter, dispatcher, batch partial success / atomic rollback / idempotency dedupe (`npm test`)
|
|
285
421
|
|
|
286
|
-
|
|
422
|
+
---
|
|
287
423
|
|
|
288
|
-
|
|
424
|
+
## ❓ Troubleshooting the Notion MCP server
|
|
425
|
+
|
|
426
|
+
- **"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.
|
|
427
|
+
- **"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.
|
|
428
|
+
- **"No parent page configured"** — pass `parent` in the call, or set `NOTION_PAGE_ID` to a default.
|
|
429
|
+
- **"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`.
|
|
430
|
+
- **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).
|
|
431
|
+
- **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 ...`.
|
|
432
|
+
- **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).
|
|
433
|
+
|
|
434
|
+
### Getting help
|
|
435
|
+
|
|
436
|
+
- [GitHub Issues](https://github.com/awkoy/notion-mcp-server/issues)
|
|
437
|
+
- [Notion API reference](https://developers.notion.com/reference/intro)
|
|
438
|
+
- [Model Context Protocol spec](https://modelcontextprotocol.io)
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## 💬 FAQ: Notion MCP server
|
|
443
|
+
|
|
444
|
+
### What is the Notion MCP server and how does it work?
|
|
445
|
+
|
|
446
|
+
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.
|
|
447
|
+
|
|
448
|
+
### How do I connect Claude to Notion using MCP?
|
|
449
|
+
|
|
450
|
+
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.
|
|
451
|
+
|
|
452
|
+
### What is a Notion Personal Access Token and how do I get one?
|
|
453
|
+
|
|
454
|
+
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).
|
|
455
|
+
|
|
456
|
+
### What's the difference between this Notion MCP server and the official Notion MCP?
|
|
457
|
+
|
|
458
|
+
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).
|
|
459
|
+
|
|
460
|
+
### Can I use this Notion MCP server with Cursor, ChatGPT, or Cline?
|
|
461
|
+
|
|
462
|
+
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.
|
|
463
|
+
|
|
464
|
+
### Is it safe to give an AI my Notion token?
|
|
465
|
+
|
|
466
|
+
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.
|
|
467
|
+
|
|
468
|
+
### Does this work with self-hosted or local-only LLMs?
|
|
469
|
+
|
|
470
|
+
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.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## 🤝 Contributing
|
|
475
|
+
|
|
476
|
+
PRs welcome. Fork → branch → commit → push → PR. Run `npm test` before submitting.
|
|
477
|
+
|
|
478
|
+
## 📄 License
|
|
289
479
|
|
|
480
|
+
MIT — see [LICENSE](./LICENSE).
|