The Notion MCP server exposes 24 tools whose definitions cost approximately 17,476 tokens in every single conversation, whether or not any of them is ever called. Against the 19 static rules in the catalogue it scores 86 out of 100 — grade B — raising 95 scored findings in 3 of the six categories.
Its strongest category is naming at 100/100; the weakest is schemas at 68/100. It is catalogued here under Productivity and Docs, alongside other servers agents reach for in the same kind of work.
At 17,476 tokens the surface takes about 9% of a 200,000-token context window, and it takes it on every turn — this is rent, not a one-off cost. That averages 728 tokens per tool definition, counted with the o200k_base encoding.
Against a category median of 2,360 tokens, this surface runs about 641% above the middle of the pack.
The findings below are grouped by severity, and each links to the rule that produced it.
`schemas/loose` accounts for 40 findings. Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes. On this surface it fires on `API-get-self`, `API-post-search` and `API-patch-block-children`, among 8 others.
37 findings come from `schemas/param-desc-missing`. Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes. On this surface it fires on `API-get-user`, `API-post-search` and `API-update-a-block`, among 9 others.
`schemas/complexity-budget` contributes 8 findings. Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often. On this surface it fires on `API-patch-page`, `API-post-page` and `API-create-a-comment`, among 4 others.
Notion's surface raises 4 further rules — `design/overlap-cluster`, `design/enum-in-prose`, `surface/token-budget` and `surface/tool-budget` — listed in full below.
warn 95 findings
Findings that measurably degrade tool selection.
Parameter "direction" enumerates its values in prose but declares no enum.
Values enumerated in prose ('one of: a, b, c') are invisible to schema validation: the model can misspell them and the server only finds out at call time. If the value set is closed, encode it as an enum.
The direction to sort. Possible values include `ascending` and `descending`.
Parameter "timestamp" enumerates its values in prose but declares no enum.
Values enumerated in prose ('one of: a, b, c') are invisible to schema validation: the model can misspell them and the server only finds out at call time. If the value set is closed, encode it as an enum.
The name of the timestamp to sort against. Possible values include `last_edited_time`.
Parameter "property" enumerates its values in prose but declares no enum.
Values enumerated in prose ('one of: a, b, c') are invisible to schema validation: the model can misspell them and the server only finds out at call time. If the value set is closed, encode it as an enum.
The name of the property to filter by. Currently the only property you can filter by is the object type. Possible values include `object`. Limitation: Currently the only filter allowed is `object` which will filter by type of object (either `page` or `data_source`)
4 tools share the name prefix "api_get" — likely one tool with include-flags or a mode parameter.
Tools whose descriptions substantially overlap force the model to pick between near-synonyms — the classic cause of wrong-tool calls. Clusters are usually one tool wearing several names; consolidate with include-flags or a mode parameter.
API-get-user, API-get-users, API-get-self, API-get-block-children
7 tools share the name prefix "api_retrieve" — likely one tool with include-flags or a mode parameter.
Tools whose descriptions substantially overlap force the model to pick between near-synonyms — the classic cause of wrong-tool calls. Clusters are usually one tool wearing several names; consolidate with include-flags or a mode parameter.
API-retrieve-a-block, API-retrieve-a-page, API-retrieve-a-page-property, API-retrieve-a-comment, API-retrieve-a-data-source, API-retrieve-a-database, API-retrieve-page-markdown
3 tools share the name prefix "api_update" — likely one tool with include-flags or a mode parameter.
Tools whose descriptions substantially overlap force the model to pick between near-synonyms — the classic cause of wrong-tool calls. Clusters are usually one tool wearing several names; consolidate with include-flags or a mode parameter.
API-update-a-block, API-update-a-data-source, API-update-page-markdown
21 tools have overlapping descriptions (trigram Jaccard up to 0.85) — consolidation candidates.
Tools whose descriptions substantially overlap force the model to pick between near-synonyms — the classic cause of wrong-tool calls. Clusters are usually one tool wearing several names; consolidate with include-flags or a mode parameter.
API-get-user, API-get-self, API-post-search, API-get-block-children, API-patch-block-children, API-retrieve-a-block, API-update-a-block, API-delete-a-block, API-retrieve-a-page, API-patch-page, API-post-page, API-retrieve-a-page-property, API-retrieve-a-comment, API-create-a-comment, API-query-data-source, API-retrieve-a-data-source, API-update-a-data-source, API-create-a-data-source, API-list-data-source-templates, API-retrieve-a-database, API-move-page
2 tools have overlapping descriptions (trigram Jaccard up to 0.80) — consolidation candidates.
Tools whose descriptions substantially overlap force the model to pick between near-synonyms — the classic cause of wrong-tool calls. Clusters are usually one tool wearing several names; consolidate with include-flags or a mode parameter.
API-retrieve-page-markdown, API-update-page-markdown
Input schema uses oneOf/anyOf 3 times (budget 2).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Input schema uses oneOf/anyOf 3 times (budget 2).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Input schema nests 4 levels deep (budget 3).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Input schema uses oneOf/anyOf 3 times (budget 2).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Input schema uses oneOf/anyOf 3 times (budget 2).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Input schema uses oneOf/anyOf 3 times (budget 2).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Input schema nests 4 levels deep (budget 3).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Input schema uses oneOf/anyOf 4 times (budget 2).
Deeply nested inputs, very wide property lists, and stacked oneOf/anyOf force the model to solve a constraint puzzle before every call. Flatter, narrower inputs get filled correctly more often.
Bare "type: object" with no properties at /inputSchema — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/sort/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/filter/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/children/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/children/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/type/anyOf/0 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/type/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/properties/anyOf/0 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/properties/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/properties/anyOf/0 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/properties/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/children/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/children/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/parent/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/rich_text/items/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/rich_text/items/anyOf/0/properties/text — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/rich_text/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/rich_text/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/filter_properties/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/filter_properties/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/filter/anyOf/0 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/filter/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/sorts/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/sorts/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/title/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/title/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/description/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/description/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/properties/anyOf/0 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/properties/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/properties/anyOf/0 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/properties/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Bare "type: object" with no properties at /inputSchema/properties/title/items/anyOf/2 — the model has nothing to go on.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/title/items/anyOf/2 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/replace_content/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/update_content/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/update_content/anyOf/0/properties/content_updates/items — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/insert_content/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/insert_content/anyOf/0/properties/position — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
"additionalProperties: true" at /inputSchema/properties/replace_content_range/anyOf/0 — accepts arbitrary keys silently.
Loose schemas (bare objects, untyped arrays, open additionalProperties) turn schema validation off exactly where the model is most likely to hallucinate argument shapes.
Parameter "user_id" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "sort" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "filter" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "type" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "properties" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "archived" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "icon" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "cover" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "emoji" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "external" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "type" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "url" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "parent" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "properties" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "parent" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "rich_text" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "text" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "filter" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "sorts" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "start_cursor" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "page_size" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "archived" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "in_trash" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "title" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "description" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "properties" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "parent" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "properties" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "title" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "start_cursor" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "page_size" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "parent" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "replace_content" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "update_content" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "insert_content" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "replace_content_range" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
Parameter "type" has no description.
Undescribed parameters get filled by guesswork. Every property the model must populate deserves a sentence saying what it means and what shape it takes.
tools/list footprint is ~17476 tokens (o200k_base, approximate) — above the 10000-token budget. Every conversation pays this before the first user message.
The serialized tools/list payload is injected into every conversation that connects the server, before any work happens. Its token cost is a per-message tax.
Server exposes 24 tools (warn threshold 20 exceeded). Consider consolidating into fewer intent-shaped tools.
Tool selection accuracy degrades as the surface grows: more near-neighbours to confuse, more tokens spent before the first user message. Fewer, intent-shaped tools outperform one-tool-per-endpoint surfaces.
3 of the six categories come through clean: naming, descriptions and annotations. In practice that means the tool names follow one convention end to end, every tool carries a description long enough to disambiguate it and read and write operations are machine-distinguishable before a client runs them.
The heaviest definition is `API-update-page-markdown` at 1,281 tokens, roughly 7% of the total; the lightest, `API-get-self`, costs 594. Every tool carries a description, ranging from 49 to 280 characters.
All 24 declare annotations, so a client can tell a read from a write before running one. Input schemas average 3 top-level parameters and nest 4 levels deep at their worst, with 1 tool taking no input at all.
All 24 tools as published, heaviest first. Token counts use the o200k_base encoding and are approximate.
Notion ranks 3 of 3 scanned Productivity and Docs servers, 10 points below the category median of 96.
The closest comparisons in this directory are Memory, Microsoft Learn and MongoDB, which sit nearest on score and face the same kind of surface-design pressure.