@notionhq/notion-mcp-server 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dockerignore ADDED
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ Dockerfile
3
+ docker-compose.yml
package/Dockerfile ADDED
@@ -0,0 +1,36 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ # Use Node.js LTS as the base image
4
+ FROM node:20-slim AS builder
5
+
6
+ # Set working directory
7
+ WORKDIR /app
8
+
9
+ # Copy package.json and package-lock.json
10
+ COPY package*.json ./
11
+
12
+ # Install dependencies
13
+ RUN --mount=type=cache,target=/root/.npm npm ci --ignore-scripts --omit-dev
14
+
15
+ # Copy source code
16
+ COPY . .
17
+
18
+ # Build the package
19
+ RUN --mount=type=cache,target=/root/.npm npm run build
20
+
21
+ # Install package globally
22
+ RUN --mount=type=cache,target=/root/.npm npm link
23
+
24
+ # Minimal image for runtime
25
+ FROM node:20-slim
26
+
27
+ # Copy built package from builder stage
28
+ COPY scripts/notion-openapi.json /usr/local/scripts/
29
+ COPY --from=builder /usr/local/lib/node_modules/@notionhq/notion-mcp-server /usr/local/lib/node_modules/@notionhq/notion-mcp-server
30
+ COPY --from=builder /usr/local/bin/notion-mcp-server /usr/local/bin/notion-mcp-server
31
+
32
+ # Set default environment variables
33
+ ENV OPENAPI_MCP_HEADERS="{}"
34
+
35
+ # Set entrypoint
36
+ ENTRYPOINT ["notion-mcp-server"]
package/README.md CHANGED
@@ -4,10 +4,24 @@
4
4
 
