compare
Analyze and compare data schemas between producers and consumers to detect mismatches and prevent runtime errors through contract validation.
Instructions
Full analysis pipeline: extract producer schemas, trace consumer usage, and compare them to find mismatches. Returns a detailed report.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| producerDir | Yes | Path to MCP server source directory | |
| consumerDir | Yes | Path to consumer/client source directory | |
| format | No | Output format | |
| strict | No | Strict mode for comparison | |
| direction | No | Data flow direction (default: producer_to_consumer) |
Implementation Reference
- src/index.ts:178-192 (registration)Registration of the 'compare' tool in the MCP ListTools response, defining name, description, and input schema.{ name: 'compare', description: 'Full analysis pipeline: extract producer schemas, trace consumer usage, and compare them to find mismatches. Returns a detailed report.', inputSchema: { type: 'object', properties: { producerDir: { type: 'string', description: 'Path to MCP server source directory' }, consumerDir: { type: 'string', description: 'Path to consumer/client source directory' }, format: { type: 'string', enum: ['json', 'markdown', 'summary'], description: 'Output format' }, strict: { type: 'boolean', description: 'Strict mode for comparison' }, direction: { type: 'string', enum: ['producer_to_consumer', 'consumer_to_producer', 'bidirectional'], description: 'Data flow direction (default: producer_to_consumer)' }, }, required: ['producerDir', 'consumerDir'], }, },
- src/index.ts:57-63 (schema)Zod input schema (CompareInput) used for validating 'compare' tool arguments.const CompareInput = z.object({ producerDir: z.string().describe('Path to MCP server source directory'), consumerDir: z.string().describe('Path to consumer/client source directory'), format: z.enum(['json', 'markdown', 'summary']).optional().describe('Output format (default: json)'), strict: z.boolean().optional().describe('Strict mode: treat missing optional properties as warnings'), direction: z.enum(['producer_to_consumer', 'consumer_to_producer', 'bidirectional']).optional().describe('Data flow direction for compatibility checking (default: producer_to_consumer)'), });
- src/index.ts:377-403 (handler)Tool handler in CallToolRequest: parses input, invokes compareDirectories, formats and returns the result.case 'compare': { const input = CompareInput.parse(args); log(`Comparing: ${input.producerDir} vs ${input.consumerDir}`); const result = await compareDirectories( input.producerDir, input.consumerDir, { strict: input.strict, direction: input.direction } ); const format = (input.format || 'json') as OutputFormat; const output = formatResult(result, format); log(`Analysis complete: ${result.summary.matchCount} matches, ${result.summary.mismatchCount} mismatches`); return { content: [ { type: 'text', text: output, }, ], }; }
- src/compare/index.ts:321-337 (helper)Convenience helper: orchestrates extraction from directories and calls compareSchemas.export async function compareDirectories( backendDir: string, frontendDir: string, options: CompareOptions = {} ): Promise<TraceResult> { // Import dynamically to avoid circular deps const { extractProducerSchemas } = await import('../extract/index.js'); const { traceConsumerUsage } = await import('../trace/index.js'); console.log(`\n[Compare] Backend: ${backendDir}`); console.log(`[Compare] Frontend: ${frontendDir}\n`); const producers = await extractProducerSchemas({ rootDir: backendDir }); const consumers = await traceConsumerUsage({ rootDir: frontendDir }); return compareSchemas(producers, consumers, options); }
- src/compare/index.ts:27-93 (helper)Core comparison function: matches producers and consumers, detects argument and property mismatches.export function compareSchemas( producers: ProducerSchema[], consumers: ConsumerSchema[], options: CompareOptions = {} ): TraceResult { // Default to producer_to_consumer (API response pattern) const direction = options.direction || 'producer_to_consumer'; console.log(`[Comparator] Comparing ${producers.length} producers vs ${consumers.length} consumers`); console.log(`[Comparator] Direction: ${direction}`); const matches: Match[] = []; const mismatches: Mismatch[] = []; // Index producers by tool name for quick lookup const producerMap = new Map<string, ProducerSchema>(); for (const producer of producers) { producerMap.set(producer.toolName, producer); } // Analyze each consumer usage for (const consumer of consumers) { const producer = producerMap.get(consumer.toolName); if (!producer) { // Tool not found in producer definitions mismatches.push({ toolName: consumer.toolName, issueType: 'UNKNOWN_TOOL', description: `Tool "${consumer.toolName}" is called but not defined in producer`, consumerLocation: consumer.callSite, }); continue; } // Check argument mismatches (consumer → producer direction) const argMismatches = checkArgumentMismatches(producer, consumer, direction); mismatches.push(...argMismatches); // Check expected property mismatches (producer → consumer direction) const propMismatches = checkPropertyMismatches(producer, consumer, options, direction); mismatches.push(...propMismatches); // If no mismatches for this call, it's a match if (argMismatches.length === 0 && propMismatches.length === 0) { matches.push({ toolName: consumer.toolName, producerLocation: producer.location, consumerLocation: consumer.callSite, }); } } return { timestamp: new Date().toISOString(), producerSource: producers.length > 0 ? producers[0].location.file : '', consumerSource: consumers.length > 0 ? consumers[0].callSite.file : '', matches, mismatches, summary: { totalTools: producers.length, totalCalls: consumers.length, matchCount: matches.length, mismatchCount: mismatches.length, }, }; }