Back

Notion

Notion: search, pages as markdown, databases and their rows, comments, and people

18 capabilitiesView Source ↗

Notion MCP Server

A Model Context Protocol server for Notion: search, pages as markdown, databases and their rows, comments, and the people in a workspace, read and written.

This server is the Notion connector of DataToRAG, a hosted MCP gateway with per-user OAuth, alongside its Google Workspace and Atlassian connectors.

Tools

Tool What it does Notion endpoint
notion_search Finds pages and data sources by title POST /v1/search
notion_get_page One page: properties as plain values, content as markdown GET /v1/pages/{id}, GET /v1/pages/{id}/markdown
notion_get_database A database, its data sources, and each one's property schema GET /v1/databases/{id}, GET /v1/data_sources/{id}
notion_query_data_source Rows of a data source, with a filter, sorts and a property allowlist POST /v1/data_sources/{id}/query
notion_get_comments Open comments on a page or block GET /v1/comments
notion_list_users People and bots in the workspace, one by id, or the connection's own bot ("me") GET /v1/users, /v1/users/{id}, /v1/users/me
notion_create_page A page under a page, or a row in a data source, with properties as plain values and content as markdown GET /v1/data_sources/{id} (rows), POST /v1/pages
notion_update_page Title, properties, icon, cover, template; in_trash true or false is the delete and the restore PATCH /v1/pages/{id}
notion_update_page_content Passage edits, whole replace, or insert, in markdown PATCH /v1/pages/{id}/markdown
notion_move_page A page to another page or a data source POST /v1/pages/{id}/move
notion_duplicate_page A copy made by reading the page and creating a new one; says what it did not carry GET page and markdown, POST /v1/pages
notion_create_comment A comment on a page or block, or a reply in a discussion POST /v1/comments
notion_update_comment, notion_delete_comment Comments this connection created PATCH, DELETE /v1/comments/{id}
notion_create_database A database under a page, with its data source's schema as a simple map POST /v1/databases
notion_update_data_source Title, add, rename, retype or remove properties, trash PATCH /v1/data_sources/{id}
notion_create_view, notion_update_view Database views with filter, sorts and configuration in Notion's JSON POST /v1/views, PATCH /v1/views/{id}

notion_query_data_source also reads a saved view's rows with view_id (POST /v1/views/{id}/queries, then GET /v1/views/{id}/queries/{query_id}); Notion returns those rows as ids.

Every id argument takes a Notion id (with or without dashes) or a Notion URL that contains one.

What the tools return

  • Properties as plain values. A Notion property is a typed envelope; these tools return {"Status": "Done", "Assignee": ["Test User"], "Due": "2026-10-20"}. People come back as names (or ids when Notion gives no name), dates as a string or {start, end}, relations as ids, rollups unwrapped, and a property type this server does not know as Notion sent it.
  • Lists in one shape. Every list tool returns {results, has_more, next_cursor}, the same with no results as with some. next_cursor is the cursor argument for the next page. Notion's page size caps at 100, and a full page comes back with has_more: true, never as the whole set.
  • Plain values in, too. The write tools take the same plain values and encode them against the data source schema. An unknown property name, a wrong value type, or a select or status option that does not exist is refused in words before anything is sent. Text over 2,000 characters is split into several pieces; lists are capped at Notion's 100.
  • Search matches titles. Notion's search API matches titles, not page content, and Notion describes its index as not immediate and not guaranteed complete.

How sharing works, and what "not found" means

A Notion connection sees only the pages that were shared with it. During the connect step the person connecting picks pages; a parent page brings every page under it. More can be added at any time without reconnecting: open the page in Notion, open the ••• menu at the top right, choose Connections, and add the DataToRAG connection.

Notion answers a page that is not shared exactly as it answers a page that does not exist: 404 object_not_found (observed). So when a tool says a page or database was not found, either is possible, and the error says how to share it. A page that is not shared also never appears in notion_search.

How authentication works

Each MCP session authenticates with the user's Notion OAuth access token, passed in the X-User-Token HTTP header when the session is initialized. There are no app credentials in this server: the DataToRAG gateway runs the OAuth flow, keeps and refreshes the tokens, and hands this server only the calling user's access token. A call with no token is refused before any request.

Notion's OAuth has no scopes: what a connection can do is set once on the connection, and what it can see is the pages shared with it. If Notion refuses the token (401 unauthorized), the tool says the Notion connection needs reconnecting.

