UNPKG

@kivanio/mcp-new-relic

Version:

MCP server for querying New Relic errors and metrics

258 lines 10.5 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; import dotenv from 'dotenv'; // Load environment variables dotenv.config(); // Configuration const API_KEY = process.env.NEW_RELIC_API_KEY; const ACCOUNT_ID = process.env.NEW_RELIC_ACCOUNT_ID; const REGION = process.env.NEW_RELIC_REGION || 'US'; // API endpoint const API_URL = REGION === 'EU' ? 'https://api.eu.newrelic.com/graphql' : 'https://api.newrelic.com/graphql'; // Create server instance const server = new Server({ name: 'mcp-new-relic', version: '1.0.0', }, { capabilities: { tools: {}, }, }); // Helper function to execute NRQL query async function executeNrqlQuery(nrqlQuery) { const graphqlQuery = { query: ` { actor { account(id: ${ACCOUNT_ID}) { nrql(query: "${nrqlQuery}") { results } } } } `, }; try { const response = await axios.post(API_URL, graphqlQuery, { headers: { 'Content-Type': 'application/json', 'API-Key': API_KEY, }, }); return response.data.data.actor.account.nrql.results || []; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`New Relic API error: ${error.response?.status} - ${error.response?.statusText}`); } throw error; } } // Format error results for display function formatErrorResults(results, limit) { if (!results || results.length === 0) { return 'No errors found! 🎉'; } // Sort by count descending const sortedResults = [...results].sort((a, b) => b.count - a.count); const displayResults = limit ? sortedResults.slice(0, limit) : sortedResults; let output = `=== New Relic Error Summary ===\n`; output += `Total unique error types: ${results.length}\n\n`; output += `Top Errors by Frequency:\n`; output += '-'.repeat(80) + '\n'; displayResults.forEach((result, index) => { const errorMessage = result.facet[0] || 'N/A'; const errorClass = result.facet[1] || 'N/A'; output += `\n${index + 1}. Error Count: ${result.count}\n`; output += ` Class: ${errorClass}\n`; output += ` Message: ${errorMessage.substring(0, 200)}${errorMessage.length > 200 ? '...' : ''}\n`; }); if (sortedResults[0]) { output += '\n' + '='.repeat(80) + '\n'; output += 'MOST FREQUENT ERROR:\n'; output += `Count: ${sortedResults[0].count}\n`; output += `Class: ${sortedResults[0].facet[1] || 'N/A'}\n`; output += `Message: ${sortedResults[0].facet[0] || 'N/A'}\n`; } return output; } // Handle tool listing server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'query_errors', description: 'Query New Relic for application errors', inputSchema: { type: 'object', properties: { timeRange: { type: 'string', description: 'Time range for the query (e.g., "1 hour ago", "4 hours ago", "24 hours ago", "7 days ago")', default: '4 hours ago', }, appName: { type: 'string', description: 'Application name pattern to filter (supports LIKE patterns)', }, limit: { type: 'number', description: 'Maximum number of error types to return', default: 20, minimum: 1, maximum: 100, }, }, }, }, { name: 'query_custom', description: 'Execute a custom NRQL query on New Relic', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The NRQL query to execute', }, }, required: ['query'], }, }, { name: 'query_metrics', description: 'Query New Relic for application performance metrics', inputSchema: { type: 'object', properties: { timeRange: { type: 'string', description: 'Time range for the query (e.g., "1 hour ago", "4 hours ago", "24 hours ago")', default: '1 hour ago', }, appName: { type: 'string', description: 'Application name pattern to filter', default: '%kivanio%', }, metrics: { type: 'array', items: { type: 'string', enum: ['response_time', 'throughput', 'error_rate', 'apdex'], }, description: 'Which metrics to query', default: ['response_time', 'throughput', 'error_rate'], }, }, }, }, ], }; }); // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'query_errors': { const timeRange = args?.timeRange || '4 hours ago'; const appName = args?.appName || '%kivanio%'; const limit = args?.limit || 20; const nrqlQuery = `SELECT count(*), latest(error.message), latest(error.class) FROM TransactionError WHERE appName LIKE '${appName}' SINCE ${timeRange} FACET error.message, error.class LIMIT ${limit}`; const results = await executeNrqlQuery(nrqlQuery); const formattedOutput = formatErrorResults(results, limit); return { content: [ { type: 'text', text: formattedOutput, }, ], }; } case 'query_custom': { if (!args?.query) { throw new McpError(ErrorCode.InvalidParams, 'Query parameter is required'); } const results = await executeNrqlQuery(args.query); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2), }, ], }; } case 'query_metrics': { const timeRange = args?.timeRange || '1 hour ago'; const appName = args?.appName || '%kivanio%'; const metrics = args?.metrics || [ 'response_time', 'throughput', 'error_rate', ]; const queries = { response_time: `SELECT average(duration) as 'Response Time (ms)' FROM Transaction WHERE appName LIKE '${appName}' SINCE ${timeRange}`, throughput: `SELECT rate(count(*), 1 minute) as 'Requests per minute' FROM Transaction WHERE appName LIKE '${appName}' SINCE ${timeRange}`, error_rate: `SELECT percentage(count(*), WHERE error IS true) as 'Error Rate (%)' FROM Transaction WHERE appName LIKE '${appName}' SINCE ${timeRange}`, apdex: `SELECT apdex(duration, 0.5) as 'Apdex Score' FROM Transaction WHERE appName LIKE '${appName}' SINCE ${timeRange}`, }; let output = `=== New Relic Performance Metrics ===\n`; output += `Application: ${appName}\n`; output += `Time Range: Since ${timeRange}\n\n`; for (const metric of metrics) { if (queries[metric]) { try { const results = await executeNrqlQuery(queries[metric]); if (results.length > 0) { output += `${metric .replace('_', ' ') .toUpperCase()}: ${JSON.stringify(results[0], null, 2)}\n\n`; } } catch (error) { output += `${metric .replace('_', ' ') .toUpperCase()}: Error fetching metric\n\n`; } } } return { content: [ { type: 'text', text: output, }, ], }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Error executing tool: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP New Relic server started'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); }); //# sourceMappingURL=index.js.map