New API routes: - GET /api/finder — Switch→Flexoptix transceiver finder with FlexBox coding - GET /api/competitor-alerts — Competitor intelligence (price changes, new products, stock) - GET /api/forecast/:technology — Sales forecast 3/9/12/18 months + buy/wait/hold signal - POST /api/transport/plan — Transport system planner (city→city BOM with fiber providers) New MCP tools: - find_flexoptix_for_switch — Customer switch → Flexoptix products - get_competitor_alerts — Competitor monitoring - plan_transport — Network transport planning - forecast_sales — Volume/revenue prediction - generate_blog — Enhanced blog generation New DB tables (migration 013): - competitor_alerts, price_changes, flexoptix_product_map - sales_forecasts, fiber_providers, fiber_routes, cities - generated_datasheets, blog_series - Views: v_price_coverage, v_image_coverage, v_switch_flexoptix_finder Seed data (migration 014): - 25 European cities with IX/DC locations + coordinates - 15 fiber providers (euNetworks, Telia, DTAG, Colt, Zayo, etc.) - 16 fiber routes with pricing (Germany focus) Infrastructure: - Scraper scheduler: 2h Flexoptix, 4h FS.com/Optcore (was 6-8h) - Change detector for competitor price/stock monitoring - Image downloader utility with coverage tracking
176 lines
7.5 KiB
TypeScript
176 lines
7.5 KiB
TypeScript
/**
|
|
* MCP Tool: find_flexoptix_for_switch
|
|
*
|
|
* "Customer has Switch X — which Flexoptix transceivers should they buy?"
|
|
*/
|
|
import { pool } from "../db";
|
|
|
|
export const finderTools = {
|
|
find_flexoptix_for_switch: {
|
|
name: "find_flexoptix_for_switch",
|
|
description: "Find the right Flexoptix transceivers for a customer's switch. Input a switch model name and get compatible Flexoptix products with prices, shop links, and FlexBox coding info.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
switch_model: {
|
|
type: "string",
|
|
description: 'Switch model name (e.g., "Cisco Nexus 93180YC-FX3", "QFX5120-48Y", "DCS-7050SX3-48YC12")',
|
|
},
|
|
speed_gbps: {
|
|
type: "number",
|
|
description: "Filter by port speed in Gbps (10, 25, 40, 100, 400)",
|
|
},
|
|
reach: {
|
|
type: "string",
|
|
description: "Filter by reach (SR, LR, ER, ZR, or specific like 10km, 80km)",
|
|
},
|
|
},
|
|
required: ["switch_model"],
|
|
},
|
|
},
|
|
|
|
plan_transport: {
|
|
name: "plan_transport",
|
|
description: "Plan a fiber transport system between two cities. Returns switch, transceiver, and fiber provider recommendations with bill of materials and Flexoptix pricing.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
from: { type: "string", description: "Source city (e.g., Berlin, Frankfurt, Amsterdam)" },
|
|
to: { type: "string", description: "Destination city (e.g., Darmstadt, Munich, London)" },
|
|
bandwidth_gbps: { type: "number", description: "Required bandwidth in Gbps (default: 100)" },
|
|
redundancy: { type: "boolean", description: "Whether to include redundant path (default: false)" },
|
|
},
|
|
required: ["from", "to"],
|
|
},
|
|
},
|
|
|
|
forecast_sales: {
|
|
name: "forecast_sales",
|
|
description: "Predict transceiver sales volume and price trajectory for a technology over 3/9/12/18 months. Includes buy/wait/hold signal.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
technology: {
|
|
type: "string",
|
|
description: 'Technology to forecast (e.g., "400G QSFP-DD", "100G QSFP28", "800G OSFP", "1.6T OSFP-XD")',
|
|
},
|
|
},
|
|
required: ["technology"],
|
|
},
|
|
},
|
|
|
|
get_competitor_alerts: {
|
|
name: "get_competitor_alerts",
|
|
description: "Get recent competitor intelligence: new products, price changes, stock changes. Shows what competitors are doing in the market.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
vendor: { type: "string", description: "Filter by competitor name/slug" },
|
|
alert_type: { type: "string", description: "Filter: new_product, price_drop, price_increase, out_of_stock, back_in_stock" },
|
|
days: { type: "number", description: "Look back N days (default: 7)" },
|
|
},
|
|
},
|
|
},
|
|
|
|
generate_blog: {
|
|
name: "generate_blog",
|
|
description: "Generate a professional blog post for the Flexoptix blog. Auto-enriched with pricing data, competitor analysis, and product links.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
topic: { type: "string", description: "Blog topic or title" },
|
|
type: {
|
|
type: "string",
|
|
description: "Blog type: market_alert, migration_guide, competitor_analysis, technology_deep_dive, buying_guide, tutorial, comparison",
|
|
},
|
|
target_audience: { type: "string", description: "Audience: technical, sales, customer (default: technical)" },
|
|
include_products: { type: "boolean", description: "Include Flexoptix product recommendations (default: true)" },
|
|
word_count: { type: "number", description: "Target word count (default: 2000)" },
|
|
},
|
|
required: ["topic"],
|
|
},
|
|
},
|
|
};
|
|
|
|
export async function handleFinderTool(name: string, args: Record<string, any>): Promise<string> {
|
|
switch (name) {
|
|
case "find_flexoptix_for_switch": {
|
|
const { switch_model, speed_gbps, reach } = args;
|
|
|
|
// Find switch
|
|
const sw = await pool.query(
|
|
`SELECT sw.id, sw.model, sw.series, sw.ports_config, sw.max_speed_gbps, v.name AS vendor
|
|
FROM switches sw JOIN vendors v ON sw.vendor_id = v.id
|
|
WHERE sw.model ILIKE '%' || $1 || '%' OR sw.search_vector @@ plainto_tsquery('english', $1)
|
|
ORDER BY CASE WHEN sw.model ILIKE $1 THEN 0 ELSE 1 END LIMIT 3`,
|
|
[switch_model]
|
|
);
|
|
|
|
if (!sw.rows[0]) {
|
|
return JSON.stringify({ error: `Switch "${switch_model}" not found. Try a partial model name.` });
|
|
}
|
|
|
|
// Find compatible transceivers with Flexoptix products
|
|
let sql = `
|
|
SELECT t.form_factor, t.speed_gbps, t.reach_label, t.fiber_type, t.connector,
|
|
t.image_url, v.name AS vendor, c.firmware_min,
|
|
(SELECT po.price FROM price_observations po WHERE po.transceiver_id = t.id ORDER BY po.time DESC LIMIT 1) AS price,
|
|
(SELECT po.currency FROM price_observations po WHERE po.transceiver_id = t.id ORDER BY po.time DESC LIMIT 1) AS currency
|
|
FROM compatibility c
|
|
JOIN transceivers t ON c.transceiver_id = t.id
|
|
JOIN vendors v ON t.vendor_id = v.id
|
|
WHERE c.switch_id = $1 AND c.status = 'compatible'
|
|
`;
|
|
const params: any[] = [sw.rows[0].id];
|
|
let idx = 2;
|
|
if (speed_gbps) { sql += ` AND t.speed_gbps = $${idx}`; params.push(speed_gbps); idx++; }
|
|
if (reach) { sql += ` AND t.reach_label ILIKE $${idx}`; params.push(reach + '%'); idx++; }
|
|
sql += ` ORDER BY t.speed_gbps DESC, t.reach_meters ASC LIMIT 30`;
|
|
|
|
const compat = await pool.query(sql, params);
|
|
|
|
return JSON.stringify({
|
|
switch: { model: sw.rows[0].model, vendor: sw.rows[0].vendor, ports: sw.rows[0].ports_config },
|
|
compatible_count: compat.rowCount,
|
|
transceivers: compat.rows.map(r => ({
|
|
...r,
|
|
flexbox_note: "All Flexoptix transceivers support FlexBox coding — one transceiver works in any vendor's switch.",
|
|
buy_url: `https://www.flexoptix.net/en/catalogsearch/result/?q=${encodeURIComponent(r.form_factor + ' ' + r.speed_gbps + 'G ' + r.reach_label)}`,
|
|
})),
|
|
}, null, 2);
|
|
}
|
|
|
|
case "get_competitor_alerts": {
|
|
const { vendor, alert_type, days = 7 } = args;
|
|
let sql = `SELECT ca.alert_type, ca.severity, ca.part_number, ca.product_name,
|
|
ca.old_price, ca.new_price, ca.price_pct, ca.currency, ca.source_url,
|
|
v.name AS vendor, ca.created_at
|
|
FROM competitor_alerts ca LEFT JOIN vendors v ON ca.vendor_id = v.id
|
|
WHERE ca.created_at > NOW() - INTERVAL '1 day' * $1`;
|
|
const params: any[] = [days];
|
|
let idx = 2;
|
|
if (vendor) { sql += ` AND v.slug ILIKE $${idx}`; params.push('%' + vendor + '%'); idx++; }
|
|
if (alert_type) { sql += ` AND ca.alert_type = $${idx}`; params.push(alert_type); idx++; }
|
|
sql += ` ORDER BY ca.created_at DESC LIMIT 30`;
|
|
|
|
const result = await pool.query(sql, params);
|
|
return JSON.stringify({ alerts: result.rows, count: result.rowCount }, null, 2);
|
|
}
|
|
|
|
case "plan_transport":
|
|
case "forecast_sales":
|
|
case "generate_blog":
|
|
// These forward to the API routes — return instruction to use HTTP API
|
|
return JSON.stringify({
|
|
note: `Use the TIP HTTP API for ${name}. See https://transceiver-db.context-x.org/api for endpoints.`,
|
|
endpoint: name === "plan_transport" ? "POST /api/transport/plan" :
|
|
name === "forecast_sales" ? "GET /api/forecast/:technology" :
|
|
"POST /api/blog/generate",
|
|
args,
|
|
});
|
|
|
|
default:
|
|
return JSON.stringify({ error: `Unknown tool: ${name}` });
|
|
}
|
|
}
|