Every request pins Notion-Version: 2026-03-11, the version Notion's markdown endpoints require.

Rate limits

Notion allows about three requests a second per connection in a workspace. A read refused for rate (429, or Notion's 529) is sent again inside the call, at most three requests in all, and only when Notion's Retry-After asks for a short wait; otherwise the refusal comes back with the wait in words. A write is never sent twice: its refusal comes back at once with the wait in words, and a write that got no answer is reported as possibly applied. Gateway and proxy failures are never retried.

Running it

This is a service plugin of the DataToRAG gateway, not a server to run on its own. The gateway starts it as a child process, sets PORT, and sends each user's Notion access token in the X-User-Token header when it opens a session. For development, from the repository root:

pnpm install
pnpm --filter @datatorag-mcp/notion-mcp run build
pnpm --filter @datatorag-mcp/notion-mcp run test

The process exposes /mcp (Streamable HTTP) and /health on its port.

License

MIT

Capabilities

notion-mcp__notion_create_comment

Adds a comment to a Notion page, to a block, or as a reply in an existing discussion. Takes exactly one of page_id, block_id and discussion_id, and the text as plain text. A new comment on a selection of text is not possible through Notion's API; a reply to an existing one is, by its discussion_id. Returns the comment's id, discussion_id, created_time, created_by, text and parent.

Parameters

textstring/ The comment as plain text. Over 2,000 characters is split into several text pieces.
page_idstring/ The page's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
block_idstring/ The block's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
discussion_idstring/ The discussion to reply in, from notion_get_comments.
notion-mcp__notion_create_database

Creates a Notion database under a page, with one data source and that data source's property schema. The schema is a map of property name to type, for example {"Task": "title", "Due": "date", "Stage": {"type": "status", "options": ["Todo", "Done"]}}. A map with no title property gets one named Name. Returns the database's id, title, url, parent, is_inline and data_sources (their ids are what notion_create_page and notion_query_data_source take).

Parameters

titlestring/ The database's title as plain text. Over 2,000 characters is split into several text pieces; the limit is 200,000.
is_inlineboolean/ true shows the database inside the parent page's content; false (the default) makes it a full-page database.
propertiesobject/ Property name to type. A type is a name (title, rich_text, number, select, multi_select, status, date, people, files, checkbox, url, email, phone_number, created_time, created_by, last_edited_time, last_edited_by, place, verification, unique_id) or an object with settings: {"type": "select", "options": ["A", {"name": "B", "color": "red"}]}, {"type": "number", "format": "dollar"}, {"type": "formula", "expression": "..."}, {"type": "relation", "data_source_id": "...", "dual": true}, {"type": "unique_id", "prefix": "T"}. Any type also takes "description", and "config" with Notion's own settings object (the form a rollup takes).
descriptionstring/ The database's description as plain text.
parent_page_idstring/ The page the database is created in: its Notion id or URL.
notion-mcp__notion_create_page

Creates one Notion page: under a page, or as a row of a data source. Takes the parent, a title, property values as plain values (for a row), content as markdown, and an optional template. For a row, the data source schema is read once and each value is encoded against it. Returns the new page's id, url, title, parent and properties. A failed create is not sent again; a create that got no answer may still have happened, and notion_search by title shows whether it did. Content and a template are not taken together.

Parameters

titlestring/ The page's title as plain text. Over 2,000 characters is split into several text pieces; the limit is 200,000.
contentstring/ The page content as Notion's enhanced markdown, with headings, lists, tables, code and to-dos. Absent creates an empty page.
templatestring/ "default" for the data source's default template, or a template page's id or URL. Notion fills in the template's content and properties in the background after the call returns.
propertiesobject/ Property values as plain values keyed by property name, the shape notion_get_page returns: {"Status": "Done", "Tags": ["Docs"], "Due": "2026-10-30", "Estimate": 3, "Owner": ["<user id>"], "Done": true}. Values are checked against the data source schema before the write: an unknown name, a wrong value type or a select or status option that does not exist is refused in words, with the names the schema has. People and relations take ids; files take links; null clears a value. Formula, rollup, created and edited times, and unique ids are set by Notion and refused.
allow_asyncboolean/ With content, true lets Notion accept a large page and build it in the background; the result is then a task id, not the page.
parent_page_idstring/ The parent page's Notion id or URL. Exactly one of parent_page_id and parent_data_source_id is set.
parent_data_source_idstring/ The parent data source's Notion id (notion_get_database lists a database's data sources). The page becomes a row of it. Exactly one of parent_page_id and parent_data_source_id is set.
notion-mcp__notion_create_view

