/** * Knowledge tools: search_knowledge_base, search_manuals, get_hype_cycle */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { pool } from "../db.js"; // Norton-Bass diffusion model (inline, since MCP server has separate build) interface TechGen { name: string; speedGbps: number; formFactor: string; introYear: number; peakYear: number; p: number; q: number; m: number; k: number; t0: number; } const TECH_GENS: TechGen[] = [ { name: "1G SFP", speedGbps: 1, formFactor: "SFP", introYear: 2001, peakYear: 2012, p: 0.03, q: 0.38, m: 500, k: 0.45, t0: 2008 }, { name: "10G SFP+", speedGbps: 10, formFactor: "SFP+", introYear: 2006, peakYear: 2018, p: 0.03, q: 0.42, m: 600, k: 0.50, t0: 2014 }, { name: "25G SFP28", speedGbps: 25, formFactor: "SFP28", introYear: 2015, peakYear: 2022, p: 0.04, q: 0.45, m: 200, k: 0.55, t0: 2019 }, { name: "40G QSFP+", speedGbps: 40, formFactor: "QSFP+", introYear: 2010, peakYear: 2019, p: 0.025, q: 0.35, m: 150, k: 0.40, t0: 2016 }, { name: "100G QSFP28", speedGbps: 100, formFactor: "QSFP28", introYear: 2014, peakYear: 2024, p: 0.03, q: 0.40, m: 400, k: 0.48, t0: 2020 }, { name: "400G QSFP-DD", speedGbps: 400, formFactor: "QSFP-DD", introYear: 2020, peakYear: 2027, p: 0.035, q: 0.50, m: 300, k: 0.52, t0: 2025 }, { name: "800G OSFP", speedGbps: 800, formFactor: "OSFP", introYear: 2023, peakYear: 2029, p: 0.04, q: 0.55, m: 250, k: 0.55, t0: 2027 }, { name: "1.6T OSFP-XD", speedGbps: 1600, formFactor: "OSFP-XD", introYear: 2025, peakYear: 2032, p: 0.03, q: 0.45, m: 180, k: 0.48, t0: 2030 }, { name: "CPO", speedGbps: 1600, formFactor: "CPO", introYear: 2024, peakYear: 2033, p: 0.02, q: 0.30, m: 50, k: 0.35, t0: 2031 }, { name: "400ZR Coherent", speedGbps: 400, formFactor: "QSFP-DD", introYear: 2021, peakYear: 2026, p: 0.04, q: 0.50, m: 80, k: 0.55, t0: 2024 }, { name: "LPO", speedGbps: 800, formFactor: "LPO", introYear: 2024, peakYear: 2029, p: 0.035, q: 0.48, m: 100, k: 0.50, t0: 2027 }, ]; function bassCumulative(t: number, p: number, q: number): number { const pq = p + q; const exp = Math.exp(-pq * t); return (1 - exp) / (1 + (q / p) * exp); } function bassRate(t: number, p: number, q: number): number { const pq = p + q; const exp = Math.exp(-pq * t); const d = 1 + (q / p) * exp; return (pq * pq / q) * (exp / (d * d)); } function classifyHypePhase(progressRatio: number): string { if (progressRatio < 0.15) return "Innovation Trigger"; if (progressRatio < 0.35) return "Peak of Inflated Expectations"; if (progressRatio < 0.55) return "Trough of Disillusionment"; if (progressRatio < 0.85) return "Slope of Enlightenment"; if (progressRatio < 1.3) return "Plateau of Productivity"; return "Legacy / Decline"; } function findTech(query: string): TechGen | undefined { const q = query.toLowerCase().trim(); return TECH_GENS.find(t => t.name.toLowerCase().includes(q)) || TECH_GENS.find(t => q.includes(t.formFactor.toLowerCase())) || (() => { const m = q.match(/^(\d+(?:\.\d+)?)\s*(g|t)\b/i); if (m) { const gbps = m[2].toLowerCase() === "t" ? parseFloat(m[1]) * 1000 : parseFloat(m[1]); return TECH_GENS.find(t => t.speedGbps === gbps); } return undefined; })(); } export async function registerKnowledgeTools(server: McpServer): Promise { // --- Tool: search_knowledge_base --- server.tool( "search_knowledge_base", "Search troubleshooting guides, FAQs, and best practices. Use for diagnosing transceiver/switch issues.", { query: z.string().describe("Problem description or question, e.g. 'low Rx power QSFP28' or 'link flapping SFP+'"), category: z.enum(["troubleshooting", "faq", "best_practice", "configuration"]).optional(), severity: z.enum(["critical", "high", "medium", "low"]).optional().describe("Filter by issue severity"), }, async ({ query, category, severity }) => { const conditions = [`kb.search_vector @@ plainto_tsquery('english', $1)`]; const values: unknown[] = [query]; let idx = 2; if (category) { conditions.push(`kb.category = $${idx}`); values.push(category); idx++; } if (severity) { conditions.push(`kb.severity = $${idx}`); values.push(severity); idx++; } const result = await pool.query( `SELECT kb.id, kb.title, kb.content, kb.category, kb.severity, kb.affected_products, kb.resolution, kb.source_url, ts_rank(kb.search_vector, plainto_tsquery('english', $1)) as rank FROM knowledge_base kb WHERE ${conditions.join(" AND ")} ORDER BY rank DESC LIMIT 10`, values ); if (result.rows.length === 0) { return { content: [{ type: "text", text: `No knowledge base entries found for: "${query}". Knowledge base grows as vendor FAQs are scraped.`, }], }; } return { content: [{ type: "text", text: JSON.stringify({ results: result.rows, count: result.rows.length }, null, 2), }], }; } ); // --- Tool: search_manuals --- server.tool( "search_manuals", "Search switch manuals, configuration guides, and compatibility lists. Returns relevant document excerpts.", { query: z.string().describe("Query, e.g. 'configure DWDM on Juniper MX' or 'QSFP28 installation guide'"), vendor: z.string().optional().describe("Document vendor filter, e.g. 'Cisco', 'Juniper', 'Arista'"), doc_type: z.enum(["manual", "config_guide", "compatibility_list", "datasheet", "release_notes"]).optional(), }, async ({ query, vendor, doc_type }) => { const conditions = [`d.search_vector @@ plainto_tsquery('english', $1)`]; const values: unknown[] = [query]; let idx = 2; if (vendor) { conditions.push(`v.name ILIKE $${idx}`); values.push(`%${vendor}%`); idx++; } if (doc_type) { conditions.push(`d.doc_type = $${idx}`); values.push(doc_type); idx++; } const result = await pool.query( `SELECT d.id, d.title, d.doc_type, d.version, v.name as vendor, d.source_url, d.pages, d.summary, ts_rank(d.search_vector, plainto_tsquery('english', $1)) as rank FROM documents d LEFT JOIN vendors v ON v.id = d.vendor_id WHERE ${conditions.join(" AND ")} ORDER BY rank DESC LIMIT 10`, values ); if (result.rows.length === 0) { return { content: [{ type: "text", text: `No manuals found for: "${query}". Document library grows as manuals are processed by OCR pipeline.`, }], }; } return { content: [{ type: "text", text: JSON.stringify({ results: result.rows, count: result.rows.length }, null, 2), }], }; } ); // --- Tool: get_hype_cycle --- server.tool( "get_hype_cycle", "Get Hype Cycle position and Norton-Bass diffusion forecast for a transceiver technology. Shows market maturity, adoption phase, and price trajectory.", { technology: z.string().describe("Technology to analyze, e.g. '800G OSFP', 'CPO', '400ZR', '100G LR4', 'DWDM'"), include_forecast: z.boolean().default(true).describe("Include Norton-Bass 5-year adoption forecast"), }, async ({ technology, include_forecast }) => { // Look for market metrics data const result = await pool.query( `SELECT mm.time, mm.metric_type, mm.value, mm.notes FROM market_metrics mm WHERE mm.technology ILIKE $1 OR mm.notes ILIKE $1 ORDER BY mm.time DESC LIMIT 50`, [`%${technology}%`] ); // Also check news for recent developments const newsResult = await pool.query( `SELECT title, published_at, source, summary FROM news_articles WHERE search_vector @@ plainto_tsquery('english', $1) OR title ILIKE $2 ORDER BY published_at DESC LIMIT 5`, [technology, `%${technology}%`] ); // Norton-Bass Diffusion Model computation const currentYear = new Date().getFullYear(); const tech = findTech(technology); let hypeData: Record | null = null; let forecast: Record | undefined = undefined; if (tech) { const t = Math.max(0, currentYear - tech.introYear); const yearsToPeak = tech.peakYear - tech.introYear; const progressRatio = t / yearsToPeak; const adoption = bassCumulative(t, tech.p, tech.q); const rate = bassRate(t, tech.p, tech.q); const phase = classifyHypePhase(progressRatio); // Position on curve (0-100) based on progress ratio const positionPct = Math.min(100, Math.round( progressRatio < 0.15 ? progressRatio / 0.15 * 15 : progressRatio < 0.35 ? 15 + (progressRatio - 0.15) / 0.20 * 15 : progressRatio < 0.55 ? 30 + (progressRatio - 0.35) / 0.20 * 20 : progressRatio < 0.85 ? 50 + (progressRatio - 0.55) / 0.30 * 30 : 80 + Math.min(20, (progressRatio - 0.85) / 0.45 * 20) )); hypeData = { technology: tech.name, phase, position: positionPct, adoption_pct: Math.round(adoption * 100), yearly_adoption_rate: Math.round(rate * 100), intro_year: tech.introYear, peak_year: tech.peakYear, years_to_plateau: Math.max(0, tech.peakYear + 3 - currentYear), model: "Norton-Bass Multigenerational Diffusion", parameters: { p: tech.p, q: tech.q, m: tech.m, k: tech.k, t0: tech.t0 }, }; if (include_forecast) { const fiveYear = Array.from({ length: 5 }, (_, i) => { const yr = currentYear + i + 1; const ft = yr - tech.introYear; const fa = bassCumulative(ft, tech.p, tech.q); const pr = ft / yearsToPeak; return { year: yr, adoption_pct: Math.round(fa * 100), phase: classifyHypePhase(pr) }; }); const aspDecline = progressRatio < 0.3 ? 5 : progressRatio < 0.6 ? 35 : progressRatio < 1.0 ? 15 : 5; forecast = { model: "Norton-Bass Multigenerational Diffusion", current_adoption_pct: Math.round(adoption * 100), estimated_peak_year: tech.peakYear, years_to_plateau: Math.max(0, tech.peakYear + 3 - currentYear), asp_decline_rate_pct: aspDecline, price_trend: aspDecline > 20 ? "Rapidly declining" : aspDecline > 10 ? "Moderately declining" : "Stabilizing", five_year_projection: fiveYear, }; } } return { content: [{ type: "text", text: JSON.stringify({ technology, hype_cycle: hypeData || { note: `No model data for "${technology}". Known: 1G, 10G, 25G, 40G, 100G, 400G, 800G, 1.6T, CPO, LPO, 400ZR` }, market_metrics: result.rows, recent_news: newsResult.rows, norton_bass_forecast: forecast, }, null, 2), }], }; } ); }