Easy NotionBLOCK
Markdown-first Notion MCP server. ~6-7x fewer response tokens vs official Notion MCP. 43 tools.
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
Markdown-first MCP server that connects AI agents to Notion. Agents write markdown — easy-notion-mcp converts it to Notion's block API and back again.
43 tools · 24 block types · ~6–7× fewer response tokens vs official Notion MCP · Documented round-trip support
[](https://www.npmjs.com/package/easy-notion-mcp) [](LICENSE) [](package.json) [](https://discord.gg/S8cghJSVBU) [](https://glama.ai/mcp/servers/Grey-Iris/easy-notion-mcp)
npx easy-notion-mcp
[See it in action →](https://www.notion.so/easy-notion-mcp-327be876242f817f9129ff1a5a624814) Live Notion page created and managed entirely through easy-notion-mcp.
Contents: Comparison · Setup · CLI profiles · Config · Why markdown · How it works · Tools · MCP resources · Block types · Round-trip · Databases · Cookbook · Security · Stability · FAQ · Community
How does easy-notion-mcp compare to other Notion MCP servers?
1f6a2a352f58OBSERVED · 2026-10-08Connect
Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.
claude mcp add easy-notion-mcp --env NOTION_TOKEN=${NOTION_TOKEN} -- npx -y [email protected]Exposed tools (38)
22 read · 14 write · 2 destructive. Blast radius: 2 tools can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.
| Tool | Risk | Description |
|---|---|---|
add_comment | write | Add a comment to a page. Supports inline markdown and page mentions with @[Title](notion-url). Unlike append_content, a mention the integration cannot resolve is not downgraded to a plain link and returns no warning, so the call can fail. Returns { id, content }. |
add_database_entries | write | Create multiple entries in a database in one call. Each entry uses the same simple key-value format as add_database_entry. Returns per-entry results \u2014 partial failures don |
archive_page | read | Archive a page in Notion. |
create_database | write | Create a database under a parent page. Supported property types and extras: - title - rich_text (alias: text) - number (optional: format, for example |
create_page | write | Create a page |
create_page_from_file | write | Create a Notion page from a local markdown file. The server reads and validates the file, then creates the same result as create_page without sending file contents through the agent context. The server converts the markdown to native Notion blocks (not flat text) and automatically handles Notion |
create_view | write | Create a Notion database view. Pass database_id. Dashboard views and dashboard widget placement are not supported. |
delete_database_entry | destructive | Delete (archive) a database entry. |
delete_view | destructive | Delete a Notion database view. Destructive: confirm must be exactly true. |
fake_tool | read | Fake |
find_replace | read | Find and replace text on a page. Preserves uploaded files and blocks that aren |
get_config | read | Report this server |
get_database | read | Get a database |
get_me | read | Get the current bot user. |
get_view | read | Retrieve one Notion database view by ID. Returns a curated summary (id, name, type, url, data_source_id); set include_config: true for the full raw Notion view object. |
list_comments | read | List comments on a page. |
list_databases | read | List all databases the integration can access. Returns database names and IDs \u2014 use get_database on any result to see its schema. |
list_pages | read | List child pages under a parent page. Each row returns id, title, created_time, and last_edited_time. Timestamps are full ISO-8601 values from Notion, rounded to the minute, and last_edited_time advances on page content and property edits. |
list_users | read | List workspace users. |
list_views | read | List Notion database views. Pass exactly one of database_id or data_source_id. Returns a curated summary of each view (id, name, type, url, data_source_id) plus pagination cursors; set include_config: true for the full raw Notion view objects. |
markdown-conventions | read | Supported markdown syntax for page creation, appends, replacements, targeted updates, and reads. |
move_page | write | Move a page to a new parent page. |
property-pagination | read | How read_page and query_database paginate long Notion property values. |
query_view | read | Query a Notion database view. Creates a temporary view query, fetches database row results, then deletes the query. |
read_page | read | Read a page |
replace_content | read | Replaces all page content with the provided markdown atomically (one Notion API call). Notion |
restore_page | read | Restore an archived page. |
restore_toggle | read | Restore an archived toggle or toggleable heading by archived block ID. Use the block ID returned by archive_toggle; Notion does not expose archived child enumeration for title search or read_page include_archived. |
search | read | Search Notion pages or databases. Use filter: |
search_in_page | read | Search raw Notion block plain text inside a page, optionally scoped to one toggle or toggleable heading by title. Matching is case-insensitive plain substring search. |
share_page | read | Return the page URL that can be shared from Notion. |
update-data-source-guide | write | Full-list schema semantics, raw/helper payload modes, and examples for update_data_source. |
update_block | write | Update a single block in place by ID. Preserves the block |
update_data_source | write | CRITICAL: full-list semantics. When you update a select or status property |
update_page | write | Update page title, icon, or cover. Cover accepts an image URL, or a file:// path (stdio transport only) which will be uploaded to Notion. In HTTP transport, the file:// form is rejected — use an HTTPS URL instead. |
update_section | write | DESTRUCTIVE, no rollback: this tool deletes blocks in the section, then writes new blocks. If the write fails mid-call, the section is left partially or fully emptied; for most sections the heading anchor is deleted, so a retry can fail with |
update_view | write | Update a Notion database view. Pass at least one update field. Null filter, sorts, or quick_filters values are forwarded to clear those fields. |
warning-shapes | write | Warning codes and response shapes emitted by markdown read and write tools. |
Trust audit
BLOCKgrade D · trust 63/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | FAIL |
| L2 | Instruction surface (what it tells the agent) | FAIL |
| L3 | Class-specific surface | WARN |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- declared (10 observation(s))
- Network
- declared (5 observation(s))
- Shell
- declared (1 observation(s))
- Dependencies
- not all pinned
- Secrets in source
- found
Findings (25)
exec(cmd: string, args: string[]): Promise<{ code: number; stdout: string; stderr: string }>;| BOM prefix | `"# Title"` | BOM becomes part of first line. `marked.lexer` may handle it (marked does strip BOM per GFM spec). | Likely handled by marked. **Needs verification test.** |
| Zero-width joiner | `"Helloworld"` | Passes through as content in `createRichText`. `marked` treats as text. | Round-trips cleanly (invisible char preserved). Notion may or may not strip it server-s
"/usr/local/bin/claude",
const secret = "ntn_supersecrettokenvalue123456";
delete_database_entry, delete_view
exec(cmd: string, args: string[]): Promise<{ code: number; stdout: string; stderr: string }>;return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../.meta/bench/corpus");
import type { RichText } from "../../../src/types.js";const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
import { NOTION_VERSION } from "../../src/notion-version.js";import { blocksToMarkdown } from "../../src/blocks-to-markdown.js";| `curl http://127.0.0.1:13333/` (default env) | **FAIL** (curl exit 56) — see Blocker B |
$ docker exec ... wget -qO- http://127.0.0.1:3333/ → {"status":"ok",...}This is independent of Blocker B. Orchestrators (compose `condition: service_healthy`, k8s, swarm) will treat the container as never-ready / restart it. The HEALTHCHECK is a headline feature of this P
- Server on 127.0.0.1:3333: confirmed (`curl http://127.0.0.1:3333/` → 200, `curl http://127.0.0.1:3333/mcp` → 401)
- **Verify:** `curl -sS http://127.0.0.1:3333/` → `{"status":"ok",...}`.@modelcontextprotocol/sdk, @notionhq/client, dotenv, express, marked, @types/express, @types/node, @types/supertest
- The listing benchmark includes the npm package, but the README Option A number is not measured against npm; it is the workflow report's hosted model. The npm surface has `API-patch-page` and `API-up
- The npm comparator is not pinned in the script. `scripts/bench/token-compare.ts:125-128` installs `@notionhq/notion-mcp-server@latest`, though the report records v2.3.0 at `.meta/research/token-reme
- The local server is described as HEAD at `.meta/research/token-remeasure-2026-04-28.md:16`, but the report does not record the commit SHA. Current worktree HEAD during this audit is `4ef55ba6ea74ab3
| `POST /v1/oauth/token` | `oauth.token` (`Client.d.ts:286`) | n/a — already used inside `auth/oauth-provider.ts`, not exposed as a tool (correct). | — | [ref](https://developers.notion.com/reference/
| `POST /v1/oauth/introspect` | `oauth.introspect` (`Client.d.ts:293`) | low | Validate a token's scope/expiry. Used by OAuth-aware clients. | [ref](https://developers.notion.com/reference/introspect-
| Scopes unenforced | If documenting the boundary suffices for 1.0: one sentence in the OAuth docs stating "a valid bearer grants the full tool surface; requested scopes are not enforced." If enforcin
- **OAuth mode:** authenticated caller does the same, but the file lands in the caller's own workspace. Still a trust-boundary escalation from "can access your own Notion workspace" to "can read host
Gates applied: no_behavioural_pass.
1f6a2a352f58full audit observations/trust-audit/mcp-server/grey-iris__easy-notion.json · Report an issue / request a re-scanAudit history
Every audit this server has had. A grade with a past is a grade somebody is still checking.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-08 | 1f6a2a352f58 | BLOCK | D | 63 | first audit |
Questions
What is the Easy Notion MCP server?
Markdown-first Notion MCP server. ~6-7x fewer response tokens vs official Notion MCP. 43 tools.
What tools does Easy Notion expose?
38 in total: 22 read-only, 14 that write, and 2 that can delete or overwrite (delete_database_entry, delete_view). Every one is listed on this page with its risk.
Is Easy Notion safe to connect to an agent?
No — not without reading the findings first. The audit graded it D (63/100) and found 3 critical or high issues in the source. Each one is listed on this page with the file and line it is on. Separately from the audit: 2 of its tools can destroy data, so scope the token you give it to what you actually need.
What credentials does Easy Notion need?
It reads ANTHROPIC_API_KEY, NOTION_OAUTH_CLIENT_ID, NOTION_OAUTH_CLIENT_SECRET, NOTION_TOKEN and OAUTH_REDIRECT_URI from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.
How does Easy Notion run?
It speaks stdio and streamable-http, so it runs as a local process your client starts. It is published on npm as easy-notion-mcp at 1.1.0.
How current is this page?
The grade is for one exact copy of the source (1f6a2a352f58), read on 2026-10-08. The repository is watched and re-audited when it changes.