/** * Pricing tools: get_pricing, compare_prices, get_competitor_stock */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { pool } from "../db.js"; export async function registerPricingTools(server: McpServer): Promise { // --- Tool: get_pricing --- server.tool( "get_pricing", "Get current prices and availability across all sources for a specific transceiver. Includes price history if requested.", { part_number: z.string().describe("Part number or slug, e.g. 'qsfp28-lr4' or 'SFP-10G-LR'"), vendor: z.string().optional().describe("Filter to specific vendor only"), include_history: z.boolean().default(false).describe("Include 30-day price history"), }, async ({ part_number, vendor, include_history }) => { // Find transceiver const txResult = await pool.query( `SELECT t.id, t.slug, t.standard_name, t.form_factor, t.speed, t.reach_label FROM transceivers t WHERE t.slug ILIKE $1 OR t.part_number ILIKE $1 OR t.standard_name ILIKE $1 LIMIT 1`, [`%${part_number}%`] ); if (txResult.rows.length === 0) { return { content: [{ type: "text", text: `No transceiver found for "${part_number}".` }], }; } const tx = txResult.rows[0]; const vendorCondition = vendor ? `AND v.name ILIKE $2` : ""; const baseParams: unknown[] = vendor ? [tx.id, `%${vendor}%`] : [tx.id]; // Current prices (latest per vendor) const currentPrices = await pool.query( `SELECT DISTINCT ON (po.source_vendor_id) v.name as vendor, po.price, po.currency, po.stock_level, po.quantity_available, po.url, po.time FROM price_observations po JOIN vendors v ON v.id = po.source_vendor_id WHERE po.transceiver_id = $1 ${vendorCondition} ORDER BY po.source_vendor_id, po.time DESC`, baseParams ); let history = null; if (include_history) { const histResult = await pool.query( `SELECT v.name as vendor, po.price, po.currency, po.stock_level, po.time FROM price_observations po JOIN vendors v ON v.id = po.source_vendor_id WHERE po.transceiver_id = $1 ${vendorCondition} AND po.time > NOW() - INTERVAL '30 days' ORDER BY po.time DESC LIMIT 200`, baseParams ); history = histResult.rows; } const response = { transceiver: { slug: tx.slug, standard: tx.standard_name, form_factor: tx.form_factor, speed: tx.speed, reach: tx.reach_label, }, current_prices: currentPrices.rows, cheapest: currentPrices.rows.length > 0 ? currentPrices.rows.reduce((min, r) => r.price < min.price ? r : min) : null, history: include_history ? history : undefined, }; return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }], }; } ); // --- Tool: compare_prices --- server.tool( "compare_prices", "Compare prices across all vendors for a transceiver. Shows savings opportunities vs competitors.", { transceiver_query: z.string().describe("Free text or part number to search for"), competitors: z.array(z.string()).optional().describe("Limit to specific competitors"), }, async ({ transceiver_query, competitors }) => { const vendorFilter = competitors && competitors.length > 0 ? `AND v.name ILIKE ANY(ARRAY[${competitors.map((_, i) => `$${i + 2}`).join(",")}])` : ""; const values: unknown[] = [transceiver_query]; if (competitors) { competitors.forEach((c) => values.push(`%${c}%`)); } const result = await pool.query( `SELECT t.slug, t.standard_name, t.form_factor, t.speed, t.reach_label, v.name as vendor, v.type, po.price, po.currency, po.stock_level, po.time FROM price_observations po JOIN transceivers t ON t.id = po.transceiver_id JOIN vendors v ON v.id = po.source_vendor_id WHERE (t.standard_name ILIKE $1 OR t.slug ILIKE $1) ${vendorFilter} AND po.time = ( SELECT MAX(time) FROM price_observations WHERE transceiver_id = t.id AND source_vendor_id = v.id ) ORDER BY t.slug, po.price ASC`, values ); if (result.rows.length === 0) { return { content: [{ type: "text", text: `No pricing data found for "${transceiver_query}".` }], }; } // Group by transceiver const grouped: Record; min_price: number; max_price: number; savings_vs_max: number; }> = {}; for (const row of result.rows) { if (!grouped[row.slug]) { grouped[row.slug] = { transceiver: { slug: row.slug, standard: row.standard_name, form_factor: row.form_factor, speed: row.speed, reach: row.reach_label, }, prices: [], min_price: Infinity, max_price: 0, savings_vs_max: 0, }; } grouped[row.slug].prices.push({ vendor: row.vendor, type: row.type, price: parseFloat(row.price), currency: row.currency, stock: row.stock_level, }); grouped[row.slug].min_price = Math.min(grouped[row.slug].min_price, parseFloat(row.price)); grouped[row.slug].max_price = Math.max(grouped[row.slug].max_price, parseFloat(row.price)); } for (const slug of Object.keys(grouped)) { const g = grouped[slug]; g.savings_vs_max = Math.round((1 - g.min_price / g.max_price) * 100); } return { content: [{ type: "text", text: JSON.stringify({ comparisons: Object.values(grouped) }, null, 2), }], }; } ); // --- Tool: get_competitor_stock --- server.tool( "get_competitor_stock", "Check live stock levels at a specific competitor. Useful for identifying sales opportunities when competitor is out of stock.", { competitor: z.string().describe("Competitor name, e.g. 'FS.COM', 'Optcore', 'ProLabs'"), product_query: z.string().optional().describe("Optional product filter"), out_of_stock_only: z.boolean().default(false).describe("Only show out-of-stock items (sales opportunities)"), }, async ({ competitor, product_query, out_of_stock_only }) => { const conditions = [`v.name ILIKE $1`]; const values: unknown[] = [`%${competitor}%`]; let idx = 2; if (product_query) { conditions.push(`(t.standard_name ILIKE $${idx} OR t.slug ILIKE $${idx})`); values.push(`%${product_query}%`); idx++; } if (out_of_stock_only) { conditions.push(`po.stock_level IN ('out_of_stock', 'discontinued')`); } const result = await pool.query( `SELECT DISTINCT ON (t.id) v.name as competitor, t.slug, t.standard_name, t.form_factor, t.speed, t.reach_label, po.price, po.currency, po.stock_level, po.quantity_available, po.time FROM price_observations po JOIN vendors v ON v.id = po.source_vendor_id JOIN transceivers t ON t.id = po.transceiver_id WHERE ${conditions.join(" AND ")} ORDER BY t.id, po.time DESC LIMIT 100`, values ); const inStock = result.rows.filter((r) => r.stock_level === "in_stock").length; const outOfStock = result.rows.filter((r) => ["out_of_stock", "discontinued"].includes(r.stock_level) ).length; return { content: [{ type: "text", text: JSON.stringify({ competitor, summary: { total: result.rows.length, in_stock: inStock, out_of_stock: outOfStock }, items: result.rows, sales_opportunities: out_of_stock_only ? result.rows.length : `${outOfStock} items out of stock at ${competitor}`, }, null, 2), }], }; } ); }