compare_techs
Compare 2-4 technologies side-by-side with per-dimension winners and compatibility matrix for informed tech stack decisions.
Instructions
Side-by-side comparison of 2-4 technologies with per-dimension winners and compatibility matrix.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| technologies | Yes | Technologies to compare | |
| context | No | Context for scoring |
Implementation Reference
- src/tools/compare.ts:115-223 (handler)The primary handler function for the 'compare_techs' tool. It validates input technologies, computes scores and grades, determines dimension winners, builds Markdown tables for overall scores, per-dimension winners, compatibility matrix, and generates a final verdict with recommendation.export function executeCompareTechs(input: CompareTechsInput): { text: string; isError?: boolean } { const { technologies, context = 'default' } = input; // Validate all technologies exist const allTechIds = getAllTechIds(); const invalidTechs: string[] = []; for (const techId of technologies) { if (!techExists(techId)) { invalidTechs.push(techId); } } if (invalidTechs.length > 0) { const error = techNotFoundError(invalidTechs[0], allTechIds); return { text: error.toResponseText(), isError: true }; } // Check for duplicates const uniqueTechs = [...new Set(technologies)]; if (uniqueTechs.length !== technologies.length) { const error = new McpError(ErrorCode.INVALID_INPUT, 'Duplicate technologies in comparison list'); return { text: error.toResponseText(), isError: true }; } // Build comparison data const comparisons: TechComparison[] = technologies.map((techId) => { const tech = getTechnology(techId)!; const scores = getScores(techId, context)!; const overall = calculateOverallScore(scores); return { id: techId, name: tech.name, scores, overall, grade: scoreToGrade(overall) }; }); // Sort by overall score const sorted = [...comparisons].sort((a, b) => b.overall - a.overall); // Determine dimension winners const dimensionWinners = determineDimensionWinners(comparisons); // Build response const techNames = comparisons.map((t) => t.name).join(' vs '); let text = `## Comparison: ${techNames} (context: ${context}) ### Overall Scores | Technology | Score | Grade | |------------|-------|-------| `; for (const tech of sorted) { text += `| ${tech.name} | ${tech.overall} | ${tech.grade} |\n`; } // Per-dimension breakdown text += ` ### Per-Dimension Winners | Dimension | Winner | Margin | Notes | |-----------|--------|--------|-------| `; for (const w of dimensionWinners) { const winnerDisplay = w.winner ?? 'Tie'; const marginDisplay = w.winner ? `+${w.margin}` : '-'; text += `| ${w.dimension} | ${winnerDisplay} | ${marginDisplay} | ${w.notes} |\n`; } // Compatibility matrix (for all pairs) text += '\n### Compatibility Matrix\n| Pair | Score | Verdict |\n|------|-------|---------|\n'; for (let i = 0; i < comparisons.length; i++) { for (let j = i + 1; j < comparisons.length; j++) { const a = comparisons[i]; const b = comparisons[j]; const score = getCompatibility(a.id, b.id); const verdict = getCompatibilityVerdict(score); text += `| ${a.id} ↔ ${b.id} | ${score} | ${verdict} |\n`; } } // Verdict const leader = sorted[0]; const runnerUp = sorted[1]; const overallMargin = leader.overall - runnerUp.overall; text += '\n'; if (overallMargin < 3) { text += `**Verdict**: Close call between ${leader.name} and ${runnerUp.name}\n`; text += `**Recommendation**: Both are strong choices; consider your specific priorities.`; } else { text += `**Verdict**: ${leader.name} leads with ${leader.overall}/100\n`; // Find what leader is best at const leaderStrengths = dimensionWinners.filter((w) => w.winner === leader.name).map((w) => w.dimension); if (leaderStrengths.length > 0) { const strengthsText = leaderStrengths.slice(0, 2).join(' and '); text += `**Recommendation**: Consider ${leader.name} for ${strengthsText} priorities.`; } } text += `\n\nData version: ${DATA_VERSION}`; return { text }; }
- src/tools/compare.ts:22-29 (schema)Zod schema defining the input structure for the compare_techs tool, including technologies array and optional context.export const CompareTechsInputSchema = z.object({ technologies: z .array(z.string().min(1)) .min(2) .max(4) .describe('Technology IDs to compare (2-4 technologies)'), context: z.enum(CONTEXTS).optional().default('default').describe('Context for score lookup') });
- src/server.ts:87-107 (registration)MCP server registration of the 'compare_techs' tool, specifying title, description, input schema, and asynchronous executor that parses input and calls the handler.// Register compare_techs tool (local) server.registerTool( compareTechsToolDefinition.name, { title: 'Compare Technologies', description: compareTechsToolDefinition.description, inputSchema: { technologies: z.array(z.string().min(1)).min(2).max(4).describe('Technologies to compare'), context: z.enum(CONTEXTS).optional().describe('Context for scoring') } }, async (args) => { debug('compare_techs called', args); const input = CompareTechsInputSchema.parse(args); const { text, isError } = executeCompareTechs(input); return { content: [{ type: 'text', text }], isError }; } );
- src/tools/compare.ts:77-110 (helper)Helper function that computes winners for each scoring dimension across compared technologies, calculating margins and categorizing ties or degrees of victory.function determineDimensionWinners(techs: TechComparison[]): DimensionWinner[] { const winners: DimensionWinner[] = []; for (const dim of SCORE_DIMENSIONS) { const sorted = [...techs].sort((a, b) => b.scores[dim] - a.scores[dim]); const first = sorted[0]; const second = sorted[1]; const margin = first.scores[dim] - second.scores[dim]; let winner: string | null; let notes: string; if (margin < 3) { // Tie if margin is less than 3 winner = null; notes = 'Tie'; } else if (margin < 10) { winner = first.name; notes = 'Close competition'; } else { winner = first.name; notes = 'Clear winner'; } winners.push({ dimension: DIMENSION_LABELS[dim], winner, margin, notes }); } return winners; }
- src/tools/compare.ts:36-57 (schema)Tool definition object used for MCP tool registration, including name, description, and JSON schema for inputs.export const compareTechsToolDefinition = { name: 'compare_techs', description: 'Side-by-side comparison of 2-4 technologies with per-dimension winners and compatibility matrix.', inputSchema: { type: 'object' as const, properties: { technologies: { type: 'array', items: { type: 'string' }, minItems: 2, maxItems: 4, description: 'Technology IDs to compare (e.g., ["nextjs", "sveltekit", "nuxt"])' }, context: { type: 'string', enum: CONTEXTS, description: 'Context for scoring (default, mvp, enterprise)' } }, required: ['technologies'] } };