├── .gitignore ├── LICENSE ├── README.md ├── demo.png ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2024 LogLM 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mcp-server-prometheus 2 | 3 | MCP server for interacting with Prometheus metrics and data. 4 | 5 | This is a TypeScript-based MCP server that implements a Prometheus API interface. It provides a bridge between Claude and your Prometheus server through the Model Context Protocol (MCP). 6 | 7 | mcp-server-prometheus MCP server 8 | 9 | ## Demo 10 | 11 | ![demo](/demo.png) 12 | 13 | ## Features 14 | 15 | ### Resources 16 | 17 | - List and access Prometheus metric schema 18 | - Each metric resource provides: 19 | - Metric name and description 20 | - Detailed metadata from Prometheus 21 | - Statistical information (count, min, max) 22 | - JSON mime type for structured data access 23 | 24 | ### Current Capabilities 25 | 26 | - List all available Prometheus metrics with descriptions 27 | - Read detailed metric information including: 28 | - Metadata and help text 29 | - Current statistical data (count, min, max values) 30 | - Basic authentication support for secured Prometheus instances 31 | 32 | ## Configuration 33 | 34 | The server requires the following environment variable: 35 | 36 | - `PROMETHEUS_URL`: The base URL of your Prometheus instance 37 | 38 | Optional authentication configuration: 39 | 40 | - `PROMETHEUS_USERNAME`: Username for basic auth (if required) 41 | - `PROMETHEUS_PASSWORD`: Password for basic auth (if required) 42 | 43 | ## Development 44 | 45 | Install dependencies: 46 | 47 | ```bash 48 | npm install 49 | ``` 50 | 51 | Build the server: 52 | 53 | ```bash 54 | npm run build 55 | ``` 56 | 57 | For development with auto-rebuild: 58 | 59 | ```bash 60 | npm run watch 61 | ``` 62 | 63 | ## Installation 64 | 65 | To use with Claude Desktop, add the server config: 66 | 67 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 68 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json` 69 | 70 | ```json 71 | { 72 | "mcpServers": { 73 | "mcp-server-prometheus": { 74 | "command": "/path/to/mcp-server-prometheus/build/index.js", 75 | "env": { 76 | "PROMETHEUS_URL": "http://your-prometheus-instance:9090" 77 | } 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | ### Debugging 84 | 85 | Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector): 86 | 87 | ```bash 88 | npm run inspector 89 | ``` 90 | 91 | The Inspector will provide a URL to access debugging tools in your browser. 92 | 93 | ## API Structure 94 | 95 | The server exposes Prometheus metrics through the following URI structure: 96 | 97 | - Base URI: `http://your-prometheus-instance:9090` 98 | - Metric URIs: `http://your-prometheus-instance:9090/metrics/{metric_name}` 99 | 100 | Each metric resource returns JSON data containing: 101 | 102 | - Metric name 103 | - Metadata (help text, type) 104 | - Current statistics (count, min, max) 105 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loglmhq/mcp-server-prometheus/6350a33d5236117c4e6346baf4fafbf6e452087d/demo.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@loglm/mcp-server-prometheus", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@loglm/mcp-server-prometheus", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "@modelcontextprotocol/sdk": "0.6.0" 12 | }, 13 | "bin": { 14 | "mcp-server-prometheus": "build/index.js" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20.11.24", 18 | "typescript": "^5.3.3" 19 | } 20 | }, 21 | "node_modules/@modelcontextprotocol/sdk": { 22 | "version": "0.6.0", 23 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", 24 | "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", 25 | "license": "MIT", 26 | "dependencies": { 27 | "content-type": "^1.0.5", 28 | "raw-body": "^3.0.0", 29 | "zod": "^3.23.8" 30 | } 31 | }, 32 | "node_modules/@types/node": { 33 | "version": "20.17.9", 34 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", 35 | "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", 36 | "dev": true, 37 | "license": "MIT", 38 | "dependencies": { 39 | "undici-types": "~6.19.2" 40 | } 41 | }, 42 | "node_modules/bytes": { 43 | "version": "3.1.2", 44 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 45 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 46 | "license": "MIT", 47 | "engines": { 48 | "node": ">= 0.8" 49 | } 50 | }, 51 | "node_modules/content-type": { 52 | "version": "1.0.5", 53 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 54 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 55 | "license": "MIT", 56 | "engines": { 57 | "node": ">= 0.6" 58 | } 59 | }, 60 | "node_modules/depd": { 61 | "version": "2.0.0", 62 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 63 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 64 | "license": "MIT", 65 | "engines": { 66 | "node": ">= 0.8" 67 | } 68 | }, 69 | "node_modules/http-errors": { 70 | "version": "2.0.0", 71 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 72 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 73 | "license": "MIT", 74 | "dependencies": { 75 | "depd": "2.0.0", 76 | "inherits": "2.0.4", 77 | "setprototypeof": "1.2.0", 78 | "statuses": "2.0.1", 79 | "toidentifier": "1.0.1" 80 | }, 81 | "engines": { 82 | "node": ">= 0.8" 83 | } 84 | }, 85 | "node_modules/iconv-lite": { 86 | "version": "0.6.3", 87 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 88 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 89 | "license": "MIT", 90 | "dependencies": { 91 | "safer-buffer": ">= 2.1.2 < 3.0.0" 92 | }, 93 | "engines": { 94 | "node": ">=0.10.0" 95 | } 96 | }, 97 | "node_modules/inherits": { 98 | "version": "2.0.4", 99 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 100 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 101 | "license": "ISC" 102 | }, 103 | "node_modules/raw-body": { 104 | "version": "3.0.0", 105 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 106 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 107 | "license": "MIT", 108 | "dependencies": { 109 | "bytes": "3.1.2", 110 | "http-errors": "2.0.0", 111 | "iconv-lite": "0.6.3", 112 | "unpipe": "1.0.0" 113 | }, 114 | "engines": { 115 | "node": ">= 0.8" 116 | } 117 | }, 118 | "node_modules/safer-buffer": { 119 | "version": "2.1.2", 120 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 121 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 122 | "license": "MIT" 123 | }, 124 | "node_modules/setprototypeof": { 125 | "version": "1.2.0", 126 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 127 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 128 | "license": "ISC" 129 | }, 130 | "node_modules/statuses": { 131 | "version": "2.0.1", 132 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 133 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 134 | "license": "MIT", 135 | "engines": { 136 | "node": ">= 0.8" 137 | } 138 | }, 139 | "node_modules/toidentifier": { 140 | "version": "1.0.1", 141 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 142 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 143 | "license": "MIT", 144 | "engines": { 145 | "node": ">=0.6" 146 | } 147 | }, 148 | "node_modules/typescript": { 149 | "version": "5.7.2", 150 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 151 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 152 | "dev": true, 153 | "license": "Apache-2.0", 154 | "bin": { 155 | "tsc": "bin/tsc", 156 | "tsserver": "bin/tsserver" 157 | }, 158 | "engines": { 159 | "node": ">=14.17" 160 | } 161 | }, 162 | "node_modules/undici-types": { 163 | "version": "6.19.8", 164 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 165 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 166 | "dev": true, 167 | "license": "MIT" 168 | }, 169 | "node_modules/unpipe": { 170 | "version": "1.0.0", 171 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 172 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 173 | "license": "MIT", 174 | "engines": { 175 | "node": ">= 0.8" 176 | } 177 | }, 178 | "node_modules/zod": { 179 | "version": "3.23.8", 180 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", 181 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", 182 | "license": "MIT", 183 | "funding": { 184 | "url": "https://github.com/sponsors/colinhacks" 185 | } 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@loglm/mcp-server-prometheus", 3 | "version": "0.1.0", 4 | "description": "MCP server for interacting with Prometheus", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "mcp-server-prometheus": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector node build/index.js" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "0.6.0" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^20.11.24", 24 | "typescript": "^5.3.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import { 6 | ListResourcesRequestSchema, 7 | ListToolsRequestSchema, 8 | ReadResourceRequestSchema, 9 | } from "@modelcontextprotocol/sdk/types.js"; 10 | 11 | export type PrometheusServerParams = { 12 | prometheusUrl: string; 13 | prometheusUsername?: string; 14 | prometheusPassword?: string; 15 | }; 16 | 17 | /** 18 | * Creates and configures a Prometheus MCP server 19 | */ 20 | export function createPrometheusServer(params: PrometheusServerParams) { 21 | const server = new Server( 22 | { 23 | name: "@loglm/mcp-server-prometheus", 24 | version: "0.1.0", 25 | }, 26 | { 27 | capabilities: { 28 | resources: {}, 29 | tools: {}, 30 | }, 31 | } 32 | ); 33 | 34 | if (!params.prometheusUrl) { 35 | throw new Error("prometheusUrl is required"); 36 | } 37 | 38 | const prometheusUrl = new URL(params.prometheusUrl); 39 | const resourceBaseUrl = new URL(prometheusUrl); 40 | 41 | // Helper functions for fetching Prometheus data 42 | async function fetchMetricMetadata(): Promise { 43 | const url = new URL("/api/v1/metadata", prometheusUrl); 44 | const response = await fetch(url.toString(), { 45 | headers: { 46 | Accept: "application/json", 47 | ...(params.prometheusUsername && params.prometheusPassword 48 | ? { 49 | Authorization: `Basic ${btoa( 50 | `${params.prometheusUsername}:${params.prometheusPassword}` 51 | )}`, 52 | } 53 | : {}), 54 | }, 55 | }); 56 | 57 | if (!response.ok) { 58 | throw new Error(`Prometheus API error: ${response.statusText}`); 59 | } 60 | 61 | return response.json(); 62 | } 63 | 64 | async function fetchMetricDetails(metricName: string): Promise { 65 | const metadataUrl = new URL("/api/v1/metadata", prometheusUrl); 66 | const metadataResponse = await fetch( 67 | `${metadataUrl}?metric=${metricName}`, 68 | { 69 | headers: { 70 | Accept: "application/json", 71 | ...(params.prometheusUsername && params.prometheusPassword 72 | ? { 73 | Authorization: `Basic ${btoa( 74 | `${params.prometheusUsername}:${params.prometheusPassword}` 75 | )}`, 76 | } 77 | : {}), 78 | }, 79 | } 80 | ); 81 | 82 | if (!metadataResponse.ok) { 83 | throw new Error(`Prometheus API error: ${metadataResponse.statusText}`); 84 | } 85 | 86 | const queryUrl = new URL("/api/v1/query", prometheusUrl); 87 | const queries = [ 88 | `count(${metricName})`, 89 | `min(${metricName})`, 90 | `max(${metricName})`, 91 | ]; 92 | 93 | const queryPromises = queries.map((query) => 94 | fetch(`${queryUrl}?query=${encodeURIComponent(query)}`, { 95 | headers: { 96 | Accept: "application/json", 97 | ...(params.prometheusUsername && params.prometheusPassword 98 | ? { 99 | Authorization: `Basic ${btoa( 100 | `${params.prometheusUsername}:${params.prometheusPassword}` 101 | )}`, 102 | } 103 | : {}), 104 | }, 105 | }).then((res) => { 106 | if (!res.ok) { 107 | throw new Error(`Prometheus API error: ${res.statusText}`); 108 | } 109 | return res.json(); 110 | }) 111 | ); 112 | 113 | const [metadata, countData, minData, maxData] = await Promise.all([ 114 | metadataResponse.json(), 115 | ...queryPromises, 116 | ]); 117 | 118 | return { 119 | metadata, 120 | statistics: { 121 | count: countData.data.result[0]?.value[1] || 0, 122 | min: minData.data.result[0]?.value[1] || 0, 123 | max: maxData.data.result[0]?.value[1] || 0, 124 | }, 125 | }; 126 | } 127 | 128 | // Handler implementations 129 | server.setRequestHandler(ListResourcesRequestSchema, async () => { 130 | const data = await fetchMetricMetadata(); 131 | 132 | if (data.status !== "success") { 133 | throw new Error("Failed to fetch metrics metadata"); 134 | } 135 | 136 | return { 137 | resources: Object.entries(data.data).map( 138 | ([metricName, metadata]: [string, any]) => ({ 139 | uri: new URL(`metrics/${metricName}`, resourceBaseUrl).href, 140 | mimeType: "application/json", 141 | name: metricName, 142 | description: metadata[0]?.help || "No description available", 143 | }) 144 | ), 145 | }; 146 | }); 147 | 148 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => { 149 | const resourceUrl = new URL(request.params.uri); 150 | const pathComponents = resourceUrl.pathname.split("/"); 151 | const metricName = pathComponents[pathComponents.length - 1]; 152 | 153 | const metricData = await fetchMetricDetails(metricName); 154 | 155 | const content = { 156 | name: metricName, 157 | metadata: metricData.metadata.data[metricName]?.[0] || {}, 158 | statistics: metricData.statistics, 159 | }; 160 | 161 | return { 162 | contents: [ 163 | { 164 | uri: request.params.uri, 165 | mimeType: "application/json", 166 | text: JSON.stringify(content, null, 2), 167 | }, 168 | ], 169 | }; 170 | }); 171 | 172 | server.setRequestHandler(ListToolsRequestSchema, async () => { 173 | return { 174 | tools: [], 175 | }; 176 | }); 177 | 178 | return server; 179 | } 180 | 181 | /** 182 | * Main function to run the server 183 | */ 184 | async function main() { 185 | const PROMETHEUS_URL = process.env.PROMETHEUS_URL; 186 | 187 | if (!PROMETHEUS_URL) { 188 | console.error("PROMETHEUS_URL environment variable is not set"); 189 | process.exit(1); 190 | } 191 | 192 | const PROMETHEUS_USERNAME = process.env.PROMETHEUS_USERNAME; 193 | const PROMETHEUS_PASSWORD = process.env.PROMETHEUS_PASSWORD; 194 | 195 | const transport = new StdioServerTransport(); 196 | const server = createPrometheusServer({ 197 | prometheusUrl: PROMETHEUS_URL, 198 | prometheusUsername: PROMETHEUS_USERNAME, 199 | prometheusPassword: PROMETHEUS_PASSWORD, 200 | }); 201 | 202 | await server.connect(transport); 203 | console.error("Prometheus MCP Server running on stdio"); 204 | } 205 | 206 | // Run the server 207 | main().catch((error) => { 208 | console.error("Server error:", error); 209 | process.exit(1); 210 | }); 211 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | --------------------------------------------------------------------------------