diff --git a/src/components/AnalyticsTab.tsx b/src/components/AnalyticsTab.tsx index 9086b1e..11e7096 100644 --- a/src/components/AnalyticsTab.tsx +++ b/src/components/AnalyticsTab.tsx @@ -1,15 +1,29 @@ import React, { useState, useEffect } from 'react'; -import { BarChart3, Activity, Clock, Cpu, Server, CheckCircle, Zap, RefreshCw, Layers, Compass, ArrowUpRight } from 'lucide-react'; -import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar, PieChart, Pie, Cell } from 'recharts'; +import { BarChart3, Activity, Clock, Zap, Server, CheckCircle, XCircle, RefreshCw, Layers, ArrowUpRight, TrendingUp, Gauge } from 'lucide-react'; +import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar, PieChart, Pie, Cell, CartesianGrid } from 'recharts'; import { ServerStats } from '../types'; interface AnalyticsTabProps { stats: ServerStats | null; } +const CATEGORY_COLORS: Record = { + web: '#4f46e5', + code: '#0ea5e9', + data: '#10b981', + media: '#f59e0b', + memory: '#a855f7', + network: '#ec4899', + dev: '#6366f1', + custom: '#94a3b8', +}; + +const PIE_COLORS = ['#4f46e5', '#0ea5e9', '#10b981', '#f59e0b', '#a855f7', '#ec4899', '#6366f1', '#94a3b8']; + export const AnalyticsTab: React.FC = ({ stats }) => { const [routerStats, setRouterStats] = useState(null); const [loading, setLoading] = useState(false); + const [lastUpdated, setLastUpdated] = useState(''); const fetchRouterStats = async () => { try { @@ -18,6 +32,7 @@ export const AnalyticsTab: React.FC = ({ stats }) => { if (res.ok) { const data = await res.json(); setRouterStats(data); + setLastUpdated(new Date().toLocaleTimeString()); } } catch (err) { console.error('Failed to fetch router stats:', err); @@ -32,179 +47,224 @@ export const AnalyticsTab: React.FC = ({ stats }) => { return () => clearInterval(interval); }, []); - const totalToolCalls = routerStats?.totalToolCalls || stats?.mcpRequests || 142; - const avgLatency = routerStats?.avgLatencyMs || stats?.avgLatencyMs || 4; + const totalCalls = routerStats?.totalRoutedCalls ?? 0; + const totalSuccess = routerStats?.totalSuccess ?? 0; + const totalErrors = routerStats?.totalErrors ?? 0; + const successRate = totalCalls > 0 ? Math.round((totalSuccess / totalCalls) * 100) : 0; + const errorRate = totalCalls > 0 ? Math.round((totalErrors / totalCalls) * 100) : 0; + const avgLatency = routerStats?.avgLatencyMs ?? stats?.avgLatencyMs ?? 0; - const topToolsData = routerStats?.topTools && routerStats.topTools.length > 0 - ? routerStats.topTools.map((t: any) => ({ name: t.toolName, calls: t.callCount })) - : [ - { name: 'verify_and_refine_loop', calls: 38 }, - { name: 'diff_patch_validator', calls: 31 }, - { name: 'tree_of_thought_brancher', calls: 27 }, - { name: 'code_ast_inspector', calls: 24 }, - { name: 'web_scrape_markdown', calls: 19 }, - ]; + const toolMetrics = routerStats?.toolMetrics || []; + const topTools = toolMetrics.slice(0, 8).map((t: any) => ({ name: t.toolName, calls: t.totalCalls })); + const categoryData = (routerStats?.categoryBreakdown || []).map((c: any) => ({ name: c.category, value: c.totalCalls })); + const serverData = (routerStats?.serverBreakdown || []).slice(0, 10); + const recentExecutions = routerStats?.recentExecutions || []; + const trafficData = (routerStats?.trafficByHour || []).map((b: any) => ({ + hour: b.hour.slice(11, 16), + calls: b.count, + success: b.success, + errors: b.errors, + latency: b.avgLatencyMs, + })); - const trafficData = [ - { time: '10:00', requests: 12, mcp: 8, rest: 4, latency: 5 }, - { time: '10:05', requests: 19, mcp: 14, rest: 5, latency: 4 }, - { time: '10:10', requests: 34, mcp: 28, rest: 6, latency: 6 }, - { time: '10:15', requests: 27, mcp: 20, rest: 7, latency: 4 }, - { time: '10:20', requests: 45, mcp: 36, rest: 9, latency: 5 }, - { time: '10:25', requests: 52, mcp: 42, rest: 10, latency: 3 }, - { time: '10:30', requests: stats?.totalRequests || 68, mcp: stats?.mcpRequests || 54, rest: stats?.restRequests || 14, latency: avgLatency }, - ]; + const fmtHour = (iso: string) => new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); + + const EmptyState = ({ message }: { message: string }) => ( +
+ +

{message}

+
+ ); return (
- {/* Header Banner */} + {/* Header */}
Live MCP Router & Telemetry Engine -

Real-Time Router Performance & Tool Audit

+

Router Performance & Tool Audit

- Monitor zero-key algorithmic tool loops, remote Road Sign dispatch latencies, and agent invocation trends across the universal MCP Source of Truth. + Real invocation traffic, latency, and success rates across {stats?.totalMCPServersCount ?? 0} registered MCP servers. {lastUpdated && Last sync {lastUpdated}}

-
- {/* Top Metrics Cards */} -
-
-
- Total MCP Requests - -
-
- {stats?.totalRequests || totalToolCalls} -
-
- JSON-RPC & SSE Protocol Traffic -
+ {/* Metric Cards */} +
+
+
Total Calls
+
{totalCalls}
+
routed tool invocations
+
+
+
Success
+
{successRate}%
+
{totalSuccess} succeeded
+
+
+
Errors
+
{errorRate}%
+
{totalErrors} failed
+
+
+
Avg Latency
+
{avgLatency}ms
+
per routed call
+
+
+
Active Tools
+
{stats?.registeredToolsCount ?? 0}
+
across active servers
+
+
+
Servers
+
{stats?.activeMCPServersCount ?? 0}/{stats?.totalMCPServersCount ?? 0}
+
active / registered
+
+
+ + {/* Traffic + Category */} +
+
+

+ + Request Volume & Latency (per hour) +

+ {trafficData.length === 0 ? : ( +
+ + + + + + + + + + + + + + + + +
+ )}
-
-
- Aggregated Tools - -
-
- {stats?.registeredToolsCount || 27} -
-
- Across {stats?.activeMCPServersCount || 4} Servers & Road Signs -
-
- -
-
- Router Dispatch Latency - -
-
- {avgLatency} ms -
-
- Sub-millisecond local in-memory routing -
-
- -
-
- Server Memory - -
-
- {stats?.memoryUsageMb || 34} MB -
-
- Node.js Runtime Container Heap +
+

+ + Traffic by Category +

+ {categoryData.length === 0 ? : ( +
+ + + + {categoryData.map((_: any, i: number) => )} + + + + +
+ )} +
+ {categoryData.map((c: any, i: number) => ( + + + {c.name} + + ))}
- {/* Traffic Charts */} -
- {/* Request Time Series */} -
-

- - MCP Request Volume & Routing Latency Trend -

- -
- - - - - - - - - - - - - - -
-
- - {/* Top Tools Bar Chart */} + {/* Top Tools + Server Breakdown */} +
-

+

Most Invoked Tools

+ {topTools.length === 0 ? : ( +
+ + + + + + + + +
+ )} +
-
- - - - - - - - -
+
+

+ + Traffic by Server +

+ {serverData.length === 0 ? : ( +
+ {serverData.map((s: any) => { + const pct = totalCalls > 0 ? (s.totalCalls / totalCalls) * 100 : 0; + return ( +
+
+ {s.serverName} + {s.totalCalls} · {s.avgLatencyMs}ms +
+
+
+
+
+ ); + })} +
+ )}
- {/* Recent Telemetry Executions */} - {routerStats?.recentExecutions && routerStats.recentExecutions.length > 0 && ( -
-

- Live Tool Call Stream Log ({routerStats.recentExecutions.length}) -

-
- {routerStats.recentExecutions.map((log: any, idx: number) => ( + {/* Live Execution Stream */} +
+

+ + Live Tool Call Stream + {recentExecutions.length > 0 && ({recentExecutions.length} recent)} +

+ {recentExecutions.length === 0 ? ( +

No executions recorded yet — traffic will stream here in real time.

+ ) : ( +
+ {recentExecutions.map((log: any, idx: number) => (
-
- - {log.toolName} +
+ + {log.toolName} + {log.serverName}
-
- {log.durationMs}ms - {new Date(log.timestamp).toLocaleTimeString()} +
+ 5000 ? 'text-amber-600' : ''}>{log.durationMs}ms + {fmtHour(log.timestamp)}
))}
-
- )} + )} +
); }; - diff --git a/src/components/MCPHubTab.tsx b/src/components/MCPHubTab.tsx index 0249ceb..a520750 100644 --- a/src/components/MCPHubTab.tsx +++ b/src/components/MCPHubTab.tsx @@ -758,7 +758,7 @@ export const MCPHubTab: React.FC = ({
Router Latency - {stats?.avgLatencyMs || 3}ms + {stats?.avgLatencyMs || 0}ms
diff --git a/src/components/OverviewTab.tsx b/src/components/OverviewTab.tsx index fda4bbc..a16c76e 100644 --- a/src/components/OverviewTab.tsx +++ b/src/components/OverviewTab.tsx @@ -101,7 +101,7 @@ export const OverviewTab: React.FC = ({ stats, setActiveTab })
- {stats?.registeredToolsCount || 19} + {stats?.registeredToolsCount || 0} Active Tools

Web, Code, Data, Media, Memory

diff --git a/src/server/mcpRegistry.ts b/src/server/mcpRegistry.ts index 1802819..fb2f254 100644 --- a/src/server/mcpRegistry.ts +++ b/src/server/mcpRegistry.ts @@ -1872,6 +1872,20 @@ class MCPRegistryManager { lastError?: string; }> = new Map(); + // Recent execution stream (ring buffer, capped) + private recentExecutions: Array<{ + toolName: string; + serverName: string; + category: string; + durationMs: number; + success: boolean; + timestamp: string; + error?: string; + }> = []; + + // Traffic bucketed by hour for time-series charting + private trafficByHour: Map = new Map(); + // Record tool execution in the MCP router stats public recordUsage(toolName: string, serverId: string, serverName: string, category: string, durationMs: number, success: boolean, error?: string) { const existing = this.toolMetrics.get(toolName) || { @@ -1895,6 +1909,26 @@ class MCPRegistryManager { existing.totalDurationMs += durationMs; existing.lastExecutedAt = new Date().toISOString(); this.toolMetrics.set(toolName, existing); + + // Recent execution stream (ring buffer, cap 100) + this.recentExecutions.unshift({ + toolName, + serverName, + category, + durationMs, + success, + timestamp: new Date().toISOString(), + error, + }); + if (this.recentExecutions.length > 100) this.recentExecutions.pop(); + + // Traffic bucketed by hour + const hour = new Date().toISOString().slice(0, 13) + ':00'; + const bucket = this.trafficByHour.get(hour) || { hour, count: 0, success: 0, errors: 0, totalLatencyMs: 0 }; + bucket.count++; + if (success) bucket.success++; else bucket.errors++; + bucket.totalLatencyMs += durationMs; + this.trafficByHour.set(hour, bucket); } // Get complete Router Usage Report @@ -1928,12 +1962,40 @@ class MCPRegistryManager { const totalDuration = list.reduce((acc, m) => acc + m.totalDurationMs, 0); const avgLatencyMs = totalRoutedCalls > 0 ? Math.round(totalDuration / totalRoutedCalls) : 0; + // Category rollup + const catMap = new Map(); + for (const m of list) { + const c = catMap.get(m.category) || { category: m.category, totalCalls: 0, successCount: 0, errorCount: 0 }; + c.totalCalls += m.totalCalls; c.successCount += m.successCount; c.errorCount += m.errorCount; + catMap.set(m.category, c); + } + + // Server rollup + const srvMap = new Map(); + for (const m of list) { + const s = srvMap.get(m.serverName) || { serverName: m.serverName, totalCalls: 0, successCount: 0, errorCount: 0, totalDurationMs: 0 }; + s.totalCalls += m.totalCalls; s.successCount += m.successCount; s.errorCount += m.errorCount; s.totalDurationMs += m.totalDurationMs; + srvMap.set(m.serverName, s); + } + + const serverBreakdown = Array.from(srvMap.values()) + .map(s => ({ ...s, avgLatencyMs: s.totalCalls > 0 ? Math.round(s.totalDurationMs / s.totalCalls) : 0 })) + .sort((a, b) => b.totalCalls - a.totalCalls); + + const trafficByHour = Array.from(this.trafficByHour.values()) + .sort((a, b) => a.hour.localeCompare(b.hour)) + .map(b => ({ ...b, avgLatencyMs: b.count > 0 ? Math.round(b.totalLatencyMs / b.count) : 0 })); + return { totalRoutedCalls, totalSuccess, totalErrors, avgLatencyMs, toolMetrics: list.sort((a, b) => b.totalCalls - a.totalCalls), + recentExecutions: this.recentExecutions.slice(0, 50), + trafficByHour, + categoryBreakdown: Array.from(catMap.values()).sort((a, b) => b.totalCalls - a.totalCalls), + serverBreakdown, }; }