diff --git a/README.md b/README.md index 7ba4323..f565f25 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ openapi-mcp-generator --input path/to/openapi.json --output path/to/output/dir - | `--port` | `-p` | Port for web-based transports | `3000` | | `--default-include` | | Default behavior for x-mcp filtering. Accepts `true` or `false` (case-insensitive). `true` = include by default, `false` = exclude by default. | `true` | | `--force` | | Overwrite existing files in the output directory without confirmation | `false` | +| `--simplifyTypes` | `-st` | Flatten single-element type arrays in the JSON schema to their single value | `false` | ## 📦 Programmatic API diff --git a/src/generator/server-code.ts b/src/generator/server-code.ts index 6b6a447..217f06b 100644 --- a/src/generator/server-code.ts +++ b/src/generator/server-code.ts @@ -25,7 +25,7 @@ export function generateMcpServerCode( serverVersion: string ): string { // Extract tools from API - const tools = extractToolsFromApi(api, options.defaultInclude ?? true); + const tools = extractToolsFromApi(api, options.defaultInclude ?? true, options.simplifyTypes); // Determine base URL const determinedBaseUrl = determineBaseUrl(api, options.baseUrl); diff --git a/src/index.ts b/src/index.ts index d588a24..68f12c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,6 +87,10 @@ program true ) .option('--force', 'Overwrite existing files without prompting') + .option( + '-st, --simplifyTypes', + 'Flatten single-element type arrays in the JSON schema to their single value' + ) .version(pkg.version) // 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 6371623..94c3c7f 100644 --- a/src/parser/extract-tools.ts +++ b/src/parser/extract-tools.ts @@ -15,7 +15,8 @@ import { shouldIncludeOperationForMcp } from '../utils/helpers.js'; */ export function extractToolsFromApi( api: OpenAPIV3.Document, - defaultInclude: boolean = true + defaultInclude: boolean = true, + simplifyTypes?: string, ): McpToolDefinition[] { const tools: McpToolDefinition[] = []; const usedNames = new Set(); @@ -80,8 +81,10 @@ export function extractToolsFromApi( operation.description || operation.summary || `Executes ${method.toUpperCase()} ${path}`; // Generate input schema and extract parameters - const { inputSchema, parameters, requestBodyContentType } = - generateInputSchemaAndDetails(operation); + const { inputSchema, parameters, requestBodyContentType } = generateInputSchemaAndDetails( + operation, + simplifyTypes + ); // Extract parameter details for execution const executionParameters = parameters.map((p) => ({ name: p.name, in: p.in })); @@ -115,7 +118,10 @@ export function extractToolsFromApi( * @param operation OpenAPI operation object * @returns Input schema, parameters, and request body content type */ -export function generateInputSchemaAndDetails(operation: OpenAPIV3.OperationObject): { +export function generateInputSchemaAndDetails( + operation: OpenAPIV3.OperationObject, + simplifyTypes?: string +): { inputSchema: JSONSchema7 | boolean; parameters: OpenAPIV3.ParameterObject[]; requestBodyContentType?: string; @@ -131,7 +137,11 @@ export function generateInputSchemaAndDetails(operation: OpenAPIV3.OperationObje allParameters.forEach((param) => { if (!param.name || !param.schema) return; - const paramSchema = mapOpenApiSchemaToJsonSchema(param.schema as OpenAPIV3.SchemaObject); + const paramSchema = mapOpenApiSchemaToJsonSchema( + param.schema as OpenAPIV3.SchemaObject, + undefined, + simplifyTypes + ); if (typeof paramSchema === 'object') { paramSchema.description = param.description || paramSchema.description; } @@ -193,7 +203,8 @@ export function generateInputSchemaAndDetails(operation: OpenAPIV3.OperationObje */ export function mapOpenApiSchemaToJsonSchema( schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, - seen: WeakSet = new WeakSet() + seen: WeakSet = new WeakSet(), + simplifyTypes?: string ): JSONSchema7 | boolean { // Handle reference objects if ('$ref' in schema) { @@ -238,6 +249,10 @@ export function mapOpenApiSchemaToJsonSchema( } else if (!jsonSchema.type) { jsonSchema.type = 'null'; } + } else { + if (Array.isArray(jsonSchema.type) && jsonSchema.type.length === 1 && simplifyTypes) { + jsonSchema.type = jsonSchema.type[0]; + } } // Recursively process object properties @@ -248,7 +263,8 @@ export function mapOpenApiSchemaToJsonSchema( if (typeof propSchema === 'object' && propSchema !== null) { mappedProps[key] = mapOpenApiSchemaToJsonSchema( propSchema as OpenAPIV3.SchemaObject, - seen + seen, + simplifyTypes ); } else if (typeof propSchema === 'boolean') { mappedProps[key] = propSchema; @@ -266,7 +282,8 @@ export function mapOpenApiSchemaToJsonSchema( ) { jsonSchema.items = mapOpenApiSchemaToJsonSchema( jsonSchema.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, - seen + seen, + simplifyTypes ); } return jsonSchema; diff --git a/src/types/index.ts b/src/types/index.ts index e36eb3b..e02a819 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -35,6 +35,8 @@ export interface CliOptions { * false = exclude by default unless x-mcp explicitly enables. */ defaultInclude?: boolean; + /** Flatten single-element type arrays in the JSON schema to their single value */ + simplifyTypes?: string; } /**