Skip to main content
Glama
SailingCoder

grafana-mcp-analyzer

by SailingCoder
grafana-client.ts6.45 kB
import axios from 'axios'; import { parseCurlCommand } from './curl-parser.js'; import type { HttpRequest, HttpResponse, ExtractedData, HealthStatus } from '../types/index.js'; const DEFAULT_TIMEOUT = 30000; // 默认请求超时时间(30秒) - 避免长时间等待 /** * 执行Grafana查询 */ export async function executeQuery(request: HttpRequest | { curl: string }, baseUrl: string): Promise<HttpResponse> { try { let actualRequest: HttpRequest; // 检查是否是curl格式的查询 if ('curl' in request) { actualRequest = parseCurlCommand(request.curl); } else { actualRequest = request; } // 确保URL存在 if (!actualRequest.url) { throw new Error('缺少URL配置'); } const url = actualRequest.url.startsWith('http') ? actualRequest.url : `${baseUrl}/${actualRequest.url}`; const response = await axios({ url, method: actualRequest.method || 'POST', headers: actualRequest.headers || { 'Content-Type': 'application/json' }, data: actualRequest.data, params: actualRequest.params, timeout: actualRequest.timeout || DEFAULT_TIMEOUT, ...actualRequest.axiosConfig }); return { success: true, status: response.status, statusText: response.statusText, headers: Object.fromEntries( Object.entries(response.headers).map(([k, v]) => [k, String(v)]) ), data: response.data }; } catch (error: any) { if (error.response) { // 服务器返回了错误响应 return { success: false, status: error.response.status, statusText: error.response.statusText, headers: Object.fromEntries( Object.entries(error.response.headers || {}).map(([k, v]) => [k, String(v)]) ), data: error.response.data, error: `HTTP错误: ${error.response.status} ${error.response.statusText}` }; } else if (error.request) { // 请求已发送但未收到响应 return { success: false, error: `无响应: ${error.message || '请求超时或网络错误'}` }; } else { // 请求设置时出现问题 return { success: false, error: `请求错误: ${error.message || '未知错误'}` }; } } } /** * 从HTTP响应中提取数据 */ export function extractData(response: HttpResponse): ExtractedData { if (!response.success) { return { hasData: false, type: 'error', status: String(response.status || 'error'), timestamp: new Date().toISOString(), data: { error: response.error }, metadata: { error: response.error, status: response.status } }; } // 检测数据类型 let type = 'unknown'; let hasData = false; if (response.data) { if (response.data.results) { type = 'grafana-query'; hasData = true; } else if (response.data.series) { type = 'timeseries'; hasData = Array.isArray(response.data.series) && response.data.series.length > 0; } else if (response.data.tables) { type = 'tables'; hasData = Array.isArray(response.data.tables) && response.data.tables.length > 0; } else if (response.data.responses) { type = 'elasticsearch'; hasData = Array.isArray(response.data.responses) && response.data.responses.length > 0; } else if (Array.isArray(response.data)) { type = 'array'; hasData = response.data.length > 0; } else if (typeof response.data === 'object') { type = 'object'; hasData = Object.keys(response.data).length > 0; } else { type = typeof response.data; hasData = response.data !== null && response.data !== undefined; } } return { hasData, type, status: String(response.status || 200), timestamp: new Date().toISOString(), data: response.data, metadata: { contentType: response.headers?.['content-type'], responseSize: JSON.stringify(response.data).length } }; } /** * 检查健康状态 */ export async function checkHealth( url: string, options?: { timeout?: number; expectedStatus?: number; } ): Promise<HealthStatus> { try { const response = await axios.get(url, { timeout: options?.timeout || 5000 }); const expectedStatus = options?.expectedStatus || 200; return { status: response.status === expectedStatus ? 'healthy' : 'warning', timestamp: new Date().toISOString(), message: `状态码: ${response.status}`, details: { status: response.status, statusText: response.statusText, expectedStatus } }; } catch (error: any) { return { status: 'unhealthy', timestamp: new Date().toISOString(), message: error.code === 'ECONNABORTED' ? '健康检查超时' : error.message, details: { code: error.code, isTimeout: error.code === 'ECONNABORTED' } }; } } /** * 批量执行查询(并发执行多个查询) * * @param requests 查询请求数组 * @param baseUrl 基础URL * @param options 选项配置 * @returns 批量查询结果 */ export async function executeBatchQueries( requests: HttpRequest[], baseUrl: string = '', options: { concurrency?: number; failFast?: boolean } = {} ): Promise<HttpResponse[]> { const { concurrency = 5, failFast = false } = options; if (requests.length === 0) { return []; } // 分批执行以控制并发数 const results: HttpResponse[] = []; for (let i = 0; i < requests.length; i += concurrency) { const batch = requests.slice(i, i + concurrency); const batchPromises = batch.map(request => executeQuery(request, baseUrl)); try { const batchResults = await Promise.all(batchPromises); results.push(...batchResults); // 如果启用快速失败且有失败的请求,立即停止 if (failFast && batchResults.some(result => !result.success)) { break; } } catch (error) { // 如果整个批次失败,添加错误结果 const errorResults = batch.map(() => ({ success: false, error: `批量查询失败: ${error}`, status: undefined, data: undefined })); results.push(...errorResults); if (failFast) { break; } } } return results; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SailingCoder/grafana-mcp-analyzer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server