@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,40 @@
1
+ import { OpenAPIV3 } from 'openapi-types'
2
+
3
+ /**
4
+ * Identifies file upload parameters in an OpenAPI operation
5
+ * @param operation The OpenAPI operation object to check
6
+ * @returns Array of parameter names that are file uploads
7
+ */
8
+ export function isFileUploadParameter(operation: OpenAPIV3.OperationObject): string[] {
9
+ const fileParams: string[] = []
10
+
11
+ if (!operation.requestBody) return fileParams
12
+
13
+ const requestBody = operation.requestBody as OpenAPIV3.RequestBodyObject
14
+ const content = requestBody.content || {}
15
+
16
+ // Check multipart/form-data content type for file uploads
17
+ const multipartContent = content['multipart/form-data']
18
+ if (!multipartContent?.schema) return fileParams
19
+
20
+ const schema = multipartContent.schema as OpenAPIV3.SchemaObject
21
+ if (schema.type !== 'object' || !schema.properties) return fileParams
22
+
23
+ // Look for properties with type: string, format: binary which indicates file uploads
24
+ Object.entries(schema.properties).forEach(([propName, prop]) => {
25
+ const schemaProp = prop as OpenAPIV3.SchemaObject
26
+ if (schemaProp.type === 'string' && schemaProp.format === 'binary') {
27
+ fileParams.push(propName)
28
+ }
29
+
30
+ // Check for array of files
31
+ if (schemaProp.type === 'array' && schemaProp.items) {
32
+ const itemSchema = schemaProp.items as OpenAPIV3.SchemaObject
33
+ if (itemSchema.type === 'string' && itemSchema.format === 'binary') {
34
+ fileParams.push(propName)
35
+ }
36
+ }
37
+ })
38
+
39
+ return fileParams
40
+ }
@@ -0,0 +1,529 @@
1
+ import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
2
+ import type { JSONSchema7 as IJsonSchema } from 'json-schema'
3
+ import type { ChatCompletionTool } from 'openai/resources/chat/completions'
4
+ import type { Tool } from '@anthropic-ai/sdk/resources/messages/messages'
5
+
6
+ type NewToolMethod = {
7
+ name: string
8
+ description: string
9
+ inputSchema: IJsonSchema & { type: 'object' }
10
+ returnSchema?: IJsonSchema
11
+ }
12
+
13
+ type FunctionParameters = {
14
+ type: 'object'
15
+ properties?: Record<string, unknown>
16
+ required?: string[]
17
+ [key: string]: unknown
18
+ }
19
+
20
+ export class OpenAPIToMCPConverter {
21
+ private schemaCache: Record<string, IJsonSchema> = {}
22
+ private nameCounter: number = 0
23
+
24
+ constructor(private openApiSpec: OpenAPIV3.Document | OpenAPIV3_1.Document) {}
25
+
26
+ /**
27
+ * Resolve a $ref reference to its schema in the openApiSpec.
28
+ * Returns the raw OpenAPI SchemaObject or null if not found.
29
+ */
30
+ private internalResolveRef(ref: string, resolvedRefs: Set<string>): OpenAPIV3.SchemaObject | null {
31
+ if (!ref.startsWith('#/')) {
32
+ return null
33
+ }
34
+ if (resolvedRefs.has(ref)) {
35
+ return null
36
+ }
37
+
38
+ const parts = ref.replace(/^#\//, '').split('/')
39
+ let current: any = this.openApiSpec
40
+ for (const part of parts) {
41
+ current = current[part]
42
+ if (!current) return null
43
+ }
44
+ resolvedRefs.add(ref)
45
+ return current as OpenAPIV3.SchemaObject
46
+ }
47
+
48
+ /**
49
+ * Convert an OpenAPI schema (or reference) into a JSON Schema object.
50
+ * Uses caching and handles cycles by returning $ref nodes.
51
+ */
52
+ convertOpenApiSchemaToJsonSchema(
53
+ schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
54
+ resolvedRefs: Set<string>,
55
+ resolveRefs: boolean = false,
56
+ ): IJsonSchema {
57
+ if ('$ref' in schema) {
58
+ const ref = schema.$ref
59
+ if (!resolveRefs) {
60
+ if (ref.startsWith('#/components/schemas/')) {
61
+ return {
62
+ $ref: ref.replace(/^#\/components\/schemas\//, '#/$defs/'),
63
+ ...('description' in schema ? { description: schema.description as string } : {}),
64
+ }
65
+ }
66
+ console.error(`Attempting to resolve ref ${ref} not found in components collection.`)
67
+ // deliberate fall through
68
+ }
69
+ // Create base schema with $ref and description if present
70
+ const refSchema: IJsonSchema = { $ref: ref }
71
+ if ('description' in schema && schema.description) {
72
+ refSchema.description = schema.description as string
73
+ }
74
+
75
+ // If already cached, return immediately with description
76
+ if (this.schemaCache[ref]) {
77
+ return this.schemaCache[ref]
78
+ }
79
+
80
+ const resolved = this.internalResolveRef(ref, resolvedRefs)
81
+ if (!resolved) {
82
+ // TODO: need extensive tests for this and we definitely need to handle the case of self references
83
+ console.error(`Failed to resolve ref ${ref}`)
84
+ return {
85
+ $ref: ref.replace(/^#\/components\/schemas\//, '#/$defs/'),
86
+ description: 'description' in schema ? ((schema.description as string) ?? '') : '',
87
+ }
88
+ } else {
89
+ const converted = this.convertOpenApiSchemaToJsonSchema(resolved, resolvedRefs, resolveRefs)
90
+ this.schemaCache[ref] = converted
91
+
92
+ return converted
93
+ }
94
+ }
95
+
96
+ // Handle inline schema
97
+ const result: IJsonSchema = {}
98
+
99
+ if (schema.type) {
100
+ result.type = schema.type as IJsonSchema['type']
101
+ }
102
+
103
+ // Convert binary format to uri-reference and enhance description
104
+ if (schema.format === 'binary') {
105
+ result.format = 'uri-reference'
106
+ const binaryDesc = 'absolute paths to local files'
107
+ result.description = schema.description ? `${schema.description} (${binaryDesc})` : binaryDesc
108
+ } else {
109
+ if (schema.format) {
110
+ result.format = schema.format
111
+ }
112
+ if (schema.description) {
113
+ result.description = schema.description
114
+ }
115
+ }
116
+
117
+ if (schema.enum) {
118
+ result.enum = schema.enum
119
+ }
120
+
121
+ if (schema.default !== undefined) {
122
+ result.default = schema.default
123
+ }
124
+
125
+ // Handle object properties
126
+ if (schema.type === 'object') {
127
+ result.type = 'object'
128
+ if (schema.properties) {
129
+ result.properties = {}
130
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
131
+ result.properties[name] = this.convertOpenApiSchemaToJsonSchema(propSchema, resolvedRefs, resolveRefs)
132
+ }
133
+ }
134
+ if (schema.required) {
135
+ result.required = schema.required
136
+ }
137
+ if (schema.additionalProperties === true || schema.additionalProperties === undefined) {
138
+ result.additionalProperties = true
139
+ } else if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
140
+ result.additionalProperties = this.convertOpenApiSchemaToJsonSchema(schema.additionalProperties, resolvedRefs, resolveRefs)
141
+ } else {
142
+ result.additionalProperties = false
143
+ }
144
+ }
145
+
146
+ // Handle arrays - ensure binary format conversion happens for array items too
147
+ if (schema.type === 'array' && schema.items) {
148
+ result.type = 'array'
149
+ result.items = this.convertOpenApiSchemaToJsonSchema(schema.items, resolvedRefs, resolveRefs)
150
+ }
151
+
152
+ // oneOf, anyOf, allOf
153
+ if (schema.oneOf) {
154
+ result.oneOf = schema.oneOf.map((s) => this.convertOpenApiSchemaToJsonSchema(s, resolvedRefs, resolveRefs))
155
+ }
156
+ if (schema.anyOf) {
157
+ result.anyOf = schema.anyOf.map((s) => this.convertOpenApiSchemaToJsonSchema(s, resolvedRefs, resolveRefs))
158
+ }
159
+ if (schema.allOf) {
160
+ result.allOf = schema.allOf.map((s) => this.convertOpenApiSchemaToJsonSchema(s, resolvedRefs, resolveRefs))
161
+ }
162
+
163
+ return result
164
+ }
165
+
166
+ convertToMCPTools(): {
167
+ tools: Record<string, { methods: NewToolMethod[] }>
168
+ openApiLookup: Record<string, OpenAPIV3.OperationObject & { method: string; path: string }>
169
+ zip: Record<string, { openApi: OpenAPIV3.OperationObject & { method: string; path: string }; mcp: NewToolMethod }>
170
+ } {
171
+ const apiName = 'API'
172
+
173
+ const openApiLookup: Record<string, OpenAPIV3.OperationObject & { method: string; path: string }> = {}
174
+ const tools: Record<string, { methods: NewToolMethod[] }> = {
175
+ [apiName]: { methods: [] },
176
+ }
177
+ const zip: Record<string, { openApi: OpenAPIV3.OperationObject & { method: string; path: string }; mcp: NewToolMethod }> = {}
178
+ for (const [path, pathItem] of Object.entries(this.openApiSpec.paths || {})) {
179
+ if (!pathItem) continue
180
+
181
+ for (const [method, operation] of Object.entries(pathItem)) {
182
+ if (!this.isOperation(method, operation)) continue
183
+
184
+ const mcpMethod = this.convertOperationToMCPMethod(operation, method, path)
185
+ if (mcpMethod) {
186
+ const uniqueName = this.ensureUniqueName(mcpMethod.name)
187
+ mcpMethod.name = uniqueName
188
+ // Apply description prefix to the already-built description (which includes error responses)
189
+ mcpMethod.description = this.getDescription(mcpMethod.description)
190
+ tools[apiName]!.methods.push(mcpMethod)
191
+ openApiLookup[apiName + '-' + uniqueName] = { ...operation, method, path }
192
+ zip[apiName + '-' + uniqueName] = { openApi: { ...operation, method, path }, mcp: mcpMethod }
193
+ }
194
+ }
195
+ }
196
+
197
+ return { tools, openApiLookup, zip }
198
+ }
199
+
200
+ /**
201
+ * Convert the OpenAPI spec to OpenAI's ChatCompletionTool format
202
+ */
203
+ convertToOpenAITools(): ChatCompletionTool[] {
204
+ const tools: ChatCompletionTool[] = []
205
+
206
+ for (const [path, pathItem] of Object.entries(this.openApiSpec.paths || {})) {
207
+ if (!pathItem) continue
208
+
209
+ for (const [method, operation] of Object.entries(pathItem)) {
210
+ if (!this.isOperation(method, operation)) continue
211
+
212
+ const parameters = this.convertOperationToJsonSchema(operation, method, path)
213
+ const tool: ChatCompletionTool = {
214
+ type: 'function',
215
+ function: {
216
+ name: operation.operationId!,
217
+ description: this.getDescription(operation.summary || operation.description || ''),
218
+ parameters: parameters as FunctionParameters,
219
+ },
220
+ }
221
+ tools.push(tool)
222
+ }
223
+ }
224
+
225
+ return tools
226
+ }
227
+
228
+ /**
229
+ * Convert the OpenAPI spec to Anthropic's Tool format
230
+ */
231
+ convertToAnthropicTools(): Tool[] {
232
+ const tools: Tool[] = []
233
+
234
+ for (const [path, pathItem] of Object.entries(this.openApiSpec.paths || {})) {
235
+ if (!pathItem) continue
236
+
237
+ for (const [method, operation] of Object.entries(pathItem)) {
238
+ if (!this.isOperation(method, operation)) continue
239
+
240
+ const parameters = this.convertOperationToJsonSchema(operation, method, path)
241
+ const tool: Tool = {
242
+ name: operation.operationId!,
243
+ description: this.getDescription(operation.summary || operation.description || ''),
244
+ input_schema: parameters as Tool['input_schema'],
245
+ }
246
+ tools.push(tool)
247
+ }
248
+ }
249
+
250
+ return tools
251
+ }
252
+
253
+ private convertComponentsToJsonSchema(): Record<string, IJsonSchema> {
254
+ const components = this.openApiSpec.components || {}
255
+ const schema: Record<string, IJsonSchema> = {}
256
+ for (const [key, value] of Object.entries(components.schemas || {})) {
257
+ schema[key] = this.convertOpenApiSchemaToJsonSchema(value, new Set())
258
+ }
259
+ return schema
260
+ }
261
+ /**
262
+ * Helper method to convert an operation to a JSON Schema for parameters
263
+ */
264
+ private convertOperationToJsonSchema(
265
+ operation: OpenAPIV3.OperationObject,
266
+ method: string,
267
+ path: string,
268
+ ): IJsonSchema & { type: 'object' } {
269
+ const schema: IJsonSchema & { type: 'object' } = {
270
+ type: 'object',
271
+ properties: {},
272
+ required: [],
273
+ $defs: this.convertComponentsToJsonSchema(),
274
+ }
275
+
276
+ // Handle parameters (path, query, header, cookie)
277
+ if (operation.parameters) {
278
+ for (const param of operation.parameters) {
279
+ const paramObj = this.resolveParameter(param)
280
+ if (paramObj && paramObj.schema) {
281
+ const paramSchema = this.convertOpenApiSchemaToJsonSchema(paramObj.schema, new Set())
282
+ // Merge parameter-level description if available
283
+ if (paramObj.description) {
284
+ paramSchema.description = paramObj.description
285
+ }
286
+ schema.properties![paramObj.name] = paramSchema
287
+ if (paramObj.required) {
288
+ schema.required!.push(paramObj.name)
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ // Handle requestBody
295
+ if (operation.requestBody) {
296
+ const bodyObj = this.resolveRequestBody(operation.requestBody)
297
+ if (bodyObj?.content) {
298
+ if (bodyObj.content['application/json']?.schema) {
299
+ const bodySchema = this.convertOpenApiSchemaToJsonSchema(bodyObj.content['application/json'].schema, new Set())
300
+ if (bodySchema.type === 'object' && bodySchema.properties) {
301
+ for (const [name, propSchema] of Object.entries(bodySchema.properties)) {
302
+ schema.properties![name] = propSchema
303
+ }
304
+ if (bodySchema.required) {
305
+ schema.required!.push(...bodySchema.required)
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ return schema
313
+ }
314
+
315
+ private isOperation(method: string, operation: any): operation is OpenAPIV3.OperationObject {
316
+ return ['get', 'post', 'put', 'delete', 'patch'].includes(method.toLowerCase())
317
+ }
318
+
319
+ private isParameterObject(param: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject): param is OpenAPIV3.ParameterObject {
320
+ return !('$ref' in param)
321
+ }
322
+
323
+ private isRequestBodyObject(body: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject): body is OpenAPIV3.RequestBodyObject {
324
+ return !('$ref' in body)
325
+ }
326
+
327
+ private resolveParameter(param: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject): OpenAPIV3.ParameterObject | null {
328
+ if (this.isParameterObject(param)) {
329
+ return param
330
+ } else {
331
+ const resolved = this.internalResolveRef(param.$ref, new Set())
332
+ if (resolved && (resolved as OpenAPIV3.ParameterObject).name) {
333
+ return resolved as OpenAPIV3.ParameterObject
334
+ }
335
+ }
336
+ return null
337
+ }
338
+
339
+ private resolveRequestBody(body: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject): OpenAPIV3.RequestBodyObject | null {
340
+ if (this.isRequestBodyObject(body)) {
341
+ return body
342
+ } else {
343
+ const resolved = this.internalResolveRef(body.$ref, new Set())
344
+ if (resolved) {
345
+ return resolved as OpenAPIV3.RequestBodyObject
346
+ }
347
+ }
348
+ return null
349
+ }
350
+
351
+ private resolveResponse(response: OpenAPIV3.ResponseObject | OpenAPIV3.ReferenceObject): OpenAPIV3.ResponseObject | null {
352
+ if ('$ref' in response) {
353
+ const resolved = this.internalResolveRef(response.$ref, new Set())
354
+ if (resolved) {
355
+ return resolved as OpenAPIV3.ResponseObject
356
+ } else {
357
+ return null
358
+ }
359
+ }
360
+ return response
361
+ }
362
+
363
+ private convertOperationToMCPMethod(operation: OpenAPIV3.OperationObject, method: string, path: string): NewToolMethod | null {
364
+ if (!operation.operationId) {
365
+ console.warn(`Operation without operationId at ${method} ${path}`)
366
+ return null
367
+ }
368
+
369
+ const methodName = operation.operationId
370
+
371
+ const inputSchema: IJsonSchema & { type: 'object' } = {
372
+ $defs: this.convertComponentsToJsonSchema(),
373
+ type: 'object',
374
+ properties: {},
375
+ required: [],
376
+ }
377
+
378
+ // Handle parameters (path, query, header, cookie)
379
+ if (operation.parameters) {
380
+ for (const param of operation.parameters) {
381
+ const paramObj = this.resolveParameter(param)
382
+ if (paramObj && paramObj.schema) {
383
+ const schema = this.convertOpenApiSchemaToJsonSchema(paramObj.schema, new Set(), false)
384
+ // Merge parameter-level description if available
385
+ if (paramObj.description) {
386
+ schema.description = paramObj.description
387
+ }
388
+ inputSchema.properties![paramObj.name] = schema
389
+ if (paramObj.required) {
390
+ inputSchema.required!.push(paramObj.name)
391
+ }
392
+ }
393
+ }
394
+ }
395
+
396
+ // Handle requestBody
397
+ if (operation.requestBody) {
398
+ const bodyObj = this.resolveRequestBody(operation.requestBody)
399
+ if (bodyObj?.content) {
400
+ // Handle multipart/form-data for file uploads
401
+ // We convert the multipart/form-data schema to a JSON schema and we require
402
+ // that the user passes in a string for each file that points to the local file
403
+ if (bodyObj.content['multipart/form-data']?.schema) {
404
+ const formSchema = this.convertOpenApiSchemaToJsonSchema(bodyObj.content['multipart/form-data'].schema, new Set(), false)
405
+ if (formSchema.type === 'object' && formSchema.properties) {
406
+ for (const [name, propSchema] of Object.entries(formSchema.properties)) {
407
+ inputSchema.properties![name] = propSchema
408
+ }
409
+ if (formSchema.required) {
410
+ inputSchema.required!.push(...formSchema.required!)
411
+ }
412
+ }
413
+ }
414
+ // Handle application/json
415
+ else if (bodyObj.content['application/json']?.schema) {
416
+ const bodySchema = this.convertOpenApiSchemaToJsonSchema(bodyObj.content['application/json'].schema, new Set(), false)
417
+ // Merge body schema into the inputSchema's properties
418
+ if (bodySchema.type === 'object' && bodySchema.properties) {
419
+ for (const [name, propSchema] of Object.entries(bodySchema.properties)) {
420
+ inputSchema.properties![name] = propSchema
421
+ }
422
+ if (bodySchema.required) {
423
+ inputSchema.required!.push(...bodySchema.required!)
424
+ }
425
+ } else {
426
+ // If the request body is not an object, just put it under "body"
427
+ inputSchema.properties!['body'] = bodySchema
428
+ inputSchema.required!.push('body')
429
+ }
430
+ }
431
+ }
432
+ }
433
+
434
+ // Build description including error responses
435
+ let description = operation.summary || operation.description || ''
436
+ if (operation.responses) {
437
+ const errorResponses = Object.entries(operation.responses)
438
+ .filter(([code]) => code.startsWith('4') || code.startsWith('5'))
439
+ .map(([code, response]) => {
440
+ const responseObj = this.resolveResponse(response)
441
+ let errorDesc = responseObj?.description || ''
442
+ return `${code}: ${errorDesc}`
443
+ })
444
+
445
+ if (errorResponses.length > 0) {
446
+ description += '\nError Responses:\n' + errorResponses.join('\n')
447
+ }
448
+ }
449
+
450
+ // Extract return type (response schema)
451
+ const returnSchema = this.extractResponseType(operation.responses)
452
+
453
+ // Generate Zod schema from input schema
454
+ try {
455
+ // const zodSchemaStr = jsonSchemaToZod(inputSchema, { module: "cjs" })
456
+ // console.log(zodSchemaStr)
457
+ // // Execute the function with the zod instance
458
+ // const zodSchema = eval(zodSchemaStr) as z.ZodType
459
+
460
+ return {
461
+ name: methodName,
462
+ description,
463
+ inputSchema,
464
+ ...(returnSchema ? { returnSchema } : {}),
465
+ }
466
+ } catch (error) {
467
+ console.warn(`Failed to generate Zod schema for ${methodName}:`, error)
468
+ // Fallback to a basic object schema
469
+ return {
470
+ name: methodName,
471
+ description,
472
+ inputSchema,
473
+ ...(returnSchema ? { returnSchema } : {}),
474
+ }
475
+ }
476
+ }
477
+
478
+ private extractResponseType(responses: OpenAPIV3.ResponsesObject | undefined): IJsonSchema | null {
479
+ // Look for a success response
480
+ const successResponse = responses?.['200'] || responses?.['201'] || responses?.['202'] || responses?.['204']
481
+ if (!successResponse) return null
482
+
483
+ const responseObj = this.resolveResponse(successResponse)
484
+ if (!responseObj || !responseObj.content) return null
485
+
486
+ if (responseObj.content['application/json']?.schema) {
487
+ const returnSchema = this.convertOpenApiSchemaToJsonSchema(responseObj.content['application/json'].schema, new Set(), false)
488
+ returnSchema['$defs'] = this.convertComponentsToJsonSchema()
489
+
490
+ // Preserve the response description if available and not already set
491
+ if (responseObj.description && !returnSchema.description) {
492
+ returnSchema.description = responseObj.description
493
+ }
494
+
495
+ return returnSchema
496
+ }
497
+
498
+ // If no JSON response, fallback to a generic string or known formats
499
+ if (responseObj.content['image/png'] || responseObj.content['image/jpeg']) {
500
+ return { type: 'string', format: 'binary', description: responseObj.description || '' }
501
+ }
502
+
503
+ // Fallback
504
+ return { type: 'string', description: responseObj.description || '' }
505
+ }
506
+
507
+ private ensureUniqueName(name: string): string {
508
+ if (name.length <= 64) {
509
+ return name
510
+ }
511
+
512
+ const truncatedName = name.slice(0, 64 - 5) // Reserve space for suffix
513
+ const uniqueSuffix = this.generateUniqueSuffix()
514
+ return `${truncatedName}-${uniqueSuffix}`
515
+ }
516
+
517
+ private generateUniqueSuffix(): string {
518
+ this.nameCounter += 1
519
+ return this.nameCounter.toString().padStart(4, '0')
520
+ }
521
+
522
+ private getDescription(description: string): string {
523
+ // Only add "Notion | " prefix for the Notion API
524
+ if (this.openApiSpec.info.title === 'Notion API') {
525
+ return "Notion | " + description
526
+ }
527
+ return description
528
+ }
529
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "declaration": true,
5
+ "declarationMap": true,
6
+ "sourceMap": true,
7
+ "outDir": "./build",
8
+ "target": "es2021",
9
+ "lib": ["es2022"],
10
+ "jsx": "react-jsx",
11
+ "module": "es2022",
12
+ "moduleResolution": "Bundler",
13
+ "types": [
14
+ "node"
15
+ ],
16
+ "resolveJsonModule": true,
17
+ "allowJs": true,
18
+ "checkJs": false,
19
+ "isolatedModules": true,
20
+ "allowSyntheticDefaultImports": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+ "strict": true,
23
+ "skipLibCheck": true
24
+ },
25
+ "include": [ "test/**/*.ts", "scripts/**/*.ts", "src/**/*.ts"]
26
+ }