|
| 1 | +# Notion MCP Server |
| 2 | + |
| 3 | +> [!NOTE] |
| 4 | +> |
| 5 | +> We’ve introduced **Notion MCP**, a remote MCP server with the following improvements: |
| 6 | +> - Easy installation via standard OAuth. No need to fiddle with JSON or API token anymore. |
| 7 | +> - Powerful tools tailored to AI agents. These tools are designed with optimized token consumption in mind. |
| 8 | +> |
| 9 | +> Learn more and try it out [here](https://developers.notion.com/docs/mcp) |
| 10 | +
|
| 11 | + |
| 12 | + |
| 13 | + |
| 14 | +This project implements an [MCP server](https://spec.modelcontextprotocol.io/) for the [Notion API](https://developers.notion.com/reference/intro). |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +### Installation |
| 19 | + |
| 20 | +#### 1. Setting up Integration in Notion: |
| 21 | +Go to [https://www.notion.so/profile/integrations](https://www.notion.so/profile/integrations) and create a new **internal** integration or select an existing one. |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +While we limit the scope of Notion API's exposed (for example, you will not be able to delete databases via MCP), there is a non-zero risk to workspace data by exposing it to LLMs. Security-conscious users may want to further configure the Integration's _Capabilities_. |
| 26 | + |
| 27 | +For example, you can create a read-only integration token by giving only "Read content" access from the "Configuration" tab: |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +#### 2. Connecting content to integration: |
| 32 | +Ensure relevant pages and databases are connected to your integration. |
| 33 | + |
| 34 | +To do this, visit the **Access** tab in your internal integration settings. Edit access and select the pages you'd like to use. |
| 35 | + |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +Alternatively, you can grant page access individually. You'll need to visit the target page, and click on the 3 dots, and select "Connect to integration". |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +#### 3. Adding MCP config to your client: |
| 44 | + |
| 45 | +##### Using npm: |
| 46 | + |
| 47 | +**Cursor & Claude:** |
| 48 | + |
| 49 | +Add the following to your `.cursor/mcp.json` or `claude_desktop_config.json` (MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`) |
| 50 | + |
| 51 | +**Option 1: Using NOTION_TOKEN (recommended)** |
| 52 | +```javascript |
| 53 | +{ |
| 54 | + "mcpServers": { |
| 55 | + "notionApi": { |
| 56 | + "command": "npx", |
| 57 | + "args": ["-y", "@notionhq/notion-mcp-server"], |
| 58 | + "env": { |
| 59 | + "NOTION_TOKEN": "ntn_****" |
| 60 | + } |
| 61 | + } |
| 62 | + } |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +**Option 2: Using OPENAPI_MCP_HEADERS (for advanced use cases)** |
| 67 | +```javascript |
| 68 | +{ |
| 69 | + "mcpServers": { |
| 70 | + "notionApi": { |
| 71 | + "command": "npx", |
| 72 | + "args": ["-y", "@notionhq/notion-mcp-server"], |
| 73 | + "env": { |
| 74 | + "OPENAPI_MCP_HEADERS": "{\"Authorization\": \"Bearer ntn_****\", \"Notion-Version\": \"2022-06-28\" }" |
| 75 | + } |
| 76 | + } |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +**Zed** |
| 82 | + |
| 83 | +Add the following to your `settings.json` |
| 84 | + |
| 85 | +```json |
| 86 | +{ |
| 87 | + "context_servers": { |
| 88 | + "some-context-server": { |
| 89 | + "command": { |
| 90 | + "path": "npx", |
| 91 | + "args": ["-y", "@notionhq/notion-mcp-server"], |
| 92 | + "env": { |
| 93 | + "OPENAPI_MCP_HEADERS": "{\"Authorization\": \"Bearer ntn_****\", \"Notion-Version\": \"2022-06-28\" }" |
| 94 | + } |
| 95 | + }, |
| 96 | + "settings": {} |
| 97 | + } |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +##### Using Docker: |
| 103 | + |
| 104 | +There are two options for running the MCP server with Docker: |
| 105 | + |
| 106 | +###### Option 1: Using the official Docker Hub image: |
| 107 | + |
| 108 | +Add the following to your `.cursor/mcp.json` or `claude_desktop_config.json`: |
| 109 | + |
| 110 | +**Using NOTION_TOKEN (recommended):** |
| 111 | +```javascript |
| 112 | +{ |
| 113 | + "mcpServers": { |
| 114 | + "notionApi": { |
| 115 | + "command": "docker", |
| 116 | + "args": [ |
| 117 | + "run", |
| 118 | + "--rm", |
| 119 | + "-i", |
| 120 | + "-e", "NOTION_TOKEN", |
| 121 | + "mcp/notion" |
| 122 | + ], |
| 123 | + "env": { |
| 124 | + "NOTION_TOKEN": "ntn_****" |
| 125 | + } |
| 126 | + } |
| 127 | + } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +**Using OPENAPI_MCP_HEADERS (for advanced use cases):** |
| 132 | +```javascript |
| 133 | +{ |
| 134 | + "mcpServers": { |
| 135 | + "notionApi": { |
| 136 | + "command": "docker", |
| 137 | + "args": [ |
| 138 | + "run", |
| 139 | + "--rm", |
| 140 | + "-i", |
| 141 | + "-e", "OPENAPI_MCP_HEADERS", |
| 142 | + "mcp/notion" |
| 143 | + ], |
| 144 | + "env": { |
| 145 | + "OPENAPI_MCP_HEADERS": "{\"Authorization\":\"Bearer ntn_****\",\"Notion-Version\":\"2022-06-28\"}" |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +This approach: |
| 153 | +- Uses the official Docker Hub image |
| 154 | +- Properly handles JSON escaping via environment variables |
| 155 | +- Provides a more reliable configuration method |
| 156 | + |
| 157 | +###### Option 2: Building the Docker image locally: |
| 158 | + |
| 159 | +You can also build and run the Docker image locally. First, build the Docker image: |
| 160 | + |
| 161 | +```bash |
| 162 | +docker compose build |
| 163 | +``` |
| 164 | + |
| 165 | +Then, add the following to your `.cursor/mcp.json` or `claude_desktop_config.json`: |
| 166 | + |
| 167 | +**Using NOTION_TOKEN (recommended):** |
| 168 | +```javascript |
| 169 | +{ |
| 170 | + "mcpServers": { |
| 171 | + "notionApi": { |
| 172 | + "command": "docker", |
| 173 | + "args": [ |
| 174 | + "run", |
| 175 | + "--rm", |
| 176 | + "-i", |
| 177 | + "-e", |
| 178 | + "NOTION_TOKEN=ntn_****", |
| 179 | + "notion-mcp-server" |
| 180 | + ] |
| 181 | + } |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +**Using OPENAPI_MCP_HEADERS (for advanced use cases):** |
| 187 | +```javascript |
| 188 | +{ |
| 189 | + "mcpServers": { |
| 190 | + "notionApi": { |
| 191 | + "command": "docker", |
| 192 | + "args": [ |
| 193 | + "run", |
| 194 | + "--rm", |
| 195 | + "-i", |
| 196 | + "-e", |
| 197 | + "OPENAPI_MCP_HEADERS={\"Authorization\": \"Bearer ntn_****\", \"Notion-Version\": \"2022-06-28\"}", |
| 198 | + "notion-mcp-server" |
| 199 | + ] |
| 200 | + } |
| 201 | + } |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +Don't forget to replace `ntn_****` with your integration secret. Find it from your integration configuration tab: |
| 206 | + |
| 207 | + |
| 208 | + |
| 209 | +### Transport Options |
| 210 | + |
| 211 | +The Notion MCP Server supports two transport modes: |
| 212 | + |
| 213 | +#### STDIO Transport (Default) |
| 214 | +The default transport mode uses standard input/output for communication. This is the standard MCP transport used by most clients like Claude Desktop. |
| 215 | + |
| 216 | +```bash |
| 217 | +# Run with default stdio transport |
| 218 | +npx @notionhq/notion-mcp-server |
| 219 | + |
| 220 | +# Or explicitly specify stdio |
| 221 | +npx @notionhq/notion-mcp-server --transport stdio |
| 222 | +``` |
| 223 | + |
| 224 | +#### Streamable HTTP Transport |
| 225 | +For web-based applications or clients that prefer HTTP communication, you can use the Streamable HTTP transport: |
| 226 | + |
| 227 | +```bash |
| 228 | +# Run with Streamable HTTP transport on port 3000 (default) |
| 229 | +npx @notionhq/notion-mcp-server --transport http |
| 230 | + |
| 231 | +# Run on a custom port |
| 232 | +npx @notionhq/notion-mcp-server --transport http --port 8080 |
| 233 | + |
| 234 | +# Run with a custom authentication token |
| 235 | +npx @notionhq/notion-mcp-server --transport http --auth-token "your-secret-token" |
| 236 | +``` |
| 237 | + |
| 238 | +When using Streamable HTTP transport, the server will be available at `http://0.0.0.0:<port>/mcp`. |
| 239 | + |
| 240 | +##### Authentication |
| 241 | +The Streamable HTTP transport requires bearer token authentication for security. You have three options: |
| 242 | + |
| 243 | +**Option 1: Auto-generated token (recommended for development)** |
| 244 | +```bash |
| 245 | +npx @notionhq/notion-mcp-server --transport http |
| 246 | +``` |
| 247 | +The server will generate a secure random token and display it in the console: |
| 248 | +``` |
| 249 | +Generated auth token: a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab |
| 250 | +Use this token in the Authorization header: Bearer a1b2c3d4e5f6789abcdef0123456789abcdef0123456789abcdef0123456789ab |
| 251 | +``` |
| 252 | + |
| 253 | +**Option 2: Custom token via command line (recommended for production)** |
| 254 | +```bash |
| 255 | +npx @notionhq/notion-mcp-server --transport http --auth-token "your-secret-token" |
| 256 | +``` |
| 257 | + |
| 258 | +**Option 3: Custom token via environment variable (recommended for production)** |
| 259 | +```bash |
| 260 | +AUTH_TOKEN="your-secret-token" npx @notionhq/notion-mcp-server --transport http |
| 261 | +``` |
| 262 | + |
| 263 | +The command line argument `--auth-token` takes precedence over the `AUTH_TOKEN` environment variable if both are provided. |
| 264 | + |
| 265 | +##### Making HTTP Requests |
| 266 | +All requests to the Streamable HTTP transport must include the bearer token in the Authorization header: |
| 267 | + |
| 268 | +```bash |
| 269 | +# Example request |
| 270 | +curl -H "Authorization: Bearer your-token-here" \ |
| 271 | + -H "Content-Type: application/json" \ |
| 272 | + -H "mcp-session-id: your-session-id" \ |
| 273 | + -d '{"jsonrpc": "2.0", "method": "initialize", "params": {}, "id": 1}' \ |
| 274 | + http://localhost:3000/mcp |
| 275 | +``` |
| 276 | + |
| 277 | +**Note:** Make sure to set either the `NOTION_TOKEN` environment variable (recommended) or the `OPENAPI_MCP_HEADERS` environment variable with your Notion integration token when using either transport mode. |
| 278 | + |
| 279 | +### Examples |
| 280 | + |
| 281 | +1. Using the following instruction |
| 282 | +``` |
| 283 | +Comment "Hello MCP" on page "Getting started" |
| 284 | +``` |
| 285 | + |
| 286 | +AI will correctly plan two API calls, `v1/search` and `v1/comments`, to achieve the task |
| 287 | + |
| 288 | +2. Similarly, the following instruction will result in a new page named "Notion MCP" added to parent page "Development" |
| 289 | +``` |
| 290 | +Add a page titled "Notion MCP" to page "Development" |
| 291 | +``` |
| 292 | + |
| 293 | +3. You may also reference content ID directly |
| 294 | +``` |
| 295 | +Get the content of page 1a6b35e6e67f802fa7e1d27686f017f2 |
| 296 | +``` |
| 297 | + |
| 298 | +### Development |
| 299 | + |
| 300 | +Build |
| 301 | + |
| 302 | +``` |
| 303 | +npm run build |
| 304 | +``` |
| 305 | + |
| 306 | +Execute |
| 307 | + |
| 308 | +``` |
| 309 | +npx -y --prefix /path/to/local/notion-mcp-server @notionhq/notion-mcp-server |
| 310 | +``` |
| 311 | + |
| 312 | +Publish |
| 313 | + |
| 314 | +``` |
| 315 | +npm publish --access public |
| 316 | +``` |
0 commit comments