/** * Equivalences & price-history tools: find_equivalences, get_price_history */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { pool } from "../db.js"; export async function registerEquivalencesTools(server: McpServer): Promise { // --- Tool: find_equivalences --- server.tool( "find_equivalences", `Find Flexoptix equivalent transceivers for a competitor product (or vice-versa). Uses the TIP equivalences database (63k+ verified cross-brand mappings, 93.9% avg confidence). Example: "What Flexoptix alternative exists for Cisco GLC-LH-SMD?" → Returns Flexoptix part numbers, pricing, specs, and confidence.`, { part_number: z.string().describe("Competitor or Flexoptix part number, e.g. 'GLC-LH-SMD', 'SFP-10G-LR', 'QSFP-100G-PSM4'"), vendor: z.string().optional().describe("Vendor filter, e.g. 'Cisco', 'Juniper', 'FS.COM'. Leave empty to search all vendors."), min_confidence: z.number().min(0).max(1).default(0.8).describe("Minimum confidence threshold (0–1). Default 0.8."), max_results: z.number().default(10).describe("Maximum results to return"), }, async ({ part_number, vendor, min_confidence, max_results }) => { const conditions = [ "e.status IN ('approved', 'auto_approved')", `e.confidence >= $1`, `(cx.part_number ILIKE $2 OR cx.standard_name ILIKE $2 OR fx.part_number ILIKE $2 OR fx.standard_name ILIKE $2)`, ]; const values: unknown[] = [min_confidence, `%${part_number}%`]; let idx = 3; if (vendor) { conditions.push(`cv.name ILIKE $${idx}`); values.push(`%${vendor}%`); idx++; } const result = await pool.query( `SELECT e.confidence, e.match_basis, e.status, -- Flexoptix product fx.part_number AS flexoptix_pn, fx.standard_name AS flexoptix_std, fx.form_factor AS flexoptix_form_factor, fx.speed AS flexoptix_speed, fx.reach_label AS flexoptix_reach, fx.fiber_type AS flexoptix_fiber, fx.market_status AS flexoptix_market_status, fx.price_verified_eur AS flexoptix_price_eur, fx.product_page_url AS flexoptix_url, -- Competitor product cx.part_number AS competitor_pn, cx.standard_name AS competitor_std, cx.form_factor AS competitor_form_factor, cx.speed AS competitor_speed, cx.reach_label AS competitor_reach, cx.price_verified_eur AS competitor_price_eur, cv.name AS competitor_vendor FROM transceiver_equivalences e JOIN transceivers fx ON fx.id = e.flexoptix_id JOIN transceivers cx ON cx.id = e.competitor_id JOIN vendors cv ON cv.id = cx.vendor_id WHERE ${conditions.join(" AND ")} ORDER BY e.confidence DESC LIMIT $${idx}`, [...values, max_results] ); if (result.rows.length === 0) { return { content: [{ type: "text", text: `No equivalences found for "${part_number}"${vendor ? ` from ${vendor}` : ""} with confidence ≥ ${min_confidence}.\n\nTry a broader search term or lower confidence threshold.`, }], }; } const lines = result.rows.map((r, i) => { const conf = `${(parseFloat(r.confidence) * 100).toFixed(0)}%`; const basis = Array.isArray(r.match_basis) ? r.match_basis.join(", ") : r.match_basis; const fxPrice = r.flexoptix_price_eur ? `€${r.flexoptix_price_eur}` : "—"; const compPrice = r.competitor_price_eur ? `€${r.competitor_price_eur}` : "—"; return [ `${i + 1}. Competitor: ${r.competitor_vendor} **${r.competitor_pn}** (${r.competitor_std || r.competitor_form_factor}, ${r.competitor_speed}, ${r.competitor_reach || "—"}) @ ${compPrice}`, ` → Flexoptix: **${r.flexoptix_pn}** (${r.flexoptix_std || r.flexoptix_form_factor}, ${r.flexoptix_speed}, ${r.flexoptix_reach || "—"}) @ ${fxPrice} | Status: ${r.flexoptix_market_status || "—"}`, ` Confidence: ${conf} | Match basis: ${basis}`, r.flexoptix_url ? ` Product page: ${r.flexoptix_url}` : "", ].filter(Boolean).join("\n"); }); return { content: [{ type: "text", text: `## Equivalences for "${part_number}"\n\nFound ${result.rows.length} match(es):\n\n${lines.join("\n\n")}`, }], }; } ); // --- Tool: get_price_history --- server.tool( "get_price_history", `Get price history for a transceiver over time (from 392k+ price observations across 60+ competitors). Returns daily min/max/avg prices per source vendor for charting and trend analysis. Useful for: price trend analysis, sourcing decisions, identifying cheapest vendor window.`, { part_number: z.string().describe("Part number, slug, or standard name, e.g. 'QSFP-40G-SR4', '100GBASE-LR4'"), days: z.number().default(30).describe("Number of days of history to return (max 365)"), vendor: z.string().optional().describe("Filter to specific source vendor, e.g. 'FS.COM', 'Mouser'. Leave empty for all."), }, async ({ part_number, days, vendor }) => { const daysLimited = Math.min(days, 365); // Resolve transceiver const tx = await pool.query( `SELECT t.id, t.part_number, t.standard_name, t.form_factor, t.speed, v.name as vendor_name FROM transceivers t LEFT JOIN vendors v ON v.id = t.vendor_id WHERE t.slug ILIKE $1 OR t.part_number ILIKE $1 OR t.standard_name ILIKE $1 LIMIT 1`, [`%${part_number}%`] ); if (tx.rows.length === 0) { return { content: [{ type: "text", text: `Transceiver not found: "${part_number}"` }], }; } const txRow = tx.rows[0]; const conditions = [ `po.transceiver_id = $1`, `po.time >= NOW() - INTERVAL '${daysLimited} days'`, `po.price > 0`, `po.is_anomalous IS NOT TRUE`, ]; const values: unknown[] = [txRow.id]; let idx = 2; if (vendor) { conditions.push(`sv.name ILIKE $${idx}`); values.push(`%${vendor}%`); idx++; } const series = await pool.query( `SELECT DATE_TRUNC('day', po.time) AS day, sv.name AS source_vendor, MIN(po.price)::numeric(12,2) AS price_min, MAX(po.price)::numeric(12,2) AS price_max, AVG(po.price)::numeric(12,2) AS price_avg, po.currency, COUNT(*) AS observations FROM price_observations po LEFT JOIN vendors sv ON sv.id = po.source_vendor_id WHERE ${conditions.join(" AND ")} GROUP BY DATE_TRUNC('day', po.time), sv.name, po.currency ORDER BY day ASC, source_vendor`, values ); if (series.rows.length === 0) { return { content: [{ type: "text", text: `No price history found for "${txRow.part_number}" in the last ${daysLimited} days.`, }], }; } // Summarize by vendor const byVendor = new Map(); for (const row of series.rows) { const key = row.source_vendor || "Unknown"; const cur = byVendor.get(key); if (!cur) { byVendor.set(key, { min: parseFloat(row.price_min), max: parseFloat(row.price_max), latest: parseFloat(row.price_avg), currency: row.currency, points: parseInt(row.observations) }); } else { cur.min = Math.min(cur.min, parseFloat(row.price_min)); cur.max = Math.max(cur.max, parseFloat(row.price_max)); cur.latest = parseFloat(row.price_avg); // last in order = latest cur.points += parseInt(row.observations); } } const vendorLines = [...byVendor.entries()] .sort((a, b) => a[1].min - b[1].min) .map(([v, d]) => `- **${v}**: ${d.currency} ${d.min}–${d.max} (latest avg: ${d.latest}, ${d.points} observations)` ); const allMins = [...byVendor.values()].map(d => d.min); const overallMin = Math.min(...allMins); const overallMax = Math.max(...[...byVendor.values()].map(d => d.max)); const cheapestVendor = [...byVendor.entries()].sort((a, b) => a[1].min - b[1].min)[0]; return { content: [{ type: "text", text: [ `## Price History: ${txRow.part_number} (${txRow.vendor_name || "—"})`, `**Standard:** ${txRow.standard_name || "—"} | **Form factor:** ${txRow.form_factor} | **Speed:** ${txRow.speed}`, `**Period:** Last ${daysLimited} days | **Total observations:** ${series.rows.reduce((s, r) => s + parseInt(r.observations), 0)}`, ``, `### Price Range (all vendors)`, `- Overall min: **${overallMin}** | max: **${overallMax}**`, `- Cheapest source: **${cheapestVendor[0]}** @ ${cheapestVendor[1].min}`, ``, `### By Vendor`, ...vendorLines, ].join("\n"), }], }; } ); }