Adds a view to a Notion database: a table, board, list, calendar, timeline, gallery, form, chart, map or dashboard over one of its data sources, with an optional filter, sorts and layout configuration in Notion's JSON. Returns the view's id, name, type, database_id, data_source_id, url, filter and sorts. The view id is what notion_query_data_source takes as view_id.

Parameters

namestring/ The view's name, shown on its tab.
typestring/ The view's layout, one of the listed types.
sortsarray/ A list of Notion sort objects, sent unchanged.
filterobject/ A Notion filter object, sent unchanged, in the form notion_query_data_source takes.
database_idstring/ The database's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
configurationobject/ Notion's view configuration object, sent unchanged; its type matches the view's type (for example a board's group_by).
data_source_idstring/ The data source the view shows's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
notion-mcp__notion_delete_comment

Deletes one Notion comment. Notion's API deletes only comments created through the same connection, and answers not found for any other. Returns the comment id and deleted true.

Parameters

comment_idstring/ The comment's id, from notion_get_comments or notion_create_comment.
notion-mcp__notion_duplicate_page

Copies one Notion page under a parent page or into a data source, by reading the source page and creating a new one; Notion's API has no duplicate. The copy carries the title, the content as markdown, the icon and cover when they are an emoji or a link, and, in a data source, the property values the target schema accepts. Comments, page history, page-level permissions, files stored in Notion, and blocks with no markdown form are not carried by a duplicate; content is copied as Notion's markdown rendering, so some block formatting can differ. Returns the new page's id and url, properties_not_copied with the reason for each, unknown_block_ids (blocks that could not be copied) and content_truncated.

Parameters

titlestring/ The copy's title. Absent keeps the source page's title.
page_idstring/ The page to copy's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
parent_page_idstring/ The parent page's Notion id or URL. Exactly one of parent_page_id and parent_data_source_id is set.
parent_data_source_idstring/ The parent data source's Notion id (notion_get_database lists a database's data sources). The page becomes a row of it. Exactly one of parent_page_id and parent_data_source_id is set.
notion-mcp__notion_get_comments

Reads the open comments on one Notion page or block. Each comment is reduced to id, discussion_id (comments in one thread share it), created_time, created_by (a name, or an id when Notion gives no name), text and parent. Resolved comments are not returned by Notion's API. The result is {results, has_more, next_cursor}, with the same shape when there are no results. has_more true means more results exist than this page holds, and next_cursor is the cursor argument that returns them.

Parameters

cursorstring/ The next_cursor value from an earlier call with the same arguments. Returns the page of results after that one. Absent for the start of the list.
block_idstring/ The page or block's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
page_sizeinteger/ How many results one call returns, from 1 to 100. Default 50. A full page comes back with has_more true and a next_cursor for the rest.
notion-mcp__notion_get_database

Reads one Notion database and the schema of each of its data sources. In Notion's current API a database is a container of one or more data sources, and rows and properties live on the data source; most databases have one. Returns the database's id, title, url, parent and in_trash, and data_sources: for each, its id, name and properties as a list of {name, type}, with options (the option names) for select, multi_select and status properties. The data source id is the one notion_query_data_source takes.

Parameters

database_idstring/ The database's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
notion-mcp__notion_get_page

Reads one Notion page: its title, url, parent, timestamps and properties, and its content as markdown. Properties come back as plain values keyed by property name, for example {"Status": "Done", "Assignee": ["Test User"], "Due": "2026-10-20"}. A database row is a page, so a row's id returns that row. The markdown is Notion's own rendering; truncated is true when Notion cut it short (very long pages), and unknown_block_ids lists blocks with no markdown form, such as embeds and link previews. A page that is not shared with the DataToRAG connection reads as not found.

Parameters

page_idstring/ The page's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
include_markdownboolean/ Whether to fetch the page content as markdown, which is a second request. Default true. False returns the properties alone, which is enough to read a database row's values.
notion-mcp__notion_list_users

