Purpose
Easy Notion MCP is a "Markdown-first" MCP server for connecting AI agents to Notion. The agent side only writes Markdown, and the server is responsible for converting it into Notion's block API format, with support for converting back to Markdown in reverse. Its target scenario is to let agents edit Notion page content in the manner of "editing code text": read a page to get Markdown, modify the string, then write it back.
Main Capabilities
- Markdown and Notion block interconversion: supports standard GFM Markdown, preserves 24 block types, and documents round-trip support.
- Saves tokens: reading page content uses about 6-7 times fewer response tokens than the official Notion MCP. The official server returns raw API JSON, with each block carrying about 120 tokens of metadata; easy-notion-mcp omits this metadata, which is not needed when reading content, while preserving >=94% of page content. The savings come from metadata omission, not encoding efficiency; when information is equal, the two formats have similar cost. What is typically omitted is block UUIDs, timestamps, author objects, annotation wrappers, etc. Database queries also have about a 7x benefit with full content.
- 43 tools, divided into 7 categories (42 under HTTP, excluding the stdio-only create_page_from_file), covering pages, navigation, databases, and many other types of operations.
- Page operations: write/read markdown, replace_content, update_section (located by heading name), find_replace, etc.; pages can be set with emoji icons and cover images.
- Database operations: agents pass simple key-value pairs such as { "Status": "Done" }, and the server fetches the database schema at runtime and converts automatically; the schema is cached for 5 minutes.
- Error self-correction prompts: an incorrect heading name returns the available headings; a missing page suggests sharing it with the integration; an incorrect filter condition prompts you to call get_database first.
- Complex content support: nested toggles, mixed-content columns, deep list nesting, full Unicode (Japanese, Chinese, Arabic, emoji), etc. all have round-trip test coverage.
- CLI configuration filemode: suitable when an agent needs Notion access but does not load the full MCP tool surface, or uses different Notion integrations for different permission modes. The configuration file is located by default at ~/.config/easy-notion-mcp/profiles.json, and references environment variable names rather than raw tokens; the readonly profile can be used for read commands, and the readwrite profile for modification commands. CLI destructive commands support --dry-run preflight (returning plan fields such as would_delete_block_ids, would_update, would_archive, would_restore, without modifying Notion). A lightweight skill is located in the repository at skills/easy-notion-cli/, used to teach agents to prefer the CLI over registering multiple MCP servers.
How to Use
- Create a Notion integration, copy the token, and share the page with it.
- Connect to an MCP client. Common ways:
- Claude Code: register the server with user-level configuration (-s user), and pass NOTION_TOKEN directly to the MCP subprocess via -e; the shell environment and rcfile are unaffected. You can set a default parent page for create_page: add -e NOTION_ROOT_PAGE_ID=<page-id>.
- Generic MCP client: provide the token in the parent shell environment (that export form persists only for the current shell session unless written to the shell rcfile, and you should be aware of the security implications).
- Claude Desktop / Cursor / Windsurf: add it in the MCP configuration file; the configuration file locations are claude_desktop_config.json, .cursor/mcp.json, and ~/.windsurf/mcp.json respectively.
- Claude Desktop connector: Settings -> Connectors -> Add custom connector, enter http://localhost:3333/mcp,浏览器会打开 Notion authorization page, select the shared page and Allow.
- Project-level registration: write the configuration in the .mcp.json file in the project root directory (note: do not commit this file with placeholder credentials to the repository).
- Docker platforms such as Dify / n8n / FlowiseAI: run the HTTP server on the host machine, use host.docker.internal instead of localhost in the platform settings, and add the bearer request header; HTTP binds to 127.0.0.1 by default, static token mode requires NOTION_MCP_BEARER, and container access requires setting NOTION_MCP_BIND_HOST=0.0.0.0.
- Transport methods:
- stdio (API token mode).
- HTTP (OAuth or API token mode): run npx -p easy-notion-mcp easy-notion-mcp-http to start the HTTP server with OAuth. OAuth credentials require creating a public integration at notion.so/profile/integrations, and setting http://localhost:3333/callback as the redirect URI. In OAuth mode, create_page does not require NOTION_ROOT_PAGE_ID; pages are created in the user's private workspace by default.
Use cases
- Let AI agents read, write, and edit Notion pages and databases, using Markdown as the operating interface.
- Agent workflows that are sensitive to context budget and want to reduce the token consumption of reading pages.
- Scenarios that require editing operations such as locating by title, partial replacement, and fine-grained find_replace.
- Scenarios that need to connect to Notion via HTTP on Docker platforms (Dify / n8n / FlowiseAI).
- Scenarios that need to separate access to Notion with different permission modes (readonly / readwrite).
Limitations
- HTTP transport is intended for trusted networks: single-operator self-hosting with a bearer key, or OAuth for shared deployments; it is not hardened for direct public internet exposure, and remote access requires a reverse proxy with TLS in front.
- Static token mode must set a bearer; a missing or incorrect bearer returns 401 {"error": "invalid_token"}; the bearer is compared using crypto.timingSafeEqual; rotating the key is done by restarting the server with a new value.
- Binds to 127.0.0.1 by default, accessible only to local processes; setting NOTION_MCP_BIND_HOST=0.0.0.0 exposes all interfaces, or specify an IP to expose a single interface; a bearer is required regardless of the binding.
- The /mcp endpoint does not have DNS-rebinding protection; the CORS for the OAuth registration/token endpoints (/register, /token, /revoke) is relatively permissive. The bearer, or the per-user bearer for OAuth, is the only trust boundary between the network and the Notion workspace.
- file:// uploads are limited to stdio: file:// URLs passed to create_page, append_content, replace_content, update_section, update_page.cover are rejected under HTTP; local file workFor streams, use stdio (create_page_from_file is also limited to stdio), or host the file at an HTTPS URL.
- Destructive tools support dry_run: true for preflight checks; dry-run does not upload or validate local file:// Markdown (because it would create a Notion upload), and replace_content's dry-run also cannot expose Notion-side unmatched_blocks or truncated fields.
- restore_toggle operates by ID: you must pass the archived block ID returned by archive_toggle; Notion does not expose enumeration of archived child items, so restoring by title search or via the read_page include_archived workflow is not supported.
- Database writes reject unknown property names and unsupported types, giving a clear error rather than silently dropping them; before writing, you should first call get_database to confirm property names and types.
- Supported property types for writing: title, rich_text, number, select, multi_select, date, checkbox, url, email, phone, status, relation, people. For people, pass a single user ID string or an array of IDs. Computed types (formula, rollup, unique_id, created_time, last_edited_time, created_by, last_edited_by) are generated by Notion and cannot be set via the API; writing values for files, verification, place, location, and button is also rejected. For relation writes, pass a single page-ID string or an array; an empty array clears the relation.
- About the .env file (contributor-related only): the server loads .env from the current working directory via dotenv, and this only works conveniently when running from a cloned repository checkout directory (node dist/index.js after npm install && npm run build); it is not loaded when invoked via npx easy-notion-mcp or a global install from any directory.
- If there are problems during setup, you can ask in the #easy-notion-mcp channel of the Discord community; submit bugs to GitHub issues.