279 lines
11 KiB
TypeScript
279 lines
11 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
// --- 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<string, unknown> | null = null;
|
|
let forecast: Record<string, unknown> | 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),
|
|
}],
|
|
};
|
|
}
|
|
);
|
|
}
|