Lists the people and bots in the Notion workspace, or reads one of them. Each user is reduced to id, type (person or bot), name, and email for a person when Notion includes it; a bot also carries workspace_name. With user_id set to "me", returns the DataToRAG connection's own bot user, whose workspace_name names the workspace this connection is in. The result is {results, has_more, next_cursor}, with the same shape when there are no results. has_more true means more results exist than this page holds, and next_cursor is the cursor argument that returns them. A single user comes back in the same shape, as a one-item results list.

Parameters

cursorstring/ The next_cursor value from an earlier call with the same arguments. Returns the page of results after that one. Absent for the start of the list.
user_idstring/ A Notion user id, with or without dashes, or "me" for the connection's own bot user. Absent lists the workspace.
page_sizeinteger/ How many results one call returns, from 1 to 100. Default 50. A full page comes back with has_more true and a next_cursor for the rest.
notion-mcp__notion_move_page

Moves one Notion page to a new parent: another page, or a data source (where it becomes a row). Pages only; Notion's API does not move databases or other blocks. Returns the page's id, url and new parent.

Parameters

page_idstring/ The page's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
parent_page_idstring/ The parent page's Notion id or URL. Exactly one of parent_page_id and parent_data_source_id is set.
parent_data_source_idstring/ The parent data source's Notion id (notion_get_database lists a database's data sources). The page becomes a row of it. Exactly one of parent_page_id and parent_data_source_id is set.
notion-mcp__notion_query_data_source

Reads the rows of one Notion data source (a table inside a database), with an optional filter, sort order, property allowlist, page size and cursor. Each row is {id, url, created_time, last_edited_time, properties}, with properties as plain values keyed by property name. Filter and sorts are passed to Notion as written, in Notion's query JSON; notion_get_database lists the property names and types they refer to. The result is {results, has_more, next_cursor}, with the same shape when there are no results. has_more true means more results exist than this page holds, and next_cursor is the cursor argument that returns them. With view_id in place of data_source_id, the rows are a saved view's, in its own filter and sort order, and each row is its id alone, which is what Notion's view query returns; notion_get_page reads a row's properties. A view query's results stay cached for 15 minutes; total_count is the number of rows it matched, and incomplete true means Notion's 10,000-row cache cut them short.

Parameters

sortsarray/ A list of Notion sort objects, sent unchanged, applied in order. Example: [{"property": "Due", "direction": "ascending"}] or [{"timestamp": "last_edited_time", "direction": "descending"}].
cursorstring/ The next_cursor value from an earlier call with the same arguments. Returns the page of results after that one. Absent for the start of the list.
filterobject/ A Notion filter object, sent unchanged. Example: {"property": "Status", "status": {"equals": "Done"}}; compound filters use {"and": [...]} or {"or": [...]}. Notion's filter reference documents the condition each property type takes. Absent returns every row.
view_idstring/ A database view's id, or a database URL whose ?v= names the view. Takes the place of data_source_id; filter, sorts and properties are not taken with it.
page_sizeinteger/ How many results one call returns, from 1 to 100. Default 25. A full page comes back with has_more true and a next_cursor for the rest.
propertiesarray/ Property names to keep in each row. Absent keeps every property. Names that a row does not have are left out of that row.
data_source_idstring/ The data source's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
notion-mcp__notion_search

Searches the Notion pages and data sources shared with the DataToRAG connection, by title. Notion's search matches titles, not page content, and Notion describes its index as not immediate and not guaranteed complete, so a page created moments ago can be missing. Takes an optional query, an optional object type, a page size and a cursor. Each result is reduced to id, object (page or data_source), title, url, parent ({type, id}) and last_edited_time. The result is {results, has_more, next_cursor}, with the same shape when there are no results. has_more true means more results exist than this page holds, and next_cursor is the cursor argument that returns them. A page or database that exists but is not shared with the connection does not appear.

Parameters

querystring/ Text matched against titles. Absent or empty returns every shared page and data source, most recently edited at the top.
cursorstring/ The next_cursor value from an earlier call with the same arguments. Returns the page of results after that one. Absent for the start of the list.
objectstring/ Limits results to pages or to data sources (the tables inside a database). Absent returns both.
page_sizeinteger/ How many results one call returns, from 1 to 100. Default 20. A full page comes back with has_more true and a next_cursor for the rest.
notion-mcp__notion_update_comment

Replaces the text of one Notion comment. Notion's API edits only comments created through the same connection, and answers not found for any other. Returns the comment.

