diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f9e74..e5d2d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.1.2] - 2025-06-08 + +### Fixed +- Prevent stack overflow (RangeError: Maximum call stack size exceeded) when processing recursive or cyclic OpenAPI schemas (e.g., self-referencing objects). +- Added cycle detection to schema mapping, ensuring robust handling of recursive structures. + +## [3.1.1] - 2025-05-26 + +### Added +- Introduced a new executable command-line script for easier usage in Unix-like environments. + +### Changed +- Use new CLI entry point to use the new `bin/openapi-mcp-generator.js` file. +- Updated build script to ensure the new CLI file has the correct permissions. +- Refactored `index.ts` to streamline argument parsing and error handling. + + ## [3.1.0] - 2025-05-18 ### Added @@ -18,6 +35,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved module structure with better exports - Enhanced detection of module execution context +## [3.0.0] - 2025-04-26 + +### Added +- Streamable HTTP support for OpenAPI MCP generator, enabling efficient handling of large payloads and real-time data transfer. +- Major architectural refactor to support streaming responses and requests. + +### Fixed +- Multiple bugs related to HTTP/HTTPS connection handling, stream closure, and error propagation in streaming scenarios. +- Fixed resource leak issues on server aborts and client disconnects during streaming. + +### Changed +- Major version bump due to breaking changes in API and internal structures to support streaming. +- Updated documentation to reflect new streaming capabilities and usage instructions. +- Enhanced performance and robustness of HTTP/HTTPS transport layers. + ## [2.0.0] - 2025-04-12 ### Added @@ -53,4 +85,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for GET, POST, PUT, DELETE methods - Basic error handling - Simple CLI interface -- Basic TypeScript support \ No newline at end of file +- Basic TypeScript support diff --git a/package-lock.json b/package-lock.json index 4b15961..183362a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openapi-mcp-generator", - "version": "3.1.0", + "version": "3.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openapi-mcp-generator", - "version": "3.1.0", + "version": "3.1.2", "license": "MIT", "dependencies": { "@apidevtools/swagger-parser": "^10.1.1", @@ -14,7 +14,7 @@ "openapi-types": "^12.1.3" }, "bin": { - "openapi-mcp-generator": "dist/index.js" + "openapi-mcp-generator": "bin/openapi-mcp-generator.js" }, "devDependencies": { "@types/node": "^22.15.2", diff --git a/package.json b/package.json index 77840db..90c1912 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openapi-mcp-generator", - "version": "3.1.1", + "version": "3.1.2", "description": "Generates MCP server code from OpenAPI specifications", "license": "MIT", "author": "Harsha", diff --git a/src/index.ts b/src/index.ts index 54629d5..f222bf5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,7 +72,7 @@ program (val) => parseInt(val, 10) ) .option('--force', 'Overwrite existing files without prompting') - .version('3.1.1') // Match package.json version + .version('3.1.2') // Match package.json version .action(options => { runGenerator(options) .catch((error) => { diff --git a/src/parser/extract-tools.ts b/src/parser/extract-tools.ts index 8816095..3b3e597 100644 --- a/src/parser/extract-tools.ts +++ b/src/parser/extract-tools.ts @@ -153,13 +153,15 @@ export function generateInputSchemaAndDetails(operation: OpenAPIV3.OperationObje } /** - * Maps an OpenAPI schema to a JSON Schema + * Maps an OpenAPI schema to a JSON Schema with cycle protection. * * @param schema OpenAPI schema object or reference + * @param seen WeakSet tracking already visited schema objects * @returns JSON Schema representation */ export function mapOpenApiSchemaToJsonSchema( - schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject + schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, + seen: WeakSet = new WeakSet() ): JSONSchema7 | boolean { // Handle reference objects if ('$ref' in schema) { @@ -170,57 +172,68 @@ export function mapOpenApiSchemaToJsonSchema( // Handle boolean schemas if (typeof schema === 'boolean') return schema; - // Create a copy of the schema to modify - const jsonSchema: JSONSchema7 = { ...schema } as any; - - // Convert integer type to number (JSON Schema compatible) - if (schema.type === 'integer') jsonSchema.type = 'number'; - - // Remove OpenAPI-specific properties that aren't in JSON Schema - delete (jsonSchema as any).nullable; - delete (jsonSchema as any).example; - delete (jsonSchema as any).xml; - delete (jsonSchema as any).externalDocs; - delete (jsonSchema as any).deprecated; - delete (jsonSchema as any).readOnly; - delete (jsonSchema as any).writeOnly; - - // Handle nullable properties by adding null to the type - if (schema.nullable) { - if (Array.isArray(jsonSchema.type)) { - if (!jsonSchema.type.includes('null')) jsonSchema.type.push('null'); - } else if (typeof jsonSchema.type === 'string') { - jsonSchema.type = [jsonSchema.type as JSONSchema7TypeName, 'null']; - } else if (!jsonSchema.type) { - jsonSchema.type = 'null'; - } + // Detect cycles + if (seen.has(schema)) { + console.warn(`Cycle detected in schema${schema.title ? ` "${schema.title}"` : ''}, returning generic object to break recursion.`); + return { type: 'object' }; } + seen.add(schema); - // Recursively process object properties - if (jsonSchema.type === 'object' && jsonSchema.properties) { - const mappedProps: { [key: string]: JSONSchema7 | boolean } = {}; + try { + // Create a copy of the schema to modify + const jsonSchema: JSONSchema7 = { ...schema } as any; - for (const [key, propSchema] of Object.entries(jsonSchema.properties)) { - if (typeof propSchema === 'object' && propSchema !== null) { - mappedProps[key] = mapOpenApiSchemaToJsonSchema(propSchema as OpenAPIV3.SchemaObject); - } else if (typeof propSchema === 'boolean') { - mappedProps[key] = propSchema; + // Convert integer type to number (JSON Schema compatible) + if (schema.type === 'integer') jsonSchema.type = 'number'; + + // Remove OpenAPI-specific properties that aren't in JSON Schema + delete (jsonSchema as any).nullable; + delete (jsonSchema as any).example; + delete (jsonSchema as any).xml; + delete (jsonSchema as any).externalDocs; + delete (jsonSchema as any).deprecated; + delete (jsonSchema as any).readOnly; + delete (jsonSchema as any).writeOnly; + + // Handle nullable properties by adding null to the type + if (schema.nullable) { + if (Array.isArray(jsonSchema.type)) { + if (!jsonSchema.type.includes('null')) jsonSchema.type.push('null'); + } else if (typeof jsonSchema.type === 'string') { + jsonSchema.type = [jsonSchema.type as JSONSchema7TypeName, 'null']; + } else if (!jsonSchema.type) { + jsonSchema.type = 'null'; } } - jsonSchema.properties = mappedProps; - } + // Recursively process object properties + if (jsonSchema.type === 'object' && jsonSchema.properties) { + const mappedProps: { [key: string]: JSONSchema7 | boolean } = {}; - // Recursively process array items - if ( - jsonSchema.type === 'array' && - typeof jsonSchema.items === 'object' && - jsonSchema.items !== null - ) { - jsonSchema.items = mapOpenApiSchemaToJsonSchema( - jsonSchema.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject - ); - } + for (const [key, propSchema] of Object.entries(jsonSchema.properties)) { + if (typeof propSchema === 'object' && propSchema !== null) { + mappedProps[key] = mapOpenApiSchemaToJsonSchema(propSchema as OpenAPIV3.SchemaObject, seen); + } else if (typeof propSchema === 'boolean') { + mappedProps[key] = propSchema; + } + } - return jsonSchema; + jsonSchema.properties = mappedProps; + } + + // Recursively process array items + if ( + jsonSchema.type === 'array' && + typeof jsonSchema.items === 'object' && + jsonSchema.items !== null + ) { + jsonSchema.items = mapOpenApiSchemaToJsonSchema( + jsonSchema.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, + seen + ); + } + return jsonSchema; + } finally { + seen.delete(schema); + } }