Skip to main content
Glama

MCP Datadog Playcourt

by adisaputra10
index.js28.2 kB
#!/usr/bin/env node import dotenv from 'dotenv'; import axios from 'axios'; // Load environment variables dotenv.config(); const API_KEY = process.env.DATADOG_API_KEY; const APP_KEY = process.env.DATADOG_APP_KEY; const SITE = process.env.DATADOG_SITE || 'datadoghq.com'; if (!API_KEY || !APP_KEY) { console.error('Error: DATADOG_API_KEY and DATADOG_APP_KEY must be set in .env file'); process.exit(1); } const client = axios.create({ baseURL: `https://api.${SITE}`, headers: { 'DD-API-KEY': API_KEY, 'DD-APPLICATION-KEY': APP_KEY, 'Content-Type': 'application/json' } }); /** * Query Datadog metrics with custom query string (from Metric Explorer) * Supports queries like: * - avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod} * - avg:kubernetes.memory.usage{kube_namespace:pijarsekolah-prod} by {kube_deployment} * - max:kubernetes.cpu.usage.total{kube_deployment:pijarsekolah-mail-api} */ async function queryMetrics(query, from, to) { try { const response = await client.get('/api/v1/query', { params: { query, from, to } }); return response.data; } catch (error) { console.error('Error querying metrics:', error.response?.data || error.message); throw error; } } /** * Advanced metrics query with support for metric expressions * Handles complex queries with aggregations, filters, and grouping */ async function queryMetricsAdvanced(metricExpression, from, to, options = {}) { try { const params = { query: metricExpression, from, to }; if (options.pointCount) { params.interpolation = options.interpolation || 'linear'; } const response = await client.get('/api/v1/query', { params }); return response.data; } catch (error) { console.error('Error in advanced query:', error.response?.data || error.message); throw error; } } /** * Query APM metrics (services, endpoints, traces) * Examples: * - trace.express.request.duration * - trace.web.request.duration * - trace.db.query.duration */ async function queryAPMMetrics(serviceName, from, to, options = {}) { try { const metric = options.metric || 'trace.web.request.duration'; const aggregator = options.aggregator || 'avg'; const groupBy = options.groupBy ? ` by {${options.groupBy.join(',')}}` : ''; const query = `${aggregator}:${metric}{service:${serviceName}}${groupBy}`; const response = await client.get('/api/v1/query', { params: { query, from, to } }); return response.data; } catch (error) { console.error('Error querying APM metrics:', error.response?.data || error.message); throw error; } } /** * Query Host metrics (CPU, Memory, Network, Disk) * Examples: * - system.cpu.user * - system.memory.usable * - system.net.bytes_rcvd * - system.disk.used */ async function queryHostMetrics(hostname, from, to, options = {}) { try { const metric = options.metric || 'system.cpu.user'; const aggregator = options.aggregator || 'avg'; const query = `${aggregator}:${metric}{host:${hostname}}`; const response = await client.get('/api/v1/query', { params: { query, from, to } }); return response.data; } catch (error) { console.error('Error querying host metrics:', error.response?.data || error.message); throw error; } } /** * Query Host CPU and Memory metrics in one call */ async function queryHostCpuMemory(hostname, from, to, options = {}) { try { const aggregator = options.aggregator || 'avg'; // Query both CPU and Memory const cpuQuery = `${aggregator}:system.cpu.user{host:${hostname}}`; const memQuery = `${aggregator}:system.memory.usable{host:${hostname}}`; const [cpuResponse, memResponse] = await Promise.all([ client.get('/api/v1/query', { params: { query: cpuQuery, from, to } }), client.get('/api/v1/query', { params: { query: memQuery, from, to } }) ]); return { cpu: cpuResponse.data, memory: memResponse.data, hostname, period: { from, to } }; } catch (error) { console.error('Error querying host CPU/Memory:', error.response?.data || error.message); throw error; } } /** * Query DBM (Database Monitoring) metrics * Examples: * - postgresql.queries.active * - postgresql.replication_delay * - mysql.queries * - mysql.replication_delay */ async function queryDBMMetrics(dbHost, from, to, options = {}) { try { const dbType = options.dbType || 'postgresql'; const metric = options.metric || `${dbType}.queries.active`; const aggregator = options.aggregator || 'avg'; const groupBy = options.groupBy ? ` by {${options.groupBy.join(',')}}` : ''; const query = `${aggregator}:${metric}{dbhost:${dbHost}}${groupBy}`; const response = await client.get('/api/v1/query', { params: { query, from, to } }); return response.data; } catch (error) { console.error('Error querying DBM metrics:', error.response?.data || error.message); throw error; } } /** * Get list of APM services */ async function getAPMServices(from, to) { try { const query = `avg:trace.web.request.count{*} by {service}`; const response = await client.get('/api/v1/query', { params: { query, from, to } }); const services = new Set(); if (response.data.series) { response.data.series.forEach(series => { const match = series.scope?.match(/service:([^,}]+)/); if (match) { services.add(match[1]); } }); } return Array.from(services); } catch (error) { console.error('Error getting APM services:', error.response?.data || error.message); throw error; } } /** * Get list of monitored hosts */ async function getMonitoredHosts(from, to) { try { const query = `avg:system.cpu.user{*} by {host}`; const response = await client.get('/api/v1/query', { params: { query, from, to } }); const hosts = new Set(); if (response.data.series) { response.data.series.forEach(series => { const match = series.scope?.match(/host:([^,}]+)/); if (match) { hosts.add(match[1]); } }); } return Array.from(hosts); } catch (error) { console.error('Error getting monitored hosts:', error.response?.data || error.message); throw error; } } /** * Get list of monitored databases (DBM) */ async function getMonitoredDatabases(from, to, options = {}) { try { const dbType = options.dbType || 'postgresql'; const query = `avg:${dbType}.queries.active{*} by {dbhost}`; const response = await client.get('/api/v1/query', { params: { query, from, to } }); const databases = new Set(); if (response.data.series) { response.data.series.forEach(series => { const match = series.scope?.match(/dbhost:([^,}]+)/); if (match) { databases.add(match[1]); } }); } return Array.from(databases); } catch (error) { console.error('Error getting monitored databases:', error.response?.data || error.message); throw error; } } /** * Get all deployments in namespace */ async function getAllDeployments(namespace, from, to) { try { const query = `avg:kubernetes.cpu.usage.total{kube_namespace:${namespace}} by {kube_deployment}`; const response = await client.get('/api/v1/query', { params: { query, from, to } }); const deployments = new Set(); if (response.data.series) { response.data.series.forEach(series => { const match = series.scope?.match(/kube_deployment:([^,}]+)/); if (match) { deployments.add(match[1]); } }); } return Array.from(deployments); } catch (error) { console.error('Error getting deployments:', error.response?.data || error.message); throw error; } } /** * Query metrics for specific deployment */ async function getDeploymentMetrics(namespace, deployment, metricType, from, to) { try { const queries = { cpu: `avg:kubernetes.cpu.usage.total{kube_namespace:${namespace},kube_deployment:${deployment}}`, memory: `avg:kubernetes.memory.usage{kube_namespace:${namespace},kube_deployment:${deployment}}`, cpu_max: `max:kubernetes.cpu.usage.total{kube_namespace:${namespace},kube_deployment:${deployment}}`, memory_max: `max:kubernetes.memory.usage{kube_namespace:${namespace},kube_deployment:${deployment}}` }; const query = queries[metricType]; if (!query) { throw new Error(`Unknown metric type: ${metricType}`); } const response = await client.get('/api/v1/query', { params: { query, from, to } }); return response.data; } catch (error) { console.error(`Error querying ${metricType} for ${deployment}:`, error.response?.data || error.message); throw error; } } /** * Batch query multiple deployments */ async function batchQueryDeployments(namespace, deployments, metricType, from, to) { const results = {}; for (const deployment of deployments) { try { console.log(`Querying ${deployment}...`); const data = await getDeploymentMetrics(namespace, deployment, metricType, from, to); results[deployment] = data; } catch (error) { console.error(`Failed to query ${deployment}:`, error.message); results[deployment] = { error: error.message }; } } return results; } /** * Parse metric data into structured format */ function parseMetricsData(data, deployment) { if (!data.series || data.series.length === 0) { return null; } const series = data.series[0]; const points = series.pointlist || []; if (points.length === 0) { return null; } const values = points.map(p => p[1]); const timestamps = points.map(p => new Date(p[0])); return { deployment, metric: series.metric, unit: series.unit?.[0]?.shortName || 'unknown', points: points.length, min: Math.min(...values), max: Math.max(...values), avg: values.reduce((a, b) => a + b, 0) / values.length, data: points }; } /** * Aggregate data to hourly */ function aggregateToHourly(metricData) { if (!metricData || !metricData.data) { return null; } const hourly = {}; metricData.data.forEach(([timestamp, value]) => { const date = new Date(timestamp); const hourKey = date.toISOString().substring(0, 13) + ':00:00Z'; if (!hourly[hourKey]) { hourly[hourKey] = []; } hourly[hourKey].push(value); }); const result = {}; Object.entries(hourly).forEach(([hour, values]) => { result[hour] = { min: Math.min(...values), max: Math.max(...values), avg: values.reduce((a, b) => a + b, 0) / values.length, count: values.length }; }); return result; } // CLI Interface async function main() { const args = process.argv.slice(2); const command = args[0]; if (!command) { console.log(` MCP Datadog CLI =============== Commands: list-deployments <namespace> <from> <to> List all deployments in namespace query-cpu <namespace> <deployment> <from> <to> Query CPU metrics query-memory <namespace> <deployment> <from> <to> Query memory metrics batch-query <namespace> <from> <to> <metric-type> Query all deployments query-metrics <query> <from> <to> Query with custom metric expression query-metrics-export <query> <from> <to> <filename> Query and export to JSON file Custom Query Examples (from Metric Explorer): node index.js query-metrics "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod}" 1732646400 1732732799 node index.js query-metrics "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod} by {kube_deployment}" 1732646400 1732732799 node index.js query-metrics "max:kubernetes.memory.usage{kube_deployment:pijarsekolah-mail-api}" 1732646400 1732732799 node index.js query-metrics-export "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod} by {kube_deployment}" 1732646400 1732732799 result.json Standard Examples: node index.js list-deployments pijarsekolah-prod 1732646400 1732732799 node index.js query-cpu pijarsekolah-prod pijarsekolah-mail-api 1732646400 1732732799 node index.js batch-query pijarsekolah-prod 1732646400 1732732799 cpu `); return; } try { if (command === 'list-deployments') { const [namespace, from, to] = args.slice(1); console.log(`Fetching deployments from ${namespace}...`); const deployments = await getAllDeployments(namespace, parseInt(from), parseInt(to)); console.log(`\nFound ${deployments.length} deployments:\n`); deployments.forEach(d => console.log(` - ${d}`)); console.log(`\n✓ Complete list exported`); } else if (command === 'query-cpu') { const [namespace, deployment, from, to] = args.slice(1); const data = await getDeploymentMetrics(namespace, deployment, 'cpu', parseInt(from), parseInt(to)); const parsed = parseMetricsData(data, deployment); if (parsed) { console.log(`\nCPU Metrics for ${deployment}:`); console.log(` Min: ${parsed.min.toFixed(2)} ${parsed.unit}`); console.log(` Max: ${parsed.max.toFixed(2)} ${parsed.unit}`); console.log(` Avg: ${parsed.avg.toFixed(2)} ${parsed.unit}`); console.log(` Data points: ${parsed.points}`); const hourly = aggregateToHourly(parsed); console.log(`\nHourly Aggregation:`); Object.entries(hourly).slice(0, 5).forEach(([hour, stats]) => { console.log(` ${hour}: min=${stats.min.toFixed(2)}, avg=${stats.avg.toFixed(2)}, max=${stats.max.toFixed(2)}`); }); } else { console.log(`\nNo data found for ${deployment}`); } } else if (command === 'query-memory') { const [namespace, deployment, from, to] = args.slice(1); const data = await getDeploymentMetrics(namespace, deployment, 'memory', parseInt(from), parseInt(to)); const parsed = parseMetricsData(data, deployment); if (parsed) { console.log(`\nMemory Metrics for ${deployment}:`); console.log(` Min: ${(parsed.min / 1024 / 1024).toFixed(2)} MB`); console.log(` Max: ${(parsed.max / 1024 / 1024).toFixed(2)} MB`); console.log(` Avg: ${(parsed.avg / 1024 / 1024).toFixed(2)} MB`); console.log(` Data points: ${parsed.points}`); } else { console.log(`\nNo data found for ${deployment}`); } } else if (command === 'batch-query') { const [namespace, from, to, metricType] = args.slice(1); console.log(`Fetching all deployments...`); const deployments = await getAllDeployments(namespace, parseInt(from), parseInt(to)); console.log(`Found ${deployments.length} deployments. Querying ${metricType} metrics...\n`); const results = await batchQueryDeployments(namespace, deployments, metricType, parseInt(from), parseInt(to)); console.log(`\n\nResults Summary:`); const header = ['Deployment'.padEnd(45), 'Min'.padEnd(15), 'Avg'.padEnd(15), 'Max'.padEnd(15)].join(' '); console.log(header); console.log('-'.repeat(90)); Object.entries(results).forEach(([deployment, data]) => { if (data.error) { const line = [deployment.padEnd(45), `ERROR: ${data.error}`].join(' '); console.log(line); } else { const parsed = parseMetricsData(data, deployment); if (parsed) { const unit = metricType === 'memory' ? 'MB' : 'mcores'; const divisor = metricType === 'memory' ? 1024 * 1024 : 1000000; const line = [ deployment.padEnd(45), (parsed.min/divisor).toFixed(2).padEnd(15), (parsed.avg/divisor).toFixed(2).padEnd(15), (parsed.max/divisor).toFixed(2).padEnd(15) ].join(' '); console.log(line); } } }); } else if (command === 'apm-services') { // apm-services from to // Lists all APM services in the given time period const from = parseInt(args[1]); const to = parseInt(args[2]); console.log('Fetching APM services...\n'); const services = await getAPMServices(from, to); console.log(`Found ${services.length} APM services:\n`); services.forEach((service, index) => { console.log(`${index + 1}. ${service}`); }); } else if (command === 'apm-metrics') { // apm-metrics service_name from to [--metric metric_name] [--aggregator avg|max|min|sum] const serviceName = args[1]; const from = parseInt(args[2]); const to = parseInt(args[3]); const options = {}; for (let i = 4; i < args.length; i++) { if (args[i] === '--metric' && args[i + 1]) { options.metric = args[++i]; } if (args[i] === '--aggregator' && args[i + 1]) { options.aggregator = args[++i]; } } console.log(`Querying APM metrics for service: ${serviceName}\n`); const data = await queryAPMMetrics(serviceName, from, to, options); if (data.series && data.series.length > 0) { console.log(`Found ${data.series.length} metric series:\n`); data.series.forEach((series, index) => { console.log(`Series ${index + 1}: ${series.metric}`); console.log(` Scope: ${series.scope}`); console.log(` Data points: ${series.pointlist?.length || 0}`); if (series.pointlist && series.pointlist.length > 0) { const values = series.pointlist.map(p => p[1]); console.log(` Min: ${Math.min(...values).toFixed(2)}, Max: ${Math.max(...values).toFixed(2)}, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}`); } }); } else { console.log('No data found'); } } else if (command === 'hosts') { // hosts from to // Lists all monitored hosts const from = parseInt(args[1]); const to = parseInt(args[2]); console.log('Fetching monitored hosts...\n'); const hosts = await getMonitoredHosts(from, to); console.log(`Found ${hosts.length} monitored hosts:\n`); hosts.forEach((host, index) => { console.log(`${index + 1}. ${host}`); }); } else if (command === 'host-metrics') { // host-metrics hostname from to [--metric metric_name] [--aggregator avg|max|min|sum] const hostname = args[1]; const from = parseInt(args[2]); const to = parseInt(args[3]); const options = {}; for (let i = 4; i < args.length; i++) { if (args[i] === '--metric' && args[i + 1]) { options.metric = args[++i]; } if (args[i] === '--aggregator' && args[i + 1]) { options.aggregator = args[++i]; } } console.log(`Querying metrics for host: ${hostname}\n`); const data = await queryHostMetrics(hostname, from, to, options); if (data.series && data.series.length > 0) { console.log(`Found ${data.series.length} metric series:\n`); data.series.forEach((series, index) => { console.log(`Series ${index + 1}: ${series.metric}`); console.log(` Scope: ${series.scope}`); console.log(` Data points: ${series.pointlist?.length || 0}`); if (series.pointlist && series.pointlist.length > 0) { const values = series.pointlist.map(p => p[1]); console.log(` Min: ${Math.min(...values).toFixed(2)}, Max: ${Math.max(...values).toFixed(2)}, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}`); } }); } else { console.log('No data found'); } } else if (command === 'host-cpu-memory') { // host-cpu-memory hostname from to [--aggregator avg|max|min|sum] const hostname = args[1]; const from = parseInt(args[2]); const to = parseInt(args[3]); const options = {}; for (let i = 4; i < args.length; i++) { if (args[i] === '--aggregator' && args[i + 1]) { options.aggregator = args[++i]; } } console.log(`Querying CPU & Memory for host: ${hostname}\n`); const data = await queryHostCpuMemory(hostname, from, to, options); console.log('CPU Metrics:'); if (data.cpu.series && data.cpu.series.length > 0) { data.cpu.series.forEach(series => { if (series.pointlist && series.pointlist.length > 0) { const values = series.pointlist.map(p => p[1]); console.log(` Min: ${Math.min(...values).toFixed(2)}%, Max: ${Math.max(...values).toFixed(2)}%, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}%`); } }); } else { console.log(' No data'); } console.log('\nMemory Metrics:'); if (data.memory.series && data.memory.series.length > 0) { data.memory.series.forEach(series => { if (series.pointlist && series.pointlist.length > 0) { const values = series.pointlist.map(p => p[1] / 1024 / 1024 / 1024); console.log(` Min: ${Math.min(...values).toFixed(2)} GB, Max: ${Math.max(...values).toFixed(2)} GB, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)} GB`); } }); } else { console.log(' No data'); } } else if (command === 'databases') { // databases from to [--type postgresql|mysql|...] const from = parseInt(args[1]); const to = parseInt(args[2]); const options = {}; for (let i = 3; i < args.length; i++) { if (args[i] === '--type' && args[i + 1]) { options.dbType = args[++i]; } } console.log(`Fetching monitored databases...\n`); const databases = await getMonitoredDatabases(from, to, options); console.log(`Found ${databases.length} monitored databases (${options.dbType || 'all'}):\n`); databases.forEach((db, index) => { console.log(`${index + 1}. ${db}`); }); } else if (command === 'dbm-metrics') { // dbm-metrics db_host from to [--type postgresql|mysql] [--metric metric_name] [--aggregator avg|max|min|sum] const dbHost = args[1]; const from = parseInt(args[2]); const to = parseInt(args[3]); const options = {}; for (let i = 4; i < args.length; i++) { if (args[i] === '--type' && args[i + 1]) { options.dbType = args[++i]; } if (args[i] === '--metric' && args[i + 1]) { options.metric = args[++i]; } if (args[i] === '--aggregator' && args[i + 1]) { options.aggregator = args[++i]; } } console.log(`Querying DBM metrics for database: ${dbHost}\n`); const data = await queryDBMMetrics(dbHost, from, to, options); if (data.series && data.series.length > 0) { console.log(`Found ${data.series.length} metric series:\n`); data.series.forEach((series, index) => { console.log(`Series ${index + 1}: ${series.metric}`); console.log(` Scope: ${series.scope}`); console.log(` Data points: ${series.pointlist?.length || 0}`); if (series.pointlist && series.pointlist.length > 0) { const values = series.pointlist.map(p => p[1]); console.log(` Min: ${Math.min(...values).toFixed(2)}, Max: ${Math.max(...values).toFixed(2)}, Avg: ${(values.reduce((a, b) => a + b) / values.length).toFixed(2)}`); } }); } else { console.log('No data found'); } } else if (command === 'query-metrics') { // query-metrics "metric_query" from to const query = args[1]; const from = parseInt(args[2]); const to = parseInt(args[3]); if (!query || !from || !to) { console.error('Usage: query-metrics <query> <from> <to>'); console.error('Example: query-metrics "avg:kubernetes.cpu.usage.total{kube_namespace:pijarsekolah-prod}" 1732646400 1732732799'); process.exit(1); } console.log(`\nExecuting query: ${query}`); console.log(`From: ${new Date(from * 1000).toISOString()}`); console.log(`To: ${new Date(to * 1000).toISOString()}\n`); const data = await queryMetricsAdvanced(query, from, to); if (data.series && data.series.length > 0) { console.log(`✓ Query successful. Found ${data.series.length} series.\n`); data.series.forEach((series, index) => { console.log(`\nSeries ${index + 1}:`); console.log(` Metric: ${series.metric}`); console.log(` Scope: ${series.scope || 'N/A'}`); console.log(` Expression: ${series.expression || 'N/A'}`); console.log(` Data Points: ${series.pointlist?.length || 0}`); if (series.pointlist && series.pointlist.length > 0) { const values = series.pointlist.map(p => p[1]); const min = Math.min(...values); const max = Math.max(...values); const avg = values.reduce((a, b) => a + b, 0) / values.length; const unit = series.unit?.[0]?.shortName || 'unknown'; console.log(` Min: ${min.toFixed(2)} ${unit}`); console.log(` Max: ${max.toFixed(2)} ${unit}`); console.log(` Avg: ${avg.toFixed(2)} ${unit}`); console.log(`\n First 5 data points:`); series.pointlist.slice(0, 5).forEach(([ts, val]) => { const date = new Date(ts); console.log(` ${date.toISOString()}: ${val.toFixed(2)} ${unit}`); }); } }); } else { console.log(`No data found for this query.`); } } else if (command === 'query-metrics-export') { // query-metrics-export "metric_query" from to filename const query = args[1]; const from = parseInt(args[2]); const to = parseInt(args[3]); const filename = args[4] || 'metrics-export.json'; if (!query || !from || !to) { console.error('Usage: query-metrics-export <query> <from> <to> [filename]'); process.exit(1); } console.log(`\nExecuting query: ${query}`); console.log(`Exporting to: ${filename}\n`); const data = await queryMetricsAdvanced(query, from, to); // Format export data const exportData = { query: query, timestamp: new Date().toISOString(), from: new Date(from * 1000).toISOString(), to: new Date(to * 1000).toISOString(), seriesCount: data.series?.length || 0, series: (data.series || []).map(series => ({ metric: series.metric, scope: series.scope, expression: series.expression, unit: series.unit?.[0]?.shortName || 'unknown', dataPoints: series.pointlist?.length || 0, data: series.pointlist || [] })) }; const fs = await import('fs/promises'); await fs.writeFile(filename, JSON.stringify(exportData, null, 2)); console.log(`✓ Data exported successfully to: ${filename}`); console.log(` Total series: ${exportData.seriesCount}`); exportData.series.forEach((s, i) => { console.log(` Series ${i + 1}: ${s.scope} (${s.dataPoints} data points)`); }); } else { console.error(`Unknown command: ${command}`); process.exit(1); } } catch (error) { console.error('Error:', error.message); process.exit(1); } } main();

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/adisaputra10/mcp-datadog'

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