Parameters

textstring/ The new text, as plain text. Over 2,000 characters is split into several text pieces.
comment_idstring/ The comment's id, from notion_get_comments or notion_create_comment.
notion-mcp__notion_update_data_source

Changes one Notion data source (the table inside a database): its title, its property schema, or whether it is in the trash. The schema change is a map keyed by a property's current name: null removes the property for every row, {"name": "New"} renames it, and a type (alone or with name) changes its type and settings, in the forms notion_create_database takes. A select's options list replaces the whole list. Changing a type can leave existing values unreadable in the new type. Returns the data source's id, title, in_trash and property schema.

Parameters

titlestring/ The data source's title as plain text. Over 2,000 characters is split into several text pieces; the limit is 200,000.
in_trashboolean/ true moves the data source to the trash; false restores it.
propertiesobject/ Property changes keyed by current name: null removes, {"name": "..."} renames, a type or {"type": ..., settings} changes the type.
data_source_idstring/ The data source's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
notion-mcp__notion_update_page

Changes one Notion page's title, property values, icon or cover, applies a template, or moves it to or from the trash. Property values are plain values checked against the page's data source schema (one read of the page and one of its schema). in_trash true is how a page is deleted through Notion's API, and in_trash false restores it. Omitted properties keep their values; a list value replaces the whole list. Returns the page's id, url, title, parent, in_trash and properties.

Parameters

iconstringnull/ An emoji, or an image URL. null removes the icon.
coverstringnull/ A cover image URL. null removes the cover.
titlestring/ The page's title as plain text. Over 2,000 characters is split into several text pieces; the limit is 200,000.
page_idstring/ The page's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
in_trashboolean/ true moves the page to the trash (the delete); false restores it from the trash.
templatestring/ "default" for the data source's default template, or a template page's id or URL. Notion fills in the template's content and properties in the background after the call returns.
propertiesobject/ Property values as plain values keyed by property name, the shape notion_get_page returns: {"Status": "Done", "Tags": ["Docs"], "Due": "2026-10-30", "Estimate": 3, "Owner": ["<user id>"], "Done": true}. Values are checked against the data source schema before the write: an unknown name, a wrong value type or a select or status option that does not exist is refused in words, with the names the schema has. People and relations take ids; files take links; null clears a value. Formula, rollup, created and edited times, and unique ids are set by Notion and refused.
notion-mcp__notion_update_page_content

Edits one Notion page's content as markdown, in one of three modes. edits replaces exact passages, each {old_str, new_str, replace_all_matches}; an edit whose old_str matches nothing is listed in warnings while the others apply. replace_with replaces the whole content. insert adds markdown at the start, at the end (the default), or after a selection. Child pages and databases are kept unless allow_deleting_content is true, and Notion refuses an edit that would remove them. The content of a synced page is not edited through Notion's API. Returns the page id and Notion's warnings.

Parameters

afterstring/ With insert: a selection of existing content to insert after, written "start text...end text".
editsarray/ Passage replacements, at most 100, applied in one request. notion_get_page shows the markdown they match against.
insertstring/ Markdown to add to the page, placed by position or after.
page_idstring/ The page's Notion id, with or without dashes, or a Notion URL that contains it (the id is read from the URL).
positionstring/ With insert: start or end of the page. Default end.
allow_asyncboolean/ true lets Notion apply a large change in the background; the result is then a task id and carries no warnings.
replace_withstring/ Markdown that replaces the page's whole content.
allow_deleting_contentboolean/ With edits or replace_with: true lets the change remove child pages and databases. Sent to Notion only when set.
notion-mcp__notion_update_view

Changes one Notion database view's name, filter, sorts or layout configuration. null clears the filter or the sorts; omitted fields keep their values. A view's type is not changed through Notion's API. Returns the view in the shape notion_create_view returns.

Parameters

namestring/ The view's new name, shown on its tab.
sortsarraynull/ A list of Notion property sort objects, sent unchanged. null clears the sorts.
filterobjectnull/ A Notion filter object, sent unchanged. null clears the filter.
view_idstring/ The view's id, or a database URL whose ?v= names the view.
configurationobject/ Notion's view configuration object, sent unchanged.

Connect

Add this to your MCP client config to access all integrations through the gateway.

{
  "mcpServers": {
    "datatorag": {
      "url": "https://datatorag.com/mcp"
    }
  }
}