#
tokens: 8161/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .env.example
├── .gitignore
├── .npmignore
├── package-lock.json
├── package.json
├── README.md
└── server.js
```

# Files

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
1 | .aider*
2 | .env
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
1 | # Notion API Key
2 | NOTION_API_KEY=your_notion_api_key_here
3 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | .aider*
 2 | .env
 3 | # Dependency directories
 4 | node_modules/
 5 | 
 6 | # Environment variables
 7 | .env
 8 | 
 9 | # Logs
10 | logs
11 | *.log
12 | npm-debug.log*
13 | 
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 | 
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 | 
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | 
26 | # nyc test coverage
27 | .nyc_output
28 | 
29 | # Compiled binary addons (https://nodejs.org/api/addons.html)
30 | build/Release
31 | 
32 | # Dependency directories
33 | node_modules/
34 | jspm_packages/
35 | 
36 | # TypeScript cache
37 | *.tsbuildinfo
38 | 
39 | # Optional npm cache directory
40 | .npm
41 | 
42 | # Optional eslint cache
43 | .eslintcache
44 | 
45 | # Optional REPL history
46 | .node_repl_history
47 | 
48 | # Output of 'npm pack'
49 | *.tgz
50 | 
51 | # dotenv environment variable files
52 | .env
53 | .env.development.local
54 | .env.test.local
55 | .env.production.local
56 | .env.local
57 | 
58 | # Mac files
59 | .DS_Store
60 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Notion MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server that connects Claude and other AI assistants to your Notion workspace. This integration allows AI assistants to interact with your Notion databases, pages, and blocks.
  4 | 
  5 | ## What is this?
  6 | 
  7 | This tool acts as a bridge between AI assistants (like Claude) and your Notion workspace. It allows the AI to:
  8 | - View and search your Notion databases
  9 | - Create and update pages
 10 | - Manage content blocks
 11 | - And much more!
 12 | 
 13 | ## Step-by-Step Setup Guide
 14 | 
 15 | ### Prerequisites
 16 | - [Node.js](https://nodejs.org/) (version 14 or higher)
 17 | - A Notion account
 18 | - Claude Desktop app (if using with Claude)
 19 | 
 20 | ### 1. Getting Your Notion API Key
 21 | 
 22 | 1. Go to [https://www.notion.so/my-integrations](https://www.notion.so/my-integrations)
 23 | 2. Click the blue **"+ New integration"** button
 24 | 3. Fill in the details:
 25 |    - **Name**: Choose a name like "Claude Assistant" or "AI Helper"
 26 |    - **Logo**: Optional
 27 |    - **Associated workspace**: Select your Notion workspace
 28 | 4. Click **"Submit"**
 29 | 5. On the next page, find the **"Internal Integration Token"** section
 30 | 6. Click **"Show"** and copy the token (it starts with `secret_`)
 31 | 
 32 | ## 2. Setting Up This Server
 33 | 
 34 | ### Download the Repository
 35 | 
 36 | **Option A: Download as ZIP (Recommended for beginners)**
 37 | 1. Go to the GitHub repository: https://github.com/Sjotie/notionMCP/
 38 | 2. Click the green "Code" button at the top right
 39 | 3. Select "Download ZIP"
 40 | 4. Once downloaded, extract the ZIP file to a location on your computer
 41 |    - Windows: Right-click the ZIP file and select "Extract All"
 42 |    - Mac: Double-click the ZIP file to extract
 43 | 
 44 | **Option B: Clone with Git (For users familiar with Git)**
 45 | 1. Open a command prompt or terminal
 46 |    - Windows: Press `Win+R`, type `cmd`, and press Enter
 47 |    - Mac: Open Terminal from Applications > Utilities
 48 | 2. Navigate to where you want to store the repository
 49 |    ```
 50 |    cd path/to/desired/location
 51 |    ```
 52 | 3. Clone the repository
 53 |    ```
 54 |    git clone https://github.com/Sjotie/notionMCP/
 55 |    ```
 56 | 
 57 | ### Navigate to the Project Directory
 58 | 
 59 | After downloading or cloning, you need to navigate to the project folder using the `cd` (change directory) command:
 60 | 
 61 | **If you downloaded the ZIP (Option A):**
 62 | 1. Open a command prompt or terminal
 63 | 2. Use the `cd` command to navigate to where you extracted the ZIP file:
 64 |    ```
 65 |    cd path/to/extracted/folder/notionMCP
 66 |    ```
 67 |    
 68 |    For example:
 69 |    - On Windows: `cd C:\Users\YourName\Downloads\notionMCP`
 70 |    - On Mac: `cd /Users/YourName/Downloads/notionMCP`
 71 | 
 72 | **If you cloned with Git (Option B):**
 73 | 1. The repository should have been cloned into a folder named "notionMCP"
 74 | 2. If you're still in the same terminal window after cloning, simply type:
 75 |    ```
 76 |    cd notionMCP
 77 |    ```
 78 | 
 79 | **How to know you're in the right directory:**
 80 | - After using the `cd` command, you can check your current location:
 81 |   - On Windows: Type `dir` and press Enter - you should see files like `server.js`
 82 |   - On Mac: Type `ls` and press Enter - you should see files like `server.js`
 83 | 
 84 | ### Install Dependencies
 85 | 
 86 | Once you're in the notionMCP directory, install the required dependencies:
 87 | 
 88 | ```
 89 | npm install
 90 | ```
 91 | 
 92 | This will install all the necessary Node.js packages. You should see a progress bar and eventually a message indicating the installation is finished. It might say something along the lines of "X Packages are looking for funding" - this is completely normal and means it worked.
 93 | 
 94 | ### 3. Connecting to Notion Pages
 95 | 
 96 | For security, Notion requires you to explicitly grant access to each page or database:
 97 | 
 98 | 1. Open Notion and navigate to a page or database you want the AI to access
 99 | 2. Click the **"•••"** (three dots) in the top-right corner
100 | 3. Select **"Add connections"**
101 | 4. Find and select the integration you created earlier
102 | 5. Repeat for any other pages or databases you want to make accessible
103 | 
104 | ### 4. Connecting to Claude Desktop
105 | 
106 | 1. Locate your Claude Desktop configuration file:
107 |    - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
108 |      (Type this path in File Explorer address bar)
109 |    - Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`
110 |      (In Finder, press Cmd+Shift+G and paste this path)
111 | 
112 | 2. Open the file in a text editor. If it doesn't exist, create it with the following content:
113 |    ```json
114 |    {
115 |      "mcpServers": {
116 |        "notion": {
117 |          "command": "node",
118 |          "args": [
119 |            "C:\\path\\to\\notion-mcp-server\\server.js"
120 |          ],
121 |          "env": {
122 |            "NOTION_API_KEY": "your_notion_api_key_here"
123 |          }
124 |        }
125 |      }
126 |    }
127 |    ```
128 | 
129 | 3. Replace:
130 |    - `C:\\path\\to\\notion-mcp-server\\server.js` with the actual path to the server.js file
131 |      - Windows: Use double backslashes (\\\\) in the path
132 |      - Mac: Use forward slashes (/)
133 |    - `your_notion_api_key_here` with your Notion API key
134 | 
135 | 4. Save the file and restart Claude Desktop
136 | 
137 | ### 5. Testing the Connection
138 | 
139 | 1. Start a new conversation in Claude
140 | 2. Ask Claude to interact with your Notion workspace, for example:
141 |    - "Show me a list of my Notion databases"
142 |    - "Create a new page in my Tasks database with title 'Test Task'"
143 | 
144 | ## Available Tools
145 | 
146 | The server provides these tools to AI assistants:
147 | 
148 | - **list-databases**: View all accessible databases
149 | - **query-database**: Get entries from a database
150 | - **create-page**: Add a new page to a database
151 | - **update-page**: Modify an existing page
152 | - **create-database**: Create a new database
153 | - **update-database**: Modify a database structure
154 | - **get-page**: View a specific page
155 | - **get-block-children**: View content blocks
156 | - **append-block-children**: Add content to a page
157 | - **update-block**: Edit content blocks
158 | - **get-block**: View a specific block
159 | - **search**: Find content across your workspace
160 | 
161 | ## Troubleshooting
162 | 
163 | ### Common Issues:
164 | 
165 | 1. **"Connection failed" in Claude**
166 |    - Make sure the server path in claude_desktop_config.json is correct
167 |    - Check that your Notion API key is valid
168 |    - Ensure Node.js is installed
169 | 
170 | 2. **"Access denied" when accessing Notion content**
171 |    - Make sure you've shared the page/database with your integration
172 |    - Check that your API key has the necessary permissions
173 | 
174 | 3. **Server won't start**
175 |    - Ensure all dependencies are installed (`npm install`)
176 |    - Check that the .env file exists with your API key
177 | 
178 | ### Getting Help
179 | 
180 | If you encounter issues not covered here, please:
181 | - Check the console output for error messages
182 | - Ensure your Notion API key is valid
183 | - Verify that your integration has access to the pages/databases
184 | 
185 | ## License
186 | 
187 | MIT
188 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@sjotie/notion-mcp-server",
 3 |   "version": "1.0.3",
 4 |   "description": "MCP server for Notion integration",
 5 |   "type": "module",
 6 |   "main": "server.js",
 7 |   "scripts": {
 8 |     "start": "node server.js",
 9 |     "dev": "nodemon server.js"
10 |   },
11 |   "keywords": [
12 |     "notion",
13 |     "mcp",
14 |     "model-context-protocol"
15 |   ],
16 |   "author": "Sjotie",
17 |   "license": "MIT",
18 |   "bin": {
19 |     "notion-mcp-server": "./server.js"
20 |   },
21 |   "dependencies": {
22 |     "@modelcontextprotocol/sdk": "^0.7.0",
23 |     "@notionhq/client": "^2.2.16",
24 |     "dotenv": "^16.4.7",
25 |     "zod": "^3.24.2"
26 |   },
27 |   "devDependencies": {
28 |     "nodemon": "^3.0.2"
29 |   },
30 |   "repository": {
31 |     "type": "git",
32 |     "url": "git+https://github.com/Sjotie/notionMCP.git"
33 |   },
34 |   "bugs": {
35 |     "url": "https://github.com/Sjotie/notionMCP/issues"
36 |   },
37 |   "homepage": "https://github.com/Sjotie/notionMCP#readme"
38 | }
39 | 
```

--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import { z } from "zod";
  6 | import { Client } from "@notionhq/client";
  7 | import dotenv from "dotenv";
  8 | 
  9 | // Load environment variables
 10 | dotenv.config();
 11 | 
 12 | // Initialize Notion client
 13 | const notion = new Client({
 14 |   auth: process.env.NOTION_API_KEY,
 15 | });
 16 | 
 17 | // Create MCP server
 18 | const server = new Server({
 19 |   name: "notion-mcp",
 20 |   version: "1.0.0",
 21 | }, {
 22 |   capabilities: {
 23 |     tools: {}
 24 |   }
 25 | });
 26 | 
 27 | // Add a request interceptor for debugging
 28 | server.setRequestHandler(z.object({
 29 |   method: z.string(),
 30 |   params: z.any().optional()
 31 | }), async (request) => {
 32 |   console.error("Received request:", JSON.stringify(request, null, 2));
 33 |   
 34 |   // Let the request continue to be handled by other handlers
 35 |   return undefined;
 36 | }, { priority: -1 });
 37 | 
 38 | // List databases tool
 39 | server.setRequestHandler(z.object({
 40 |   method: z.literal("tools/list")
 41 | }), async () => {
 42 |   return {
 43 |     tools: [
 44 |       {
 45 |         name: "list-databases",
 46 |         description: "List all databases the integration has access to",
 47 |         inputSchema: {
 48 |           type: "object",
 49 |           properties: {}
 50 |         }
 51 |       },
 52 |       {
 53 |         name: "query-database",
 54 |         description: "Query a database",
 55 |         inputSchema: {
 56 |           type: "object",
 57 |           properties: {
 58 |             database_id: {
 59 |               type: "string",
 60 |               description: "ID of the database to query"
 61 |             },
 62 |             filter: {
 63 |               type: "object",
 64 |               description: "Optional filter criteria"
 65 |             },
 66 |             sorts: {
 67 |               type: "array",
 68 |               description: "Optional sort criteria"
 69 |             },
 70 |             start_cursor: {
 71 |               type: "string",
 72 |               description: "Optional cursor for pagination"
 73 |             },
 74 |             page_size: {
 75 |               type: "number",
 76 |               description: "Number of results per page",
 77 |               default: 100
 78 |             }
 79 |           },
 80 |           required: ["database_id"]
 81 |         }
 82 |       },
 83 |       {
 84 |         name: "create-page",
 85 |         description: "Create a new page in a database",
 86 |         inputSchema: {
 87 |           type: "object",
 88 |           properties: {
 89 |             parent_id: {
 90 |               type: "string",
 91 |               description: "ID of the parent database"
 92 |             },
 93 |             properties: {
 94 |               type: "object",
 95 |               description: "Page properties"
 96 |             },
 97 |             children: {
 98 |               type: "array",
 99 |               description: "Optional content blocks"
100 |             }
101 |           },
102 |           required: ["parent_id", "properties"]
103 |         }
104 |       },
105 |       {
106 |         name: "update-page",
107 |         description: "Update an existing page",
108 |         inputSchema: {
109 |           type: "object",
110 |           properties: {
111 |             page_id: {
112 |               type: "string",
113 |               description: "ID of the page to update"
114 |             },
115 |             properties: {
116 |               type: "object",
117 |               description: "Updated page properties"
118 |             },
119 |             archived: {
120 |               type: "boolean",
121 |               description: "Whether to archive the page"
122 |             }
123 |           },
124 |           required: ["page_id", "properties"]
125 |         }
126 |       },
127 |       {
128 |         name: "create-database",
129 |         description: "Create a new database",
130 |         inputSchema: {
131 |           type: "object",
132 |           properties: {
133 |             parent_id: {
134 |               type: "string",
135 |               description: "ID of the parent page"
136 |             },
137 |             title: {
138 |               type: "array",
139 |               description: "Database title as rich text array"
140 |             },
141 |             properties: {
142 |               type: "object",
143 |               description: "Database properties schema"
144 |             },
145 |             icon: {
146 |               type: "object",
147 |               description: "Optional icon for the database"
148 |             },
149 |             cover: {
150 |               type: "object",
151 |               description: "Optional cover for the database"
152 |             }
153 |           },
154 |           required: ["parent_id", "title", "properties"]
155 |         }
156 |       },
157 |       {
158 |         name: "update-database",
159 |         description: "Update an existing database",
160 |         inputSchema: {
161 |           type: "object",
162 |           properties: {
163 |             database_id: {
164 |               type: "string",
165 |               description: "ID of the database to update"
166 |             },
167 |             title: {
168 |               type: "array",
169 |               description: "Optional new title as rich text array"
170 |             },
171 |             description: {
172 |               type: "array",
173 |               description: "Optional new description as rich text array"
174 |             },
175 |             properties: {
176 |               type: "object",
177 |               description: "Optional updated properties schema"
178 |             }
179 |           },
180 |           required: ["database_id"]
181 |         }
182 |       },
183 |       {
184 |         name: "get-page",
185 |         description: "Retrieve a page by its ID",
186 |         inputSchema: {
187 |           type: "object",
188 |           properties: {
189 |             page_id: {
190 |               type: "string",
191 |               description: "ID of the page to retrieve"
192 |             }
193 |           },
194 |           required: ["page_id"]
195 |         }
196 |       },
197 |       {
198 |         name: "get-block-children",
199 |         description: "Retrieve the children blocks of a block",
200 |         inputSchema: {
201 |           type: "object",
202 |           properties: {
203 |             block_id: {
204 |               type: "string",
205 |               description: "ID of the block (page or block)"
206 |             },
207 |             start_cursor: {
208 |               type: "string",
209 |               description: "Cursor for pagination"
210 |             },
211 |             page_size: {
212 |               type: "number",
213 |               description: "Number of results per page",
214 |               default: 100
215 |             }
216 |           },
217 |           required: ["block_id"]
218 |         }
219 |       },
220 |       {
221 |         name: "append-block-children",
222 |         description: "Append blocks to a parent block",
223 |         inputSchema: {
224 |           type: "object",
225 |           properties: {
226 |             block_id: {
227 |               type: "string",
228 |               description: "ID of the parent block (page or block)"
229 |             },
230 |             children: {
231 |               type: "array",
232 |               description: "List of block objects to append"
233 |             },
234 |             after: {
235 |               type: "string",
236 |               description: "Optional ID of an existing block to append after"
237 |             }
238 |           },
239 |           required: ["block_id", "children"]
240 |         }
241 |       },
242 |       {
243 |         name: "update-block",
244 |         description: "Update a block's content or archive status",
245 |         inputSchema: {
246 |           type: "object",
247 |           properties: {
248 |             block_id: {
249 |               type: "string",
250 |               description: "ID of the block to update"
251 |             },
252 |             block_type: {
253 |               type: "string",
254 |               description: "The type of block (paragraph, heading_1, to_do, etc.)"
255 |             },
256 |             content: {
257 |               type: "object",
258 |               description: "The content for the block based on its type"
259 |             },
260 |             archived: {
261 |               type: "boolean",
262 |               description: "Whether to archive (true) or restore (false) the block"
263 |             }
264 |           },
265 |           required: ["block_id", "block_type", "content"]
266 |         }
267 |       },
268 |       {
269 |         name: "get-block",
270 |         description: "Retrieve a block by its ID",
271 |         inputSchema: {
272 |           type: "object",
273 |           properties: {
274 |             block_id: {
275 |               type: "string",
276 |               description: "ID of the block to retrieve"
277 |             }
278 |           },
279 |           required: ["block_id"]
280 |         }
281 |       },
282 |       {
283 |         name: "search",
284 |         description: "Search Notion for pages or databases",
285 |         inputSchema: {
286 |           type: "object",
287 |           properties: {
288 |             query: {
289 |               type: "string",
290 |               description: "Search query string",
291 |               default: ""
292 |             },
293 |             filter: {
294 |               type: "object",
295 |               description: "Optional filter criteria"
296 |             },
297 |             sort: {
298 |               type: "object",
299 |               description: "Optional sort criteria"
300 |             },
301 |             start_cursor: {
302 |               type: "string",
303 |               description: "Cursor for pagination"
304 |             },
305 |             page_size: {
306 |               type: "number",
307 |               description: "Number of results per page",
308 |               default: 100
309 |             }
310 |           }
311 |         }
312 |       }
313 |     ]
314 |   };
315 | });
316 | 
317 | // Define a single CallToolRequestSchema handler for all tools
318 | server.setRequestHandler(z.object({
319 |   method: z.literal("tools/call"),
320 |   params: z.object({
321 |     name: z.string(),
322 |     arguments: z.any()
323 |   })
324 | }), async (request) => {
325 |   const { name, arguments: args } = request.params;
326 |   
327 |   try {
328 |     // Handle each tool based on name
329 |     if (name === "list-databases") {
330 |       const response = await notion.search({
331 |         filter: {
332 |           property: "object",
333 |           value: "database",
334 |         },
335 |         page_size: 100,
336 |         sort: {
337 |           direction: "descending",
338 |           timestamp: "last_edited_time",
339 |         },
340 |       });
341 | 
342 |       return {
343 |         content: [
344 |           {
345 |             type: "text",
346 |             text: JSON.stringify(response.results, null, 2),
347 |           },
348 |         ],
349 |       };
350 |     }
351 | 
352 |     else if (name === "query-database") {
353 |       console.error("Query database handler called with:", JSON.stringify(args, null, 2));
354 |       const { database_id, filter, sorts, start_cursor, page_size } = args;
355 |       
356 |       const queryParams = {
357 |         database_id,
358 |         page_size: page_size || 100,
359 |       };
360 | 
361 |       if (filter) queryParams.filter = filter;
362 |       if (sorts) queryParams.sorts = sorts;
363 |       if (start_cursor) queryParams.start_cursor = start_cursor;
364 | 
365 |       const response = await notion.databases.query(queryParams);
366 | 
367 |       return {
368 |         content: [
369 |           {
370 |             type: "text",
371 |             text: JSON.stringify(response, null, 2),
372 |           },
373 |         ],
374 |       };
375 |     }
376 | 
377 |     else if (name === "create-page") {
378 |       const { parent_id, properties, children } = args;
379 |       
380 |       const pageParams = {
381 |         parent: { database_id: parent_id },
382 |         properties,
383 |       };
384 | 
385 |       if (children) {
386 |         pageParams.children = children;
387 |       }
388 | 
389 |       const response = await notion.pages.create(pageParams);
390 | 
391 |       return {
392 |         content: [
393 |           {
394 |             type: "text",
395 |             text: JSON.stringify(response, null, 2),
396 |           },
397 |         ],
398 |       };
399 |     }
400 | 
401 |     else if (name === "update-page") {
402 |       const { page_id, properties, archived } = args;
403 |       
404 |       const updateParams = {
405 |         page_id,
406 |         properties,
407 |       };
408 | 
409 |       if (archived !== undefined) {
410 |         updateParams.archived = archived;
411 |       }
412 | 
413 |       const response = await notion.pages.update(updateParams);
414 | 
415 |       return {
416 |         content: [
417 |           {
418 |             type: "text",
419 |             text: JSON.stringify(response, null, 2),
420 |           },
421 |         ],
422 |       };
423 |     }
424 | 
425 |     else if (name === "create-database") {
426 |       let { parent_id, title, properties, icon, cover } = args;
427 |       
428 |       // Remove dashes if present in parent_id
429 |       parent_id = parent_id.replace(/-/g, "");
430 | 
431 |       const databaseParams = {
432 |         parent: {
433 |           type: "page_id",
434 |           page_id: parent_id,
435 |         },
436 |         title,
437 |         properties,
438 |       };
439 | 
440 |       // Set default emoji if icon is specified but emoji is empty
441 |       if (icon && icon.type === "emoji" && !icon.emoji) {
442 |         icon.emoji = "📄"; // Default document emoji
443 |         databaseParams.icon = icon;
444 |       } else if (icon) {
445 |         databaseParams.icon = icon;
446 |       }
447 | 
448 |       if (cover) {
449 |         databaseParams.cover = cover;
450 |       }
451 | 
452 |       const response = await notion.databases.create(databaseParams);
453 | 
454 |       return {
455 |         content: [
456 |           {
457 |             type: "text",
458 |             text: JSON.stringify(response, null, 2),
459 |           },
460 |         ],
461 |       };
462 |     }
463 | 
464 |     else if (name === "update-database") {
465 |       const { database_id, title, description, properties } = args;
466 |       
467 |       const updateParams = {
468 |         database_id,
469 |       };
470 | 
471 |       if (title !== undefined) {
472 |         updateParams.title = title;
473 |       }
474 | 
475 |       if (description !== undefined) {
476 |         updateParams.description = description;
477 |       }
478 | 
479 |       if (properties !== undefined) {
480 |         updateParams.properties = properties;
481 |       }
482 | 
483 |       const response = await notion.databases.update(updateParams);
484 | 
485 |       return {
486 |         content: [
487 |           {
488 |             type: "text",
489 |             text: JSON.stringify(response, null, 2),
490 |           },
491 |         ],
492 |       };
493 |     }
494 | 
495 |     else if (name === "get-page") {
496 |       let { page_id } = args;
497 |       
498 |       // Remove dashes if present in page_id
499 |       page_id = page_id.replace(/-/g, "");
500 | 
501 |       const response = await notion.pages.retrieve({ page_id });
502 | 
503 |       return {
504 |         content: [
505 |           {
506 |             type: "text",
507 |             text: JSON.stringify(response, null, 2),
508 |           },
509 |         ],
510 |       };
511 |     }
512 | 
513 |     else if (name === "get-block-children") {
514 |       let { block_id, start_cursor, page_size } = args;
515 |       
516 |       // Remove dashes if present in block_id
517 |       block_id = block_id.replace(/-/g, "");
518 | 
519 |       const params = {
520 |         block_id,
521 |         page_size: page_size || 100,
522 |       };
523 | 
524 |       if (start_cursor) {
525 |         params.start_cursor = start_cursor;
526 |       }
527 | 
528 |       const response = await notion.blocks.children.list(params);
529 | 
530 |       return {
531 |         content: [
532 |           {
533 |             type: "text",
534 |             text: JSON.stringify(response, null, 2),
535 |           },
536 |         ],
537 |       };
538 |     }
539 | 
540 |     else if (name === "append-block-children") {
541 |       let { block_id, children, after } = args;
542 |       
543 |       // Remove dashes if present in block_id
544 |       block_id = block_id.replace(/-/g, "");
545 | 
546 |       const params = {
547 |         block_id,
548 |         children,
549 |       };
550 | 
551 |       if (after) {
552 |         params.after = after.replace(/-/g, ""); // Ensure after ID is properly formatted
553 |       }
554 | 
555 |       const response = await notion.blocks.children.append(params);
556 | 
557 |       return {
558 |         content: [
559 |           {
560 |             type: "text",
561 |             text: JSON.stringify(response, null, 2),
562 |           },
563 |         ],
564 |       };
565 |     }
566 | 
567 |     else if (name === "update-block") {
568 |       let { block_id, block_type, content, archived } = args;
569 |       
570 |       // Remove dashes if present in block_id
571 |       block_id = block_id.replace(/-/g, "");
572 | 
573 |       const updateParams = {
574 |         block_id,
575 |         [block_type]: content,
576 |       };
577 | 
578 |       if (archived !== undefined) {
579 |         updateParams.archived = archived;
580 |       }
581 | 
582 |       const response = await notion.blocks.update(updateParams);
583 | 
584 |       return {
585 |         content: [
586 |           {
587 |             type: "text",
588 |             text: JSON.stringify(response, null, 2),
589 |           },
590 |         ],
591 |       };
592 |     }
593 | 
594 |     else if (name === "get-block") {
595 |       let { block_id } = args;
596 |       
597 |       // Remove dashes if present in block_id
598 |       block_id = block_id.replace(/-/g, "");
599 | 
600 |       const response = await notion.blocks.retrieve({ block_id });
601 | 
602 |       return {
603 |         content: [
604 |           {
605 |             type: "text",
606 |             text: JSON.stringify(response, null, 2),
607 |           },
608 |         ],
609 |       };
610 |     }
611 | 
612 |     else if (name === "search") {
613 |       const { query, filter, sort, start_cursor, page_size } = args;
614 |       
615 |       const searchParams = {
616 |         query: query || "",
617 |         page_size: page_size || 100,
618 |       };
619 | 
620 |       if (filter) {
621 |         searchParams.filter = filter;
622 |       }
623 | 
624 |       if (sort) {
625 |         searchParams.sort = sort;
626 |       }
627 | 
628 |       if (start_cursor) {
629 |         searchParams.start_cursor = start_cursor;
630 |       }
631 | 
632 |       const response = await notion.search(searchParams);
633 | 
634 |       return {
635 |         content: [
636 |           {
637 |             type: "text",
638 |             text: JSON.stringify(response, null, 2),
639 |           },
640 |         ],
641 |       };
642 |     }
643 |     
644 |     // If we get here, the tool name wasn't recognized
645 |     return {
646 |       isError: true,
647 |       content: [
648 |         {
649 |           type: "text",
650 |           text: `Unknown tool: ${name}`,
651 |         },
652 |       ],
653 |     };
654 |   } catch (error) {
655 |     return {
656 |       isError: true,
657 |       content: [
658 |         {
659 |           type: "text",
660 |           text: `Error executing ${name}: ${error.message}`,
661 |         },
662 |       ],
663 |     };
664 |   }
665 | });
666 | 
667 | // Start the server
668 | async function main() {
669 |   const transport = new StdioServerTransport();
670 |   await server.connect(transport);
671 |   console.error("Notion MCP Server running on stdio");
672 | }
673 | 
674 | // Add error handling for unhandled rejections
675 | process.on('unhandledRejection', (reason, promise) => {
676 |   console.error('Unhandled Rejection at:', promise, 'reason:', reason);
677 | });
678 | 
679 | main().catch((error) => {
680 |   console.error("Fatal error in main():", error);
681 |   process.exit(1);
682 | });
683 | 
```