SolarWinds Logs MCP Server

import { SolarWindsApiClient } from '../api-client.js'; import { generateHistogram, generateAsciiChart } from '../utils/histogram.js'; import { HistogramOptions, SolarWindsSearchParams } from '../utils/types.js'; /** * Visualize SolarWinds Observability logs as a histogram * @param apiClient SolarWinds Observability API client * @param args Visualization parameters * @returns Histogram visualization as formatted text or JSON for Claude visualization */ export async function visualizeLogs( apiClient: SolarWindsApiClient, args: Record<string, any> ): Promise<string> { try { // Get current date/time const now = new Date(); const oneDayAgo = new Date(now); oneDayAgo.setDate(oneDayAgo.getDate() - 1); // Set up search parameters const searchParams: SolarWindsSearchParams = { filter: args.filter, entityId: args.entityId, group: args.group, pageSize: args.pageSize || 1000, direction: 'backward', // Always use backward for visualization to get oldest to newest // Histograms always need a time range to be meaningful startTime: args.startTime !== undefined ? args.startTime : oneDayAgo.toISOString(), endTime: args.endTime !== undefined ? args.endTime : now.toISOString() }; // Set up histogram options const histogramOptions: HistogramOptions = { interval: (args.interval as 'minute' | 'hour' | 'day') || 'hour', useUtc: args.use_utc || false, }; // Perform the search const response = await apiClient.searchEvents(searchParams); // Convert the logs to a format compatible with the histogram generator const events = response.logs.map(log => ({ id: log.id, received_at: log.time, display_received_at: log.time, hostname: log.hostname, program: log.program, message: log.message, // Add other required fields with placeholder values generated_at: log.time, source_name: log.hostname, source_id: 0, source_ip: '', facility: '', severity: log.severity })); // Generate histogram data const histogramData = generateHistogram(events, histogramOptions); // Check if the user wants JSON output for Claude visualization if (args.format === 'json') { // Format the data for Claude visualization const timeRanges = histogramData.data.map(point => point.time); const counts = histogramData.data.map(point => point.count); const claudeVisualizationData = { timeRanges, counts, total: histogramData.total, queryParams: { query: searchParams.filter || '', startTime: searchParams.startTime, endTime: searchParams.endTime } }; return JSON.stringify(claudeVisualizationData, null, 2); } // Generate ASCII chart for text output const chart = generateAsciiChart(histogramData); // Format the response let result = ''; // Add search parameters result += 'Visualization Parameters:\n'; if (searchParams.filter) result += `Query: ${searchParams.filter}\n`; if (searchParams.entityId) result += `Entity ID: ${searchParams.entityId}\n`; if (searchParams.group) result += `Group: ${searchParams.group}\n`; result += `Start Time: ${searchParams.startTime}\n`; result += `End Time: ${searchParams.endTime}\n`; result += `Interval: ${histogramOptions.interval}\n`; result += `Timezone: ${histogramOptions.useUtc ? 'UTC' : 'Local'}\n`; result += `Page Size: ${searchParams.pageSize}\n`; result += '\n'; // Add search metadata result += `Analyzed ${response.logs.length} logs\n`; if (response.pageInfo.nextPage) result += 'Note: More logs available. Results may be incomplete.\n'; result += '\n'; // Add chart result += chart; // Add note about JSON format result += '\n\nTip: Add "format": "json" to get data in a format that Claude can visualize as a chart.\n'; return result; } catch (error) { throw error; } }