Skip to main content
Glama
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.