Notion
How the workspace is navigated and edited through the MCP tools. Tool names below are
written unprefixed (fetch, search, update-page); clients namespace them, usually
as notion-fetch and so on.
How the workspace is laid out
- A teamspace root holds two pages: the container page and
Unsorted.Unsortedis the inbox and stays outside the container on purpose. - The container holds everything else: the root pillars (
About,Finances,Projects), the domain pages, andArchive. Each domain page holds its pillar children. - A domain page carries its own instructions inline. There is no separate README or instructions child page: the prose sits on the domain page above its child list, and Notion renders the child list itself. Never write an inventory of child pages into a page's text. It drifts, and the rendered list does not.
- Agent-facing rules live under an
Agentsheading at the end of that prose. Everything above it describes the domain and is written for a person. Everything under it is working rules: what to load, what the guardrails are, what format a new page takes. - Teamspaces mirror each other's names on purpose. The same domain and pillar names appear in more than one teamspace, so a bare title never identifies a page. See "Duplicate titles" below.
- The workspace is the authority, not this description. When what you fetch does not match the shape above, follow what is there and say so.
Load skills before pages
- Pull every skill the session will plausibly need up front, before the first page fetch. Deciding at the point of need is too late: a leaf page fetch is large, and a skill loaded after it competes with the page content for the room to use it.
- The writing skill applies to every word written into a page, so it is always one of them.
- Read the domain page first, then load in one pass what its
Agentssection names and the skills that match the task:listing-scrapefor a rental listing,jd-scrapefor a job posting,gitwhen repo work follows from the note. - Err toward loading one skill too many. A skill is small next to a page of content, and the cost of not having it is work redone.
Mapping the tree
- No tool lists a teamspace's root children.
get-teamsreturns teamspace ids, fetching a teamspace id returns 404, andsearchreturns a flat ranked list with no parent information. Do not go looking for the tool that does this. - Start from a known page URL and fetch downward. This is the cheap path, and the reason the container page exists.
- Without a known URL, reconstruct roots from
list-recent-pages, thenfetchthe candidates. A page sitting at teamspace root is the one whose ancestor path comes back empty. That empty path is the only available signal. - Scope every search.
searchtakesteamspace_idandpage_url, and a scoped query beats a bare one by enough to be worth the extra fetch that finds the id. Setpage_sizelow andmax_highlight_lengthto 0 when you only need ids.
Fetch cost
- Fetch cost is bimodal. A container page returns little more than its child list. A leaf page returns its full content.
- A database page fetch costs like a leaf. It returns the full schema, every property, and every view definition. When you want rows, query the data source instead. Fetch the database page only when you need the schema itself.
- Map containers first, open leaves last. Walk the structure with container fetches until you know which leaf you actually need, then open that one. Opening leaves early is the main way a Notion session runs out of context.
Duplicate titles
- Disambiguate by ancestor path. Titles repeat across teamspaces by design, and
searchreturns the duplicates undifferentiated. - Icons do not disambiguate. They do not appear in search results.
- Two candidates with the same title means stop and ask, rather than picking the higher-ranked one. Filing a note under the wrong person or the wrong domain is silent and hard to notice later.
Editing
- Read
notion://docs/enhanced-markdown-specbefore writing page content the first time in a session. Pass the URI as theidtofetch. Notion-flavored markdown is not standard markdown, and guessing its syntax corrupts the page. update_contentmatchesold_strexactly, against Notion's escaped form. A dollar amount is stored as\$3,600, not$3,600. Fetch the page immediately before the edit and copyold_strout of that output rather than retyping it.- Send the smallest edit region that is still unique. Leave
replace_all_matchesat its default: failing on an ambiguous match is the behavior you want. - Never pass
allow_deleting_content: trueon a page with child pages. Without it, anupdate_contentorreplace_contentthat would orphan children fails and lists them, which is the guard working. Pass it only when the user has asked for those specific children to go. - Prefer
update_contentoverreplace_content. Replacing whole page content is how children get orphaned in the first place.
There is no delete
- No tool trashes or deletes a page.
move-pagesis the only relocation available. - Never offer deletion. Move the page under
Archiveinstead, then tell the user to trash it themselves if that is what they want. - Moving to the workspace parent makes a page private. Use a
page_idparent.
Stale reads
- Re-fetch anything read more than a few turns ago, before acting on it. The user edits the workspace while you work.
- A write against a stale id fails, and a page the user trashed mid-task fails a
move-pagescall with an error that does not obviously say "trashed". Re-fetch first and the error never happens.