notion-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| PORT | No | Server listen port. | 8322 |
| FILES_DIR | No | Local file store path. | /data/files |
| SHARED_DIR | No | Path to the cross-MCP shared mount. | /shared |
| NOTION_TOKEN | No | Notion internal integration token (starts with ntn_). Required. | |
| DRIVE_ACCOUNT | No | Google account email for Drive access (optional). | |
| XBERG_BASE_URL | No | Base URL for OCR delegation to an xberg server. | http://xberg:8000 |
| GOOGLE_CLIENT_ID | No | OAuth client ID for Google Drive integration (optional). | |
| MCP_BEARER_TOKEN | No | Bearer token for authenticating MCP clients. Required for HTTP mode, not for stdio mode. | |
| DRIVE_FOLDER_NAME | No | Shared Drive folder name. | mcp-google-accounts |
| XBERG_VLM_API_KEY | No | API key for the xberg VLM backend (optional). | |
| NOTION_API_VERSION | No | Notion API version header. | 2026-03-11 |
| GMAIL_TOKEN_DB_PATH | No | Legacy alternative to GOOGLE_TOKEN_DB_PATH. | |
| GOOGLE_CLIENT_SECRET | No | OAuth client secret for Google Drive integration (optional). | |
| GOOGLE_TOKEN_DB_PATH | No | Path to google-accounts-mcp's SQLite token DB (read-only). Legacy GMAIL_TOKEN_DB_PATH is also honored. | /google-data/tokens.db |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| notion_search_pagesA | Search Notion for pages the integration has access to. Returns titles and page IDs — use these IDs with notion_upload_file / list_files_on_page. The integration only sees pages explicitly shared with it in Notion. in_trash=True searches trashed pages instead of active ones (restore matches with notion_restore_page). |
| notion_search_databasesA | Search Notion for databases the integration has access to. Returns titles, IDs, and property schemas. Use the database ID with notion_query_database, notion_describe_database, or notion_create_page (with database_id). The integration only sees databases explicitly shared with it in Notion. |
| notion_list_files_on_pageA | List all file/image/pdf/video/audio blocks on a Notion page. Returns block IDs and filenames — use block IDs with notion_download_file. |
| notion_purge_shared_filesA | Delete files from the cross-MCP shared mount (SHARED_DIR, /shared inside the container) whose modification time is older than max_age_hours. Use this to clean up attachments handed over from the companion google-accounts-mcp server once they've been uploaded to Notion. A host-level TTL sweep (if you run one) makes this automatic; the tool is mainly for agents that want to clean up proactively at the end of a workflow without waiting for it. Set dry_run=True to list victims without deleting. Only regular files are touched — subdirectories are left alone. |
| notion_purge_fileA | Delete a single file from the cross-MCP shared mount by bare filename. Use this after a successful upload to free the shared slot instead of waiting for the daily TTL sweep (or calling notion_purge_shared_files with max_age_hours=0, which would wipe any unrelated in-flight handoffs). Rejects any filename containing path separators. |
| notion_list_local_filesA | List files available to notion-mcp by scheme, so the agent can verify a handoff before attaching it to Notion. location: • 'files' (default) — the notion-mcp private store under FILES_DIR. These are addressable as source='local:'. • 'shared' — the cross-MCP shared mount under SHARED_DIR. Files placed here by google-accounts-mcp (download_attachment with destination='shared') are addressable as source='shared:'. Optional substring filter on the filename. |
| notion_upload_fileA | Upload a file to Notion and attach it as a new block. source accepts one of:
• parent_page_id is a Notion page ID (find via notion_search_pages) OR any block ID that supports children (toggle, column, callout, ...) to attach the file inside that block. position controls where the new block lands among the parent's children: 'end' (default), 'start', or a block ID to insert directly AFTER that block (get block IDs from notion_read_page(include_block_ids=True) or notion_list_files_on_page). filename overrides the source filename when provided. caption is shown below the file block in Notion. Files larger than 20 MB are uploaded via Notion's multi-part protocol automatically. To swap the file in an existing block without moving it, use notion_replace_file instead. embed_html=True attaches an .html/.htm file as an embed block that Notion renders in the page, instead of a downloadable file block (caption is not applied to embeds). Cross-MCP file transferTo upload a Gmail attachment to a Notion page:
|
| notion_replace_fileA | Replace the file inside an EXISTING file/image/pdf/video/audio block, in place — the block keeps its position on the page. Use this to update a document without re-arranging anything; use notion_upload_file(position=...) to add a new block instead. block_id: the file block to update (from notion_list_files_on_page or
notion_read_page(include_block_ids=True)).
source: same schemes as notion_upload_file ( The new file must map to the same block type as the existing block (e.g. you cannot replace an image block's content with a PDF — Notion blocks are typed). In that case delete the block and upload anew. |
| notion_import_file_from_urlA | Import a file into Notion directly from a public HTTPS URL — the bytes go Notion-side, never through this server or MCP parameters. url must be publicly accessible over HTTPS. filename must carry an extension Notion accepts (it determines the block type). The import is asynchronous: this tool polls until the upload reaches a terminal status or wait_seconds elapses. parent_page_id and position behave as in notion_upload_file. Size limits are plan-dependent (5 MiB free / 5 GiB paid). No sha256 is reported — the bytes never pass through this server; verify via notion_download_file if integrity matters. |
| notion_set_page_visualA | Set or remove a Notion page's icon or cover image. target: 'icon' or 'cover'.
Provide exactly one of:
• emoji — a single emoji character (icon only), e.g. '📊'.
• source — an image via the usual schemes ( |
| notion_describe_databaseA | Retrieve a Notion database's schema. Returns each property name and type — use this to find the exact files & media column name to pass to notion_upload_file_to_database / notion_add_file_to_row. database_id can be found by calling notion_list_files_on_page on the page that holds the inline database (child_database blocks will show), or pasted from the database URL. |
| notion_upload_file_to_databaseA | Upload a file and create a NEW row in a Notion database with the file attached to the given files & media column. source: |
| notion_add_file_to_rowA | Upload a file and attach it to an EXISTING database row's files & media column. page_id: the row (page) ID — paste from the row's "Copy link"
or obtain via notion_search_pages.
source: same schemes as notion_upload_file: Cross-MCP file transferTo attach a Gmail attachment to an existing Notion row:
Files never traverse MCP parameters — no size limits. Alternative via Drive: download_attachment → drive_upload(local_filename=...) → notion_add_file_to_row(source='drive:<file_id>', ...). |
| notion_batch_add_file_to_rowA | Attach files to many existing Notion rows in one call. items_json is a JSON array of objects. Each item takes the same shape as the single-shot tool's parameters:: default_files_property and default_mode fill in any item that omits those fields — pass them once at the batch level instead of repeating per item. Items are uploaded with bounded concurrency (NOTION_BATCH_CONCURRENCY, default 3) so a batch of N finishes in roughly ceil(N/concurrency) waves rather than serially — a batch of ~8 used to exceed the client's 4-minute timeout when serial. Concurrent items targeting the SAME row are still serialised (the 'append' path is a read-modify-write). The 429/5xx backoff in the client absorbs any rate-limit bursts. Per-item failures are reported in the output (in input order) but do NOT abort the batch — use the per-item status to retry individual items. Files are staged in the shared mount; the typical pattern is to stage N files via google-accounts-mcp download_attachment, call this tool once, then call notion_purge_file to remove each staged file. |
| notion_download_fileA | Download a file from a Notion file/image/pdf/video/audio block. destination is one of: filename overrides the inferred name. When destination=local, filename
may include subdirectories (e.g. Cross-MCP file transfer (Notion → Gmail)To email a file stored in Notion:
|
| notion_extract_file_textA | Extract text from a PDF anywhere notion-mcp can reach — a file stored in Notion, the shared cross-MCP mount, the private store, or the Drive folder — without moving the bytes over MCP. source schemes: 'notion:<block_id>' (file/pdf block on a page — get block IDs from notion_list_files_on_page), 'shared:', 'local:', 'drive:'. Returns JSON: {"text", "page_count", "pages_returned", "mode", "truncated", "filename"} (+ "ocr_used"/"ocr_pages"/"ocr_engine" when OCR ran). Response ceiling 200 KB — truncation happens on a page boundary with a trailing marker naming the pages to fetch next. pages: pdftotext-style spec — None/"" (all), "3", "1-5", "1,3,5", "1-3,7". Out-of-range pages are silently dropped. mode: 'text' (flowing), 'layout' (preserve columns), 'tables' (extract_tables → markdown). Applies to native extraction only. OCR (scanned / image-only PDFs) — delegated to the fleet xberg service (XBERG_BASE_URL env; pages rasterised locally, sent as PNGs): ocr='auto' (default) — pages whose native text is < 20 chars are OCR'd (tesseract backend, seconds/page). "ocr_error" in the response means OCR was needed but xberg was unreachable/failed. ocr='off' — native extraction only. ocr='force' — OCR every requested page (garbled font encodings). ocr='llm' — transcribe pages with a vision model (xberg vlm backend → LiteLLM; env: XBERG_VLM_API_KEY, optional XBERG_VLM_MODEL / XBERG_VLM_BASE_URL). Best for handwriting and messy tables; slower and metered. |
| notion_render_file_pageA | Render one page of a PDF (from Notion, shared, local, or Drive) as a viewable image. The default return is a real MCP image content block, so you SEE the page directly — for a scanned document this is often all you need. Use when notion_extract_file_text isn't enough: charts, stamps, layouts, visual validation. source schemes as in notion_extract_file_text ('notion:<block_id>', 'shared:', 'local:', 'drive:'). Set return_base64=True for a JSON envelope {"image_base64", "mime_type", "width", "height", "page", "page_count", "filename"} instead (for relaying bytes programmatically). Errors return a JSON string with an "error" key in both modes. page: 1-indexed. max_width: 200..4000 px (default 1200). format: 'jpeg' (default) or 'png'. quality: JPEG 1..100 (default 75). |
| notion_read_pageA | Read a Notion page's properties and content as markdown. Returns the page title, properties, and full block content rendered as readable markdown. Nested blocks (toggles, list children, columns) are fetched recursively up to max_depth levels. page_id: Notion page ID (from notion_search_pages or a URL). max_depth: how many levels of nested blocks to fetch (1-5, default 3). include_block_ids: if True, each block line is suffixed with an HTML comment containing the block UUID, e.g. . Use these IDs with notion_update_block / notion_delete_block. |
| notion_create_pageA | Create a new Notion page with optional markdown content. For a standalone page under another page, provide parent_page_id. For a database row, provide database_id and properties_json. title: the page title. content: markdown text that becomes the page body. Supports headings, lists, quotes, code blocks, inline formatting. parent_page_id: ID of the parent page. database_id: ID of a database to add the row to. properties_json: JSON object of extra Notion properties (for database rows). Keys are property names, values are Notion API property value objects. A wiki page's verification is set here too: {"Verification": {"verification": {"state": "verified"}}}. icon: an emoji ("🚀"), "custom_emoji:" (see notion_list_custom_emojis), "icon:[:]" for a native Notion icon (e.g. "icon:pizza:blue"), or an image URL. template: for database rows: "default" applies the database's default template, or pass a template ID from notion_list_templates. The template supplies the body, so leave content empty. position: where the page lands among a parent PAGE's children: "start", "end", or a block ID to insert after. silent: True skips the update, @mention and Person-property notifications this write would send. native_markdown: True sends content to Notion's own markdown parser instead of converting it to blocks here. Use it for Notion-specific markup (tables, callouts, columns, toggles, mentions) and for very large pages, which Notion then processes as a background task. markdown_version: with native_markdown, "v2" opts into Notion-flavored Markdown (a preview); default is the stable syntax. |
| notion_update_pageA | Update a Notion page's title, icon, or other properties. page_id: the page to update. title: new title (leave empty to keep current). properties_json: JSON object of properties to update. Keys are property names, values are Notion API property value objects. Omitted properties are left unchanged. Wiki verification is a property too: {"Verification": {"verification": {"state": "verified"}}}. icon: "remove" to clear it, or an emoji, "custom_emoji:", "icon:[:]" (native Notion icon) or an image URL. silent: True skips the update, @mention and Person-property notifications this write would send. |
| notion_append_contentA | Add markdown content to an existing Notion page. Parses the markdown into blocks and adds them to the page.
Supports: headings (# ## ###), bullet/numbered/to-do lists, quotes,
fenced code blocks, dividers (---), paragraphs with inline formatting
(bold, italic, page_id: the page (or any block that supports children) to add to. content: markdown text to add. position: 'end' (default), 'start', or a block ID to insert directly AFTER that block (IDs from notion_read_page(include_block_ids=True)). silent: True skips the notifications this write would send. |
| notion_add_tabsA | Add a tab block (a row of tabs, each with its own content) to a page. page_id: the page (or any block that supports children) to add to. tabs_json: JSON array, one object per tab: [{"label": "Overview", "icon": "📋", "content": "markdown…"}, {"label": "Details", "content": "…"}] label is required; icon (an emoji) and content are optional. position: 'end' (default), 'start', or a block ID to insert after. |
| notion_archive_pageA | Archive (soft-delete) a Notion page. The page can be restored from the Notion trash within 30 days using notion_restore_page. page_id: the page to archive. |
| notion_restore_pageA | Restore an archived page from the Notion trash. page_id: the archived page to restore. |
| notion_update_blockA | Update an existing block's text content. block_id: ID of the block to update (from notion_read_page output). content: new text for the block (supports inline markdown). block_type: the block type (paragraph, heading_1, heading_2, heading_3, bulleted_list_item, numbered_list_item, to_do, quote, callout, toggle, code). If omitted, the block is fetched to detect it. |
| notion_delete_blockA | Delete a block from a Notion page. This removes the block and all its children. The deletion is permanent (blocks cannot be restored). block_id: ID of the block to delete. |
| notion_query_databaseA | Query a Notion database with optional filter, sort, and pagination. Returns matching rows with their properties. Use notion_describe_database first to see the schema and property names. database_id: the database to query.
filter_json: Notion API filter object as JSON. Example:
{"property": "Status", "select": {"equals": "Done"}}
Compound example:
{"and": [
{"property": "Status", "select": {"equals": "Active"}},
{"property": "Priority", "number": {"greater_than": 3}}
]}
Status properties can also be filtered by group (To-do / In
progress / Complete): {"property": "Status",
"status": {"group_equals": "In progress"}}
sorts_json: JSON array of sort objects. Example:
[{"property": "Due", "direction": "ascending"}]
Use "ascending" or "descending".
limit: page size (default 50, max 100 — Notion's hard cap).
start_cursor: opaque cursor returned by a previous call. Pass it
to fetch the next page. When the output shows
|
| notion_create_databaseA | Create a new Notion database under a page. parent_page_id: page to create the database under. title: database title (optional for a typed database, which is otherwise named after its type). schema_json: JSON object mapping property names to their type config in Notion API format. A title property is required. Example: { "Name": {"title": {}}, "Status": {"select": {"options": [ {"name": "To Do", "color": "red"}, {"name": "Done", "color": "green"} ]}}, "Due Date": {"date": {}}, "Priority": {"number": {"format": "number"}}, "Tags": {"multi_select": {"options": [ {"name": "bug"}, {"name": "feature"} ]}}, "Assignee": {"people": {}}, "Done": {"checkbox": {}}, "Notes": {"rich_text": {}}, "Link": {"url": {}} } database_type: "tasks", "projects" or "skills" creates one of Notion's typed databases with its built-in schema INSTEAD of schema_json (they cannot be combined). Run notion_describe_database afterwards for the exact property names. inline: True shows the database inline on the parent page; default is a full-page database. |
| notion_add_data_sourceA | Add another data source (a second table with its own schema) to an existing database. Most databases have one; the other database tools here operate on a database's FIRST data source. database_id: the database to add to. title: name of the new data source. schema_json: property schema, same format as notion_create_database (a title property is required). |
| notion_list_templatesA | List the page templates defined on a database. Pass a template's ID (or "default") as notion_create_page's template argument to create a row from it. database_id: the database to inspect. name: optional case-insensitive substring filter. |
| notion_update_databaseA | Update a Notion database's title, description, or property schema. database_id: the database to update. title: new database title (leave empty to keep current). description: new description (leave empty to keep current). properties_json: JSON object of property changes. Notion API format:
Multiple changes can be combined in one call. Use notion_describe_database first to see current schema. |
| notion_get_propertyA | Retrieve a single property from a Notion page with full pagination. Useful for large properties (relations, rollups, long rich_text) that get truncated in the normal page response. Provide either property_name or property_id. page_id: the page to read from. property_name: human-readable property name (looked up from page metadata). property_id: Notion property ID (from notion_describe_database schema). Takes precedence over property_name if both given. |
| notion_get_commentsA | Get all comments on a Notion page. Returns comment text, author, and timestamps. Use this to read discussion threads on a page. page_id: the page to get comments from. |
| notion_get_commentA | Get a single comment by ID — text, author, timestamps and the discussion thread it belongs to. comment_id: from notion_get_comments or notion_add_comment. |
| notion_add_commentA | Add a comment to a Notion page or reply to a discussion thread. page_id: the page to comment on. text: comment text. Sent as native API markdown — inline formatting, inline equations, and @mentions all work. discussion_id: if provided, the comment is a reply to this existing discussion thread. Get discussion IDs from notion_get_comments. |
| notion_update_commentA | Edit a comment previously created by this integration (Notion returns 404 for comments created by anyone else). comment_id: from notion_get_comments or notion_add_comment. text: replacement text (native API markdown — inline formatting, equations, @mentions). |
| notion_delete_commentA | Delete a comment previously created by this integration (Notion returns 404 for comments created by anyone else). comment_id: from notion_get_comments or notion_add_comment. |
| notion_list_viewsA | List the views defined on a Notion database — name, type, id, and whether each carries a saved filter/sorts. Use the view id with notion_query_view to run a view's saved filter, or with notion_update_view / notion_delete_view to manage it. |
| notion_query_viewA | Run a database view's saved filter and sorts, returning the matching rows (title + id + url). Use this instead of notion_query_database when the view already encodes the filter you want — no filter JSON needed. view_id: from notion_list_views. limit: max rows to return (max 100). |
| notion_create_viewA | Create a view on a Notion database. database_id: the database to add the view to. name: display name for the view. view_type: table | board | list | calendar | timeline | gallery | chart (default table). filter_json: optional Notion filter object as JSON (same shape as notion_query_database's filter_json). sorts_json: optional JSON array of sort objects. |
| notion_update_viewA | Update a view's name, saved filter, and/or sorts. Only provided fields change (fields left empty are preserved). |
| notion_delete_viewA | Delete a view from its database. The database and its rows are untouched — only the saved view configuration is removed. |
| notion_list_usersA | List all users in the Notion workspace. Returns user names, IDs, emails, and types (person or bot). User IDs are needed for people properties and @mentions in comments. name_contains: optional case-insensitive filter on the user's name. |
| notion_get_userA | Get details for a specific Notion user by ID. Returns the user's name, email, avatar URL, and type. Use notion_list_users to find user IDs first. user_id: Notion user UUID, or "me" for this integration's own bot user (also shows the workspace it is connected to). |
| notion_read_page_markdownA | Read a Notion page's content as native Notion-flavored markdown. Uses the direct markdown API endpoint — faster than block-by-block fetching and returns Notion's own markdown representation including special tags for databases, files, and embeds. For block-level editing (update/delete individual blocks), use notion_read_page with include_block_ids=True instead. page_id: Notion page ID. markdown_version: "v2" returns Notion-flavored Markdown (a preview syntax); default is the stable syntax. |
| notion_update_page_contentA | Edit page content using search-and-replace on the page's markdown. Finds old_text in the page content and replaces it with new_text. Use notion_read_page_markdown first to see the current content and copy the exact text to replace. page_id: the page to edit. old_text: exact text to find in the page content. new_text: replacement text (use empty string to delete). replace_all: if old_text occurs more than once the edit is rejected unless this is True (then every occurrence is replaced). allow_deleting_content: must be True for an edit that would delete a child page or database. |
| notion_insert_page_contentA | Insert markdown into a page using Notion's own markdown parser — at the start, at the end, or after a piece of existing text. Unlike notion_append_content (which converts markdown to blocks locally), this understands Notion-specific markup (tables, callouts, columns, toggles, mentions) and can target a spot by its text. page_id: the page to insert into. markdown: content to insert. position: "end" (default) or "start". Ignored when after is given. after: insert directly after this existing content, given as "start text...end text" (the first and last few words of the passage, joined by three dots). Copy them from notion_read_page_markdown. |
| notion_replace_page_contentA | Replace a page's entire content with new markdown. Overwrites all existing content. Use notion_read_page_markdown first to see what's there. For targeted edits, use notion_update_page_content instead. page_id: the page to replace content on. markdown: new page content in markdown format. allow_deleting_content: must be True when the replacement would delete a child page or database that is on the page now. markdown_version: "v2" treats markdown as Notion-flavored Markdown (a preview syntax); default is the stable syntax. Very large pages are processed by Notion as a background task; if it is still running when this returns, the reply carries a task ID for notion_get_async_task. |
| notion_get_async_taskA | Check a background write that Notion had not finished when the tool that started it returned (large notion_create_page / notion_replace_page_content calls report a task ID in that case). task_id: the task ID from that reply. |
| notion_move_pageA | Move a Notion page to a new parent page or database. page_id: the page to move. new_parent_page_id: move under this page. new_parent_database_id: move into this database (as a new row). Only one of the two parent args should be set. |
| notion_query_meeting_notesA | Find AI meeting notes in the workspace (newest first by default). Read one with notion_read_meeting_notes. title_contains: simple filter on the meeting title. filter_json: full Notion meeting-notes filter instead, e.g. {"property": "created_time", "filter": {"date_is_after": "2026-10-01"}} or {"operator": "and", "filters": [...]}. Properties: title, attendees, created_time, created_by, last_edited_time, last_edited_by. Overrides title_contains. sort_json: JSON array, e.g. [{"property": "created_time", "direction": "descending"}]. limit: max results (1-50). There is no paging — narrow the filter. |
| notion_read_meeting_notesA | Read one section of an AI meeting notes block. block_id: the meeting notes block ID (from notion_query_meeting_notes, or a "[Meeting notes: …]" line in notion_read_page output). section: "summary" (default), "notes" or "transcript". |
| notion_create_meeting_notesA | Create AI meeting notes (transcript + summary) from a recording. Uses Notion AI; processing continues after this returns — poll with notion_read_meeting_notes until its status is notes_ready. NOT idempotent: calling it twice creates two sets of notes. source: an audio/video file as |
| notion_list_custom_emojisA | List the workspace's custom emojis. Use one as a page icon with icon="custom_emoji:" on notion_create_page / notion_update_page. name: optional exact-name filter. |
| notion_list_file_uploadsA | List files this integration has uploaded to Notion, with their status — useful for checking an import or finding an upload ID. status: optional filter: pending | uploaded | expired | failed. limit: max results (1-100). |
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 55 tools
Most tools are distinguishable, but the content-editing surface has several overlapping options: read_page vs read_page_markdown, append_content vs insert_page_content, update_page_content vs replace_page_content, and block-level update/delete. The descriptions provide guidance, but an agent must carefully choose among similar-sounding tools.
Every tool uses the notion_ prefix and snake_case, with a mostly consistent verb_noun pattern. A few names vary slightly in structure, such as notion_list_files_on_page and notion_batch_add_file_to_row, but the overall convention is predictable.
At 55 tools, the surface is very large for practical agent use, spanning pages, blocks, databases, views, files, comments, users, meeting notes, templates, and emojis. While the breadth reflects Notion's feature set, many niche tools make the set heavy and increase selection burden.
Coverage is extensive: page and block lifecycle, database CRUD and querying, views, comments, files, meeting notes, users, templates, and custom emojis are all represented. Minor gaps remain, such as explicit database archiving/deletion and some lower-level block creation operations, but most workflows are supported.