@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.
- package/.devcontainer/devcontainer.json +4 -0
- package/.dockerignore +3 -0
- package/.github/pull_request_template.md +8 -0
- package/.github/workflows/ci.yml +42 -0
- package/Dockerfile +36 -0
- package/LICENSE +7 -0
- package/README.md +412 -0
- package/docker-compose.yml +6 -0
- package/docs/images/connections.png +0 -0
- package/docs/images/integration-access.png +0 -0
- package/docs/images/integrations-capabilities.png +0 -0
- package/docs/images/integrations-creation.png +0 -0
- package/docs/images/page-access-edit.png +0 -0
- package/package.json +63 -0
- package/scripts/build-cli.js +30 -0
- package/scripts/notion-openapi.json +2238 -0
- package/scripts/start-server.ts +243 -0
- package/src/init-server.ts +50 -0
- package/src/openapi-mcp-server/README.md +3 -0
- package/src/openapi-mcp-server/auth/index.ts +2 -0
- package/src/openapi-mcp-server/auth/template.ts +24 -0
- package/src/openapi-mcp-server/auth/types.ts +26 -0
- package/src/openapi-mcp-server/client/__tests__/http-client-upload.test.ts +205 -0
- package/src/openapi-mcp-server/client/__tests__/http-client.integration.test.ts +282 -0
- package/src/openapi-mcp-server/client/__tests__/http-client.test.ts +537 -0
- package/src/openapi-mcp-server/client/http-client.ts +198 -0
- package/src/openapi-mcp-server/client/polyfill-headers.ts +42 -0
- package/src/openapi-mcp-server/index.ts +3 -0
- package/src/openapi-mcp-server/mcp/__tests__/proxy.test.ts +479 -0
- package/src/openapi-mcp-server/mcp/proxy.ts +250 -0
- package/src/openapi-mcp-server/openapi/__tests__/file-upload.test.ts +100 -0
- package/src/openapi-mcp-server/openapi/__tests__/parser-multipart.test.ts +602 -0
- package/src/openapi-mcp-server/openapi/__tests__/parser.test.ts +1448 -0
- package/src/openapi-mcp-server/openapi/file-upload.ts +40 -0
- package/src/openapi-mcp-server/openapi/parser.ts +529 -0
- 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
|
+
})
|