Best for
- Activation Triggers (explicit user phrases)
- Automatic Triggers (keyword patterns)
- When NOT to Use
MichelKerkmeester/opencode--skilled-agent-loops-with-spec-kit-memory/.opencode/skills/mcp-tooling/mcp-notion/SKILL.md
Operates a Notion workspace through the official Notion MCP (@notionhq/notion-mcp-server, 24 tools) over Code Mode, fills 5 API gaps with direct Notion API calls, and routes between the headless local-stdio backend and the interactive remote OAuth backend. Carries a Notion knowledge layer (data-source model, 22 property types, relations, rollups, Formulas 2.0). Embedded registration and agent safety invariants.
Decision brief
Notion workspace operations via the official Notion MCP (@notionhq/notion-mcp-server, 24 tools) over Code Mode, plus direct Notion API calls for the five capabilities the MCP does not expose. It knows Notion's data model at the schema layer — data sources, 22 property types, rel…
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/MichelKerkmeester/opencode--skilled-agent-loops-with-spec-kit-memory --skill ".opencode/skills/mcp-tooling/mcp-notion"Inspect the Agent Skill "mcp-notion" from https://github.com/MichelKerkmeester/opencode--skilled-agent-loops-with-spec-kit-memory/blob/3d386ee21366523774d89c0aff3ebbbc8fa7ff10/.opencode/skills/mcp-tooling/mcp-notion/SKILL.md at commit 3d386ee21366523774d89c0aff3ebbbc8fa7ff10. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Two shape rules cause most Notion write failures:
"notion", "notion mcp", "notion api", "mcp-notion"
"notion", "notion mcp", "notion api", "mcp-notion"
notion + any action verb (create, read, update, query, search, append, archive)
Community Notion MCP servers (suekou/mcp-notion-server, awkoy/notion-mcp-server) — this mode uses the official @notionhq/notion-mcp-server only.
Permission review
The documentation includes network, browsing, or remote request actions.
return "REMOTE_MCP" # https://mcp.notion.com/mcp (Streamable HTTP + OAuth)The documentation includes network, browsing, or remote request actions.
| **Views** (create/list/query) | **direct API** | data-source view endpoints | Not in MCP |Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 93/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 34 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Notion workspace operations via the official Notion MCP (@notionhq/notion-mcp-server, 24 tools) over Code Mode, plus direct Notion API calls for the five capabilities the MCP does not expose. It knows Notion's data model at the schema layer — data sources, 22 property types, relations, rollups, Formulas 2.0 — so an agent can create, query, and extend a workspace without guessing.
Two shape rules cause most Notion write failures:
2025-09-03). A database is now a container of one or more data sources; queries, schema, relations, and rollups all target a data source id, not a database id. Using a database id where a data-source id is required is the most common 400.Markdown round-trip tools (retrieve-page-markdown / update-page-markdown) require API version 2026-03-11 and are the token-efficient path for AI. Full detail: references/database-model.md and references/mcp-tools.md.
Failure symptom: a validation_error naming data_source_id, or property writes that vanish, means a database id was used where a data-source id was required, or content was routed to the wrong surface.
notion + any action verb (create, read, update, query, search, append, archive)create-a-page, query-data-source, retrieve-a-page (namespaced notion.notion_<name>)@notionhq/notion-mcp-server only.references/migration-inventory.md), not the importer.@markdown / sk-doc.mcp-obsidian, mcp-click-up).ALWAYS: SKILL.md (this file)
ON_DEMAND: references/mcp-tools.md (24-tool catalog + Code Mode invocation)
references/api-gap-tools.md (direct API for the 5 uncovered capabilities)
references/property-types.md (22 property types: schema, value, filter/sort)
references/database-model.md (data-source hierarchy, relations, rollups, Formulas 2.0)
references/troubleshooting.md (auth, rate-limit, version, deprecation-migration)
references/migration-inventory.md (Notion→Obsidian migration read-side inventory method)
npx @notionhq/notion-mcp-server, NOTION_TOKEN) for Code Mode / automated sessions; remote OAuth (mcp.notion.com) only when an interactive browser session is available. Code Mode is headless, so it uses the local stdio backend.def resolve_notion_backend(runtime):
"""Pick the Notion backend. Probe, never assume.
runtime.interactive -> a browser/OAuth session is available
runtime.oauth_token -> remote Notion MCP OAuth completed
runtime.notion_token -> NOTION_TOKEN (ntn_...) present for Code Mode
"""
# Remote MCP is OAuth-only and CANNOT run headless. Prefer it only when a
# human/browser session is present; it adds async-task tools the local lacks.
if runtime.interactive and runtime.oauth_token:
return "REMOTE_MCP" # https://mcp.notion.com/mcp (Streamable HTTP + OAuth)
# Default for Code Mode and any automated session: the local stdio server.
# Deprecated by Notion but the only headless-capable backend.
if runtime.notion_token:
return "LOCAL_STDIO" # notion manual in .utcp_config.json, via Code Mode
return "ESCALATE" # no auth — direct the user to INSTALL-GUIDE.md
| Operation | Surface | Tool / call | Notes |
|---|---|---|---|
| Create / retrieve / update / archive a page | MCP | create-a-page / retrieve-a-page / update-page-properties / archive-a-page | Archive, not hard delete |
| Page body as markdown (read/write) | MCP | retrieve-page-markdown / update-page-markdown | Needs API 2026-03-11; token-efficient |
| Append / update / delete blocks | MCP | block tools (append-block-children, update-a-block, delete-a-block, …) | Page content surface |
| Query / retrieve / update a data source | MCP | query-data-source + data-source tools | Target the data-source id |
| Comments (create, list) | MCP | comment tools | — |
| Users (list, retrieve, bot) | MCP | user tools | — |
| Search (by title) | MCP | search | Title-only; no full-text content search |
| File uploads | direct API | POST /v1/file_uploads (+ send/complete) | Not in MCP — see api-gap-tools.md |
| Views (create/list/query) | direct API | data-source view endpoints | Not in MCP |
| Page property items (non-truncated) | direct API | GET /v1/pages/{id}/properties/{prop} | Not in MCP |
| Async tasks (poll) | direct API / remote MCP | task-status endpoint | Native on remote MCP only |
| Daily notes | convention | knowledge-layer pattern | No API — see database-model.md |
from pathlib import Path
SKILL_ROOT = Path(__file__).resolve().parent
RESOURCE_BASES = (SKILL_ROOT / "references",)
DEFAULT_RESOURCE = "references/mcp-tools.md"
# Fallback-only: DEFAULT_RESOURCE is a defer-time suggestion, never unioned into a
# route's loaded set. Scored routes load exactly RESOURCE_MAP[intent]; zero-score
# routes load nothing and ask for disambiguation instead.
DEFAULT_RESOURCE_SEMANTICS = "fallback-only"
UNKNOWN_FALLBACK_CHECKLIST = [
"Confirm whether the request is Notion page/block ops, data-source/schema ops, an API-gap capability, install/auth, or troubleshooting",
"Provide the page id, data-source id, property name, or error text",
"Confirm whether NOTION_TOKEN (headless/Code Mode) or a remote OAuth session is available",
"Confirm the verification command before completing any write action",
]
INTENT_SIGNALS = {
"NOTION_PAGES": {
"weight": 5,
"keywords": ["page", "pages", "block", "blocks", "append", "markdown", "content",
"create page", "retrieve page", "update page", "archive", "comment",
"user", "search", "title search", "sub-page", "child page"],
},
"NOTION_DATA": {
"weight": 5,
"keywords": ["database", "data source", "datasource", "query", "row", "rows",
"relation", "rollup", "formula", "schema", "property", "filter",
"sort", "data_source_id", "two-way relation", "aggregate"],
},
"NOTION_API_GAP": {
"weight": 6,
"keywords": ["file upload", "upload a file", "view", "views", "property item",
"non-truncated", "async task", "poll task", "daily note"],
},
"NOTION_KNOWLEDGE": {
"weight": 5,
"keywords": ["property type", "property types", "select", "multi-select", "status",
"formula function", "rollup function", "data model", "hierarchy"],
},
"NOTION_MIGRATION": {
"weight": 5,
"keywords": ["migration", "migrate", "migration inventory", "notion import",
"obsidian import", "workspace inventory", "pre-migration inventory",
"relation recovery", "rollup recovery", "comment reconstruction",
"parity verification"],
},
"INSTALL": {
"weight": 6,
"keywords": ["install", "setup", "not found", "not installed", "notion token",
"ntn_", "integration token", "api token", "mcp config", "register",
"configure", "configuration", "getting started", "onboarding",
"oauth", "connect notion", "how do i install"],
},
"TROUBLESHOOT": {
"weight": 6,
"keywords": ["error", "failed", "not working", "401", "403", "429", "400",
"rate limit", "unauthorized", "forbidden", "slow", "timeout",
"validation_error", "data_source_id", "deprecated", "won't connect",
"can't connect", "version mismatch", "object_not_found"],
},
}
# NOTE: no "DEFAULT" entry — route_notion_resources() never indexes RESOURCE_MAP by
# that key; the selected `intent` is always one of the seven INTENT_SIGNALS keys. The
# no-match case is owned by DEFAULT_RESOURCE, whose fallback-only semantics mean it is
# SUGGESTED beside the disambiguation checklist, never loaded — so mcp-tools.md cannot
# leak into the DATA / API_GAP / KNOWLEDGE / MIGRATION / INSTALL / TROUBLESHOOT routes.
RESOURCE_MAP = {
"NOTION_PAGES": ["references/mcp-tools.md"],
"NOTION_DATA": ["references/database-model.md", "references/property-types.md",
"references/mcp-tools.md"],
"NOTION_API_GAP": ["references/api-gap-tools.md"],
"NOTION_KNOWLEDGE": ["references/property-types.md", "references/database-model.md"],
"NOTION_MIGRATION": ["references/migration-inventory.md"],
"INSTALL": ["references/troubleshooting.md"],
"TROUBLESHOOT": ["references/troubleshooting.md"],
}
def discover_markdown_resources() -> set[str]:
docs = []
for base in RESOURCE_BASES:
if base.exists():
docs.extend(path for path in base.rglob("*.md") if path.is_file())
return {doc.relative_to(SKILL_ROOT).as_posix() for doc in docs}
def _guard_in_skill(relative_path: str) -> str:
resolved = (SKILL_ROOT / relative_path).resolve()
resolved.relative_to(SKILL_ROOT)
if resolved.suffix.lower() != ".md":
raise ValueError(f"Only markdown skill resources are routable: {relative_path}")
return resolved.relative_to(SKILL_ROOT).as_posix()
def load_if_available(relative_path, loaded, seen, inventory) -> None:
guarded = _guard_in_skill(relative_path)
if guarded in inventory and guarded not in seen:
load(guarded)
loaded.append(guarded)
seen.add(guarded)
def route_notion_resources(request: str) -> dict:
"""Score intent labels and load available Notion reference docs."""
inventory = discover_markdown_resources()
loaded, seen = [], set()
request_lower = request.lower()
scores = {}
for intent, config in INTENT_SIGNALS.items():
score = sum(config["weight"] for kw in config["keywords"] if kw in request_lower)
if score > 0:
scores[intent] = score
if not scores:
return {
"load_level": "UNKNOWN_FALLBACK",
"needs_disambiguation": True,
"disambiguation_checklist": UNKNOWN_FALLBACK_CHECKLIST,
"suggested_fallback": DEFAULT_RESOURCE,
"resources": loaded,
}
# Error/install keywords win regardless of other signals.
if scores.get("TROUBLESHOOT", 0) > 3:
intent = "TROUBLESHOOT"
elif scores.get("INSTALL", 0) > 4:
intent = "INSTALL"
else:
intent = max(scores, key=scores.get)
for resource in RESOURCE_MAP[intent]:
load_if_available(resource, loaded, seen, inventory)
if not loaded:
return {
"load_level": "UNKNOWN_FALLBACK",
"notice": f"No Notion reference docs available for intent '{intent}'",
"disambiguation_checklist": UNKNOWN_FALLBACK_CHECKLIST,
"suggested_fallback": DEFAULT_RESOURCE,
"resources": loaded,
}
return {"intent": intent, "resources": loaded}
| Dimension | Local stdio (default, headless) | Remote MCP (interactive) |
|---|---|---|
| Transport | stdio via npx -y @notionhq/notion-mcp-server | Streamable HTTP at https://mcp.notion.com/mcp |
| Auth | NOTION_TOKEN (ntn_… internal-integration token) | OAuth (browser) |
| Headless? | Yes — the only Code-Mode-capable backend | No — interactive only |
| Tool names | create-a-page, retrieve-a-page, … | notion-create-pages, … + async tasks |
| Status | Deprecated by Notion, still functional | Recommended by Notion |
| Used by | Code Mode / automation | Human-in-the-loop clients |
Deprecation note. Notion is deprecating the open-source local server in favor of the remote OAuth server. Code Mode is headless, so the local stdio server is the correct (and only) choice here today;
references/troubleshooting.mdcarries the local→remote migration path for when the operator moves to an interactive workflow.
The registered notion manual launches @notionhq/notion-mcp-server over stdio with npx -y. It talks to Notion's REST API with the integration's NOTION_TOKEN.
Prerequisites:
notion manual in .utcp_config.json (already registered — see INSTALL-GUIDE).notion_NOTION_TOKEN (an ntn_… internal-integration token) available to Code Mode, and the integration granted content access to the target pages/data sources.Configuration (.utcp_config.json, manual_call_templates) — already applied:
{
"name": "notion",
"call_template_type": "mcp",
"config": {
"mcpServers": {
"notion": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@notionhq/notion-mcp-server"],
"env": { "NOTION_TOKEN": "${notion_NOTION_TOKEN}" }
}
}
}
}
The notion_ env prefix matches the manual name notion, so ${notion_NOTION_TOKEN} resolves. This mode documents that registration; it does not rewrite config files.
Tools: the server exposes 24 notion_* tools across 6 domains — pages (7), blocks (5), data sources (6), comments (2), users (3), search (1). Confirm every name with tool_info() / list_tools() before calling; the full catalog with per-tool inputs is in references/mcp-tools.md.
Invocation via Code Mode (call_tool_chain takes a single code string):
// Code Mode namespaces each tool as notion.notion_<tool_name>. Notion tool names are
// HYPHENATED (create-a-page, retrieve-a-page), so notion.notion_retrieve-a-page is invalid
// JS (it parses as subtraction) — use hyphen-safe BRACKET access. VERIFY once the manual is
// registered: run list_tools() to read the exact callable; Code Mode MAY instead sanitize
// hyphens to underscores (notion.notion_retrieve_a_page). Bracket form is the safe default.
const result = await call_tool_chain({
code: `
const hit = await notion["notion_search"]({ query: "Roadmap" });
const page = await notion["notion_retrieve-a-page"]({ page_id: hit.results[0].id });
return page;
`,
});
File uploads, views, non-truncated page property items, and async-task polling exist in the Notion REST API but not in the MCP tool surface; daily notes are a convention with no endpoint. Fill them with direct calls (Authorization: Bearer $notion_NOTION_TOKEN, Notion-Version: 2025-09-03, or 2026-03-11 for markdown/async). Recipes: references/api-gap-tools.md.
When to prefer the remote MCP: interactive sessions where OAuth is available and async-task tools are needed. Otherwise stay on the local stdio backend.
Limitations: search is title-only (no full-text content search); pages archive rather than hard-delete; the workspace is cloud-only (no headless filesystem); rate limit ≈ 3 req/s per integration with Retry-After.
NOTION_TOKEN; only route to the remote OAuth backend when an interactive session is genuinely available. Probe, do not assume.data_source_id.list_tools() / tool_info() before calling — Notion tool names are hyphenated (create-a-page), so the hyphen-safe callable is bracket-access notion["notion_create-a-page"] unless list_tools() shows Code Mode sanitized them to underscores.429 back off with jitter and honor Retry-After. Batch reads where possible.2025-09-03 for most tools, 2026-03-11 for markdown round-trip and async tasks.notion_NOTION_TOKEN in the Code Mode environment; never inline ntn_… in a command, note, or example.@notionhq/notion-mcp-server..utcp_config.json, .env.example, opencode.json, or hub files — the notion manual is already registered there; if it needs changing, print the config for the user and never write these files programmatically.search matches titles only; to find by content, retrieve candidate pages and inspect client-side.NOTION_TOKEN and no OAuth session → direct the user to INSTALL-GUIDE.md (create an internal integration, copy the ntn_… token to .env, grant content access).401/403 → the token is missing/wrong, or the integration was not granted access to the target page/data source.validation_error naming data_source_id → a database id was used where a data-source id is required (2.0 migration); resolve the data source first.429 → the integration is over ≈3 req/s; reduce concurrency, add backoff, or batch.references/troubleshooting.md.notion["notion_search"]({query}) returns results (empty is valid) — confirms the manual + token worklist_tools() shows notion.notion_* entries; tool_info("notion.notion_retrieve-a-page") resolves a live schemaquery-data-source returns rows for a data-source id the integration can accessupdate-page-markdown → retrieve-page-markdown) preserves contentGET) returns data with the Bearer token from the environmentbash scripts/doctor.sh reports Node/npx, the notion manual, and notion_NOTION_TOKEN state without mutatingGate 2 (Skill Routing): activates at ≥0.8 confidence for Notion workspace requests. The advisor matches on notion, notion mcp, notion database, notion data source, notion page, notion token.
Code Mode MCP: the official Notion MCP tools are invoked via mcp__code_mode__call_tool_chain; Code Mode namespaces each as notion.notion_<tool_name> — use hyphen-safe bracket access since Notion names are hyphenated. See references/mcp-tools.md.
Migration (packet 015): this mode is the read-side inventory enabler for Notion→Obsidian migration (references/migration-inventory.md) — the same knowledge layer that operates a live workspace reads its structure to drive an Obsidian import; the write-side reconstruction method lives in mcp-obsidian's references/notion-migration.md.
Memory: save Notion workflow context (target data-source ids, integration name, backend) with /memory:save when switching sessions.
Tool Usage: Bash for direct API calls and scripts/; mcp__code_mode__call_tool_chain for MCP operations; Read to load references on demand.
tool_info())| Domain | Count | Representative tools |
|---|---|---|
| Pages | 7 | create, retrieve, update, archive; markdown retrieve/update |
| Blocks | 5 | retrieve, children, append, update, delete |
| Data sources | 6 | query, retrieve, update, create, templates |
| Comments | 2 | create, list |
| Users | 3 | list, retrieve, bot |
| Search | 1 | title search |
references/api-gap-tools.md)| Gap | Endpoint family |
|---|---|
| File uploads | POST /v1/file_uploads (+ send / complete) |
| Views | data-source view endpoints |
| Page property items | GET /v1/pages/{id}/properties/{prop} |
| Async tasks | task-status endpoint (native on remote MCP) |
| Daily notes | convention (no endpoint) |
// 24 tools, namespaced notion.notion_<tool_name>; hyphenated names → bracket access.
// Confirm the exact callable with list_tools() before relying on either form.
await call_tool_chain({
code: `
const ds = await notion["notion_query-data-source"]({
data_source_id: "DATA_SOURCE_ID",
filter: { property: "Status", status: { equals: "In progress" } },
});
return ds.results.map(p => p.id);
`,
});
Reference Files (load on demand via router):
references/mcp-tools.md — the 24-tool catalog by domain + Code Mode invocationreferences/api-gap-tools.md — direct Notion API for the 5 uncovered capabilitiesreferences/property-types.md — 22 property types: schema, value, filter/sortreferences/database-model.md — data-source hierarchy, relations, rollups, Formulas 2.0references/troubleshooting.md — auth, rate-limit, API-version, deprecation-migrationreferences/migration-inventory.md — Notion→Obsidian migration read-side inventory method: the 7-step inventory procedure, the 5 API-gap reads it uses, and the read-limit constraints that shape itInstall guide (front door): INSTALL-GUIDE.md — token setup, Code Mode registration, dual-backend config.
Scripts:
scripts/install.sh — prints the Code Mode manual + notion_NOTION_TOKEN env key (read-only)scripts/doctor.sh — diagnoses Node/npx, the manual, and the tokenEmbedded Servers:
mcp-servers/notion-mcp/README.md — official @notionhq/notion-mcp-server config notes (stdio, NOTION_TOKEN, dual-backend)Examples:
examples/README.md — Code Mode Notion workflow examplesRelated Skills:
mcp-click-up — structural sibling (light workflow mode)mcp-obsidian — sibling knowledge app; the 015 migration counterpartmcp-code-mode — Code Mode MCP orchestration (used for Notion MCP invocation)External:
Frequently asked questions
Notion workspace operations via the official Notion MCP (@notionhq/notion-mcp-server, 24 tools) over Code Mode, plus direct Notion API calls for the five capabilities the MCP does not expose. It knows Notion's data model at the schema layer — data sources, 22 property types, rel…
The source record exposes this install command: npx skills add https://github.com/MichelKerkmeester/opencode--skilled-agent-loops-with-spec-kit-memory --skill ".opencode/skills/mcp-tooling/mcp-notion". Inspect the command and pinned source before running it.
Static rules flagged network in the source; the page lists the matching lines and excerpts.
Alternatives
coreyhaines31/marketingskills
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program
garrytan/gbrain
End-to-end discipline for turning any large data source (audio libraries, email takeouts, document corpora, chat exports, API dumps) into brain pages at scale. The lifecycle spine: SCHEMA → ACCESS → TRIAL → EVALUATE → IMPROVE → CODIFY → TEST → SKILLIFY → BULK → MONITOR. State is tracked in a durable JSON manifest (see MANIFEST-PATTERN.md) so any crash, session boundary, or subagent fan-out resumes from ground truth instead of memory.
alirezarezvani/claude-skills
App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklist
dotnet/skills
Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP). Use when user asks to "migrate to MTP", "switch from VSTest", "enable Microsoft.Testing.Platform", "use MTP runner", set OutputType=Exe only for test projects in Directory.Build.props, or mentions EnableMSTestRunner, EnableNUnitRunner, or UseMicrosoftTestingPlatformRunner. USE FOR: MTP behavioral differences vs VSTest (exit code 8, zero tests discovered, --ignore-exit-code, TESTINGPLATFORM_EXITCODE_IGNORE); centralizing