notion-full-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| NOTION_API_TOKEN | Yes | Your Notion API token |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
Notion MCP server for deep page reading with recursive children, surgical block editing, insert-at-position, tables, file uploads, page move/copy, and snapshot/restore.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| extensions | {
"io.modelcontextprotocol/ui": {}
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| search_pagesB | Search Notion pages by title. Returns page IDs, titles, and URLs. |
| searchC | Search Notion pages and/or databases. Args: query: Text to search for. filter_type: Restrict results to "page", "database", or "" for both. max_results: Maximum number of results to return. |
| get_page_infoB | Get metadata for a Notion page (title, parent, dates, URL). |
| read_page_deepA | Read a Notion page with all nested children recursively. Args: page_id: The Notion page ID. max_depth: Maximum nesting depth to fetch (default 5). format: Output format - 'markdown' for readable text, 'json' for full block tree. Returns full document content including nested blocks like sub-lists, toggle contents, and table rows. |
| list_block_childrenA | List direct children of a block or page (shallow, no recursion). Returns block IDs, types, and optionally their text content. Useful for finding specific block IDs before surgical edits. |
| update_block_textA | Replace all text in a specific block with new plain text. Preserves the block type but replaces all rich text content. Use find_replace_in_block for targeted text changes that preserve formatting. |
| find_replace_in_blockA | Find and replace text within a block, preserving all formatting. Replaces occurrences of old_text with new_text in each rich text segment while keeping bold, italic, links, and other annotations intact. Returns an error (not a silent no-op) if old_text does not appear in the block. The error message includes the block's current plain text so the caller can diagnose Unicode / whitespace / formatting mismatches (e.g., en-dash U+2013 vs hyphen-minus U+002D, curly vs straight quotes, non-breaking space U+00A0 vs regular space). Use update_block_text or update_table_cell for full-text replacement when find/replace doesn't match because of character mismatches. The success response includes a |
| update_block_rich_textA | Update a block's rich text with fully specified formatting. rich_text_json should be a JSON array of rich text objects, e.g.: [{"type":"text","text":{"content":"Bold text"},"annotations":{"bold":true}}] |
| insert_blocks_afterA | Insert new blocks after a specific block. blocks_json should be a JSON array of block objects. Use make_block format: [{"type":"paragraph","paragraph":{"rich_text":[{"type":"text","text":{"content":"Hello"}}]}}] For simple cases, use insert_text_after instead. |
| insert_text_afterB | Insert a simple text block after a specific block. Args: block_id: The block to insert after. text: The text content. block_type: Block type (paragraph, heading_1, heading_2, heading_3, bulleted_list_item, numbered_list_item, quote, etc.) |
| append_blocks_to_pageA | Append blocks to the end of a page. blocks_json should be a JSON array of block objects. |
| upload_fileA | Upload a file to Notion's own storage. Returns a Args: file_path: Local path to the file (PNG, JPEG, GIF, SVG, PDF, etc.), up to 20 MiB. |
| upload_image_as_blockA | Upload an image file to Notion and insert it as a new image block. The image is stored in Notion's own workspace storage, so no external image host is needed. Args: file_path: Local path to the image (PNG, JPEG, GIF, SVG, WEBP, etc.) after_block_id: If provided, insert immediately after this block (siblings). parent_id: If after_block_id is empty, append to this page/block instead. caption: Optional caption text for the image. Exactly one of after_block_id or parent_id must be provided. |
| replace_image_blockA | Replace an existing image block's content with a newly uploaded image. Preserves the block's position in the parent: the new image is inserted where the old one was and the old block is deleted. The block ID changes. Args: block_id: The existing image block to replace. file_path: Local path to the new image. caption: Optional caption for the new image (omit to drop the old one). |
| download_image_blockA | Download the image file from an image block to a local path. Works for all three image source types: external URL, Notion-hosted file, and file_upload references. Notion-hosted URLs are signed and short-lived, so this tool re-fetches them at call time. Args: block_id: The image block to download from. output_path: Local path to write the image to. |
| upload_file_as_blockA | Upload a file (PDF, doc, any attachment) to Notion and insert as a file block. Analogous to upload_image_as_block but for non-image files. Use this for PDFs, spreadsheets, archives, etc. Args: file_path: Local path to the file. after_block_id: If provided, insert after this block (siblings). parent_id: If after_block_id is empty, append to this page/block. caption: Optional caption. name: Optional display name override (defaults to the filename). |
| insert_table_rowA | Insert a new row into an existing table. The number of cells must match the table's existing width. Args: table_id: The table block ID. cells_json: JSON array. Each element is either: - a plain string (single text segment) - a list of strings (multi-segment plain text in one cell) Example for a 3-column table: '["Widget", "In stock", "42"]' after_row_id: Optional row ID to insert after. If empty, append to end. |
| update_table_cellA | Replace the text of a single cell in a table row. Args: row_id: The table_row block ID. cell_index: Zero-based index of the cell within the row. new_text: New plain-text content for the cell. Overwrites all existing rich text segments in that cell. |
| delete_blockC | Delete a specific block by ID. |
| snapshot_pageA | Create a complete snapshot of a page with all nested children. Saves to a JSON file if output_path is provided. The snapshot includes the full block tree with recursive children, suitable for restore. Subpages and inline databases are recorded as blocks but their own content is not captured. Set include_files to also download Notion-hosted images and files into a
Default path: ~/notion-snapshots/<page_id>_.json |
| restore_page_from_snapshotA | Restore a page from a previously saved snapshot. Replaces the page's content with the snapshot. The snapshot is written first and the previous blocks are removed only after that succeeds, so a failed restore leaves the page as it was. Blocks the API cannot recreate (subpages, inline databases, Notion-hosted files) are never deleted. For each one in the snapshot:
Args: page_id: The page to restore into. snapshot_path: Path to the snapshot JSON file. |
| create_pageA | Create a new Notion page under a parent page or database. Args: parent_id: ID of the parent page or database. title: Title for the new page. parent_type: "page_id" (default) or "database_id". icon_emoji: Optional emoji character to use as the page icon. children_json: Optional JSON array of block objects for initial content. properties_json: Optional JSON object of additional database properties to set. Example for setting Status: {"Status": {"status": {"name": "Done"}}} Example for setting Date: {"Due date": {"date": {"start": "2026-04-15"}}} |
| update_pageA | Update a page's title, icon, or archive status. Only fields with non-default values are sent in the PATCH body. This tool can archive a page (archived=True) but never unarchives one; use restore_page to bring a page back from trash. Args: page_id: The page to update. title: New title text. Omit or pass "" to leave unchanged. icon_emoji: New icon emoji. Omit or pass "" to leave unchanged. archived: Pass True to archive the page. Defaults to False (no change sent). properties_json: Optional JSON object of database properties to set. Example: {"Status": {"status": {"name": "Done"}}} Example: {"Due date": {"date": {"start": "2026-04-15"}}} Can be combined with title — both will be applied. |
| move_pageA | Move a page under a new parent page. The page keeps its ID, content, comments and subpages. Only page parents are supported; to place a page in a database use copy_page_to_parent. Args: page_id: The page to move. new_parent_id: The page that becomes the new parent. parent_type: Must be "page_id". |
| restore_pageA | Restore a page (and its children) from trash. This is the explicit counterpart to delete_block on a page. When called on a page that was archived via a cascade (for example, a section container that was deleted along with its subpages), restoring the parent container automatically restores the cascaded descendants. Args: page_id: The page to restore from trash. |
| copy_page_to_parentA | Recreate a page under a new parent. Prefer move_page for plain moves. Creates a new page at the target with the same title, emoji icon and content, then archives the original. Use it to duplicate a page or to place a page in a database. LIMITATIONS (read carefully):
* Block IDs of the new page differ from the original, so links to
the original blocks will point at the archived page.
* Notion-hosted images and files are copied as external references
to signed URLs that expire within about an hour. Re-upload the
source files for a permanent copy.
* Nested subpages are not copied; the tool refuses to run if any are
present.
* Block types that cannot be recreated are listed in Args: page_id: The source page to copy. new_parent_id: The target parent page or database ID. parent_type: "page_id" (default) or "database_id". archive_original: If True (default), archive the source page after the copy completes. Set False to keep both copies side-by-side. |
| create_commentC | Add a comment to a Notion page. |
| list_commentsB | List all comments on a page or block. |
| get_databaseB | Get metadata and schema for a Notion database. |
| query_databaseA | Query a Notion database with optional filters and sorts. Args: database_id: The database to query. filter_json: JSON string for a Notion filter object. Leave empty for no filter. sorts_json: JSON string for a Notion sorts array. Leave empty for no sorting. page_size: Maximum rows per page fetch (max 100). Returns at most 500 rows. |
| get_selfA | Get the current authenticated Notion user or bot info. |
| list_usersA | List all users in the workspace. |
| get_blockB | Get a single block by ID, including its type, content, and metadata. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 33 tools
Several tool pairs overlap: search vs search_pages (the former is a strict superset), update_block_text vs find_replace_in_block vs update_block_rich_text all mutate block text, insert_text_after is a convenience wrapper over insert_blocks_after, and upload_file vs upload_image_as_block/upload_file_as_block share a purpose. The descriptions mitigate this well by explicitly cross-referencing ('use upload_image_as_block instead', 'prefer move_page'), but the agent still has to read the fine print to pick correctly.
Every tool is snake_case with a clear verb_noun shape (get_page_info, update_table_cell, insert_table_row, query_database, restore_page_from_snapshot). The only deviations are the bare noun 'search' and a couple of noun-ish names, which are idiomatic and readable rather than inconsistent.
33 tools is heavy for a Notion surface, though the domain (blocks, pages, tables, files, snapshots, databases, comments, users) legitimately demands breadth. Still, a handful of mergeable pairs (search_pages into search, insert_text_after into insert_blocks_after, upload_file into the two higher-level uploaders) would trim the surface without losing capability.
Coverage is strong: full page lifecycle (create/update/move/copy/restore/archive via delete_block), deep block read and multiple write paths, table cells/rows, file and image upload/download/replace, comments, database query, and snapshot/restore. Gaps are minor but real — no comment deletion or comment retrieval by block, and no database schema creation/update, only get and query.