@mieubrisse/notion-mcp-server 2.0.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.
Files changed (36) hide show
  1. package/.devcontainer/devcontainer.json +4 -0
  2. package/.dockerignore +3 -0
  3. package/.github/pull_request_template.md +8 -0
  4. package/.github/workflows/ci.yml +42 -0
  5. package/Dockerfile +36 -0
  6. package/LICENSE +7 -0
  7. package/README.md +412 -0
  8. package/docker-compose.yml +6 -0
  9. package/docs/images/connections.png +0 -0
  10. package/docs/images/integration-access.png +0 -0
  11. package/docs/images/integrations-capabilities.png +0 -0
  12. package/docs/images/integrations-creation.png +0 -0
  13. package/docs/images/page-access-edit.png +0 -0
  14. package/package.json +63 -0
  15. package/scripts/build-cli.js +30 -0
  16. package/scripts/notion-openapi.json +2238 -0
  17. package/scripts/start-server.ts +243 -0
  18. package/src/init-server.ts +50 -0
  19. package/src/openapi-mcp-server/README.md +3 -0
  20. package/src/openapi-mcp-server/auth/index.ts +2 -0
  21. package/src/openapi-mcp-server/auth/template.ts +24 -0
  22. package/src/openapi-mcp-server/auth/types.ts +26 -0
  23. package/src/openapi-mcp-server/client/__tests__/http-client-upload.test.ts +205 -0
  24. package/src/openapi-mcp-server/client/__tests__/http-client.integration.test.ts +282 -0
  25. package/src/openapi-mcp-server/client/__tests__/http-client.test.ts +537 -0
  26. package/src/openapi-mcp-server/client/http-client.ts +198 -0
  27. package/src/openapi-mcp-server/client/polyfill-headers.ts +42 -0
  28. package/src/openapi-mcp-server/index.ts +3 -0
  29. package/src/openapi-mcp-server/mcp/__tests__/proxy.test.ts +479 -0
  30. package/src/openapi-mcp-server/mcp/proxy.ts +250 -0
  31. package/src/openapi-mcp-server/openapi/__tests__/file-upload.test.ts +100 -0
  32. package/src/openapi-mcp-server/openapi/__tests__/parser-multipart.test.ts +602 -0
  33. package/src/openapi-mcp-server/openapi/__tests__/parser.test.ts +1448 -0
  34. package/src/openapi-mcp-server/openapi/file-upload.ts +40 -0
  35. package/src/openapi-mcp-server/openapi/parser.ts +529 -0
  36. package/tsconfig.json +26 -0
