Implements all 12 MCP tools from CONCEPT document: - search_transceivers: Full-text + spec filter search with pricing - check_compatibility: Switch ↔ transceiver compatibility lookup - get_pricing: Current prices + 30-day history across all vendors - compare_prices: Multi-vendor price comparison with savings analysis - get_competitor_stock: Live competitor stock monitoring (sales opportunities) - suggest_alternatives: Similar spec alternatives optimized for price/availability - get_templates: FlexBox coding and switch config template finder - search_knowledge_base: Troubleshooting FAQ search (PostgreSQL full-text) - search_manuals: Switch manual and datasheet search - get_hype_cycle: Norton-Bass adoption forecast + Gartner phase classification - get_market_news: Aggregated news with relevance scoring - generate_blog_draft: Data-driven blog drafts saved to blog_drafts table Transport: stdio (MCP protocol 2024-11-05) Config: .mcp.json for Claude Code integration Verified: all 12 tools registered, search_transceivers returns DB results
234 lines
9.2 KiB
TypeScript
234 lines
9.2 KiB
TypeScript
/**
|
|
* Content tools: get_market_news, generate_blog_draft
|
|
*/
|
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { z } from "zod";
|
|
import { pool } from "../db.js";
|
|
|
|
export async function registerContentTools(server: McpServer): Promise<void> {
|
|
// --- Tool: get_market_news ---
|
|
server.tool(
|
|
"get_market_news",
|
|
"Get latest news from the optics and networking industry. Filtered by relevance to transceiver technology.",
|
|
{
|
|
query: z.string().optional().describe("Optional: search within news content"),
|
|
event: z.enum(["OFC", "ECOC", "CIOE", "PHOTONICS_WEST"]).optional().describe("Filter by trade show/event"),
|
|
days_back: z.number().default(30).describe("How many days back to look (default: 30)"),
|
|
min_relevance: z.number().default(0).describe("Minimum relevance score (0=all, 3=transceiver-related, 9=highly relevant)"),
|
|
},
|
|
async ({ query, event, days_back, min_relevance }) => {
|
|
const conditions = [`na.published_at > NOW() - INTERVAL '${days_back} days'`];
|
|
const values: unknown[] = [];
|
|
let idx = 1;
|
|
|
|
if (query) {
|
|
conditions.push(`(na.search_vector @@ plainto_tsquery('english', $${idx}) OR na.title ILIKE $${idx + 1})`);
|
|
values.push(query, `%${query}%`);
|
|
idx += 2;
|
|
}
|
|
if (event) {
|
|
conditions.push(`na.event = $${idx}`);
|
|
values.push(event);
|
|
idx++;
|
|
}
|
|
if (min_relevance > 0) {
|
|
conditions.push(`na.relevance_score >= $${idx}`);
|
|
values.push(min_relevance);
|
|
idx++;
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`SELECT na.title, na.source, na.published_at, na.summary,
|
|
na.source_url, na.relevance_score, na.event,
|
|
na.mentioned_vendors, na.mentioned_products
|
|
FROM news_articles na
|
|
WHERE ${conditions.join(" AND ")}
|
|
ORDER BY na.relevance_score DESC, na.published_at DESC
|
|
LIMIT 20`,
|
|
values
|
|
);
|
|
|
|
const bySource: Record<string, number> = {};
|
|
for (const row of result.rows) {
|
|
bySource[row.source] = (bySource[row.source] || 0) + 1;
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify({
|
|
articles: result.rows,
|
|
count: result.rows.length,
|
|
sources: bySource,
|
|
period: `Last ${days_back} days`,
|
|
}, null, 2),
|
|
}],
|
|
};
|
|
}
|
|
);
|
|
|
|
// --- Tool: generate_blog_draft ---
|
|
server.tool(
|
|
"generate_blog_draft",
|
|
"Generate a blog post draft based on market data, price trends, hype cycle position, and recent news. Saved to blog_drafts table for review.",
|
|
{
|
|
topic: z.enum(["hype_cycle", "price_trend", "new_product", "comparison", "tutorial"]),
|
|
technology: z.string().optional().describe("Technology to focus on, e.g. '800G OSFP', 'DWDM', '400G'"),
|
|
target_audience: z.enum(["sales", "technical", "customer", "seo"]).default("technical"),
|
|
},
|
|
async ({ topic, technology, target_audience }) => {
|
|
// Gather data for the blog post
|
|
const data: Record<string, unknown> = { topic, technology, target_audience };
|
|
|
|
// Get relevant transceivers
|
|
if (technology) {
|
|
const txResult = await pool.query(
|
|
`SELECT t.standard_name, t.form_factor, t.speed, t.reach_label,
|
|
t.fiber_type, t.category,
|
|
(SELECT MIN(po.price) FROM price_observations po
|
|
WHERE po.transceiver_id = t.id
|
|
AND po.time > NOW() - INTERVAL '7 days') as min_price,
|
|
(SELECT MAX(po.price) FROM price_observations po
|
|
WHERE po.transceiver_id = t.id
|
|
AND po.time > NOW() - INTERVAL '7 days') as max_price
|
|
FROM transceivers t
|
|
WHERE t.standard_name ILIKE $1 OR t.speed ILIKE $1
|
|
LIMIT 10`,
|
|
[`%${technology}%`]
|
|
);
|
|
data.transceivers = txResult.rows;
|
|
}
|
|
|
|
// Get recent news
|
|
const newsResult = await pool.query(
|
|
`SELECT title, source, published_at, summary
|
|
FROM news_articles
|
|
WHERE ($1 IS NULL OR title ILIKE $1 OR summary ILIKE $1)
|
|
ORDER BY relevance_score DESC, published_at DESC
|
|
LIMIT 5`,
|
|
[technology ? `%${technology}%` : null]
|
|
);
|
|
data.recent_news = newsResult.rows;
|
|
|
|
// Get price trends
|
|
const priceResult = await pool.query(
|
|
`SELECT t.standard_name, t.speed,
|
|
AVG(po.price) as avg_price,
|
|
MIN(po.price) as min_price,
|
|
COUNT(DISTINCT po.source_vendor_id) as vendor_count
|
|
FROM price_observations po
|
|
JOIN transceivers t ON t.id = po.transceiver_id
|
|
WHERE ($1 IS NULL OR t.standard_name ILIKE $1 OR t.speed ILIKE $1)
|
|
AND po.time > NOW() - INTERVAL '30 days'
|
|
GROUP BY t.id, t.standard_name, t.speed
|
|
ORDER BY vendor_count DESC
|
|
LIMIT 10`,
|
|
[technology ? `%${technology}%` : null]
|
|
);
|
|
data.price_trends = priceResult.rows;
|
|
|
|
// Generate blog outline based on topic and audience
|
|
const outlines: Record<string, Record<string, string[]>> = {
|
|
hype_cycle: {
|
|
sales: [
|
|
`## Where is ${technology || "The Market"} on the Hype Cycle?`,
|
|
"## What This Means for Your Customers",
|
|
"## When to Buy: Timing the Market",
|
|
"## Flexoptix Recommendation",
|
|
],
|
|
technical: [
|
|
`## ${technology || "Technology"} Market Analysis — Norton-Bass Diffusion Model`,
|
|
"## Current Phase: Technical Readiness",
|
|
"## Vendor Ecosystem Status",
|
|
"## Price Trajectory & ASP Forecast",
|
|
"## Deployment Considerations",
|
|
],
|
|
customer: [
|
|
`## Is ${technology || "This Technology"} Right for You?`,
|
|
"## Cost vs. Performance Analysis",
|
|
"## Compatibility & Migration Path",
|
|
"## When Will Prices Drop?",
|
|
],
|
|
seo: [
|
|
`## ${technology || "Optical Transceiver"} Market 2026: Complete Guide`,
|
|
"## Best Vendors & Pricing Comparison",
|
|
"## Compatibility Guide",
|
|
"## FAQ",
|
|
],
|
|
},
|
|
price_trend: {
|
|
sales: ["## Price Alert", "## Competitor Pricing Analysis", "## Sales Opportunity"],
|
|
technical: ["## Price Trend Analysis", "## ASP History", "## Market Drivers"],
|
|
customer: ["## How Much Should You Pay?", "## Price Forecast", "## When to Buy"],
|
|
seo: ["## Price Guide 2026", "## Best Deals", "## Comparison Table"],
|
|
},
|
|
comparison: {
|
|
sales: ["## Why Flexoptix Beats the Competition", "## Price Advantage", "## Quality & Compatibility"],
|
|
technical: ["## Vendor Comparison", "## Spec Analysis", "## Performance Benchmarks"],
|
|
customer: ["## OEM vs. Compatible: The Facts", "## Risk Analysis", "## Cost Savings"],
|
|
seo: ["## Best Transceiver Vendors 2026", "## Comparison Table", "## Reviews"],
|
|
},
|
|
tutorial: {
|
|
technical: ["## Prerequisites", "## Step-by-Step Configuration", "## Troubleshooting", "## Verification"],
|
|
customer: ["## Getting Started", "## Installation Guide", "## Tips & Tricks"],
|
|
sales: ["## Product Overview", "## Use Cases", "## Getting Support"],
|
|
seo: ["## How To Guide", "## Step-by-Step", "## FAQ"],
|
|
},
|
|
new_product: {
|
|
sales: ["## New Product Alert", "## What's New", "## Pricing & Availability"],
|
|
technical: ["## Technical Specs", "## Compatibility Matrix", "## Performance Data"],
|
|
customer: ["## What You Get", "## Why You Need It", "## How to Order"],
|
|
seo: ["## Product Announcement", "## Specs & Features", "## Where to Buy"],
|
|
},
|
|
};
|
|
|
|
const outline = outlines[topic]?.[target_audience] || [];
|
|
|
|
// Build draft content
|
|
const draft = {
|
|
title: `${technology || "Optical Transceiver"} ${topic.replace(/_/g, " ")} — ${new Date().getFullYear()} Analysis`,
|
|
topic,
|
|
technology,
|
|
target_audience,
|
|
outline,
|
|
data_points: data,
|
|
generation_note: "This is a data-driven draft. Review and enrich with specific product details before publishing.",
|
|
generated_at: new Date().toISOString(),
|
|
status: "draft",
|
|
};
|
|
|
|
// Save to blog_drafts table
|
|
await pool.query(
|
|
`INSERT INTO blog_drafts (title, topic, technology, target_audience, outline, draft_content, status)
|
|
VALUES ($1, $2, $3, $4, $5, $6, 'draft')
|
|
ON CONFLICT DO NOTHING`,
|
|
[
|
|
draft.title,
|
|
topic,
|
|
technology || null,
|
|
target_audience,
|
|
JSON.stringify(outline),
|
|
JSON.stringify(draft),
|
|
]
|
|
);
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify({
|
|
draft,
|
|
saved_to_database: true,
|
|
next_steps: [
|
|
"Review the outline and data points",
|
|
"Enrich with specific product examples from search_transceivers",
|
|
"Add competitor pricing from compare_prices",
|
|
"Include current news context from get_market_news",
|
|
"Submit to content team for writing/editing",
|
|
],
|
|
}, null, 2),
|
|
}],
|
|
};
|
|
}
|
|
);
|
|
}
|