#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { TelemetryService } from './telemetry-service.js';
import { UsageLimits, UsageData, UsageSummary, UsageWarnings, UsageTrend, SessionAnalytics, ToolUsageBreakdown } from './types.js';
class ClaudeTelemetryServer {
private server: Server;
private telemetryService: TelemetryService;
constructor() {
this.server = new Server(
{
name: 'claude-telemetry',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.telemetryService = new TelemetryService();
this.setupHandlers();
}
private setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// Core Usage Tools
{
name: 'get_current_session_usage',
description: 'Get usage metrics for the current Claude Code session (tokens, cost, activity)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'get_today_usage',
description: 'Get total usage metrics for today (tokens, cost, sessions, activity)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'get_week_usage',
description: 'Get total usage metrics for the current week (Monday-Sunday)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'get_usage_summary',
description: 'Get comprehensive usage summary for session, today, and this week in one call',
inputSchema: {
type: 'object',
properties: {},
},
},
// Limit Management Tools
{
name: 'check_usage_limits',
description: 'Check current usage against specified limits and get warnings',
inputSchema: {
type: 'object',
properties: {
daily_token_limit: {
type: 'number',
description: 'Daily token limit to check against',
},
weekly_token_limit: {
type: 'number',
description: 'Weekly token limit to check against',
},
session_token_limit: {
type: 'number',
description: 'Session token limit to check against',
},
daily_cost_limit: {
type: 'number',
description: 'Daily cost limit in USD to check against',
},
weekly_cost_limit: {
type: 'number',
description: 'Weekly cost limit in USD to check against',
},
session_cost_limit: {
type: 'number',
description: 'Session cost limit in USD to check against',
},
},
},
},
{
name: 'get_usage_warnings',
description: 'Get usage warnings with default thresholds (80% and 90% of typical limits)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'estimate_remaining_capacity',
description: 'Estimate remaining usage capacity for specified daily/weekly limits',
inputSchema: {
type: 'object',
properties: {
daily_token_limit: {
type: 'number',
description: 'Daily token limit for capacity estimation',
},
weekly_token_limit: {
type: 'number',
description: 'Weekly token limit for capacity estimation',
},
},
required: [],
},
},
// Analytics & Insights Tools
{
name: 'get_usage_trends',
description: 'Get usage trends over time to identify patterns',
inputSchema: {
type: 'object',
properties: {
days_back: {
type: 'number',
description: 'Number of days to look back (default: 7)',
default: 7,
},
},
},
},
{
name: 'get_session_analytics',
description: 'Get analytics about session patterns (averages, totals, productivity metrics)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'get_tool_usage_breakdown',
description: 'Get breakdown of tool usage and edit decisions',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'compare_usage_periods',
description: 'Compare usage between different time periods',
inputSchema: {
type: 'object',
properties: {
period1_days: {
type: 'number',
description: 'Days back for first period (default: 7)',
default: 7,
},
period2_days: {
type: 'number',
description: 'Days back for second period (default: 14)',
default: 14,
},
},
},
},
{
name: 'get_telemetry_health',
description: 'Check if telemetry system is running and accessible',
inputSchema: {
type: 'object',
properties: {},
},
},
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case 'get_current_session_usage': {
const usage = await this.telemetryService.getCurrentSessionUsage();
return {
content: [
{
type: 'text',
text: this.formatUsageData('Current Session', usage),
},
],
};
}
case 'get_today_usage': {
const usage = await this.telemetryService.getTodayUsage();
return {
content: [
{
type: 'text',
text: this.formatUsageData('Today', usage),
},
],
};
}
case 'get_week_usage': {
const usage = await this.telemetryService.getWeekUsage();
return {
content: [
{
type: 'text',
text: this.formatUsageData('This Week', usage),
},
],
};
}
case 'get_usage_summary': {
const summary = await this.telemetryService.getUsageSummary();
return {
content: [
{
type: 'text',
text: this.formatUsageSummary(summary),
},
],
};
}
case 'check_usage_limits': {
const limits: UsageLimits = {
dailyTokenLimit: typeof args?.daily_token_limit === 'number' ? args.daily_token_limit : undefined,
weeklyTokenLimit: typeof args?.weekly_token_limit === 'number' ? args.weekly_token_limit : undefined,
sessionTokenLimit: typeof args?.session_token_limit === 'number' ? args.session_token_limit : undefined,
dailyCostLimit: typeof args?.daily_cost_limit === 'number' ? args.daily_cost_limit : undefined,
weeklyCostLimit: typeof args?.weekly_cost_limit === 'number' ? args.weekly_cost_limit : undefined,
sessionCostLimit: typeof args?.session_cost_limit === 'number' ? args.session_cost_limit : undefined,
};
const warnings = await this.telemetryService.checkUsageLimits(limits);
return {
content: [
{
type: 'text',
text: this.formatUsageWarnings(warnings),
},
],
};
}
case 'get_usage_warnings': {
// Use default thresholds
const defaultLimits: UsageLimits = {
dailyTokenLimit: 100000, // Example default
weeklyTokenLimit: 500000, // Example default
sessionTokenLimit: 50000, // Example default
};
const warnings = await this.telemetryService.checkUsageLimits(defaultLimits);
return {
content: [
{
type: 'text',
text: this.formatUsageWarnings(warnings, true),
},
],
};
}
case 'estimate_remaining_capacity': {
const summary = await this.telemetryService.getUsageSummary();
const dailyLimit = typeof args?.daily_token_limit === 'number' ? args.daily_token_limit : undefined;
const weeklyLimit = typeof args?.weekly_token_limit === 'number' ? args.weekly_token_limit : undefined;
let result = '## Remaining Capacity Estimation\n\n';
if (dailyLimit) {
const remaining = Math.max(0, dailyLimit - summary.today.tokens);
const percentage = (remaining / dailyLimit) * 100;
result += `**Daily**: ${remaining.toLocaleString()} tokens remaining (${percentage.toFixed(1)}%)\n`;
}
if (weeklyLimit) {
const remaining = Math.max(0, weeklyLimit - summary.thisWeek.tokens);
const percentage = (remaining / weeklyLimit) * 100;
result += `**Weekly**: ${remaining.toLocaleString()} tokens remaining (${percentage.toFixed(1)}%)\n`;
}
if (!dailyLimit && !weeklyLimit) {
result += 'Please specify daily_token_limit and/or weekly_token_limit for capacity estimation.';
}
return {
content: [
{
type: 'text',
text: result,
},
],
};
}
case 'get_usage_trends': {
const daysBack = typeof args?.days_back === 'number' ? args.days_back : 7;
const trends = await this.telemetryService.getUsageTrends(daysBack);
return {
content: [
{
type: 'text',
text: this.formatUsageTrends(trends, daysBack),
},
],
};
}
case 'get_session_analytics': {
const analytics = await this.telemetryService.getSessionAnalytics();
return {
content: [
{
type: 'text',
text: this.formatSessionAnalytics(analytics),
},
],
};
}
case 'get_tool_usage_breakdown': {
const breakdown = await this.telemetryService.getToolUsageBreakdown();
return {
content: [
{
type: 'text',
text: this.formatToolUsageBreakdown(breakdown),
},
],
};
}
case 'compare_usage_periods': {
const period1Days = typeof args?.period1_days === 'number' ? args.period1_days : 7;
const period2Days = typeof args?.period2_days === 'number' ? args.period2_days : 14;
const [trends1, trends2] = await Promise.all([
this.telemetryService.getUsageTrends(period1Days),
this.telemetryService.getUsageTrends(period2Days)
]);
return {
content: [
{
type: 'text',
text: this.formatPeriodComparison(trends1, trends2, period1Days, period2Days),
},
],
};
}
case 'get_telemetry_health': {
const isHealthy = await this.telemetryService.isHealthy();
return {
content: [
{
type: 'text',
text: `## Telemetry System Health\n\n**Status**: ${isHealthy ? '✅ Healthy' : '❌ Unavailable'}\n\n${isHealthy ? 'Prometheus is accessible and telemetry data is being collected.' : 'Telemetry system is not responding. Check if services are running: `docker compose ps`'}`,
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
content: [
{
type: 'text',
text: `Error executing ${request.params.name}: ${errorMessage}`,
},
],
};
}
});
}
private formatUsageData(period: string, usage: UsageData): string {
return `## ${period} Usage\n\n` +
`**Tokens**: ${usage.tokens.toLocaleString()}\n` +
`**Cost**: $${usage.cost.toFixed(4)}\n` +
`**Sessions**: ${usage.sessions}\n` +
`**Active Time**: ${Math.round(usage.activeTime / 60)}m ${Math.round(usage.activeTime % 60)}s\n` +
`**Lines of Code**: ${usage.linesOfCode.toLocaleString()}\n` +
`**Commits**: ${usage.commits}\n` +
`**Edit Decisions**: ${usage.editDecisions}`;
}
private formatUsageSummary(summary: UsageSummary): string {
let result = '# Usage Summary\n\n';
result += this.formatUsageData('Current Session', summary.currentSession) + '\n\n';
result += this.formatUsageData('Today', summary.today) + '\n\n';
result += this.formatUsageData('This Week', summary.thisWeek) + '\n\n';
result += `**Period**: ${new Date(summary.period.start).toLocaleDateString()} - ${new Date(summary.period.end).toLocaleDateString()}`;
return result;
}
private formatUsageWarnings(warnings: UsageWarnings, isDefault: boolean = false): string {
let result = '## Usage Warnings\n\n';
if (warnings.warnings.length === 0) {
result += '✅ No usage warnings - you\'re within safe limits\n\n';
} else {
result += '⚠️ **Warnings**:\n';
warnings.warnings.forEach((warning: string) => {
result += `- ${warning}\n`;
});
result += '\n';
}
if (isDefault) {
result += '_Note: Using default limit thresholds. Use check_usage_limits with custom limits for precise tracking._\n\n';
}
result += '**Current Usage**:\n';
result += `- Tokens: ${warnings.usage.current.tokens.toLocaleString()}\n`;
result += `- Cost: $${warnings.usage.current.cost.toFixed(4)}\n`;
result += `- Sessions: ${warnings.usage.current.sessions}\n`;
return result;
}
private formatUsageTrends(trends: UsageTrend[], daysBack: number): string {
let result = `## Usage Trends (${daysBack} days)\n\n`;
if (trends.length === 0) {
return result + 'No trend data available.';
}
result += '| Time | Tokens | Cost | Sessions |\n';
result += '|------|--------|------|---------|\n';
trends.slice(-10).forEach(trend => {
const time = new Date(trend.timestamp).toLocaleString();
result += `| ${time} | ${trend.tokens.toLocaleString()} | $${trend.cost.toFixed(3)} | ${trend.sessions} |\n`;
});
return result;
}
private formatSessionAnalytics(analytics: SessionAnalytics): string {
return `## Session Analytics\n\n` +
`**Total Sessions**: ${analytics.totalSessions}\n` +
`**Average Tokens/Session**: ${Math.round(analytics.averageTokensPerSession).toLocaleString()}\n` +
`**Average Active Time/Session**: ${Math.round(analytics.averageActiveTimePerSession / 60)}m\n` +
`**Average Cost/Session**: $${analytics.averageCostPerSession.toFixed(4)}\n\n` +
`**Peak Session**:\n` +
`- Tokens: ${analytics.longestSession.tokens.toLocaleString()}\n` +
`- Active Time: ${Math.round(analytics.longestSession.activeTime / 60)}m\n\n` +
`**Most Productive Session**:\n` +
`- Lines of Code: ${analytics.mostProductiveSession.linesOfCode.toLocaleString()}\n` +
`- Commits: ${analytics.mostProductiveSession.commits}`;
}
private formatToolUsageBreakdown(breakdown: ToolUsageBreakdown): string {
return `## Tool Usage Breakdown\n\n` +
`**Total Edit Decisions**: ${breakdown.totalEditDecisions.toLocaleString()}\n` +
`**Average Decisions/Session**: ${Math.round(breakdown.averageDecisionsPerSession)}\n` +
`**Peak Decision Hour**: ${breakdown.peakDecisionHour}:00`;
}
private formatPeriodComparison(trends1: UsageTrend[], trends2: UsageTrend[], period1: number, period2: number): string {
const total1 = trends1.reduce((sum, t) => sum + t.tokens, 0);
const total2 = trends2.reduce((sum, t) => sum + t.tokens, 0);
const change = total1 - total2;
const changePercent = total2 > 0 ? ((change / total2) * 100) : 0;
return `## Usage Comparison\n\n` +
`**Last ${period1} days**: ${total1.toLocaleString()} tokens\n` +
`**Previous ${period2} days**: ${total2.toLocaleString()} tokens\n` +
`**Change**: ${change > 0 ? '+' : ''}${change.toLocaleString()} tokens (${changePercent > 0 ? '+' : ''}${changePercent.toFixed(1)}%)`;
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Claude Telemetry MCP server running on stdio');
}
}
const server = new ClaudeTelemetryServer();
server.run().catch(console.error);