diff --git a/src/index.ts b/src/index.ts index c1c6246..5442c9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -146,6 +146,22 @@ export { handleSonarQubeUpdateHotspotStatus, } from './handlers/index.js'; +// Helper function to parse JSON string arrays in parameters +function parseArrayParameters(params: Record, paramNames: string[]): void { + for (const key of paramNames) { + if (typeof params[key] === 'string') { + try { + const parsed = JSON.parse(params[key] as string); + if (Array.isArray(parsed)) { + params[key] = parsed; + } + } catch { + // Not valid JSON, keep as string for backward compatibility + } + } + } +} + // Lambda functions for the MCP tools /** * Lambda function for projects tool @@ -166,6 +182,16 @@ export const metricsHandler = async (params: { page: number | null; page_size: n * Lambda function for issues tool */ export const issuesHandler = async (params: Record) => { + // Parse JSON strings to arrays for array parameters + parseArrayParameters(params, [ + 'projects', 'component_keys', 'components', 'directories', 'files', + 'scopes', 'issues', 'severities', 'statuses', 'resolutions', 'types', + 'clean_code_attribute_categories', 'impact_severities', 'impact_software_qualities', + 'issue_statuses', 'rules', 'tags', 'assignees', 'authors', 'cwe', + 'owasp_top10', 'owasp_top10_v2021', 'sans_top25', 'sonarsource_security', + 'sonarsource_security_category', 'languages', 'facets', 'additional_fields' + ]); + return handleSonarQubeGetIssues(mapToSonarQubeParams(params)); }; diff --git a/src/schemas/common.ts b/src/schemas/common.ts index 86ae5e2..e109390 100644 --- a/src/schemas/common.ts +++ b/src/schemas/common.ts @@ -13,102 +13,40 @@ export const severitySchema = z export const severitiesSchema = z .union([z.array(z.enum(['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid severities - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => ['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'].includes(v)); - } - return parsed; - }) .nullable() .optional(); // Status schemas export const statusSchema = z .union([z.array(z.enum(['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid statuses - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => - ['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'].includes(v) - ); - } - return parsed; - }) .nullable() .optional(); // Resolution schemas export const resolutionSchema = z .union([z.array(z.enum(['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid resolutions - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => ['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED'].includes(v)); - } - return parsed; - }) .nullable() .optional(); // Type schemas export const typeSchema = z .union([z.array(z.enum(['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid types - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => - ['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT'].includes(v) - ); - } - return parsed; - }) .nullable() .optional(); // Clean Code taxonomy schemas export const cleanCodeAttributeCategoriesSchema = z .union([z.array(z.enum(['ADAPTABLE', 'CONSISTENT', 'INTENTIONAL', 'RESPONSIBLE'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid categories - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => - ['ADAPTABLE', 'CONSISTENT', 'INTENTIONAL', 'RESPONSIBLE'].includes(v) - ); - } - return parsed; - }) .nullable() .optional(); export const impactSeveritiesSchema = z .union([z.array(z.enum(['HIGH', 'MEDIUM', 'LOW'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid impact severities - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => ['HIGH', 'MEDIUM', 'LOW'].includes(v)); - } - return parsed; - }) .nullable() .optional(); export const impactSoftwareQualitiesSchema = z .union([z.array(z.enum(['MAINTAINABILITY', 'RELIABILITY', 'SECURITY'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid software qualities - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => ['MAINTAINABILITY', 'RELIABILITY', 'SECURITY'].includes(v)); - } - return parsed; - }) .nullable() .optional(); diff --git a/src/schemas/issues.ts b/src/schemas/issues.ts index b32e05a..1990b0b 100644 --- a/src/schemas/issues.ts +++ b/src/schemas/issues.ts @@ -130,13 +130,11 @@ export const issuesToolSchema = { project_key: z.string().optional().describe('Single project key for backward compatibility'), // Made optional to support projects array projects: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe('Filter by project keys'), component_keys: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe( @@ -144,7 +142,6 @@ export const issuesToolSchema = { ), components: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe('Alias for component_keys - filter by file paths, directories, or modules'), @@ -155,26 +152,16 @@ export const issuesToolSchema = { .describe('Return only issues on the specified components, not on their sub-components'), directories: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe('Filter by directory paths'), files: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe('Filter by specific file paths'), scopes: z .union([z.array(z.enum(['MAIN', 'TEST', 'OVERALL'])), z.string()]) - .transform((val) => { - const parsed = parseJsonStringArray(val); - // Validate that all values are valid scopes - if (parsed && Array.isArray(parsed)) { - return parsed.filter((v) => ['MAIN', 'TEST', 'OVERALL'].includes(v)); - } - return parsed; - }) .nullable() .optional() .describe('Filter by issue scopes (MAIN, TEST, OVERALL)'), @@ -186,7 +173,6 @@ export const issuesToolSchema = { // Issue filters issues: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), severity: severitySchema, // Deprecated single value @@ -208,13 +194,11 @@ export const issuesToolSchema = { // Rules and tags rules: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe('Filter by rule keys'), tags: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe( @@ -235,7 +219,6 @@ export const issuesToolSchema = { .describe('Filter to only assigned (true) or unassigned (false) issues'), assignees: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe( @@ -244,7 +227,6 @@ export const issuesToolSchema = { author: z.string().nullable().optional().describe('Filter by single issue author'), // Single author authors: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe('Filter by multiple issue authors'), // Multiple authors @@ -252,46 +234,38 @@ export const issuesToolSchema = { // Security standards cwe: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), owasp_top10: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), owasp_top10_v2021: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), // New 2021 version sans_top25: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), sonarsource_security: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), sonarsource_security_category: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), // Languages languages: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(), // Facets facets: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional() .describe( @@ -325,7 +299,6 @@ export const issuesToolSchema = { // Response optimization additional_fields: z .union([z.array(z.string()), z.string()]) - .transform(parseJsonStringArray) .nullable() .optional(),