add_comment
Add a comment to a page.
Work with Notion in markdown — write a page, edit one section, update a toggle in place — instead of assembling block JSON.
LABSCO SUMMARY
The round trip is what makes it usable: read a page as markdown, change a heading's section or one toggle, and write only that back, with update_block preserving the block identity that deep links and comment threads hang off. Two things to respect — the destructive tools announce themselves, since update_section and update_toggle delete before they write and a failed write is not rolled back, and the integration only sees pages that have been shared with it, which is the usual reason a page appears to be missing.
A markdown-first Notion server. Content goes in as GFM markdown and is converted server-side into native Notion blocks, and comes back as markdown, so a page can be read, edited and written back without reconstructing the block tree.
A Notion integration token in NOTION_TOKEN, with the pages shared to that integration — a page the integration cannot see does not exist as far as the tools are concerned. NOTION_ROOT_PAGE_ID sets the default parent for create_page. It runs as npx -y easy-notion-mcp over stdio. OAuth is the alternative for shared deployments, needing NOTION_OAUTH_CLIENT_ID and NOTION_OAUTH_CLIENT_SECRET; the HTTP server binds 127.0.0.1 and requires NOTION_MCP_BEARER in static-token mode, with NOTION_MCP_BIND_HOST to change the bind.
One command plus a key — npx easy-notion-mcp, then supply credentials
USE CASES
Hand over markdown — headings, tables, callouts, toggles, code — and it lands as native blocks rather than flat text.
Read that section by heading, rewrite just it, and leave the rest of the page untouched.
Update the block in place by id, which keeps the anchor deep links and inline comments point at.
Append the new entry as markdown, or update the toggle that holds this week's notes.
Read the schema first to see the property names and their select options, then add the entries as plain key-value pairs.
Find the database by name, check its schema, and query it with filters, sorts or a keyword search.
Archive the toggles that are finished, restore one if it turns out to be needed, and move or archive the page itself.
Counted once with only startup setup. Other versions, modes or optional credentials may expose a different list.
add_commentAdd a comment to a page.
add_database_entriesCreate 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.
add_database_entryCreate 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:
Not writable from this tool:
Example: { "Name": "Buy groceries", "Status": "Todo", "Priority": "High", "Due": "2025-03-20", "Tags": ["Personal"] }.
append_contentAppend 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> }.
archive_pageArchive a page in Notion.
archive_toggleArchive 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.
create_databaseCreate a database under a parent page.
Supported property types and extras:
Unknown property types fail with an explicit error. No silent drops.
create_pageCreate 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_fileCreate 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:
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.
create_viewCreate a Notion database view. Pass database_id. Dashboard views and dashboard widget placement are not supported.
delete_database_entryDelete (archive) a database entry.
delete_viewDelete a Notion database view. Destructive: confirm must be exactly true.
duplicate_pageDuplicate 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.
find_replaceFind 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.
get_databaseGet 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.
get_meGet the current bot user.
get_viewRetrieve 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.
list_commentsList comments on a page.
list_databasesList all databases the integration can access. Returns database names and IDs — use get_database on any result to see its schema.
list_pagesList child pages under a parent page.
list_usersList workspace users.
list_viewsList 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.
move_pageMove a page to a new parent page.
query_databaseQuery 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.
query_viewQuery a Notion database view. Creates a temporary view query, fetches database row results, then deletes the query.
read_blockRead 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_pageRead 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.
read_sectionRead 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_toggleRead 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.
replace_contentReplaces 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: [...] }.
restore_pageRestore an archived page.
restore_toggleRestore 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.
searchSearch Notion pages or databases. Use filter: 'databases' to find databases by name, then get_database for schema details.
search_in_pageSearch 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.
share_pageReturn the page URL that can be shared from Notion.
update_blockUpdate 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.
update_data_sourceCRITICAL: 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.
update_database_entryUpdate 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:
Not writable from this tool:
update_pageUpdate 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.
update_sectionDESTRUCTIVE — 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.
update_toggleDESTRUCTIVE — 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.
update_viewUpdate a Notion database view. Pass at least one update field. Null filter, sorts, or quick_filters values are forwarded to clear those fields.
BEFORE YOU INSTALL
42 tools when we connected.
CONTEXT COST
Rated by the size of the 42 tool definitions that ride along on every turn, not by how many there are
INSTALLATION
npx easy-notion-mcpclaude mcp add notion -s user \
-e NOTION_TOKEN=<YOUR_NOTION_TOKEN> \
-- npx -y easy-notion-mcpopenclaw config set mcpServers.notion.command "npx"
openclaw config set mcpServers.notion.args '["-y","easy-notion-mcp"]'
export NOTION_TOKEN=<YOUR_NOTION_TOKEN>OpenClaw hands its own environment to the server, so set NOTION_TOKEN in the shell and start OpenClaw from that same shell. The export lasts only for that shell session unless you add it to your shell startup file.
{
"mcpServers": {
"notion": {
"command": "npx",
"args": [
"-y",
"easy-notion-mcp"
],
"env": {
"NOTION_TOKEN": "<YOUR_NOTION_TOKEN>"
}
}
}
}{
"servers": {
"notion": {
"command": "npx",
"args": [
"-y",
"easy-notion-mcp"
],
"env": {
"NOTION_TOKEN": "<YOUR_NOTION_TOKEN>"
}
}
}
}Run the HTTP server on your host machine:
export NOTION_MCP_BEARER=$(openssl rand -hex 32)
NOTION_TOKEN=<YOUR_NOTION_TOKEN> \
NOTION_MCP_BIND_HOST=0.0.0.0 \
NOTION_MCP_BEARER=$NOTION_MCP_BEARER \
npx -p easy-notion-mcp easy-notion-mcp-httpThen, in your platform's MCP server settings, use http://host.docker.internal:3333/mcp as the server URL (host.docker.internal instead of localhost), and add the bearer to the request headers: Authorization: Bearer <your NOTION_MCP_BEARER value>.