Notion MCP Server
by ollehmichael
README.md
# notion-mcp-server
A Notion MCP server, iterated to our preferences.
## Lineage
**makenotion/notion-mcp-server** → **mieubrisse/notion-mcp-server** → **this fork**
The original MCP server was maintained by Notion's team. When they shifted focus to their remote MCP (Enterprise-only for database access), [mieubrisse](https://github.com/mieubrisse) forked it to keep the local server alive and added data source support. We forked mieubrisse's version and iterated from there — fixing compatibility issues, expanding the OpenAPI spec, and adding custom composite tools tailored to our workflows.
This server is developed by [Michael Han](https://github.com/ollehmichael) and [MILO](https://github.com/ollehmichael/MILO-Claude) (his AI assistant), built to power MILO's Notion integration.
---
## What We Changed
### VS Code MCP Compatibility
The upstream spec caused VS Code's MCP client to silently disable tools — no error surface, just broken calls. Root cause: VS Code's JSON Schema validator rejects custom format specifiers (`"uuid"`, `"json"`, `"date"`), and when schema compilation warns, VS Code disables the tool entirely.
Fixes applied to `scripts/notion-openapi.json`:
- Removed all `"format": "uuid"`, `"format": "json"` specifiers
- Simplified `parentRequest` schema — replaced complex `oneOf`/`$ref` with direct properties
- Removed `destructiveHint` annotation from tool definitions
### Error Handling
Upstream errors in the proxy and HTTP client crashed the MCP connection, killing the agent session with no recovery path.
- **`proxy.ts`**: Wrapped unhandled non-HTTP errors to return structured `{ status, error_type, message }` responses instead of throwing
- **`http-client.ts`**: Added catch-all for network errors without `.response` — wraps as `HttpClientError` with status `0`
### OpenAPI Spec Expansions
**Block types** — expanded `blockObjectRequest` from 3 to 22 supported types:
| Category | Types Added |
|----------|-------------|
| Headings | `heading_1`, `heading_2`, `heading_3` |
| Lists | `numbered_list_item` |
| Structure | `quote`, `divider`, `table_of_contents`, `toggle`, `column_list`, `column` |
| Rich blocks | `callout` (with emoji icon), `code` (with 70-language enum), `equation`, `bookmark` |
| Media | `image`, `video`, `file` (all external URL format) |
| Tables | `table`, `table_row` |
**Rich text annotations** — added optional `annotations` to `richTextRequest`: `bold`, `italic`, `underline`, `strikethrough`, `code`, `color` (19-value enum)
**Icon schema** — updated both POST and PATCH page endpoints to support emoji and external icon types (e.g. Notion's native SVG icons like `https://www.notion.so/icons/briefcase_brown.svg`)
**PATCH block fix** — upstream `API-update-a-block` always failed because the schema named its wrapper property `"type"`, which serialized as `{"type": <content>}` — exactly what Notion rejects in update-block requests. Fixed by surfacing block-specific properties (`to_do`, `paragraph`, `heading_1`, etc.) at the root level.
### Cherry-Picks from Upstream
| Source | Change |
|--------|--------|
| mieubrisse | Added `retrieve-a-database` endpoint (`GET /v1/databases/{id}`) |
| mieubrisse | Added `create-a-database` endpoint (`POST /v1/databases`) |
| mieubrisse | Added required `type` discriminator to `pageIdParentRequest` / `dataSourceIdParentRequest` |
| makenotion | Sanitized HTTP client error logging (removed sensitive response data from console) |
| makenotion | Added `withStringFallback()` to parser — handles double-serialized JSON from MCP clients |
---
## Custom Tools
The largest addition. Five composite tools built on top of the raw Notion API, designed for agentic task management workflows.
### `upsert-task`
Create or update a task in the Tasks DB by title. Searches for an existing non-trashed task with an exact title match. If found, patches the page properties (and appends `body_blocks` if provided). If not found, creates a new page with all properties and optional children.
Returns `{ action: 'created' | 'updated', page_id, url }`.
### `batch-delete-checkboxes`
Fetches all `to_do` blocks from a page/block and deletes them in parallel. By default only deletes checked (completed) boxes — set `checked_only: false` to delete all. Returns `{ deleted, total_found, errors }`.
### `batch-update-tasks`
Update multiple Notion tasks in a single tool call. Accepts an array of `{ page_id, properties }` and patches each in parallel. Returns `{ updated, errors }`.
### `morning-scan`
Runs two parallel queries against the Tasks DB: today's active tasks (`Today? = true`, `Status != Done`) and overdue tasks (`Due < today`, `Status != Done`, `Today? = false`). Returns `{ today: Page[], overdue: Page[], scanned_at }`.
### `check-and-create-recurring`
Checks for recurring task templates and creates new instances when due. Designed for workflows that need periodic task generation without manual intervention.
---
## Installation
### Claude Code (via `mcp add`)
```bash
NOTION_TOKEN=ntn_**** claude mcp add -s user notionapi -- npx tsx /path/to/notion-mcp-server/scripts/start-server.ts
```
The `NOTION_TOKEN` env var must be available in your shell when Claude Code starts. We store it in macOS Keychain and export from `~/.zshrc`:
```bash
# Store once
security add-generic-password -a "$USER" -s "NOTION_TOKEN" -w "ntn_****"
# In ~/.zshrc
export NOTION_TOKEN=$(security find-generic-password -a "$USER" -s "NOTION_TOKEN" -w 2>/dev/null)
```
After adding a new env var, relaunch Claude Code from terminal (`source ~/.zshrc && claude`) — desktop launchers won't pick up new shell exports.
### Claude Desktop / Cursor
```json
{
"mcpServers": {
"notionApi": {
"command": "npx",
"args": ["tsx", "/path/to/notion-mcp-server/scripts/start-server.ts"],
"env": {
"NOTION_TOKEN": "ntn_****"
}
}
}
}
```
---
## Development
```bash
npm install
npm run build
npm run dev # tsx watch mode
npm test
```
See `UPSTREAM_TRACKING.md` for a detailed record of all local modifications, cherry-picks, and the debugging journey that uncovered the VS Code schema compatibility issues.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues