366 lines
14 KiB
JavaScript

#!/usr/bin/env node
/**
* TIP MCP Server — Transceiver Intelligence Platform
*
* 15 Tools for LLM access to transceiver data, pricing, compatibility,
* hype cycle, knowledge base, news, market intelligence, and blog generation.
*
* Transport: stdio (for Claude Code, EO Global Pulse, etc.)
*
* Usage:
* tsx src/index.ts — Run MCP server via stdio
* npx @tip/mcp-server — After npm install -g
*
* Claude Code config (~/.claude/mcp.json):
* { "tip": { "command": "npx", "args": ["@tip/mcp-server"] } }
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { pool } from "./db.js";
import { registerPricingTools } from "./tools/pricing.js";
import { registerCompatibilityTools } from "./tools/compatibility.js";
import { registerKnowledgeTools } from "./tools/knowledge.js";
import { registerContentTools } from "./tools/content.js";
import { registerMarketTools } from "./tools/market.js";
async function main() {
const server = new McpServer({
name: "tip-mcp-server",
version: "0.2.0",
});
// --- Tool: search_transceivers ---
server.tool(
"search_transceivers",
"Search transceivers by free text, specs, or compatibility. Returns matching transceivers with current pricing if available.",
{
query: z.string().optional().describe("Free text query, e.g. '10km for Cisco Nexus' or '400G QSFP-DD ZR'"),
form_factor: z.string().optional().describe("SFP, SFP+, SFP28, QSFP+, QSFP28, QSFP-DD, OSFP, CFP2, etc."),
speed_gbps: z.number().optional().describe("Speed in Gbps: 1, 10, 25, 40, 100, 200, 400, 800"),
reach_label: z.string().optional().describe("SR, LR, ER, ZR, or distance like 10km, 80km"),
fiber_type: z.enum(["SMF", "MMF"]).optional().describe("Single-mode or Multi-mode fiber"),
wdm_type: z.enum(["CWDM", "DWDM"]).optional().describe("Wavelength division multiplexing type"),
vendor: z.string().optional().describe("Vendor/manufacturer filter, e.g. 'Cisco', 'Juniper', 'FS.COM', 'Flexoptix'"),
category: z.string().optional().describe("Category filter: DataCenter, AOC, DAC, DWDM, CWDM, Coherent, Metro, LongHaul, etc."),
market_status: z.enum(["Mainstream", "Growth", "Emerging", "Legacy", "EOL"]).optional().describe("Market status filter"),
max_results: z.number().default(10).describe("Maximum results to return"),
},
async ({ query, form_factor, speed_gbps, reach_label, fiber_type, wdm_type, vendor, category, market_status, max_results }) => {
const conditions: string[] = [];
const values: unknown[] = [];
let idx = 1;
if (query) {
conditions.push(`t.search_vector @@ plainto_tsquery('english', $${idx})`);
values.push(query);
idx++;
}
if (form_factor) {
conditions.push(`t.form_factor ILIKE $${idx}`);
values.push(`%${form_factor}%`);
idx++;
}
if (speed_gbps) {
conditions.push(`t.speed_gbps = $${idx}`);
values.push(speed_gbps);
idx++;
}
if (reach_label) {
conditions.push(`(t.reach_label ILIKE $${idx} OR t.standard_name ILIKE $${idx})`);
values.push(`%${reach_label}%`);
idx++;
}
if (fiber_type) {
conditions.push(`t.fiber_type = $${idx}`);
values.push(fiber_type);
idx++;
}
if (wdm_type) {
conditions.push(`t.wdm_type = $${idx}`);
values.push(wdm_type);
idx++;
}
if (vendor) {
conditions.push(`v.name ILIKE $${idx}`);
values.push(`%${vendor}%`);
idx++;
}
if (category) {
conditions.push(`t.category ILIKE $${idx}`);
values.push(`%${category}%`);
idx++;
}
if (market_status) {
conditions.push(`t.market_status = $${idx}`);
values.push(market_status);
idx++;
}
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
const orderBy = query
? `ORDER BY ts_rank(t.search_vector, plainto_tsquery('english', $1)) DESC`
: "ORDER BY t.speed_gbps DESC, t.reach_meters ASC";
values.push(max_results);
const result = await pool.query(
`SELECT t.id, t.slug, t.standard_name, t.form_factor, t.speed, t.speed_gbps,
t.reach_label, t.reach_meters, t.fiber_type, t.connector, t.wdm_type,
t.wavelengths, t.power_consumption_w, t.temp_range, t.category,
t.market_status, t.hype_cycle_phase,
v.name as vendor_name,
(SELECT jsonb_agg(jsonb_build_object(
'vendor', sv.name, 'price', po.price, 'currency', po.currency,
'stock', po.stock_level, 'url', po.url
) ORDER BY po.time DESC)
FROM price_observations po
JOIN vendors sv ON sv.id = po.source_vendor_id
WHERE po.transceiver_id = t.id
AND po.time > NOW() - INTERVAL '7 days'
) as pricing
FROM transceivers t
LEFT JOIN vendors v ON v.id = t.vendor_id
${where}
${orderBy}
LIMIT $${idx}`,
values
);
if (result.rows.length === 0) {
return {
content: [{ type: "text", text: "No transceivers found matching your criteria." }],
};
}
const formatted = result.rows.map((r) => ({
slug: r.slug,
standard: r.standard_name,
form_factor: r.form_factor,
speed: r.speed,
speed_gbps: r.speed_gbps,
reach: r.reach_label,
fiber: r.fiber_type,
connector: r.connector,
wdm: r.wdm_type,
wavelengths: r.wavelengths,
power_w: r.power_consumption_w,
temp: r.temp_range,
category: r.category,
market_status: r.market_status,
vendor: r.vendor_name,
pricing: r.pricing || [],
}));
return {
content: [{
type: "text",
text: JSON.stringify({ count: result.rows.length, transceivers: formatted }, null, 2),
}],
};
}
);
// --- Tool: check_compatibility ---
server.tool(
"check_compatibility",
"Check compatibility between a switch model and transceivers. Returns verified compatible transceivers with firmware requirements. When no exact match is found, suggests alternative transceivers that may work.",
{
switch_model: z.string().describe("Switch model, e.g. 'Cisco Nexus 93180YC-FX3' or 'Juniper EX4300'"),
transceiver_query: z.string().optional().describe("Optional: filter by transceiver type or part number"),
speed_gbps: z.number().optional().describe("Optional: filter by speed"),
reach: z.string().optional().describe("Optional: filter by reach (SR, LR, etc.)"),
},
async ({ switch_model, transceiver_query, speed_gbps, reach }) => {
// First: find the switch
const switchResult = await pool.query(
`SELECT s.id, s.model, s.series, s.max_speed_gbps, s.ports_config,
v.name as vendor
FROM switches s
JOIN vendors v ON v.id = s.vendor_id
WHERE s.model ILIKE $1 OR s.series ILIKE $1
LIMIT 5`,
[`%${switch_model}%`]
);
if (switchResult.rows.length === 0) {
// Suggest similar switches using trigram similarity
const similarResult = await pool.query(
`SELECT s.model, s.series, v.name as vendor,
similarity(s.model, $1) as sim
FROM switches s
JOIN vendors v ON v.id = s.vendor_id
WHERE similarity(s.model, $1) > 0.1
ORDER BY sim DESC
LIMIT 5`,
[switch_model]
);
const suggestions = similarResult.rows.length > 0
? `\n\nDid you mean one of these?\n${similarResult.rows.map(r => ` - ${r.vendor} ${r.model} (${r.series})`).join("\n")}`
: "";
return {
content: [{
type: "text",
text: `No switch found matching "${switch_model}". Try a shorter model name or check spelling.${suggestions}`,
}],
};
}
const sw = switchResult.rows[0];
const conditions = [`c.switch_id = $1`];
const values: unknown[] = [sw.id];
let idx = 2;
if (transceiver_query) {
conditions.push(`(t.standard_name ILIKE $${idx} OR t.slug ILIKE $${idx})`);
values.push(`%${transceiver_query}%`);
idx++;
}
if (speed_gbps) {
conditions.push(`t.speed_gbps = $${idx}`);
values.push(speed_gbps);
idx++;
}
if (reach) {
conditions.push(`t.reach_label ILIKE $${idx}`);
values.push(`%${reach}%`);
idx++;
}
const compatResult = await pool.query(
`SELECT t.id, t.slug, t.standard_name, t.form_factor, t.speed, t.speed_gbps,
t.reach_label, t.reach_meters, t.fiber_type,
c.status, c.firmware_min, c.verified_by, c.verification_method, c.notes as compat_notes,
(SELECT MIN(po.price) FROM price_observations po
WHERE po.transceiver_id = t.id AND po.time > NOW() - INTERVAL '7 days'
) as min_price
FROM compatibility c
JOIN transceivers t ON t.id = c.transceiver_id
WHERE ${conditions.join(" AND ")}
AND c.status = 'compatible'
ORDER BY t.speed_gbps DESC, t.reach_meters ASC
LIMIT 20`,
values
);
// If no compatible transceivers found, suggest alternatives
let alternatives: unknown[] = [];
if (compatResult.rows.length === 0) {
// Find what form factors / speeds this switch supports based on ports_config
const portSpeeds: number[] = [];
if (sw.max_speed_gbps) portSpeeds.push(parseFloat(sw.max_speed_gbps));
// Try to find transceivers that match the requested criteria even without verified compatibility
const altConditions: string[] = [];
const altValues: unknown[] = [];
let altIdx = 1;
if (transceiver_query) {
altConditions.push(`(t.standard_name ILIKE $${altIdx} OR t.slug ILIKE $${altIdx})`);
altValues.push(`%${transceiver_query}%`);
altIdx++;
}
if (speed_gbps) {
altConditions.push(`t.speed_gbps = $${altIdx}`);
altValues.push(speed_gbps);
altIdx++;
}
if (reach) {
altConditions.push(`t.reach_label ILIKE $${altIdx}`);
altValues.push(`%${reach}%`);
altIdx++;
}
const altWhere = altConditions.length > 0 ? `WHERE ${altConditions.join(" AND ")}` : "";
const altResult = await pool.query(
`SELECT t.slug, t.standard_name, t.form_factor, t.speed, t.speed_gbps,
t.reach_label, t.fiber_type, v.name as vendor,
(SELECT MIN(po.price) FROM price_observations po
WHERE po.transceiver_id = t.id AND po.time > NOW() - INTERVAL '7 days'
) as min_price,
-- Check if this transceiver is compatible with OTHER switches from the same vendor
(SELECT COUNT(*) FROM compatibility c2
JOIN switches sw2 ON sw2.id = c2.switch_id
WHERE c2.transceiver_id = t.id
AND c2.status = 'compatible'
AND sw2.vendor_id = (SELECT vendor_id FROM switches WHERE id = '${sw.id}')
) as same_vendor_compat_count
FROM transceivers t
LEFT JOIN vendors v ON v.id = t.vendor_id
${altWhere}
ORDER BY same_vendor_compat_count DESC, t.speed_gbps DESC
LIMIT 10`,
altValues
);
alternatives = altResult.rows.map(r => ({
...r,
min_price: r.min_price ? parseFloat(r.min_price) : null,
compatibility_note: parseInt(r.same_vendor_compat_count) > 0
? `Compatible with ${r.same_vendor_compat_count} other ${sw.vendor} switches — likely compatible but NOT verified for ${sw.model}`
: "Not verified for any switches from this vendor. Test before deploying.",
}));
}
return {
content: [{
type: "text",
text: JSON.stringify({
switch: {
model: sw.model,
series: sw.series,
vendor: sw.vendor,
max_speed_gbps: sw.max_speed_gbps,
},
compatible_transceivers: compatResult.rows.map(r => ({
slug: r.slug,
standard: r.standard_name,
form_factor: r.form_factor,
speed: r.speed,
reach: r.reach_label,
fiber: r.fiber_type,
status: r.status,
firmware_min: r.firmware_min,
verified_by: r.verified_by,
method: r.verification_method,
notes: r.compat_notes,
min_price: r.min_price ? parseFloat(r.min_price) : null,
})),
count: compatResult.rows.length,
...(compatResult.rows.length === 0 && alternatives.length > 0 ? {
no_verified_match: true,
suggested_alternatives: alternatives,
suggestion_note: `No verified compatible transceivers found for ${sw.vendor} ${sw.model}. These alternatives may work based on specs and compatibility with similar ${sw.vendor} switches, but should be tested before production deployment.`,
} : {}),
}, null, 2),
}],
};
}
);
// Register tool modules
await registerPricingTools(server);
await registerCompatibilityTools(server);
await registerKnowledgeTools(server);
await registerContentTools(server);
await registerMarketTools(server);
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
// Graceful shutdown
process.on("SIGINT", async () => {
await pool.end();
process.exit(0);
});
}
main().catch((err) => {
console.error("Fatal MCP server error:", err);
process.exit(1);
});