Notion MCP Server

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

Connect your client

Local (npm)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

It needs NOTION_TOKEN. Replace each <NAME> with your value.

Claude CodeTerminal
claude mcp add notion -e NOTION_TOKEN=<NOTION_TOKEN> -- npx -y notion-mcp-server
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "notion": {
      "command": "npx",
      "args": [
        "-y",
        "notion-mcp-server"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "notion": {
      "command": "npx",
      "args": [
        "-y",
        "notion-mcp-server"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "notion": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "notion-mcp-server"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.notion]
command = "npx"
args = ["-y", "notion-mcp-server"]

[mcp_servers.notion.env]
NOTION_TOKEN = "<NOTION_TOKEN>"
Gemini CLITerminal
gemini mcp add --scope user -e NOTION_TOKEN=<NOTION_TOKEN> notion npx -- -y notion-mcp-server
Local (container)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

It needs NOTION_TOKEN. Replace each <NAME> with your value.

Claude CodeTerminal
claude mcp add notion -e NOTION_TOKEN=<NOTION_TOKEN> -- docker run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latest
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "notion": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "-e",
        "NOTION_TOKEN",
        "ghcr.io/awkoy/notion-mcp-server:latest"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "notion": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "-e",
        "NOTION_TOKEN",
        "ghcr.io/awkoy/notion-mcp-server:latest"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
VS Code.vscode/mcp.json
{
  "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>"
      }
    }
  }
}
Codex~/.codex/config.toml
[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 CLITerminal
gemini mcp add --scope user -e NOTION_TOKEN=<NOTION_TOKEN> notion docker -- run --rm -i -e NOTION_TOKEN ghcr.io/awkoy/notion-mcp-server:latest
Local (from source)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

It needs NOTION_TOKEN. Replace each <NAME> with your value.

FirstTerminal
git clone https://github.com/awkoy/notion-mcp-server.git
cd notion-mcp-server
npm install
npm run build
Claude CodeTerminal
claude mcp add notion -e NOTION_TOKEN=<NOTION_TOKEN> -- node '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "notion": {
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "notion": {
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "notion": {
      "type": "stdio",
      "command": "node",
      "args": [
        "<ABSOLUTE_PATH_TO_CLONE>/build/index.js"
      ],
      "env": {
        "NOTION_TOKEN": "<NOTION_TOKEN>"
      }
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.notion]
command = "node"
args = ["<ABSOLUTE_PATH_TO_CLONE>/build/index.js"]

[mcp_servers.notion.env]
NOTION_TOKEN = "<NOTION_TOKEN>"
Gemini CLITerminal
gemini mcp add --scope user -e NOTION_TOKEN=<NOTION_TOKEN> notion node -- '<ABSOLUTE_PATH_TO_CLONE>/build/index.js'

Settings 12

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.

What it does

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.

What it needs

RuntimeNode.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.

Limits

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.

Who it suits

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.

Tools 3

notion_read
Runs one of the read operations, such as searching pages, getting a page as Markdown, or querying a database or view
notion_write
Runs a write operation, such as creating, updating, moving, archiving or deleting content, commenting or uploading a file; takes batches
notion_describe
Returns the JSON Schema and a working example for a single operation

Similar servers