Skip to main content
Glama

New Relic MCP Server

by cloudbring
server.ts11.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, type Tool, } from '@modelcontextprotocol/sdk/types.js'; import { NewRelicClient } from './client/newrelic-client'; import { AlertTool } from './tools/alert'; import { ApmTool } from './tools/apm'; import { EntityTool } from './tools/entity'; import { NerdGraphTool } from './tools/nerdgraph'; import { NrqlTool } from './tools/nrql'; import { RestApmTool } from './tools/rest/apm'; import { RestDeploymentsTool } from './tools/rest/deployments'; import { RestMetricsTool } from './tools/rest/metrics'; import { SyntheticsTool } from './tools/synthetics'; export class NewRelicMCPServer { private server: Server; private client: NewRelicClient; private tools: Map<string, Tool>; private defaultAccountId?: string; constructor(client?: NewRelicClient) { this.defaultAccountId = process.env.NEW_RELIC_ACCOUNT_ID; if (client) { this.client = client; } else { // Best practice for Smithery tool discovery: do not force auth at startup // Allow listing tools without credentials; validate when tools are invoked const apiKey = process.env.NEW_RELIC_API_KEY || ''; this.client = new NewRelicClient(apiKey, this.defaultAccountId); } this.server = new Server( { name: 'newrelic-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.tools = new Map(); this.registerTools(); this.setupHandlers(); } private registerTools(): void { const nrqlTool = new NrqlTool(this.client); const apmTool = new ApmTool(this.client); const entityTool = new EntityTool(this.client); const alertTool = new AlertTool(this.client); const syntheticsTool = new SyntheticsTool(this.client); const nerdGraphTool = new NerdGraphTool(this.client); const restDeployments = new RestDeploymentsTool(); const restApm = new RestApmTool(); const restMetrics = new RestMetricsTool(); // Register all tools const tools = [ nrqlTool.getToolDefinition(), apmTool.getListApplicationsTool(), entityTool.getSearchTool(), entityTool.getDetailsTool(), alertTool.getPoliciesTool(), alertTool.getIncidentsTool(), alertTool.getAcknowledgeTool(), syntheticsTool.getListMonitorsTool(), syntheticsTool.getCreateMonitorTool(), nerdGraphTool.getQueryTool(), // REST v2 tools restDeployments.getCreateTool(), restDeployments.getListTool(), restDeployments.getDeleteTool(), restApm.getListApplicationsTool(), restMetrics.getListMetricNamesTool(), restMetrics.getMetricDataTool(), restMetrics.getListApplicationHostsTool(), { name: 'get_account_details', description: 'Get New Relic account details', inputSchema: { type: 'object' as const, properties: { target_account_id: { type: 'string' as const, description: 'Optional account ID to get details for', }, }, }, }, ]; tools.forEach((tool) => { this.tools.set(tool.name, tool); }); } private setupHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: Array.from(this.tools.values()), })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const tool = this.tools.get(request.params.name); if (!tool) { throw new McpError(ErrorCode.MethodNotFound, `Tool ${request.params.name} not found`); } try { const result = await this.executeTool(request.params.name, request.params.arguments || {}); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { const message = error instanceof Error ? error.message : 'Tool execution failed'; throw new McpError(ErrorCode.InternalError, message); } }); } async start(): Promise<void> { // Only validate if credentials were provided; otherwise allow startup for tool discovery if (process.env.NEW_RELIC_API_KEY) { const isValid = await this.client.validateCredentials(); if (!isValid) { throw new Error('Invalid New Relic API credentials'); } } const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('New Relic MCP Server started'); } async executeTool( name: string, args: { target_account_id?: string; account_id?: string; [key: string]: unknown } ): Promise<unknown> { const accountId: string | undefined = args.target_account_id || args.account_id || this.defaultAccountId; if (!accountId && this.requiresAccountId(name)) { throw new Error('Account ID must be provided'); } switch (name) { case 'run_nrql_query': return await new NrqlTool(this.client).execute({ ...args, target_account_id: accountId, }); case 'list_apm_applications': return await new ApmTool(this.client).execute({ ...args, target_account_id: accountId, }); case 'create_deployment': return await new RestDeploymentsTool().create( args as Parameters<RestDeploymentsTool['create']>[0] ); case 'list_deployments_rest': return await new RestDeploymentsTool().list( args as Parameters<RestDeploymentsTool['list']>[0] ); case 'delete_deployment': return await new RestDeploymentsTool().delete( args as Parameters<RestDeploymentsTool['delete']>[0] ); case 'list_apm_applications_rest': return await new RestApmTool().listApplications( args as Parameters<RestApmTool['listApplications']>[0] ); case 'list_metric_names_for_host': return await new RestMetricsTool().listMetricNames( args as Parameters<RestMetricsTool['listMetricNames']>[0] ); case 'get_metric_data_for_host': return await new RestMetricsTool().getMetricData( args as Parameters<RestMetricsTool['getMetricData']>[0] ); case 'list_application_hosts': return await new RestMetricsTool().listApplicationHosts( args as Parameters<RestMetricsTool['listApplicationHosts']>[0] ); case 'get_account_details': return await this.client.getAccountDetails(accountId); case 'list_alert_policies': return await new AlertTool(this.client).listAlertPolicies({ ...args, target_account_id: accountId, }); case 'list_open_incidents': return await new AlertTool(this.client).listOpenIncidents({ ...args, target_account_id: accountId, }); case 'acknowledge_incident': { const { incident_id, comment } = args as Record<string, unknown>; if (typeof incident_id !== 'string' || incident_id.trim() === '') { throw new Error('acknowledge_incident: "incident_id" (non-empty string) is required'); } if (comment !== undefined && typeof comment !== 'string') { throw new Error('acknowledge_incident: "comment" must be a string when provided'); } return await new AlertTool(this.client).acknowledgeIncident({ incident_id, comment: comment as string | undefined, }); } case 'search_entities': { const { query, entity_types } = args as Record<string, unknown>; if (typeof query !== 'string' || query.trim() === '') { throw new Error('search_entities: "query" (non-empty string) is required'); } let types: string[] | undefined; if (entity_types !== undefined) { if (!Array.isArray(entity_types)) { throw new Error('search_entities: "entity_types" must be an array of strings'); } types = (entity_types as unknown[]).filter((t): t is string => typeof t === 'string'); } return await new EntityTool(this.client).searchEntities({ query, entity_types: types, target_account_id: accountId, }); } case 'get_entity_details': { const { entity_guid } = args as Record<string, unknown>; if (typeof entity_guid !== 'string' || entity_guid.trim() === '') { throw new Error('get_entity_details: "entity_guid" (non-empty string) is required'); } return await new EntityTool(this.client).getEntityDetails({ entity_guid }); } case 'list_synthetics_monitors': return await new SyntheticsTool(this.client).listSyntheticsMonitors({ ...args, target_account_id: accountId, }); case 'create_browser_monitor': { const { name, url, frequency, locations } = args as Record<string, unknown>; if (typeof name !== 'string' || name.trim() === '') { throw new Error('create_browser_monitor: "name" (non-empty string) is required'); } if (typeof url !== 'string' || url.trim() === '') { throw new Error('create_browser_monitor: "url" (non-empty string) is required'); } if (typeof frequency !== 'number' || !Number.isFinite(frequency) || frequency <= 0) { throw new Error('create_browser_monitor: "frequency" (positive number) is required'); } if ( !Array.isArray(locations) || (locations as unknown[]).some((l) => typeof l !== 'string') ) { throw new Error('create_browser_monitor: "locations" must be an array of strings'); } return await new SyntheticsTool(this.client).createBrowserMonitor({ name, url, frequency, locations: locations as string[], target_account_id: accountId, }); } case 'run_nerdgraph_query': return await new NerdGraphTool(this.client).execute(args); default: { const tool = this.tools.get(name); if (!tool) { throw new Error(`Tool ${name} not found`); } throw new Error(`Tool handler for ${name} not implemented`); } } } private requiresAccountId(toolName: string): boolean { const accountRequiredTools = [ 'run_nrql_query', 'list_apm_applications', 'search_entities', 'get_account_details', 'list_alert_policies', 'list_open_incidents', 'list_synthetics_monitors', 'create_browser_monitor', ]; return accountRequiredTools.includes(toolName); } getMetadata() { return { name: 'newrelic-mcp', version: '1.0.0', description: 'MCP server for New Relic observability platform integration', }; } getRegisteredTools(): string[] { return Array.from(this.tools.keys()); } getTool(name: string): Tool | undefined { return this.tools.get(name); } getDefaultAccountId(): string | undefined { return this.defaultAccountId; } } // Main entry point if (require.main === module) { const server = new NewRelicMCPServer(); server.start().catch(console.error); }

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/cloudbring/newrelic-mcp'

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