@@ -0,0 +1,602 @@
1
+ import { OpenAPIV3 } from 'openapi-types'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { OpenAPIToMCPConverter } from '../parser'
4
+
5
+ describe('OpenAPI Multipart Form Parser', () => {
6
+ it('converts single file upload endpoint to tool', () => {
7
+ const spec: OpenAPIV3.Document = {
8
+ openapi: '3.0.0',
9
+ info: { title: 'Test API', version: '1.0.0' },
10
+ paths: {
11
+ '/pets/{id}/photo': {
12
+ post: {
13
+ operationId: 'uploadPetPhoto',
14
+ summary: 'Upload a photo for a pet',
15
+ parameters: [
16
+ {
17
+ name: 'id',
18
+ in: 'path',
19
+ required: true,
20
+ schema: { type: 'integer' },
21
+ },
22
+ ],
23
+ requestBody: {
24
+ required: true,
25
+ content: {
26
+ 'multipart/form-data': {
27
+ schema: {
28
+ type: 'object',
29
+ required: ['photo'],
30
+ properties: {
31
+ photo: {
32
+ type: 'string',
33
+ format: 'binary',
34
+ description: 'The photo to upload',
35
+ },
36
+ caption: {
37
+ type: 'string',
38
+ description: 'Optional caption for the photo',
39
+ },
40
+ },
41
+ },
42
+ },
43
+ },
44
+ },
45
+ responses: {
46
+ '201': {
47
+ description: 'Photo uploaded successfully',
48
+ },
49
+ },
50
+ },
51
+ },
52
+ },
53
+ }
54
+
55
+ const converter = new OpenAPIToMCPConverter(spec)
56
+ const { tools } = converter.convertToMCPTools()
57
+ expect(Object.keys(tools)).toHaveLength(1)
58
+
59
+ const [tool] = Object.values(tools)
60
+ expect(tool.methods).toHaveLength(1)
61
+ const [method] = tool.methods
62
+ expect(method.name).toBe('uploadPetPhoto')
63
+ expect(method.description).toContain('Upload a photo for a pet')
64
+
65
+ // Check parameters
66
+ expect(method.inputSchema.properties).toEqual({
67
+ id: {
68
+ type: 'integer',
69
+ },
70
+ photo: {
71
+ type: 'string',
72
+ format: 'uri-reference',
73
+ description: expect.stringContaining('The photo to upload (absolute paths to local files)'),
74
+ },
75
+ caption: {
76
+ type: 'string',
77
+ description: expect.stringContaining('Optional caption'),
78
+ },
79
+ })
80
+
81
+ expect(method.inputSchema.required).toContain('id')
82
+ expect(method.inputSchema.required).toContain('photo')
83
+ expect(method.inputSchema.required).not.toContain('caption')
84
+ })
85
+
86
+ it('converts multiple file upload endpoint to tool', () => {
87
+ const spec: OpenAPIV3.Document = {
88
+ openapi: '3.0.0',
89
+ info: { title: 'Test API', version: '1.0.0' },
90
+ paths: {
91
+ '/pets/{id}/documents': {
92
+ post: {
93
+ operationId: 'uploadPetDocuments',
94
+ summary: 'Upload multiple documents for a pet',
95
+ parameters: [
96
+ {
97
+ name: 'id',
98
+ in: 'path',
99
+ required: true,
100
+ schema: { type: 'integer' },
101
+ },
102
+ ],
103
+ requestBody: {
104
+ required: true,
105
+ content: {
106
+ 'multipart/form-data': {
107
+ schema: {
108
+ type: 'object',
109
+ required: ['documents'],
110
+ properties: {
111
+ documents: {
112
+ type: 'array',
113
+ items: {
114
+ type: 'string',
115
+ format: 'binary',
116
+ },
117
+ description: 'The documents to upload (max 5 files)',
118
+ },
119
+ tags: {
120
+ type: 'array',
121
+ items: {
122
+ type: 'string',
123
+ },
124
+ description: 'Optional tags for the documents',
125
+ },
126
+ },
127
+ },
128
+ },
129
+ },
130
+ },
131
+ responses: {
132
+ '201': {
133
+ description: 'Documents uploaded successfully',
134
+ },
135
+ },
136
+ },
137
+ },
138
+ },
139
+ }
140
+
141
+ const converter = new OpenAPIToMCPConverter(spec)
142
+ const { tools } = converter.convertToMCPTools()
143
+ expect(Object.keys(tools)).toHaveLength(1)
144
+
145
+ const [tool] = Object.values(tools)
146
+ expect(tool.methods).toHaveLength(1)
147
+ const [method] = tool.methods
148
+ expect(method.name).toBe('uploadPetDocuments')
149
+ expect(method.description).toContain('Upload multiple documents')
150
+
151
+ // Check parameters
152
+ expect(method.inputSchema.properties).toEqual({
153
+ id: {
154
+ type: 'integer',
155
+ },
156
+ documents: {
157
+ type: 'array',
158
+ items: {
159
+ type: 'string',
160
+ format: 'uri-reference',
161
+ description: 'absolute paths to local files',
162
+ },
163
+ description: expect.stringContaining('max 5 files'),
164
+ },
165
+ tags: {
166
+ type: 'array',
167
+ items: {
168
+ type: 'string',
169
+ },
170
+ description: expect.stringContaining('Optional tags'),
171
+ },
172
+ })
173
+
174
+ expect(method.inputSchema.required).toContain('id')
175
+ expect(method.inputSchema.required).toContain('documents')
176
+ expect(method.inputSchema.required).not.toContain('tags')
177
+ })
178
+
179
+ it('handles complex multipart forms with mixed content', () => {
180
+ const spec: OpenAPIV3.Document = {
181
+ openapi: '3.0.0',
182
+ info: { title: 'Test API', version: '1.0.0' },
183
+ paths: {
184
+ '/pets/{id}/profile': {
185
+ post: {
186
+ operationId: 'updatePetProfile',
187
+ summary: 'Update pet profile with images and data',
188
+ parameters: [
189
+ {
190
+ name: 'id',
191
+ in: 'path',
192
+ required: true,
193
+ schema: { type: 'integer' },
194
+ },
195
+ ],
196
+ requestBody: {
197
+ required: true,
198
+ content: {
199
+ 'multipart/form-data': {
200
+ schema: {
201
+ type: 'object',
202
+ required: ['avatar', 'details'],
203
+ properties: {
204
+ avatar: {
205
+ type: 'string',
206
+ format: 'binary',
207
+ description: 'Profile picture',
208
+ },
209
+ gallery: {
210
+ type: 'array',
211
+ items: {
212
+ type: 'string',
213
+ format: 'binary',
214
+ },
215
+ description: 'Additional pet photos',
216
+ },
217
+ details: {
218
+ type: 'object',
219
+ properties: {
220
+ name: { type: 'string' },
221
+ age: { type: 'integer' },
222
+ breed: { type: 'string' },
223
+ },
224
+ },
225
+ preferences: {
226
+ type: 'array',
227
+ items: {
228
+ type: 'object',
229
+ properties: {
230
+ category: { type: 'string' },
231
+ value: { type: 'string' },
232
+ },
233
+ },
234
+ },
235
+ },
236
+ },
237
+ },
238
+ },
239
+ },
240
+ responses: {
241
+ '200': {
242
+ description: 'Profile updated successfully',
243
+ },
244
+ },
245
+ },
246
+ },
247
+ },
248
+ }
249
+
250
+ const converter = new OpenAPIToMCPConverter(spec)
251
+ const { tools } = converter.convertToMCPTools()
252
+ expect(Object.keys(tools)).toHaveLength(1)
253
+
254
+ const [tool] = Object.values(tools)
255
+ expect(tool.methods).toHaveLength(1)
256
+ const [method] = tool.methods
257
+ expect(method.name).toBe('updatePetProfile')
258
+ expect(method.description).toContain('Update pet profile')
259
+
260
+ // Check parameters
261
+ expect(method.inputSchema.properties).toEqual({
262
+ id: {
263
+ type: 'integer',
264
+ },
265
+ avatar: {
266
+ type: 'string',
267
+ format: 'uri-reference',
268
+ description: expect.stringContaining('Profile picture (absolute paths to local files)'),
269
+ },
270
+ gallery: {
271
+ type: 'array',
272
+ items: {
273
+ type: 'string',
274
+ format: 'uri-reference',
275
+ description: 'absolute paths to local files',
276
+ },
277
+ description: expect.stringContaining('Additional pet photos'),
278
+ },
279
+ details: {
280
+ type: 'object',
281
+ properties: {
282
+ name: { type: 'string' },
283
+ age: { type: 'integer' },
284
+ breed: { type: 'string' },
285
+ },
286
+ additionalProperties: true,
287
+ },
288
+ preferences: {
289
+ type: 'array',
290
+ items: {
291
+ type: 'object',
292
+ properties: {
293
+ category: { type: 'string' },
294
+ value: { type: 'string' },
295
+ },
296
+ additionalProperties: true,
297
+ },
298
+ },
299
+ })
300
+
301
+ expect(method.inputSchema.required).toContain('id')
302
+ expect(method.inputSchema.required).toContain('avatar')
303
+ expect(method.inputSchema.required).toContain('details')
304
+ expect(method.inputSchema.required).not.toContain('gallery')
305
+ expect(method.inputSchema.required).not.toContain('preferences')
306
+ })
307
+
308
+ it('handles optional file uploads in multipart forms', () => {
309
+ const spec: OpenAPIV3.Document = {
310
+ openapi: '3.0.0',
311
+ info: { title: 'Test API', version: '1.0.0' },
312
+ paths: {
313
+ '/pets/{id}/metadata': {
314
+ post: {
315
+ operationId: 'updatePetMetadata',
316
+ summary: 'Update pet metadata with optional attachments',
317
+ parameters: [
318
+ {
319
+ name: 'id',
320
+ in: 'path',
321
+ required: true,
322
+ schema: { type: 'integer' },
323
+ },
324
+ ],
325
+ requestBody: {
326
+ required: true,
327
+ content: {
328
+ 'multipart/form-data': {
329
+ schema: {
330
+ type: 'object',
331
+ required: ['metadata'],
332
+ properties: {
333
+ metadata: {
334
+ type: 'object',
335
+ required: ['name'],
336
+ properties: {
337
+ name: { type: 'string' },
338
+ description: { type: 'string' },
339
+ },
340
+ },
341
+ certificate: {
342
+ type: 'string',
343
+ format: 'binary',
344
+ description: 'Optional pet certificate',
345
+ },
346
+ vaccinations: {
347
+ type: 'array',
348
+ items: {
349
+ type: 'string',
350
+ format: 'binary',
351
+ },
352
+ description: 'Optional vaccination records',
353
+ },
354
+ },
355
+ },
356
+ },
357
+ },
358
+ },
359
+ responses: {
360
+ '200': {
361
+ description: 'Metadata updated successfully',
362
+ },
363
+ },
364
+ },
365
+ },
366
+ },
367
+ }
368
+
369
+ const converter = new OpenAPIToMCPConverter(spec)
370
+ const { tools } = converter.convertToMCPTools()
371
+ const [tool] = Object.values(tools)
372
+ const [method] = tool.methods
373
+
374
+ expect(method.name).toBe('updatePetMetadata')
375
+ expect(method.inputSchema.required).toContain('id')
376
+ expect(method.inputSchema.required).toContain('metadata')
377
+ expect(method.inputSchema.required).not.toContain('certificate')
378
+ expect(method.inputSchema.required).not.toContain('vaccinations')
379
+
380
+ expect(method.inputSchema.properties).toEqual({
381
+ id: {
382
+ type: 'integer',
383
+ },
384
+ metadata: {
385
+ type: 'object',
386
+ required: ['name'],
387
+ properties: {
388
+ name: { type: 'string' },
389
+ description: { type: 'string' },
390
+ },
391
+ additionalProperties: true,
392
+ },
393
+ certificate: {
394
+ type: 'string',
395
+ format: 'uri-reference',
396
+ description: expect.stringContaining('Optional pet certificate (absolute paths to local files)'),
397
+ },
398
+ vaccinations: {
399
+ type: 'array',
400
+ items: {
401
+ type: 'string',
402
+ format: 'uri-reference',
403
+ description: 'absolute paths to local files',
404
+ },
405
+ description: expect.stringContaining('Optional vaccination records'),
406
+ },
407
+ })
408
+ })
409
+
410
+ it('handles nested objects with file arrays in multipart forms', () => {
411
+ const spec: OpenAPIV3.Document = {
412
+ openapi: '3.0.0',
413
+ info: { title: 'Test API', version: '1.0.0' },
414
+ paths: {
415
+ '/pets/{id}/medical-records': {
416
+ post: {
417
+ operationId: 'addMedicalRecord',
418
+ summary: 'Add medical record with attachments',
419
+ parameters: [
420
+ {
421
+ name: 'id',
422
+ in: 'path',
423
+ required: true,
424
+ schema: { type: 'integer' },
425
+ },
426
+ ],
427
+ requestBody: {
428
+ required: true,
429
+ content: {
430
+ 'multipart/form-data': {
431
+ schema: {
432
+ type: 'object',
433
+ required: ['record'],
434
+ properties: {
435
+ record: {
436
+ type: 'object',
437
+ required: ['date', 'type'],
438
+ properties: {
439
+ date: { type: 'string', format: 'date' },
440
+ type: { type: 'string' },
441
+ notes: { type: 'string' },
442
+ attachments: {
443
+ type: 'array',
444
+ items: {
445
+ type: 'object',
446
+ required: ['file', 'type'],
447
+ properties: {
448
+ file: {
449
+ type: 'string',
450
+ format: 'binary',
451
+ },
452
+ type: {
453
+ type: 'string',
454
+ enum: ['xray', 'lab', 'prescription'],
455
+ },
456
+ description: { type: 'string' },
457
+ },
458
+ },
459
+ },
460
+ },
461
+ },
462
+ },
463
+ },
464
+ },
465
+ },
466
+ },
467
+ responses: {
468
+ '201': {
469
+ description: 'Medical record added successfully',
470
+ },
471
+ },
472
+ },
473
+ },
474
+ },
475
+ }
476
+
477
+ const converter = new OpenAPIToMCPConverter(spec)
478
+ const { tools } = converter.convertToMCPTools()
479
+ const [tool] = Object.values(tools)
480
+ const [method] = tool.methods
481
+
482
+ expect(method.name).toBe('addMedicalRecord')
483
+ expect(method.inputSchema.required).toContain('id')
484
+ expect(method.inputSchema.required).toContain('record')
485
+
486
+ // Verify nested structure is preserved
487
+ const recordSchema = method.inputSchema.properties!.record as any
488
+ expect(recordSchema.type).toBe('object')
489
+ expect(recordSchema.required).toContain('date')
490
+ expect(recordSchema.required).toContain('type')
491
+
492
+ // Verify nested file array structure
493
+ const attachmentsSchema = recordSchema.properties.attachments
494
+ expect(attachmentsSchema.type).toBe('array')
495
+ expect(attachmentsSchema.items.type).toBe('object')
496
+ expect(attachmentsSchema.items.properties.file.format).toBe('uri-reference')
497
+ expect(attachmentsSchema.items.properties.file.description).toBe('absolute paths to local files')
498
+ expect(attachmentsSchema.items.required).toContain('file')
499
+ expect(attachmentsSchema.items.required).toContain('type')
500
+ })
501
+
502
+ it('handles oneOf/anyOf schemas with file uploads', () => {
503
+ const spec: OpenAPIV3.Document = {
504
+ openapi: '3.0.0',
505
+ info: { title: 'Test API', version: '1.0.0' },
506
+ paths: {
507
+ '/pets/{id}/content': {
508
+ post: {
509
+ operationId: 'addPetContent',
510
+ summary: 'Add pet content (photo or document)',
511
+ parameters: [
512
+ {
513
+ name: 'id',
514
+ in: 'path',
515
+ required: true,
516
+ schema: { type: 'integer' },
517
+ },
518
+ ],
519
+ requestBody: {
520
+ required: true,
521
+ content: {
522
+ 'multipart/form-data': {
523
+ schema: {
524
+ type: 'object',
525
+ required: ['content'],
526
+ properties: {
527
+ content: {
528
+ oneOf: [
529
+ {
530
+ type: 'object',
531
+ required: ['photo', 'isProfile'],
532
+ properties: {
533
+ photo: {
534
+ type: 'string',
535
+ format: 'binary',
536
+ },
537
+ isProfile: {
538
+ type: 'boolean',
539
+ },
540
+ },
541
+ },
542
+ {
543
+ type: 'object',
544
+ required: ['document', 'category'],
545
+ properties: {
546
+ document: {
547
+ type: 'string',
548
+ format: 'binary',
549
+ },
550
+ category: {
551
+ type: 'string',
552
+ enum: ['medical', 'training', 'adoption'],
553
+ },
554
+ },
555
+ },
556
+ ],
557
+ },
558
+ },
559
+ },
560
+ },
561
+ },
562
+ },
563
+ responses: {
564
+ '201': {
565
+ description: 'Content added successfully',
566
+ },
567
+ },
568
+ },
569
+ },
570
+ },
571
+ }
572
+
573
+ const converter = new OpenAPIToMCPConverter(spec)
574
+ const { tools } = converter.convertToMCPTools()
575
+ const [tool] = Object.values(tools)
576
+ const [method] = tool.methods
577
+
578
+ expect(method.name).toBe('addPetContent')
579
+ expect(method.inputSchema.required).toContain('id')
580
+ expect(method.inputSchema.required).toContain('content')
581
+
582
+ // Verify oneOf structure is preserved
583
+ const contentSchema = method.inputSchema.properties!.content as any
584
+ expect(contentSchema.oneOf).toHaveLength(2)
585
+
586
+ // Check photo option
587
+ const photoOption = contentSchema.oneOf[0]
588
+ expect(photoOption.type).toBe('object')
589
+ expect(photoOption.properties.photo.format).toBe('uri-reference')
590
+ expect(photoOption.properties.photo.description).toBe('absolute paths to local files')
591
+ expect(photoOption.required).toContain('photo')
592
+ expect(photoOption.required).toContain('isProfile')
593
+
594
+ // Check document option
595
+ const documentOption = contentSchema.oneOf[1]
596
+ expect(documentOption.type).toBe('object')
597
+ expect(documentOption.properties.document.format).toBe('uri-reference')
598
+ expect(documentOption.properties.document.description).toBe('absolute paths to local files')
599
+ expect(documentOption.required).toContain('document')
600
+ expect(documentOption.required).toContain('category')
601
+ })
602
+ })