Summary
- audit Notion action
_scopes against the QA workbook and real action code paths
- convert invented Notion scope tokens to official capability names
- split multi-capability actions into CNF
all_of requirements when code really performs multiple primary API operations
- leave file-upload scope rows unchanged because current official docs are silent on capability requirements
Validation
uv run python -m black --check <changed notion action files>
uv run python -m compileall apps/notion/actions
uv run python scope-validation script using mercury.tools._base.action.ActionBuilder.validate_scopes(...) across all 48 Notion action classes
Bugbot
Mercury Scope Audit — Notion
Updated 2026-05-15. Covers apps/notion — 42 actions changed in one PR.
Each row shows:
- Action — Composio slug / action file
- Old
_scopes — what shipped on master
- New
_scopes — what this audit ships
- Reason — why the scope changed based on real HTTP calls in code
Notation:
any_of(A, B) = one capability is sufficient
all_of(A, B) = both capabilities are required by Mercury's real execution path
Primary sources used in priority order:
- Action code in
apps/notion/actions/*.py
- Official Notion docs / connection-capabilities guide
- QA remarks only when docs were silent
User information actions
Source: Notion user endpoints and connection-capabilities guide (get-self, get-user, get-users, capabilities). GET /v1/users/{id} and GET /v1/users require the user-information capability family; either the without-email or with-email variant is acceptable.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_ABOUT_USER | about_user.py | None | any_of(User information without email addresses, User information with email addresses) | GET /v1/users/{user_id} needs a user-information capability. |
NOTION_LIST_USERS | list_users.py | None | any_of(User information without email addresses, User information with email addresses) | GET /v1/users needs a user-information capability. |
Insert-content only actions
Source: Notion block-children / page-create / database-create docs (patch-block-children, post-page, create-a-database) plus code grep confirming these actions only perform create/append calls on their primary path.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_APPEND_BLOCK_CHILDREN | append_block_children.py | None | any_of(Insert content) | Primary call is PATCH /v1/blocks/{block_id}/children. |
NOTION_APPEND_CODE_BLOCKS | append_code_blocks.py | None | any_of(Insert content) | Primary call is PATCH /v1/blocks/{block_id}/children. |
NOTION_APPEND_LAYOUT_BLOCKS | append_layout_blocks.py | None | any_of(Insert content) | Primary call is PATCH /v1/blocks/{block_id}/children. |
NOTION_APPEND_MEDIA_BLOCKS | append_media_blocks.py | None | any_of(Insert content) | Primary call is PATCH /v1/blocks/{block_id}/children. |
NOTION_APPEND_TABLE_BLOCKS | append_table_blocks.py | None | any_of(Insert content) | Primary call is PATCH /v1/blocks/{block_id}/children. |
NOTION_APPEND_TASK_BLOCKS | append_task_blocks.py | None | any_of(Insert content) | Primary call is PATCH /v1/blocks/{block_id}/children. |
NOTION_APPEND_TEXT_BLOCKS | append_text_blocks.py | None | any_of(Insert content) | Primary call is PATCH /v1/blocks/{block_id}/children. |
NOTION_CREATE_DATABASE | create_database.py | None | any_of(Insert content) | Primary call is POST /v1/databases. |
NOTION_INSERT_ROW_DATABASE | insert_row_database.py |
Read-content only actions
Source: Notion read endpoints (retrieve-a-page, retrieve-a-block, get-block-children, retrieve-a-page-property, post-search, post-database-query, query-a-data-source, list-templates, deprecated database retrieve/query docs) plus code grep confirming these actions are read-only.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_FETCH_ALL_BLOCK_CONTENTS | fetch_all_block_contents.py | any_of(read:content) | any_of(Read content) | Fixes invented token; action only reads block metadata + children. |
NOTION_FETCH_BLOCK_CONTENTS | fetch_block_contents.py | any_of(read_content) | any_of(Read content) | Fixes invented token; action only reads GET /v1/blocks/{id}/children. |
NOTION_FETCH_BLOCK_METADATA | fetch_block_metadata.py | None | any_of(Read content) | Primary call is GET /v1/blocks/{block_id}. |
NOTION_FETCH_DATABASE | fetch_database.py | None | any_of(Read content) | Primary call is deprecated GET /v1/databases/{database_id}. |
NOTION_FETCH_ROW | fetch_row.py | None | any_of(Read content) | Primary call is GET /v1/pages/{page_id}. |
NOTION_GET_PAGE_MARKDOWN | get_page_markdown.py | None | any_of(Read content) | Primary call is GET /v1/pages/{page_id}/markdown. |
NOTION_GET_PAGE_PROPERTY | get_page_property.py | any_of(read_content) | any_of(Read content) | Fixes invented token; action only reads page-property metadata. |
NOTION_LIST_DATA_SOURCE_TEMPLATES | list_data_source_templates.py | None | any_of(Read content) | Primary call is GET /v1/data_sources/{id}/templates. |
NOTION_NOTION_DATA_FETCHER | notion_data_fetcher.py |
Comment actions
Source: Notion comment docs (create-a-comment, list-comments, retrieve-comment, working-with-comments). Read-comment and insert-comment capabilities are separate from content capabilities.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_CREATE_COMMENT | create_comment.py | any_of(comment:write) | any_of(Insert comments) | Fixes invented token; action creates comments with POST /v1/comments. |
NOTION_FETCH_COMMENTS | fetch_comments.py | any_of(insert_content) | any_of(Read comments) | Wrong capability family; list-comments requires comment-read capability. |
NOTION_RETRIEVE_COMMENT | retrieve_comment.py | None | any_of(Read comments) | GET /v1/comments/{comment_id} is read-comments, not content-read. |
Read + insert actions
Source: real code paths, not just the final write endpoint. These actions always do primary read calls first (searching / validating / copying source content) and then insert content.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_ADD_MULTIPLE_PAGE_CONTENT | add_multiple_page_content.py | any_of(Insert content) | all_of(Read content, Insert content) | Action validates parent / optional after block with reads before PATCH /children. |
NOTION_CREATE_PAGE | create_page.py | any_of(insert_content) | all_of(Read content, Insert content) | Action resolves parent via POST /search and `GET /pages |
NOTION_DUPLICATE_PAGE | duplicate_page.py | None | all_of(Read content, Insert content) | Action reads source page + blocks, then creates a new page and appends copied children. |
NOTION_INSERT_ROW_FROM_NL | insert_row_from_nl.py | None | all_of(Read content, Insert content) | Action reads DB schema first, then creates the page. |
Update-only actions
Source: code grep plus manual review of helper calls. These actions' primary API effect is an update operation; helper reads are either absent or only error/idempotency fallbacks, so the scope was kept narrow.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_DELETE_BLOCK | delete_block.py | any_of(read_content, update_content) | any_of(Update content) | Primary call is DELETE /v1/blocks/{block_id}; the helper GET only runs in an already-archived fallback path. |
NOTION_MOVE_PAGE | move_page.py | any_of(update_content) | any_of(Update content) | Fixes invented token; primary call is POST /v1/pages/{page_id}/move. |
NOTION_UPDATE_PAGE | update_page.py | None | any_of(Update content) | Primary operation is PATCH /v1/pages/{page_id}; helper reads are conditional archive/idempotency checks, not the action's default path. |
Read + update actions
Source: real code paths where the action always (or by default documented behavior) reads current state/schema and then updates content.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_ARCHIVE_PAGE | archive_page.py | any_of(read_content, update_content) | all_of(Read content, Update content) | Action always reads current page state before patching trash/archive state. |
NOTION_UPDATE_BLOCK | update_block.py | any_of(read_content, update_content) | all_of(Read content, Update content) | Default path auto-detects block type with GET /v1/blocks/{id} before PATCH. |
NOTION_UPDATE_ROW_DATABASE | update_row_database.py | any_of(read_content, update_content) | all_of(Read content, Update content) | Action reads page/schema metadata to validate property updates before PATCH /v1/pages/{id}. |
NOTION_UPDATE_SCHEMA_DATABASE | update_schema_database.py | None | all_of(Read content, Update content) | Action reads current database metadata before patching schema. |
Read + insert + update actions
Source: multi-step orchestrator actions whose primary purpose spans all three capability families on the default path.
| Action | File | Old _scopes | New _scopes | Reason |
|---|
NOTION_REPLACE_PAGE_CONTENT | replace_page_content.py | any_of(insert_content, read_content, update_content) | all_of(Read content, Insert content, Update content) | Reads existing children, archives old blocks, and appends replacements; all three capabilities are required by the default workflow. |
NOTION_UPSERT_ROW_DATABASE | upsert_row_database.py | any_of(insert_content, read_content, update_content) | all_of(Read content, Insert content, Update content) | Upsert path always queries first, then may create or update depending on match results. |
Reviewed but not changed
| Action | Why unchanged |
|---|
NOTION_ABOUT_ME | Official get-self docs say the endpoint works for connections with any capabilities, so the current no-scope behavior is correct. |
NOTION_CREATE_FILE_UPLOAD | Official file-upload docs describe the endpoint and auth header but do not currently publish a capability requirement. QA remarks inferred Insert content, but docs were silent, so left unchanged. |
NOTION_LIST_FILE_UPLOADS | Official docs describe the endpoint for the current bot connection but do not currently publish a capability requirement. QA inferred Read content; left unchanged pending a canonical doc statement. |
NOTION_RETRIEVE_FILE_UPLOAD | Official docs describe auth + response shape but do not currently publish a capability requirement. QA remarks conflicted, so left unchanged. |
NOTION_SEND_FILE_UPLOAD | Official docs describe the upload/send/complete flow but do not currently publish a capability requirement for the send step. Left unchanged pending a canonical doc statement. |
Verification
Ran:
uv run python -m black --check <changed notion action files>
uv run python -m compileall apps/notion/actions
uv run python script calling mercury.tools._base.action.ActionBuilder.validate_scopes(...) across all 48 Notion action classes