Notion MCP Server
OFFICIAL SERVERLets an assistant search, read and edit Notion pages, blocks, databases and comments through the Notion API.
Reads and edits a Notion workspace with a token, so it can run unattended in CI and agents
By Yaroslav Boiko (individual) TypeScript Local · stdio Productivity
61 popularity score ★ 180 ↓ 4.9k/wk
Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.
It needs NOTION_TOKEN. Replace each <NAME> with your value.
claude mcp add notion -e NOTION_TOKEN=<NOTION_TOKEN> -- npx -y notion-mcp-server{
"mcpServers": {
"notion": {
"command": "npx",
"args": [
"-y",
"notion-mcp-server"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}{
"mcpServers": {
"notion": {
"command": "npx",
"args": [
"-y",
"notion-mcp-server"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}{
"servers": {
"notion": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"notion-mcp-server"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}[mcp_servers.notion]
command = "npx"
args = ["-y", "notion-mcp-server"]
[mcp_servers.notion.env]
NOTION_TOKEN = "<NOTION_TOKEN>"gemini mcp add --scope user -e NOTION_TOKEN=<NOTION_TOKEN> notion npx -- -y notion-mcp-serverOpens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.
It needs NOTION_TOKEN. Replace each <NAME> with your value.
claude mcp add notion -e NOTION_TOKEN=<NOTION_TOKEN> -- docker run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latest{
"mcpServers": {
"notion": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"NOTION_TOKEN",
"ghcr.io/awkoy/notion-mcp-server:latest"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}{
"mcpServers": {
"notion": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"NOTION_TOKEN",
"ghcr.io/awkoy/notion-mcp-server:latest"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}{
"servers": {
"notion": {
"type": "stdio",
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"NOTION_TOKEN",
"ghcr.io/awkoy/notion-mcp-server:latest"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}[mcp_servers.notion]
command = "docker"
args = ["run", "--rm", "-i", "-e", "NOTION_TOKEN", "ghcr.io/awkoy/notion-mcp-server:latest"]
[mcp_servers.notion.env]
NOTION_TOKEN = "<NOTION_TOKEN>"gemini mcp add --scope user -e NOTION_TOKEN=<NOTION_TOKEN> notion docker -- run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latestOpens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.
It needs NOTION_TOKEN. Replace each <NAME> with your value.
git clone https://github.com/awkoy/notion-mcp-server.git
cd notion-mcp-server
npm install
npm run buildclaude mcp add notion -e NOTION_TOKEN=<NOTION_TOKEN> -- node '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'{
"mcpServers": {
"notion": {
"command": "node",
"args": [
"<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}{
"mcpServers": {
"notion": {
"command": "node",
"args": [
"<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}{
"servers": {
"notion": {
"type": "stdio",
"command": "node",
"args": [
"<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
],
"env": {
"NOTION_TOKEN": "<NOTION_TOKEN>"
}
}
}
}[mcp_servers.notion]
command = "node"
args = ["<ABSOLUTE_PATH_TO_CLONE>/build/index.js"]
[mcp_servers.notion.env]
NOTION_TOKEN = "<NOTION_TOKEN>"gemini mcp add --scope user -e NOTION_TOKEN=<NOTION_TOKEN> notion node -- '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'NOTION_TOKEN |
Required: Notion Personal Access Token (ntn_...) or internal integration secret; without it every call fails. |
|---|---|
NOTION_PAGE_ID |
Optional: Default parent page for create_page and create_database when no parent is given. |
NOTION_READ_ONLY |
Optional: true, 1 or yes turns off every write operation. |
NOTION_ALLOWED_OPERATIONS |
Optional: Comma-separated allowlist of operations or presets such as read, write, destructive, pages. |
NOTION_BLOCKED_OPERATIONS |
Optional: Comma-separated blocklist with the same names; overrides the allowlist. |
NOTION_CONFIRM_DESTRUCTIVE |
Optional: Asks for confirmation through the client before a destructive operation runs. |
NOTION_UPLOAD_ROOT |
Optional: Limits upload_file's local path source to one directory. |
NOTION_RATE_LIMIT |
Optional: Requests per second for the shared limiter; 3 by default. |
NOTION_FILE_URLS |
Optional: ref swaps Notion's long signed file URLs for short notion-file: refs; full by default. |
MCP_TRANSPORT |
Optional: Set to http to serve Streamable HTTP on /mcp instead of stdio. |
HOST |
Optional: Bind address in HTTP mode; 127.0.0.1 by default. |
MCP_AUTH_TOKEN |
Optional: In HTTP mode, a bearer token that every /mcp request must send. |
In a container, a setting goes in with -e NAME=value after docker run.
Notion MCP Server is a community-built server that works on a Notion workspace through Notion's public API, using a Personal Access Token or an internal integration secret instead of an interactive sign-in. That makes it usable where no one can click an Authorize button: CI jobs, scheduled scripts, background agents and self-hosted deployments.
Instead of one tool per API endpoint it exposes three tools. notion_read runs reads, notion_write runs changes, and notion_describe returns the JSON Schema and an example for a single operation. Version 3.1.2 covers 47 operations plus the trash_page alias, 48 names in all (22 reads), across pages, blocks, databases, data sources, views, templates, comments, users and file uploads. It accepts Markdown when creating or updating content, flattens database rows into plain name-to-value maps, and gives back the raw Notion shape when called with verbose: true.
Every write operation can take a batch of items. Batches run 3 at a time by default and up to 10, can roll back on the first failure with atomic: true, and an idempotency key returns the cached result for 5 minutes. Calls share a limiter set to 3 requests per second by default (NOTION_RATE_LIMIT) and retry with backoff when Notion rate-limits them.
It speaks stdio by default. With MCP_TRANSPORT=http it serves Streamable HTTP on /mcp instead, for one token per process. Operations can be cut down with an allowlist, a blocklist or a read-only switch, and destructive ones can be set to ask for confirmation first.
| Runtime | Node.js 20+orDocker |
|---|
Node.js 20 or later for the npx command (package.json at v3.1.2 sets engines node >=20; the README does not state a version), or Docker for the container. A Notion account and a token in NOTION_TOKEN: a Personal Access Token from app.notion.com/developers/tokens, or an internal integration secret connected to each page it should reach. Workspace admins can turn PATs off. PATs expire one year after they are created.
It acts only as the one token it was started with, and the HTTP mode is single-tenant too. An internal integration token sees only the pages it has been connected to. Blocking the destructive group does not stop update_page_markdown from replacing a page body; NOTION_ALLOWED_OPERATIONS=read or NOTION_READ_ONLY=true is the setting that rules out every change. NOTION_CONFIRM_DESTRUCTIVE needs a client that supports MCP elicitation; other clients get confirmation_unavailable. get_image returns images of up to 5 MB. Opt-in pagination stops at about 1000 items. If the token is missing, the server still starts but every call fails with an auth error.
Suits developers and teams who want an agent to read and change Notion without a person present, such as a CI step, a cron job or a shared bot on their own host, and who are fine handing it a token. The read/write split and the operation allowlists suit setups that need read-only or tightly limited access. This is not Notion's own server. Notion also publishes an open-source server (makenotion/notion-mcp-server, which we list) and a hosted connector; the README points people who just want to chat with Notion in claude.ai to that hosted connector.
notion_readnotion_writenotion_describe