Skip to main content
Glama

Prometheus Alertmanager MCP Server

by comqx
index.ts11.3 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // Basic configuration const DEFAULT_ALERTMANAGER_URL = "http://localhost:9093"; const DEFAULT_TIMEOUT = 10000; // 10 seconds // Create server instance const server = new McpServer({ name: "alertmanager", version: "1.0.0", }); // Helper function for Alertmanager communication async function fetchFromAlertmanager(path: string, options: RequestInit = {}): Promise<any> { const baseUrl = process.env.ALERTMANAGER_URL || DEFAULT_ALERTMANAGER_URL; const url = `${baseUrl}/api/v2/${path}`; try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT); const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`Alertmanager API error: ${response.status} ${response.statusText}`); } return await response.json(); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error fetching from Alertmanager: ${errorMessage}`); throw error; } } // Alert-related type definitions interface AlertLabel { [key: string]: string; } interface AlertAnnotation { [key: string]: string; } interface AlertStatus { state: string; silencedBy: string[]; inhibitedBy: string[]; } interface Alert { fingerprint: string; status: AlertStatus; labels: AlertLabel; annotations: AlertAnnotation; startsAt: string; endsAt: string; generatorURL: string; } interface FormattedAlert { fingerprint: string; alertname: string; severity: string; summary: string; description: string; startsAt: string; status: { state: string; silenced: boolean; inhibited: boolean; }; labels: AlertLabel; } interface Silence { id: string; status: { state: string; }; createdBy: string; comment: string; startsAt: string; endsAt: string; matchers: Array<{ name: string; value: string; isRegex: boolean; }>; } // Tool to retrieve current list of alerts server.tool( "get-alerts", { filter: z.string().optional().describe("Filtering query (e.g. alertname=~'.*CPU.*')"), silenced: z.boolean().optional().describe("Include silenced alerts"), inhibited: z.boolean().optional().describe("Include inhibited alerts"), active: z.boolean().optional().describe("Include active alerts (default: true)"), }, async ({ filter, silenced = false, inhibited = false, active = true }) => { try { // Build query parameters const params = new URLSearchParams(); if (filter) params.append("filter", filter); if (silenced) params.append("silenced", "true"); if (inhibited) params.append("inhibited", "true"); if (!active) params.append("active", "false"); // Fetch alerts const queryString = params.toString(); const path = `alerts${queryString ? '?' + queryString : ''}`; const alerts = await fetchFromAlertmanager(path) as Alert[]; // Format alerts const formattedAlerts = alerts.map((alert: Alert): FormattedAlert => ({ fingerprint: alert.fingerprint, alertname: alert.labels.alertname, severity: alert.labels.severity || 'unknown', summary: alert.annotations.summary || 'No summary provided', description: alert.annotations.description || 'No description provided', startsAt: alert.startsAt, status: { state: alert.status.state, silenced: alert.status.silencedBy.length > 0, inhibited: alert.status.inhibitedBy.length > 0, }, labels: alert.labels, })); return { content: [{ type: "text", text: JSON.stringify(formattedAlerts, null, 2) }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error fetching alerts: ${errorMessage}` }], isError: true }; } } ); // Tool to get detailed information about a specific alert server.tool( "get-alert-details", { fingerprint: z.string().describe("Alert fingerprint"), }, async ({ fingerprint }) => { try { // Fetch alert list const alerts = await fetchFromAlertmanager('alerts') as Alert[]; // Find the alert matching the fingerprint const alert = alerts.find((a: Alert) => a.fingerprint === fingerprint); if (!alert) { return { content: [{ type: "text", text: `Alert with fingerprint ${fingerprint} not found` }], isError: true }; } // Format the detailed alert information const details = { fingerprint: alert.fingerprint, alertname: alert.labels.alertname, labels: alert.labels, annotations: alert.annotations, startsAt: alert.startsAt, endsAt: alert.endsAt, generatorURL: alert.generatorURL, status: alert.status, }; return { content: [{ type: "text", text: JSON.stringify(details, null, 2) }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error fetching alert details: ${errorMessage}` }], isError: true }; } } ); // Tool to create a silence for alerts server.tool( "create-silence", { matchers: z.array(z.object({ name: z.string().describe("Matcher name (e.g. alertname)"), value: z.string().describe("Matcher value (e.g. HighCPULoad)"), isRegex: z.boolean().optional().describe("Use regex matching"), })).describe("List of matchers for alerts"), startsAt: z.string().optional().describe("Silence start time (ISO8601 format, default is current time)"), endsAt: z.string().describe("Silence end time (ISO8601 format)"), createdBy: z.string().describe("Username who created the silence"), comment: z.string().describe("Reason or explanation for the silence"), }, async ({ matchers, startsAt, endsAt, createdBy, comment }) => { try { // Prepare silence data const now = new Date().toISOString(); const silenceData = { matchers: matchers.map(m => ({ name: m.name, value: m.value, isRegex: m.isRegex || false, })), startsAt: startsAt || now, endsAt, createdBy, comment, }; // Create the silence const response = await fetchFromAlertmanager('silences', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(silenceData), }); return { content: [{ type: "text", text: `Successfully created silence with ID: ${response.silenceID}` }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error creating silence: ${errorMessage}` }], isError: true }; } } ); // Tool to get list of silences server.tool( "get-silences", { filter: z.string().optional().describe("Filtering query (e.g. createdBy=~'.*admin.*')"), }, async ({ filter }) => { try { // Build query parameters const params = new URLSearchParams(); if (filter) params.append("filter", filter); // Fetch silences const queryString = params.toString(); const path = `silences${queryString ? '?' + queryString : ''}`; const silences = await fetchFromAlertmanager(path) as Silence[]; // Format silences const formattedSilences = silences.map((silence: Silence) => ({ id: silence.id, status: silence.status.state, createdBy: silence.createdBy, comment: silence.comment, startsAt: silence.startsAt, endsAt: silence.endsAt, matchers: silence.matchers, })); return { content: [{ type: "text", text: JSON.stringify(formattedSilences, null, 2) }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error fetching silences: ${errorMessage}` }], isError: true }; } } ); // Tool to delete a silence server.tool( "delete-silence", { silenceId: z.string().describe("ID of the silence to delete"), }, async ({ silenceId }) => { try { // Delete the silence await fetchFromAlertmanager(`silence/${silenceId}`, { method: 'DELETE', }); return { content: [{ type: "text", text: `Successfully deleted silence with ID: ${silenceId}` }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error deleting silence: ${errorMessage}` }], isError: true }; } } ); // Tool to get alert groups server.tool( "get-alert-groups", { active: z.boolean().optional().describe("Include active alerts (default: true)"), silenced: z.boolean().optional().describe("Include silenced alerts"), inhibited: z.boolean().optional().describe("Include inhibited alerts"), }, async ({ active = true, silenced = false, inhibited = false }) => { try { // Build query parameters const params = new URLSearchParams(); if (!active) params.append("active", "false"); if (silenced) params.append("silenced", "true"); if (inhibited) params.append("inhibited", "true"); // Fetch alert groups const queryString = params.toString(); const path = `alerts/groups${queryString ? '?' + queryString : ''}`; const groups = await fetchFromAlertmanager(path); return { content: [{ type: "text", text: JSON.stringify(groups, null, 2) }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error fetching alert groups: ${errorMessage}` }], isError: true }; } } ); // Main process async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Alertmanager MCP Server started on stdio"); } main().catch((error: unknown) => { const errorMessage = error instanceof Error ? error.message : String(error); console.error("Fatal error in main():", errorMessage); process.exit(1); });

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/comqx/alertmanager-mcp'

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