diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d9ac56..96645a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## [0.12.3] - Unreleased -- Nothing yet. +- Render list signatures and call examples from array item schemas, keeping number, boolean, object, and unknown arrays type-correct. (Issue #221, thanks @VincXiong) ## [0.12.2] - 2026-06-27 diff --git a/src/cli/generate/tools.ts b/src/cli/generate/tools.ts index 03827d4..644ebe2 100644 --- a/src/cli/generate/tools.ts +++ b/src/cli/generate/tools.ts @@ -100,7 +100,7 @@ export function extractOptions(tool: ServerToolInfo): GeneratedOption[] { const defaultValue = getDescriptorDefault(descriptor); const formatInfo = getDescriptorFormatHint(descriptor); const placeholder = buildPlaceholder(property, type, enumValues, formatInfo?.slug); - const exampleValue = buildExampleValue(property, type, enumValues, defaultValue); + const exampleValue = buildExampleValue(property, type, enumValues, defaultValue, arrayItemType); return { property, cliName: toCliOption(property), @@ -181,7 +181,8 @@ export function buildExampleValue( property: string, type: GeneratedOption['type'], enumValues: string[] | undefined, - defaultValue: unknown + defaultValue: unknown, + arrayItemType?: GeneratedOption['arrayItemType'] ): string | undefined { if (enumValues && enumValues.length > 0) { return enumValues[0] as string; @@ -199,7 +200,16 @@ export function buildExampleValue( case 'boolean': return 'true'; case 'array': - return 'value1,value2'; + switch (arrayItemType) { + case 'number': + return '1,2'; + case 'boolean': + return 'true,false'; + case 'object': + return '[{"key":"value"}]'; + default: + return 'value1,value2'; + } case 'object': return '{"key":"value"}'; default: @@ -214,6 +224,28 @@ export function buildExampleValue( } export function pickExampleLiteral(option: GeneratedOption): string | undefined { + if (option.type === 'array') { + if (Array.isArray(option.defaultValue)) { + try { + return JSON.stringify(option.defaultValue); + } catch { + return undefined; + } + } + if (option.enumValues && option.enumValues.length > 0) { + return JSON.stringify([option.enumValues[0]]); + } + switch (option.arrayItemType) { + case 'number': + return '[1, 2]'; + case 'boolean': + return '[true, false]'; + case 'object': + return '[{"key":"value"}]'; + default: + break; + } + } if (option.enumValues && option.enumValues.length > 0) { return JSON.stringify(option.enumValues[0]); } @@ -251,7 +283,16 @@ export function buildFallbackLiteral(option: GeneratedOption): string { case 'boolean': return 'true'; case 'array': - return '["value1"]'; + switch (option.arrayItemType) { + case 'number': + return '[1]'; + case 'boolean': + return '[true]'; + case 'object': + return '[{"key":"value"}]'; + default: + return '["value1"]'; + } case 'object': return '{"key":"value"}'; default: { diff --git a/tests/generate-cli-helpers.test.ts b/tests/generate-cli-helpers.test.ts index e20b744..a8a3cb9 100644 --- a/tests/generate-cli-helpers.test.ts +++ b/tests/generate-cli-helpers.test.ts @@ -100,6 +100,9 @@ describe('generate helpers', () => { expect(buildExampleValue('itemId', 'string', undefined, undefined)).toBe('example-id'); expect(buildExampleValue('mode', 'string', ['fast'], undefined)).toBe('fast'); expect(buildExampleValue('fields', 'object', undefined, undefined)).toBe('{"key":"value"}'); + expect(buildExampleValue('scores', 'array', undefined, undefined, 'number')).toBe('1,2'); + expect(buildExampleValue('flags', 'array', undefined, undefined, 'boolean')).toBe('true,false'); + expect(buildExampleValue('records', 'array', undefined, undefined, 'object')).toBe('[{"key":"value"}]'); expect(inferType({ type: 'boolean' })).toBe('boolean'); expect(inferType({ type: 'integer' })).toBe('number'); @@ -143,6 +146,61 @@ describe('generate helpers', () => { placeholder: '', }) ).toBe('["foo", "bar"]'); + expect( + pickExampleLiteral({ + type: 'array', + arrayItemType: 'number', + exampleValue: '1,2', + property: 'scores', + cliName: 'scores', + required: true, + placeholder: '', + }) + ).toBe('[1, 2]'); + expect( + pickExampleLiteral({ + type: 'array', + arrayItemType: 'boolean', + exampleValue: 'true,false', + property: 'flags', + cliName: 'flags', + required: true, + placeholder: '', + }) + ).toBe('[true, false]'); + expect( + pickExampleLiteral({ + type: 'array', + arrayItemType: 'object', + exampleValue: '[{"key":"value"}]', + property: 'records', + cliName: 'records', + required: true, + placeholder: '', + }) + ).toBe('[{"key":"value"}]'); + expect( + pickExampleLiteral({ + type: 'array', + arrayItemType: 'number', + defaultValue: [3, 5], + property: 'scores', + cliName: 'scores', + required: true, + placeholder: '', + }) + ).toBe('[3,5]'); + expect( + pickExampleLiteral({ + type: 'array', + arrayItemType: 'string', + enumValues: ['alpha', 'beta'], + property: 'labels', + cliName: 'labels', + required: true, + placeholder: '', + }) + ).toBe('["alpha"]'); expect( pickExampleLiteral({ type: 'string', @@ -162,6 +220,36 @@ describe('generate helpers', () => { placeholder: '', }) ).toBe('"example-id"'); + expect( + buildFallbackLiteral({ + type: 'array', + arrayItemType: 'number', + property: 'scores', + cliName: 'scores', + required: false, + placeholder: '', + }) + ).toBe('[1]'); + expect( + buildFallbackLiteral({ + type: 'array', + arrayItemType: 'boolean', + property: 'flags', + cliName: 'flags', + required: false, + placeholder: '', + }) + ).toBe('[true]'); + expect( + buildFallbackLiteral({ + type: 'array', + arrayItemType: 'object', + property: 'records', + cliName: 'records', + required: false, + placeholder: '', + }) + ).toBe('[{"key":"value"}]'); expect( buildFallbackLiteral({ type: 'array', diff --git a/tests/list-detail-helpers.test.ts b/tests/list-detail-helpers.test.ts index 54c336d..4286791 100644 --- a/tests/list-detail-helpers.test.ts +++ b/tests/list-detail-helpers.test.ts @@ -150,6 +150,24 @@ describe('formatCallExpressionExample', () => { ); expect(example).toBe('mcporter call \'https://mcp.sentry.dev/mcp?agent=1.use_sentry(request: "value")\''); }); + + it('keeps array examples consistent with their item types', () => { + const example = formatCallExpressionExample('fixture', 'array_probe', [ + baseOption({ property: 'scores', type: 'array', arrayItemType: 'number', exampleValue: '1,2' }), + baseOption({ property: 'flags', type: 'array', arrayItemType: 'boolean', exampleValue: 'true,false' }), + baseOption({ + property: 'records', + type: 'array', + arrayItemType: 'object', + exampleValue: '[{"key":"value"}]', + }), + baseOption({ property: 'names', type: 'array', arrayItemType: 'string', exampleValue: 'value1,value2' }), + ]); + + expect(example).toBe( + 'mcporter call fixture.array_probe(scores: [1, 2], flags: [true, false], records: [{"key":"value"}], names: ["value1", "value2"])' + ); + }); }); describe('formatExampleBlock', () => {