5
5
  This project implements an [MCP server](https://spec.modelcontextprotocol.io/) for the [Notion API](https://developers.notion.com/reference/intro).
6
6
 
7
- ![mcp-demo](https://github.com/user-attachments/assets/1f82cd82-87e4-4d7c-8b72-486ef1f18663)
7
+ ![mcp-demo](https://github.com/user-attachments/assets/e3ff90a7-7801-48a9-b807-f7dd47f0d3d6)
8
8
 
9
9
  ### Installation
10
10
 
11
+ #### 1. Setting up Integration in Notion:
12
+ 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.
13
+
14
+ ![Creating a Notion Integration token](docs/images/integrations-creation.png)
15
+
16
+ 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_.
17
+
18
+ For example, you can create a read-only integration token by giving only "Read content" access from the "Configuration" tab:
19
+
20
+ ![Notion Integration Token Capabilities showing Read content checked](docs/images/integrations-capabilities.png)
21
+
22
+ #### 2. Adding MCP config to your client:
23
+
24
+ ##### Using npm:
11
25
  Add the following to your `.cursor/mcp.json` or `claude_desktop_config.json` (MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`)
12
26
 
13
27
  ```javascript
@@ -24,11 +38,44 @@ Add the following to your `.cursor/mcp.json` or `claude_desktop_config.json` (Ma
24
38
  }
25
39
  ```
26
40
 
41
+ ##### Using Docker:
42
+ You can also run the MCP server using Docker. First, build the Docker image:
43
+
44
+ ```bash
45
+ docker-compose build
46
+ ```
47
+
48
+ Then, add the following to your `.cursor/mcp.json` or `claude_desktop_config.json`:
49
+
50
+ ```javascript
51
+ {
52
+ "mcpServers": {
53
+ "notionApi": {
54
+ "command": "docker",
55
+ "args": [
56
+ "run",
57
+ "--rm",
58
+ "-i",
59
+ "-e",
60
+ "OPENAPI_MCP_HEADERS={\"Authorization\": \"Bearer ntn_****\", \"Notion-Version\": \"2022-06-28\"}",
61
+ "notion-mcp-server-notion-mcp-server"
62
+ ]
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
27
68
  Don't forget to replace `ntn_****` with your integration secret. Find it from your integration configuration tab:
28
- <img width="918" alt="retrieve-token" src="https://github.com/user-attachments/assets/67b44536-5333-49fa-809c-59581bf5370a" />
29
69
 
70
+ ![Copying your Integration token from the Configuration tab in the developer portal](https://github.com/user-attachments/assets/67b44536-5333-49fa-809c-59581bf5370a)
71
+
72
+ #### 3. Connecting content to integration:
30
73
  Ensure relevant pages and databases are connected to your integration.
31
74
 
75
+ To do this, you'll need to visit that page, and click on the 3 dots, and select "Connect to integration".
76
+
77
+ ![Adding Integration Token to Notion Connections](docs/images/connections.png)
78
+
32
79
  ### Examples
33
80
 
34
81
  1. Using the following instruction
@@ -0,0 +1,6 @@
1
+ services:
2
+ notion-mcp-server:
3
+ build: .
4
+ stdin_open: true
5
+ tty: true
6
+ restart: unless-stopped
Binary file
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "mcp",
7
7
  "server"
8
8
  ],
9
- "version": "1.4.0",
9
+ "version": "1.6.0",
10
10
  "license": "MIT",
11
11
  "type": "module",
12
12
  "scripts": {
@@ -248,9 +248,552 @@
248
248
  "type": "object",
249
249
  "properties": {
250
250
  "filter": {
251
- "type": "string",
251
+ "type": "object",
252
252
  "description": "When supplied, limits which pages are returned based on the [filter conditions](ref:post-database-query-filter).",
253
- "format": "json"
253
+ "or": {
254
+ "type": "array",
255
+ "items": {
256
+ "type": "object",
257
+ "properties": {
258
+ "type": "object",
259
+ "properties": {
260
+ "property": {
261
+ "type": "string"
262
+ },
263
+ "title": {
264
+ "type": "object",
265
+ "properties": {
266
+ "equals": {
267
+ "type": "string"
268
+ },
269
+ "does_not_equal": {
270
+ "type": "string"
271
+ },
272
+ "contains": {
273
+ "type": "string"
274
+ },
275
+ "does_not_contain": {
276
+ "type": "string"
277
+ },
278
+ "starts_with": {
279
+ "type": "string"
280
+ },
281
+ "ends_with": {
282
+ "type": "string"
283
+ }
284
+ }
285
+ },
286
+ "rich_text": {
287
+ "type": "object",
288
+ "properties": {
289
+ "equals": {
290
+ "type": "string"
291
+ },
292
+ "does_not_equal": {
293
+ "type": "string"
294
+ },
295
+ "contains": {
296
+ "type": "string"
297
+ },
298
+ "does_not_contain": {
299
+ "type": "string"
300
+ },
301
+ "starts_with": {
302
+ "type": "string"
303
+ },
304
+ "ends_with": {
305
+ "type": "string"
306
+ }
307
+ }
308
+ },
309
+ "url": {
310
+ "type": "object",
311
+ "properties": {
312
+ "equals": {
313
+ "type": "string"
314
+ },
315
+ "does_not_equal": {
316
+ "type": "string"
317
+ },
318
+ "contains": {
319
+ "type": "string"
320
+ },
321
+ "does_not_contain": {
322
+ "type": "string"
323
+ },
324
+ "starts_with": {
325
+ "type": "string"
326
+ },
327
+ "ends_with": {
328
+ "type": "string"
329
+ }
330
+ }
331
+ },
332
+ "email": {
333
+ "type": "object",
334
+ "properties": {
335
+ "equals": {
336
+ "type": "string"
337
+ },
338
+ "does_not_equal": {
339
+ "type": "string"
340
+ },
341
+ "contains": {
342
+ "type": "string"
343
+ },
344
+ "does_not_contain": {
345
+ "type": "string"
346
+ },
347
+ "starts_with": {
348
+ "type": "string"
349
+ },
350
+ "ends_with": {
351
+ "type": "string"
352
+ }
353
+ }
354
+ },
355
+ "phone_number": {
356
+ "type": "object",
357
+ "properties": {
358
+ "equals": {
359
+ "type": "string"
360
+ },
361
+ "does_not_equal": {
362
+ "type": "string"
363
+ },
364
+ "contains": {
365
+ "type": "string"
366
+ },
367
+ "does_not_contain": {
368
+ "type": "string"
369
+ },
370
+ "starts_with": {
371
+ "type": "string"
372
+ },
373
+ "ends_with": {
374
+ "type": "string"
375
+ }
376
+ }
377
+ },
378
+ "number": {
379
+ "type": "object",
380
+ "properties": {
381
+ "equals": {
382
+ "type": "number"
383
+ },
384
+ "does_not_equal": {
385
+ "type": "number"
386
+ },
387
+ "contains": {
388
+ "type": "number"
389
+ },
390
+ "does_not_contain": {
391
+ "type": "number"
392
+ },
393
+ "starts_with": {
394
+ "type": "number"
395
+ },
396
+ "ends_with": {
397
+ "type": "number"
398
+ }
399
+ }
400
+ },
401
+ "checkbox": {
402
+ "type": "object",
403
+ "properties": {
404
+ "equals": {
405
+ "type": "boolean"
406
+ },
407
+ "does_not_equal": {
408
+ "type": "boolean"
409
+ }
410
+ }
411
+ },
412
+ "select": {
413
+ "type": "object",
414
+ "properties": {
415
+ "equals": {
416
+ "type": "string"
417
+ },
418
+ "does_not_equal": {
419
+ "type": "string"
420
+ }
421
+ }
422
+ },
423
+ "multi_select": {
424
+ "type": "object",
425
+ "properties": {
426
+ "contains": {
427
+ "type": "string"
428
+ },
429
+ "does_not_contain": {
430
+ "type": "string"
431
+ }
432
+ }
433
+ },
434
+ "status": {
435
+ "type": "object",
436
+ "properties": {
437
+ "equals": {
438
+ "type": "string"
439
+ },
440
+ "does_not_equal": {
441
+ "type": "string"
442
+ }
443
+ }
444
+ },
445
+ "date": {
446
+ "type": "object",
447
+ "properties": {
448
+ "equals": {
449
+ "type": "string",
450
+ "format": "date"
451
+ },
452
+ "before": {
453
+ "type": "string",
454
+ "format": "date"
455
+ },
456
+ "after": {
457
+ "type": "string",
458
+ "format": "date"
459
+ },
460
+ "on_or_before": {
461
+ "type": "string",
462
+ "format": "date"
463
+ },
464
+ "on_or_after": {
465
+ "type": "string",
466
+ "format": "date"
467
+ }
468
+ }
469
+ },
470
+ "created_time": {
471
+ "type": "object",
472
+ "properties": {
473
+ "equals": {
474
+ "type": "string",
475
+ "format": "date"
476
+ },
477
+ "before": {
478
+ "type": "string",
479
+ "format": "date"
480
+ },
481
+ "after": {
482
+ "type": "string",
483
+ "format": "date"
484
+ },
485
+ "on_or_before": {
486
+ "type": "string",
487
+ "format": "date"
488
+ },
489
+ "on_or_after": {
490
+ "type": "string",
491
+ "format": "date"
492
+ }
493
+ }
494
+ },
495
+ "last_edited_time": {
496
+ "type": "object",
497
+ "properties": {
498
+ "equals": {
499
+ "type": "string",
500
+ "format": "date"
501
+ },
502
+ "before": {
503
+ "type": "string",
504
+ "format": "date"
505
+ },
506
+ "after": {
507
+ "type": "string",
508
+ "format": "date"
509
+ },
510
+ "on_or_before": {
511
+ "type": "string",
512
+ "format": "date"
513
+ },
514
+ "on_or_after": {
515
+ "type": "string",
516
+ "format": "date"
517
+ }
518
+ }
519
+ }
520
+ }
521
+ }
522
+ },
523
+ "maxItems": 100
524
+ },
525
+ "and": {
526
+ "type": "array",
527
+ "items": {
528
+ "type": "object",
529
+ "properties": {
530
+ "type": "object",
531
+ "properties": {
532
+ "property": {
533
+ "type": "string"
534
+ },
535
+ "title": {
536
+ "type": "object",
537
+ "properties": {
538
+ "equals": {
539
+ "type": "string"
540
+ },
541
+ "does_not_equal": {
542
+ "type": "string"
543
+ },
544
+ "contains": {
545
+ "type": "string"
546
+ },
547
+ "does_not_contain": {
548
+ "type": "string"
549
+ },
550
+ "starts_with": {
551
+ "type": "string"
552
+ },
553
+ "ends_with": {
554
+ "type": "string"
555
+ }
556
+ }
557
+ },
558
+ "rich_text": {
559
+ "type": "object",
560
+ "properties": {
561
+ "equals": {
562
+ "type": "string"
563
+ },
564
+ "does_not_equal": {
565
+ "type": "string"
566
+ },
567
+ "contains": {
568
+ "type": "string"
569
+ },
570
+ "does_not_contain": {
571
+ "type": "string"
572
+ },
573
+ "starts_with": {
574
+ "type": "string"
575
+ },
576
+ "ends_with": {
577
+ "type": "string"
578
+ }
579
+ }
580
+ },
581
+ "url": {
582
+ "type": "object",
583
+ "properties": {
584
+ "equals": {
585
+ "type": "string"
586
+ },
587
+ "does_not_equal": {
588
+ "type": "string"
589
+ },
590
+ "contains": {
591
+ "type": "string"
592
+ },
593
+ "does_not_contain": {
594
+ "type": "string"
595
+ },
596
+ "starts_with": {
597
+ "type": "string"
598
+ },
599
+ "ends_with": {
600
+ "type": "string"
601
+ }
602
+ }
603
+ },
604
+ "email": {
605
+ "type": "object",
606
+ "properties": {
607
+ "equals": {
608
+ "type": "string"
609
+ },
610
+ "does_not_equal": {
611
+ "type": "string"
612
+ },
613
+ "contains": {
614
+ "type": "string"
615
+ },
616
+ "does_not_contain": {
617
+ "type": "string"
618
+ },
619
+ "starts_with": {
620
+ "type": "string"
621
+ },
622
+ "ends_with": {
623
+ "type": "string"
624
+ }
625
+ }
626
+ },
627
+ "phone_number": {
628
+ "type": "object",
629
+ "properties": {
630
+ "equals": {
631
+ "type": "string"
632
+ },
633
+ "does_not_equal": {
634
+ "type": "string"
635
+ },
636
+ "contains": {
637
+ "type": "string"
638
+ },
639
+ "does_not_contain": {
640
+ "type": "string"
641
+ },
642
+ "starts_with": {
643
+ "type": "string"
644
+ },
645
+ "ends_with": {
646
+ "type": "string"
647
+ }
648
+ }
649
+ },
650
+ "number": {
651
+ "type": "object",
652
+ "properties": {
653
+ "equals": {
654
+ "type": "number"
655
+ },
656
+ "does_not_equal": {
657
+ "type": "number"
658
+ },
659
+ "contains": {
660
+ "type": "number"
661
+ },
662
+ "does_not_contain": {
663
+ "type": "number"
664
+ },
665
+ "starts_with": {
666
+ "type": "number"
667
+ },
668
+ "ends_with": {
669
+ "type": "number"
670
+ }
671
+ }
672
+ },
673
+ "checkbox": {
674
+ "type": "object",
675
+ "properties": {
676
+ "equals": {
677
+ "type": "boolean"
678
+ },
679
+ "does_not_equal": {
680
+ "type": "boolean"
681
+ }
682
+ }
683
+ },
684
+ "select": {
685
+ "type": "object",
686
+ "properties": {
687
+ "equals": {
688
+ "type": "string"
689
+ },
690
+ "does_not_equal": {
691
+ "type": "string"
692
+ }
693
+ }
694
+ },
695
+ "multi_select": {
696
+ "type": "object",
697
+ "properties": {
698
+ "contains": {
699
+ "type": "string"
700
+ },
701
+ "does_not_contain": {
702
+ "type": "string"
703
+ }
704
+ }
705
+ },
706
+ "status": {
707
+ "type": "object",
708
+ "properties": {
709
+ "equals": {
710
+ "type": "string"
711
+ },
712
+ "does_not_equal": {
713
+ "type": "string"
714
+ }
715
+ }
716
+ },
717
+ "date": {
718
+ "type": "object",
719
+ "properties": {
720
+ "equals": {
721
+ "type": "string",
722
+ "format": "date"
723
+ },
724
+ "before": {
725
+ "type": "string",
726
+ "format": "date"
727
+ },
728
+ "after": {
729
+ "type": "string",
730
+ "format": "date"
731
+ },
732
+ "on_or_before": {
733
+ "type": "string",
734
+ "format": "date"
735
+ },
736
+ "on_or_after": {
737
+ "type": "string",
738
+ "format": "date"
739
+ }
740
+ }
741
+ },
742
+ "created_time": {
743
+ "type": "object",
744
+ "properties": {
745
+ "equals": {
746
+ "type": "string",
747
+ "format": "date"
748
+ },
749
+ "before": {
750
+ "type": "string",
751
+ "format": "date"
752
+ },
753
+ "after": {
754
+ "type": "string",
755
+ "format": "date"
756
+ },
757
+ "on_or_before": {
758
+ "type": "string",
759
+ "format": "date"
760
+ },
761
+ "on_or_after": {
762
+ "type": "string",
763
+ "format": "date"
764
+ }
765
+ }
766
+ },
767
+ "last_edited_time": {
768
+ "type": "object",
769
+ "properties": {
770
+ "equals": {
771
+ "type": "string",
772
+ "format": "date"
773
+ },
774
+ "before": {
775
+ "type": "string",
776
+ "format": "date"
777
+ },
778
+ "after": {
779
+ "type": "string",
780
+ "format": "date"
781
+ },
782
+ "on_or_before": {
783
+ "type": "string",
784
+ "format": "date"
785
+ },
786
+ "on_or_after": {
787
+ "type": "string",
788
+ "format": "date"
789
+ }
790
+ }
791
+ }
792
+ }
793
+ }
794
+ },
795
+ "maxItems": 100
796
+ }
254
797
  },
255
798
  "sorts": {
256
799
  "type": "array",
@@ -276,6 +819,12 @@
276
819
  "type": "integer",
277
820
  "description": "The number of items from the full list desired in the response. Maximum: 100",
278
821
  "default": 100
822
+ },
823
+ "archived": {
824
+ "type": "boolean"
825
+ },
826
+ "in_trash": {
827
+ "type": "boolean"
279
828
  }
280
829
  }
281
830
  }
@@ -415,6 +964,97 @@
415
964
  "properties": {
416
965
  "children": {
417
966
  "type": "array",
967
+ "items": {
968
+ "type": "object",
969
+ "properties": {
970
+ "paragraph": {
971
+ "type": "object",
972
+ "properties": {
973
+ "rich_text": {
974
+ "type": "array",
975
+ "items": {
976
+ "type": "object",
977
+ "properties": {
978
+ "text": {
979
+ "type": "object",
980
+ "properties": {
981
+ "content": {
982
+ "type": "string",
983
+ "maxLength": 2000
984
+ },
985
+ "link": {
986
+ "type": ["object", "null"],
987
+ "properties": {
988
+ "url": {
989
+ "type": "string"
990
+ }
991
+ },
992
+ "required": ["url"]
993
+ }
994
+ },
995
+ "additionalProperties": false,
996
+ "required": ["content"]
997
+ },
998
+ "type": {
999
+ "enum": ["text"]
1000
+ }
1001
+ },
1002
+ "additionalProperties": false,
1003
+ "required": ["text"]
1004
+ },
1005
+ "maxItems": 100
1006
+ }
1007
+ },
1008
+ "additionalProperties": false,
1009
+ "required": ["rich_text"]
1010
+ },
1011
+ "bulleted_list_item": {
1012
+ "type": "object",
1013
+ "properties": {
1014
+ "rich_text": {
1015
+ "type": "array",
1016
+ "items": {
1017
+ "type": "object",
1018
+ "properties": {
1019
+ "text": {
1020
+ "type": "object",
1021
+ "properties": {
1022
+ "content": {
1023
+ "type": "string",
1024
+ "maxLength": 2000
1025
+ },
1026
+ "link": {
1027
+ "type": ["object", "null"],
1028
+ "properties": {
1029
+ "url": {
1030
+ "type": "string"
1031
+ }
1032
+ },
1033
+ "required": ["url"]
1034
+ }
1035
+ },
1036
+ "additionalProperties": false,
1037
+ "required": ["content"]
1038
+ },
1039
+ "type": {
1040
+ "enum": ["text"]
1041
+ }
1042
+ },
1043
+ "additionalProperties": false,
1044
+ "required": ["text"]
1045
+ },
1046
+ "maxItems": 100
1047
+ }
1048
+ },
1049
+ "additionalProperties": false,
1050
+ "required": ["rich_text"]
1051
+ },
1052
+ "type": {
1053
+ "enum": ["paragraph", "bulleted_list_item"]
1054
+ }
1055
+ },
1056
+ "additionalProperties": false
1057
+ },
418
1058
  "description": "Child content to append to a container block as an array of [block objects](ref:block)"
419
1059
  },
420
1060
  "after": {
@@ -49,7 +49,6 @@ export class HttpClient {
49
49
  }
50
50
 
51
51
  private async prepareFileUpload(operation: OpenAPIV3.OperationObject, params: Record<string, any>): Promise<FormData | null> {
52
- console.error('prepareFileUpload', { operation, params })
53
52
  const fileParams = isFileUploadParameter(operation)
54
53
  if (fileParams.length === 0) return null
55
54
 
@@ -57,7 +56,6 @@ export class HttpClient {
57
56
 
58
57
  // Handle file uploads
59
58
  for (const param of fileParams) {
60
- console.error(`extracting ${param}`, {params})
61
59
  const filePath = params[param]
62
60
  if (!filePath) {
63
61
  throw new Error(`File path must be provided for parameter: ${param}`)
@@ -163,7 +161,6 @@ export class HttpClient {
163
161
  }
164
162
 
165
163
  // first argument is url parameters, second is body parameters
166
- console.error('calling operation', { operationId, urlParameters, bodyParams, requestConfig })
167
164
  const response = await operationFn(urlParameters, hasBody ? bodyParams : undefined, requestConfig)
168
165
 
169
166
  // Convert axios headers to Headers object
@@ -76,12 +76,10 @@ export class MCPProxy {
76
76
 
77
77
  // Handle tool calling
78
78
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
79
- console.error('calling tool', request.params)
80
79
  const { name, arguments: params } = request.params
81
80
 
82
81
  // Find the operation in OpenAPI spec
83
82
  const operation = this.findOperation(name)
84
- console.error('operations', this.openApiLookup)
85
83
  if (!operation) {
86
84
  throw new Error(`Method ${name} not found`)
87
85
  }