Easy Notion Mcp
About
Markdown-first Notion MCP server — 26 tools, 92% fewer tokens, full round-trip fidelity
Details
- Author
- Grey-Iris
- Downloads
- 393
- Categories
- Productivity, Other, Knowledge Base, AI
Jump to
- 26 individually-named tools across 5 categories.
- 25 block types using standard markdown extended syntax.
- 92% fewer tokens vs official Notion MCP server.
- Full round-trip fidelity – read, modify, write without loss.
- Simple key-value pairs for database entries.
- Built-in prompt injection defense and URL sanitization.
Setting up with Highlight
This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:
- Download and install Highlight from highlightai.com/download
- Navigate to the plugins tab and select "Add Custom Plugin"
-
Configure the plugin with the settings below
Plugin Name
Easy Notion McpCommand (node, npx, python, etc.)Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.
- Enable "Start Automatically" if you want the plugin to start when Highlight launches
From the repository
Install via npx easy-notion-mcp. Use OAuth (HTTP server with npx easy-notion-mcp-http) or an API token (NOTION_TOKEN). Configure with any MCP client (Claude Code, Claude Desktop, OpenClaw, Cursor, VS Code Copilot, Windsurf). The server runs on stdio (API token) or HTTP (OAuth).
create_page
Create a Notion page from markdown, converted server-side to native Notion blocks (not flat text). The server auto-handles Notion's limits (100-block batching, 2000-char splitting, deep nesting); send large trees in one call, no pre-chunking. Supports stdio-only file:// uploads. Syntax guide: resource easy-notion://docs/markdown. Returns { id, title, url } only (no block count or per-block IDs).
create_page_from_file
Create a Notion page from a local markdown file. The server reads and validates the file, then creates the same result as create_page without sending file contents through the agent context. The server converts the markdown to native Notion blocks (not flat text) and automatically handles Notion's limits (100-block batching, 2000-char splitting, deep nesting), so large files need no pre-chunking. STDIO MODE ONLY. This tool is not available when the server runs over HTTP, because in HTTP mode the server's filesystem belongs to the server host, not the caller. Restrictions: - file_path must be an ABSOLUTE path (no relative paths, no ~ expansion) - File must be inside the configured workspace root (defaults to the server's process.cwd(); override via the NOTION_MCP_WORKSPACE_ROOT env var) - File extension must be .md or .markdown - File size must be ≤ 1 MB (1,048,576 bytes) - File must be valid UTF-8 - Symlinks are resolved and the resolved path must still be inside the workspace root For supported markdown syntax, read resource easy-notion://docs/markdown. Returns: { id, title, url }, plus note only when created at the workspace root. There is no block count and no per-block IDs in the receipt.
append_content
Append markdown content to an existing page. The server converts markdown into native Notion blocks, not flat/plain text. The server automatically handles Notion API limits: batches more than 100 child blocks, splits rich text over 2000 characters, and writes deeply nested blocks in additional passes, so callers can append large documents in one call with no need to pre-chunk or pre-split. Supports the same syntax as create_page; read resource easy-notion://docs/markdown for the full syntax guide. Returns: { success: true, blocks_added: <number> }.
replace_content
Replaces all page content with the provided markdown atomically (one Notion API call). Notion's atomic markdown endpoint converts the markdown to native Notion blocks in that one API call. On matched blocks Notion preserves the original block IDs, so deep-link anchors (`#block-id`) and inline-comment threads attached to those blocks survive the edit. Unmatched blocks (returned in `warnings` with code `unmatched_blocks`) are replaced with new IDs. NOT preserved across replace_content: `child_page` subpages, `synced_block` instances, `child_database` views, and `link_to_page` references on the source page — Enhanced Markdown has no input form for these, so they are dropped from the new page content. If the source contains them, use duplicate_page first or edit those types via the Notion UI. Bookmarks and embeds round-trip as bare URLs (Notion auto-links) and surface a `bookmark_lost_on_atomic_replace` warning so callers know the rich-bookmark UI is lost. For supported markdown syntax and warning details, read resources easy-notion://docs/markdown and easy-notion://docs/warnings. Returns: { success: true }, optionally truncated: true, optionally warnings with entries such as { code: "unmatched_blocks", block_ids: [...] }.
update_section
DESTRUCTIVE — no rollback: this tool deletes blocks in the section, then writes new blocks. If the write fails mid-call, the section is left partially or fully emptied; for most sections the heading anchor is deleted, so a retry can fail with "heading not found." For irreplaceable sections, duplicate_page the target first so you have a restore point. Update a section of a page by heading name. Finds the heading, replaces everything from that heading to the next section boundary. For H1 headings, the section extends to the next heading of any level. For H2/H3 headings, it extends to the next heading of the same or higher level. Include the heading itself in the markdown. If the section starts at the first block, the replacement markdown must start with the same heading type so following sections stay in place. With preserve_heading:true, the existing heading block ID, text, type, comments, and toggleable state are preserved, but the section body blocks and existing toggleable-heading children are still destructively replaced; replacement markdown is treated as body-only, and a leading matching heading is stripped for compatibility. More efficient than replace_content for editing one section of a large page.
find_replace
Find and replace text on a page. Preserves uploaded files and blocks that aren't touched. More efficient than replace_content for targeted text changes like fixing typos, updating URLs, or renaming terms.
read_section
Read a single page section by heading name. Uses the same heading matching and boundary rules as update_section: headings are matched case-insensitively, H1 sections end at the next heading of any level, and H2/H3 sections end at the next heading of the same or higher level. Includes the heading block itself and recursively renders nested children only for blocks inside the selected section. If unsupported nested block types are omitted, the response includes warnings. Notion AI meeting-notes blocks encountered in the result are rendered as a synthetic toggle and produce a `read_only_block_rendered` warning. Transcripts are not included from these tools.
read_block
Read one block by ID as markdown. Container blocks are fetched recursively with children. Unsupported root block types return a clear error; unsupported nested blocks are omitted and listed in warnings. Notion AI meeting-notes blocks encountered in the result are rendered as a synthetic toggle and produce a `read_only_block_rendered` warning. Transcripts are not included from these tools.
read_toggle
Read one toggle by title from a page. Searches recursively and matches plain toggle blocks plus toggleable heading_1, heading_2, and heading_3 blocks using case-insensitive trimmed text. Missing titles return the available toggle titles. Notion AI meeting-notes blocks encountered in the result are rendered as a synthetic toggle and produce a `read_only_block_rendered` warning. Transcripts are not included from these tools.
search_in_page
Search raw Notion block plain text inside a page, optionally scoped to one toggle or toggleable heading by title. Matching is case-insensitive plain substring search.
update_toggle
DESTRUCTIVE — no rollback: this tool preserves the matched toggle container block ID, then deletes its body children and appends replacement body blocks. Child block IDs inside the body change, and if the write fails mid-call the toggle can be left partially or fully emptied. For irreplaceable content, duplicate_page the target first so you have a restore point. Update the body of one toggle by title from a page. Searches recursively and matches plain toggle blocks plus toggleable heading_1, heading_2, and heading_3 blocks using case-insensitive trimmed text. The markdown is replacement body content, not a wrapper that renames the toggle, and the server converts it into native Notion blocks, not flat/plain text. The server automatically handles Notion API limits: batches more than 100 child blocks, splits rich text over 2000 characters, and writes deeply nested blocks in additional passes, so callers can send a full multi-section toggle tree in one call with no need to pre-chunk or pre-split. If the markdown parses as one matching top-level toggle or toggleable heading wrapper, that wrapper is ignored and only its children are used as the replacement body. For supported markdown syntax, read resource easy-notion://docs/markdown. Returns: { success: true, block_id, type, deleted, appended }, where deleted and appended are counts.
archive_toggle
Archive one toggle by title from a page. Searches recursively and matches plain toggle blocks plus toggleable heading_1, heading_2, and heading_3 blocks using case-insensitive trimmed text. Archives the matched container block; children are not deleted individually. Missing titles return the available toggle titles.
restore_toggle
Restore an archived toggle or toggleable heading by archived block ID. Use the block ID returned by archive_toggle; Notion does not expose archived child enumeration for title search or read_page include_archived.
update_block
Update a single block in place by ID. Preserves the block's identity (deep-link anchors and inline-comment threads attached to the block survive the edit). Use this for surgical edits: fixing a heading, toggling a checkbox, rewriting one paragraph. For multi-block edits, use append_content, replace_content, or update_section. Type lock-in: the markdown must parse to the same block type as the existing block. update_block cannot change a block's type — Notion's API forbids it. To change a block's type, use replace_content or delete + append. Updatable types: paragraph, heading_1, heading_2, heading_3, toggle, bulleted_list_item, numbered_list_item, quote, callout, to_do, code, equation. Container blocks (toggle, callout) update first-level content only — children stay untouched. Non-updatable types (divider, table, image, bookmark, etc.) accept only `archived: true` to delete the block. To delete a block, pass `archived: true` instead of `markdown`. Exactly one of `markdown` or `archived` is required.
read_page
Read a page and return metadata plus markdown. Recursively fetches nested blocks and uses the same markdown conventions accepted by create_page. If unsupported block types are omitted from the markdown, they are listed in warnings. Do NOT round-trip markdown through replace_content when omitted_block_types warnings are present; omitted blocks would be deleted. Notion AI meeting notes are rendered as a synthetic toggle containing the title, an optional recording timestamp callout, and `## Summary` / `## Notes` heading sections. Transcript sections are included only with `include_transcript: true`. A `read_only_block_rendered` warning is emitted whenever such a block is rendered, indicating that round-tripping the markdown through `replace_content` will replace the native meeting-notes block with ordinary blocks. Note on `max_blocks`: the cap counts top-level page blocks only; section descendants of meeting-notes blocks are fetched in full regardless of the cap, consistent with how nested children of normal blocks are fetched. Long titles are paginated with max_property_items. For markdown conventions, warning shapes, and pagination details, read resources easy-notion://docs/markdown, easy-notion://docs/warnings, and easy-notion://docs/property-pagination.
duplicate_page
Duplicate a page. Reads all blocks from the source and creates a new page with the same content that this server can represent. If the source contains block types this server does not yet support (e.g. child_page subpages, synced_block, child_database, link_to_page), those are omitted from the duplicate AND listed in a `warnings` field with code `omitted_block_types`. Notion AI meeting notes are duplicated as ordinary toggle/heading/paragraph blocks (summary and notes only — transcripts are not duplicated); a `read_only_block_rendered` warning is emitted to identify meeting-notes blocks whose native identity was not preserved across the duplicate. Deep-duplication of subpages is not yet supported.
update_page
Update page title, icon, or cover. Cover accepts an image URL, or a file:// path (stdio transport only) which will be uploaded to Notion. In HTTP transport, the file:// form is rejected — use an HTTPS URL instead.
archive_page
Archive a page in Notion.
search
Search Notion pages or databases. Use filter: 'databases' to find databases by name, then get_database for schema details.
list_pages
List child pages under a parent page.
share_page
Return the page URL that can be shared from Notion.
create_database
Create a database under a parent page. Supported property types and extras: - title - rich_text (alias: text) - number (optional: format, for example "dollar", "percent", "number_with_commas") - select, multi_select, status (optional: options array of strings or {name, color, description}) - date, checkbox, url, email, phone - formula (required: expression, for example "prop(\"Count\") * 2") - rollup (required: function, relation_property, rollup_property) - relation (required: data_source_id; optional: relation_type "single_property" or "dual_property", synced_property_name) - unique_id (optional: prefix, for example "ENG") - people, files - created_time, last_edited_time, created_by, last_edited_by - verification, place, location, button Unknown property types fail with an explicit error. No silent drops.
update_data_source
CRITICAL: full-list semantics. When you update a select or status property's `options` array, you MUST send the full desired list. Any existing option you omit will be permanently removed from the database, along with any relationship to rows currently using it. Rows that currently reference a removed option are silently reassigned to the default group's first option (for example "Not started" for status properties). No signal is raised. If you want to preserve the meaning of existing rows when removing an option, reclassify those rows to another explicit option before removing the option from the schema. To add one option, first call get_database, then resend the full current list with your addition appended. Cannot toggle `is_inline` on existing databases. `is_inline` is a database-level field, not a data-source field. A separate `update_database` tool may be added later. Updates a database's schema: rename properties, add or update property definitions, remove properties, change the title, or move it to/from trash. Use after get_database. Supports raw Notion payloads and schema helper payloads; read resource easy-notion://docs/update-data-source for modes, examples, status notes, and limitations. At least one of `title`, `properties`, or `in_trash` must be provided.
get_database
Get a database's schema — property names, types, and select/status options. Call this before query_database or add_database_entry to know the exact property names and valid values.
list_databases
List all databases the integration can access. Returns database names and IDs — use get_database on any result to see its schema.
query_database
Query a database with optional filters, sorts, or text search. Use text for simple keyword search across title, rich_text, url, email, and phone fields. For advanced filters, pass Notion filter syntax and call get_database first to see property names and valid options. Response shape: { results: Array<entry>, warnings?: Array<warning> }. Multi-value properties are capped by max_property_items and can emit truncated_properties; read resources easy-notion://docs/property-pagination and easy-notion://docs/warnings for details.
list_views
List Notion database views. Pass exactly one of database_id or data_source_id. Returns a curated summary of each view (id, name, type, url, data_source_id) plus pagination cursors; set include_config: true for the full raw Notion view objects.
get_view
Retrieve one Notion database view by ID. Returns a curated summary (id, name, type, url, data_source_id); set include_config: true for the full raw Notion view object.
query_view
Query a Notion database view. Creates a temporary view query, fetches database row results, then deletes the query.
create_view
Create a Notion database view. Pass database_id. Dashboard views and dashboard widget placement are not supported.
update_view
Update a Notion database view. Pass at least one update field. Null filter, sorts, or quick_filters values are forwarded to clear those fields.
delete_view
Delete a Notion database view. Destructive: confirm must be exactly true.
add_database_entry
Create one database entry using simple key-value property inputs. Call get_database first to see available property names and valid select/status options. Writable property values use simple inputs: - title, rich_text: string - number: number - select, status: option name string - multi_select: array of option name strings - date: ISO date string (start only) - checkbox: boolean - url, email, phone: string - relation: string or array of page IDs - people: string or array of user IDs Not writable from this tool: - formula, rollup, unique_id, created_time, last_edited_time, created_by, last_edited_by: computed by Notion - files, verification, place, location, button: not supported for value writes here Example: { "Name": "Buy groceries", "Status": "Todo", "Priority": "High", "Due": "2025-03-20", "Tags": ["Personal"] }.
add_database_entries
Create multiple entries in a database in one call. Each entry uses the same simple key-value format as add_database_entry. Returns per-entry results — partial failures don't block the batch.
update_database_entry
Update an existing database entry using simple key-value property inputs. Pass only properties to change; omitted properties are left unchanged. Call get_database first to see available property names and valid select/status options. Writable property values use the same simple inputs as add_database_entry: - title, rich_text: string - number: number - select, status: option name string - multi_select: array of option name strings - date: ISO date string (start only) - checkbox: boolean - url, email, phone: string - relation: string or array of page IDs - people: string or array of user IDs Not writable from this tool: - formula, rollup, unique_id, created_time, last_edited_time, created_by, last_edited_by: computed by Notion - files, verification, place, location, button: not supported for value writes here
list_comments
List comments on a page.
add_comment
Add a comment to a page.
move_page
Move a page to a new parent page.
restore_page
Restore an archived page.
delete_database_entry
Delete (archive) a database entry.
list_users
List workspace users.
get_me
Get the current bot user.
Claude Desktop / Cursor
Paste into your MCP client config file to install this server.
{
"mcpServers": {
"easy notion mcp": {
"notion": {
"command": "npx",
"args": [
"-y",
"easy-notion-mcp"
],
"env": {
"NOTION_TOKEN": "ntn_your_integration_token"
}
}
}
}
}
McpServers
{
"notion": {
"command": "npx",
"args": [
"-y",
"easy-notion-mcp"
],
"env": {
"NOTION_TOKEN": "ntn_your_integration_token"
}
}
}
Easy Notion MCP
Markdown-first MCP server that connects AI agents to Notion.<br> Agents write markdown — easy-notion-mcp converts it to Notion's block API and back again. 26 tools · 25 block types · 92% fewer tokens vs official Notion MCP · Full round-trip fidelitybash
npx easy-notion-mcp
`
See it in action → Live Notion page created and managed entirely through easy-notion-mcp.
</div>
---
Contents: Comparison · Setup · Why markdown · How it works · Tools · Block types · Round-trip · Databases · Config · Security · FAQ
How does easy-notion-mcp compare to other Notion MCP servers?
| Feature | easy-notion-mcp | Official Notion MCP (npm) | better-notion-mcp |
|---|---|---|---|
| Content format | ✅ Standard GFM markdown | ❌ Raw Notion API JSON | ⚠️ Markdown (limited block types) |
| Block types | ✅ 25 (toggles, columns, callouts, equations, embeds, tables, file uploads, task lists) | ⚠️ All (as raw JSON) | ⚠️ ~7 (headings, paragraphs, lists, code, quotes, dividers) |
| Round-trip fidelity | ✅ Full — read markdown, modify, write back | ❌ Raw JSON requires block reconstruction | ⚠️ Unsupported blocks silently dropped |
| Tools | 26 individually-named tools | 18 auto-generated from OpenAPI | 9 composite tools (39 actions) |
| File uploads | ✅ file:///path in markdown | ❌ Open feature request | ✅ 5-step lifecycle |
| Prompt injection defense | ✅ Content notice prefix + URL sanitization | ❌ | ❌ |
| Database entry format | Simple {"Status": "Done"} key-value pairs | Simplified key-value pairs | Simplified key-value pairs |
| Auth options | API token or OAuth | API token or OAuth | API token or OAuth |
How many tokens does easy-notion-mcp save?
| Operation | easy-notion-mcp | better-notion-mcp | Official Notion MCP | Savings vs Official |
|---|---|---|---|---|
| Page read | 291 tokens | ⚠️ 236 tokens | 6,536 tokens | 95.5% |
| DB query (5 rows) | 347 tokens | 704 tokens | 2,983 tokens | 88.4% |
| Search (3 results) | 298 tokens | 347 tokens | 1,824 tokens | 83.7% |
⚠️ better-notion-mcp page reads appear smaller because they silently drop 11 block types (callouts, toggles, tables, task lists, equations, bookmarks, embeds). On equal content coverage, easy-notion-mcp is more efficient.
Measured by running all three MCP servers against the same Notion content and counting tokens with tiktoken cl100k_base. Raw responses saved for verification.
How do I set up easy-notion-mcp?
With OAuth (recommended)
Run the HTTP server, then connect with any MCP client. OAuth handles authentication — no token to copy-paste.
Start the server:
`bash
npx easy-notion-mcp-http
`
Requires NOTION_OAUTH_CLIENT_ID and NOTION_OAUTH_CLIENT_SECRET env vars. See OAuth setup below.
Claude Code:
`bash
claude mcp add notion --transport http http://localhost:3333/mcp
`
OpenClaw:
`bash
openclaw config set mcpServers.notion.transport "http"
openclaw config set mcpServers.notion.url "http://localhost:3333/mcp"
`
Claude Desktop:
Go to Settings → Connectors → Add custom connector, enter http://localhost:3333/mcp.
Your browser will open to Notion's authorization page. Pick the pages to share, click Allow, done.
With API token
Create a Notion integration, copy the token, share your pages with it.
Claude Code:
`bash
claude mcp add notion -- npx -y easy-notion-mcp
`
Set the env var: export NOTION_TOKEN=ntn_your_integration_token
OpenClaw:
`bash
openclaw config set mcpServers.notion.command "npx"
openclaw config set mcpServers.notion.args '["easy-notion-mcp"]'
`
Set the env var: export NOTION_TOKEN=ntn_your_integration_token
<details><summary><strong>Claude Desktop</strong> — add to <code>claude_desktop_config.json</code></summary>
`json
{
"mcpServers": {
"notion": {
"command": "npx",
"args": ["-y", "easy-notion-mcp"],
"env": {
"NOTION_TOKEN": "ntn_your_integration_token"
}
}
}
}
`
</details>
<details><summary><strong>Cursor</strong> — add to <code>.cursor/mcp.json</code></summary>
`json
{
"mcpServers": {
"notion": {
"command": "npx",
"args": ["-y", "easy-notion-mcp"],
"env": {
"NOTION_TOKEN": "ntn_your_integration_token"
}
}
}
}
`
</details>
<details><summary><strong>VS Code Copilot</strong> — add to <code>.vscode/mcp.json</code></summary>
`json
{
"servers": {
"notion": {
"command": "npx",
"args": ["-y", "easy-notion-mcp"],
"env": {
"NOTION_TOKEN": "ntn_your_integration_token"
}
}
}
}
`
</details>
<details><summary><strong>Windsurf</strong> — add to <code>~/.windsurf/mcp.json</code></summary>
`json
{
"mcpServers": {
"notion": {
"command": "npx",
"args": ["-y", "easy-notion-mcp"],
"env": {
"NOTION_TOKEN": "ntn_your_integration_token"
}
}
}
}
`
</details>
easy-notion-mcp works with any MCP-compatible client. The server runs via stdio (API token mode) or HTTP (OAuth mode).
Why markdown-first?
The official Notion MCP npm package returns raw API JSON — deeply nested block objects with ~120 tokens of metadata per block. Other servers convert to markdown but support only a handful of block types, silently dropping callouts, toggles, tables, equations, and more.
easy-notion-mcp uses standard GFM markdown that agents already know. There's nothing new to learn, no custom tag syntax, no block objects to construct. The agent writes markdown, easy-notion-mcp handles the conversion to Notion's block API — and back again, with 25 block types preserved.
This means agents can edit existing content. Read a page, get markdown back, modify the string, write it back. Nothing is lost. Agents edit Notion pages the same way they edit code — as text.
How does easy-notion-mcp work?
Pages — write and read markdown:
`javascript
create_page({
title: "Sprint Review",
markdown: "## Decisions\n\n- Ship v2 by Friday\n- [ ] Update deploy scripts\n\n> [!WARNING]\n> Deploy window is Saturday 2–4am only"
})
`
Read it back — same markdown comes out:
`javascript
read_page({ page_id: "..." })
`
`json
{ "markdown": "## Decisions\n\n- Ship v2 by Friday\n- [ ] Update deploy scripts\n\n> [!WARNING]\n> Deploy window is Saturday 2–4am only" }
`
Modify the string, call replace_content, done. Or target a single section by heading name with update_section. Or do a surgical find_replace without touching the rest of the page. Pages can also have emoji icons and cover images set via create_page or update_page.
Databases — write simple key-value pairs:
`javascript
add_database_entry({
database_id: "...",
properties: { "Status": "Done", "Priority": "High", "Due": "2025-03-20", "Tags": ["v2", "launch"] }
})
`
No property type objects, no nested { select: { name: "Done" } } wrappers. easy-notion-mcp fetches the database schema at runtime and converts automatically. Agents pass { "Status": "Done" }, easy-notion-mcp does the rest.
Errors tell you how to fix them. A wrong heading name returns the available headings. A missing page suggests sharing it with the integration. A bad filter tells you to call get_database first. Agents can self-correct without asking the user for help.
Complex content works. Nested toggles inside toggles, columns with mixed content types (lists + code blocks + blockquotes), deep list nesting, and full unicode (Japanese, Chinese, Arabic, emoji) all round-trip cleanly. update_section heading search is case-insensitive and returns available headings on miss. add_database_entries handles partial failures — succeeded and failed entries are returned separately so agents can retry just the failures.
What tools does easy-notion-mcp provide?
easy-notion-mcp includes 26 individually-named tools across 5 categories. Each tool is self-documenting with complete usage examples — agents know exactly how to use every tool from the first message, with no extra round-trips needed.
Pages (11 tools)
| Tool | Description |
|---|---|
| create_page | Create a page from markdown |
| read_page | Read a page as markdown |
| append_content | Append markdown to a page |
| replace_content | Replace all content on a page |
| update_section | Update a section by heading name |
| find_replace | Find and replace text, preserving files |
| update_page | Update title, icon, or cover |
| duplicate_page | Copy a page and its content |
| archive_page | Move a page to trash |
| move_page | Move a page to a new parent |
| restore_page | Restore an archived page |
Navigation (3 tools)
| Tool | Description |
|---|---|
| list_pages | List child pages under a parent |
| search | Search pages and databases |
| share_page | Get the shareable URL |
Databases (8 tools)
| Tool | Description |
|---|---|
| create_database | Create a database with typed schema |
| get_database | Get database schema, property names, and options |
| list_databases | List all databases the integration can access |
| query_database | Query with filters, sorts, or text search |
| add_database_entry | Add a row using simple key-value pairs |
| add_database_entries | Add multiple rows in one call |
| update_database_entry | Update a row using simple key-value pairs |
| delete_database_entry | Delete (archive) a database entry |
easy-notion-mcp fetches the database schema, maps values to Notion's property format, and handles type conversion automatically when agents pass simple key-value pairs like { "Status": "Done" }. Schema is cached for 5 minutes to avoid redundant API calls during batch operations.
Comments (2 tools)
| Tool | Description |
|---|---|
| list_comments | List comments on a page |
| add_comment | Add a comment to a page |
Users (2 tools)
| Tool | Description |
|---|---|
| list_users | List workspace users |
| get_me | Get the current bot user |
What block types does easy-notion-mcp support?
easy-notion-mcp supports 25 block types using standard markdown syntax extended with conventions for Notion-specific blocks like toggles, columns, and callouts. Agents write familiar markdown — easy-notion-mcp handles the conversion to and from Notion's block format.
Standard markdown
| Syntax | Markdown |
|---|---|
| Headings | # H1 ## H2 ### H3 |
| Bold, italic, strikethrough | bold italic ~~strike~~ |
| Inline code | code |
| Links | text |
| Images | |
| Bullet list | - item |
| Numbered list | 1. item |
| Task list | - [ ] todo / - [x] done |
| Blockquote | > text |
| Code block | `language |
| Table | Standard pipe table syntax |
| Divider | --- |
Notion-specific syntax
| Block | Syntax |
|---|---|
| Toggle | +++ Title ... +++ |
| Columns | ::: columns / ::: column ... ::: |
| Callout (note) | > [!NOTE] |
| Callout (tip) | > [!TIP] |
| Callout (warning) | > [!WARNING] |
| Callout (important) | > [!IMPORTANT] |
| Callout (info) | > [!INFO] |
| Callout (success) | > [!SUCCESS] |
| Callout (error) | > [!ERROR] |
| Equation | $$expression$$ |
| Table of contents | [toc] |
| Embed | embed |
| Bookmark | Bare URL on its own line |
| File upload (image) | 
|
| File upload (file) | name |
Can I read and rewrite pages without losing formatting?
Yes. Round-trip fidelity is a core design guarantee of easy-notion-mcp, not a side effect.
What you write is what you read back. read_page returns the exact same markdown syntax that create_page accepts — headings, lists, tables, callouts, toggles, columns, equations, all of it.
easy-notion-mcp enables agents to read a page, modify the markdown string, and write it back without losing formatting, structure, or content. No format translation. No block reconstruction. Agents edit Notion pages the same way they edit code — as text.
What's the difference between find_replace and replace_content?
easy-notion-mcp provides three editing strategies for different use cases:
- replace_content — Replaces all content on a page with new markdown. Best for full rewrites.
- update_section — Replaces a single section identified by heading name. Best for updating one part of a page.
- find_replace — Finds and replaces specific text anywhere on the page, preserving all other content and attached files. Best for surgical edits.
How does easy-notion-mcp handle databases?
easy-notion-mcp provides 8 database tools that abstract away Notion's complex property format. Agents pass simple key-value pairs like { "Status": "Done", "Priority": "High" } — easy-notion-mcp fetches the database schema at runtime and converts to Notion's property format automatically.
easy-notion-mcp supports creating databases with typed schemas, querying with filters and sorts, and bulk operations via add_database_entries (multiple rows in one call). Schema is cached for 5 minutes to avoid redundant API calls during batch operations.
Configuration
Stdio mode (API token)
| Variable | Required | Default | Description |
|---|---|---|---|
| NOTION_TOKEN | Yes | — | Notion API integration token |
| NOTION_ROOT_PAGE_ID | No | — | Default parent page ID |
| NOTION_TRUST_CONTENT | No | false | Skip content notice on read_page responses |
OAuth / HTTP transport
Run npx easy-notion-mcp-http to start the HTTP server with OAuth support.
| Variable | Required | Default | Description |
|---|---|---|---|
| NOTION_OAUTH_CLIENT_ID | Yes | — | Notion public integration OAuth client ID |
| NOTION_OAUTH_CLIENT_SECRET | Yes | — | Notion public integration OAuth client secret |
| PORT | No | 3333 | HTTP server port |
| OAUTH_REDIRECT_URI | No | http://localhost:{PORT}/callback | OAuth callback URL |
To get OAuth credentials, create a public integration at notion.so/profile/integrations and configure http://localhost:3333/callback as the redirect URI.
In OAuth mode, create_page works without NOTION_ROOT_PAGE_ID — pages are created in the user's private workspace section by default.
What about security and prompt injection?
easy-notion-mcp includes two layers of security for production deployments:
Prompt injection defense: read_page responses include a content notice prefix instructing the agent to treat Notion data as content, not instructions. This prevents page content from hijacking agent behavior. Set NOTION_TRUST_CONTENT=true to disable this if you control the workspace.
URL sanitization: javascript:, data:, and other unsafe URL protocols are stripped and rendered as plain text. Only http:, https:, and mailto: are allowed.
Frequently Asked Questions
How is easy-notion-mcp different from the official Notion MCP server?
The official Notion MCP npm package (@notionhq/notion-mcp-server) is a raw API proxy — it returns unmodified Notion JSON, costing ~90% more tokens per operation. easy-notion-mcp converts everything to standard GFM markdown that agents already know, supports 25 block types with round-trip fidelity, and includes prompt injection defense. Notion also offers a separate hosted remote MCP server (OAuth-based) that uses a custom HTML-tag-based markdown format — easy-notion-mcp uses standard markdown syntax instead.
What MCP clients does easy-notion-mcp work with?
easy-notion-mcp works with any MCP-compatible client, including Claude Desktop, Claude Code, Cursor, VS Code Copilot, Windsurf, and OpenClaw. It supports both stdio transport (API token) and HTTP transport (OAuth). See the setup instructions for copy-pasteable configs for each client.
Does easy-notion-mcp support file uploads?
Yes. easy-notion-mcp supports file uploads using the file:/// protocol in markdown syntax. Upload images with 
and files with name.
Does easy-notion-mcp handle nested and complex content?
Yes. Nested toggles inside toggles, columns with mixed content types (lists, blockquotes, and code blocks in different columns), nested bullet and numbered lists, and full unicode support including Japanese, Chinese, Russian, Arabic, and emoji — all round-tripping cleanly.
Does easy-notion-mcp handle partial failures in batch operations?
Yes. add_database_entries returns separate succeeded and failed` arrays. If one entry fails validation, the others still get created. Agents can retry just the failures without re-sending the whole batch.
Contributing
Issues and PRs welcome on GitHub.License
MITSign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.





