From 0807bc1c0b830467c70a0eb2bf812a865c9b86bb Mon Sep 17 00:00:00 2001 From: PChol22 Date: Mon, 21 Jul 2025 17:43:17 +0200 Subject: [PATCH 1/6] filter old logs for eval mcp --- package.json | 4 ++-- src/tools/logs/tool.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ddbee89..255e9f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@winor30/mcp-server-datadog", - "version": "1.6.0", + "name": "@anyshift/datadog-mcp-server", + "version": "0.0.1", "description": "MCP server for interacting with Datadog API", "repository": { "type": "git", diff --git a/src/tools/logs/tool.ts b/src/tools/logs/tool.ts index 322a7f7..83e2b6a 100644 --- a/src/tools/logs/tool.ts +++ b/src/tools/logs/tool.ts @@ -44,6 +44,15 @@ export const createLogsToolHandlers = ( }, }) + // only keep logs that are less than 30 minutes old + const thirtyMinutesAgo = Date.now() - 30 * 60 * 1000 + + const filteredData = response.data?.filter( + (log) => + log.attributes?.timestamp !== undefined && + log.attributes.timestamp.getTime() > thirtyMinutesAgo, + ) + if (response.data == null) { throw new Error('No logs data returned') } @@ -52,7 +61,7 @@ export const createLogsToolHandlers = ( content: [ { type: 'text', - text: `Logs data: ${JSON.stringify(response.data)}`, + text: `Logs data: ${JSON.stringify(filteredData)}`, }, ], } From 8216f353527c6e57e39e31bf1188295eb84be9a2 Mon Sep 17 00:00:00 2001 From: PChol22 Date: Mon, 21 Jul 2025 17:50:39 +0200 Subject: [PATCH 2/6] fix repo in packagejson --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 255e9f9..7a882e1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "MCP server for interacting with Datadog API", "repository": { "type": "git", - "url": "https://github.com/winor30/mcp-server-datadog.git" + "url": "https://github.com:anyshift-engineering/mcp-server-datadog.git" }, "type": "module", "bin": { From b5cdb06c45270f3d41a0fa22bbb12473c20c5af2 Mon Sep 17 00:00:00 2001 From: PChol22 Date: Mon, 21 Jul 2025 18:20:19 +0200 Subject: [PATCH 3/6] fix: update 30 minutes filter --- inspector.config.json | 8 ++++++++ package.json | 3 ++- src/tools/logs/tool.ts | 20 ++++++++++++++++---- 3 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 inspector.config.json diff --git a/inspector.config.json b/inspector.config.json new file mode 100644 index 0000000..9a1590e --- /dev/null +++ b/inspector.config.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "local": { + "command": "node", + "args": ["./build/index.js"] + } + } +} diff --git a/package.json b/package.json index 7a882e1..590382f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "test": "vitest run", "test:coverage": "vitest run --coverage", "test:watch": "vitest", - "lint-staged": "lint-staged" + "lint-staged": "lint-staged", + "inspect-local": "pnpm build && npx @modelcontextprotocol/inspector --config ./inspector.config.json --server local" }, "dependencies": { "@datadog/datadog-api-client": "^1.34.1", diff --git a/src/tools/logs/tool.ts b/src/tools/logs/tool.ts index 83e2b6a..59f8652 100644 --- a/src/tools/logs/tool.ts +++ b/src/tools/logs/tool.ts @@ -29,12 +29,27 @@ export const createLogsToolHandlers = ( request.params.arguments, ) + // update from to be the max of from and 30 minutes ago + const thirtyMinutesAgo = Math.floor(Date.now() / 1000) - 30 * 60 + const adjustedFrom = Math.max(from, thirtyMinutesAgo) + + if (adjustedFrom > to) { + return { + content: [ + { + type: 'text', + text: `Logs data: ${[]}`, + }, + ], + } + } + const response = await apiInstance.listLogs({ body: { filter: { query, // `from` and `to` are in epoch seconds, but the Datadog API expects milliseconds - from: `${from * 1000}`, + from: `${adjustedFrom * 1000}`, to: `${to * 1000}`, }, page: { @@ -44,9 +59,6 @@ export const createLogsToolHandlers = ( }, }) - // only keep logs that are less than 30 minutes old - const thirtyMinutesAgo = Date.now() - 30 * 60 * 1000 - const filteredData = response.data?.filter( (log) => log.attributes?.timestamp !== undefined && From 31705ccf6768fc9577a443cfd4ca94c32f39d85a Mon Sep 17 00:00:00 2001 From: PChol22 Date: Thu, 24 Jul 2025 12:08:36 +0200 Subject: [PATCH 4/6] feat: adjust MCP timestamps to only return data prior to incident --- src/index.ts | 10 +++++-- src/tools/logs/tool.ts | 36 ++++++++++++++----------- src/tools/metrics/tool.ts | 18 +++++++++++-- src/tools/rum/tool.ts | 49 ++++++++++++++++++++++++++++++----- src/utils/adjustTimestamps.ts | 25 ++++++++++++++++++ tests/setup.ts | 1 + 6 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 src/utils/adjustTimestamps.ts diff --git a/src/index.ts b/src/index.ts index 8d1dda0..5771780 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,8 +66,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { } }) -if (!process.env.DATADOG_API_KEY || !process.env.DATADOG_APP_KEY) { - throw new Error('DATADOG_API_KEY and DATADOG_APP_KEY must be set') +if ( + !process.env.DATADOG_API_KEY || + !process.env.DATADOG_APP_KEY || + !process.env.DATADOG_EVAL_TIMESTAMP +) { + throw new Error( + '[MCP Eval Version] DATADOG_API_KEY and DATADOG_APP_KEY and DATADOG_EVAL_TIMESTAMP must be set', + ) } const datadogConfig = createDatadogConfig({ diff --git a/src/tools/logs/tool.ts b/src/tools/logs/tool.ts index 59f8652..bb138fa 100644 --- a/src/tools/logs/tool.ts +++ b/src/tools/logs/tool.ts @@ -2,6 +2,7 @@ import { ExtendedTool, ToolHandlers } from '../../utils/types' import { v2 } from '@datadog/datadog-api-client' import { createToolSchema } from '../../utils/tool' import { GetLogsZodSchema, GetAllServicesZodSchema } from './schema' +import { adjustTimestamps } from '../../utils/adjustTimestamps' type LogsToolName = 'get_logs' | 'get_all_services' type LogsTool = ExtendedTool @@ -29,11 +30,9 @@ export const createLogsToolHandlers = ( request.params.arguments, ) - // update from to be the max of from and 30 minutes ago - const thirtyMinutesAgo = Math.floor(Date.now() / 1000) - 30 * 60 - const adjustedFrom = Math.max(from, thirtyMinutesAgo) + const adjusted = adjustTimestamps(from, to) - if (adjustedFrom > to) { + if (!adjusted.ok) { return { content: [ { @@ -49,8 +48,8 @@ export const createLogsToolHandlers = ( filter: { query, // `from` and `to` are in epoch seconds, but the Datadog API expects milliseconds - from: `${adjustedFrom * 1000}`, - to: `${to * 1000}`, + from: `${adjusted.from * 1000}`, + to: `${adjusted.to * 1000}`, }, page: { limit, @@ -59,12 +58,6 @@ export const createLogsToolHandlers = ( }, }) - const filteredData = response.data?.filter( - (log) => - log.attributes?.timestamp !== undefined && - log.attributes.timestamp.getTime() > thirtyMinutesAgo, - ) - if (response.data == null) { throw new Error('No logs data returned') } @@ -73,7 +66,7 @@ export const createLogsToolHandlers = ( content: [ { type: 'text', - text: `Logs data: ${JSON.stringify(filteredData)}`, + text: `Logs data: ${JSON.stringify(response.data)}`, }, ], } @@ -84,13 +77,26 @@ export const createLogsToolHandlers = ( request.params.arguments, ) + const adjusted = adjustTimestamps(from, to) + + if (!adjusted.ok) { + return { + content: [ + { + type: 'text', + text: `Services: ${[]}`, + }, + ], + } + } + const response = await apiInstance.listLogs({ body: { filter: { query, // `from` and `to` are in epoch seconds, but the Datadog API expects milliseconds - from: `${from * 1000}`, - to: `${to * 1000}`, + from: `${adjusted.from * 1000}`, + to: `${adjusted.to * 1000}`, }, page: { limit, diff --git a/src/tools/metrics/tool.ts b/src/tools/metrics/tool.ts index 401c585..919dd1f 100644 --- a/src/tools/metrics/tool.ts +++ b/src/tools/metrics/tool.ts @@ -2,6 +2,7 @@ import { ExtendedTool, ToolHandlers } from '../../utils/types' import { v1 } from '@datadog/datadog-api-client' import { createToolSchema } from '../../utils/tool' import { QueryMetricsZodSchema } from './schema' +import { adjustTimestamps } from '../../utils/adjustTimestamps' type MetricsToolName = 'query_metrics' type MetricsTool = ExtendedTool @@ -25,9 +26,22 @@ export const createMetricsToolHandlers = ( request.params.arguments, ) + const adjusted = adjustTimestamps(from, to) + + if (!adjusted.ok) { + return { + content: [ + { + type: 'text', + text: `Metrics data: ${[]}`, + }, + ], + } + } + const response = await apiInstance.queryMetrics({ - from, - to, + from: adjusted.from, + to: adjusted.to, query, }) diff --git a/src/tools/rum/tool.ts b/src/tools/rum/tool.ts index 6949029..d3d8def 100644 --- a/src/tools/rum/tool.ts +++ b/src/tools/rum/tool.ts @@ -8,6 +8,7 @@ import { GetRumPagePerformanceZodSchema, GetRumPageWaterfallZodSchema, } from './schema' +import { adjustTimestamps } from '../../utils/adjustTimestamps' type RumToolName = | 'get_rum_events' @@ -74,10 +75,22 @@ export const createRumToolHandlers = ( request.params.arguments, ) + const adjusted = adjustTimestamps(from, to) + if (!adjusted.ok) { + return { + content: [ + { + type: 'text', + text: `RUM events data: ${[]}`, + }, + ], + } + } + const response = await apiInstance.listRUMEvents({ filterQuery: query, - filterFrom: new Date(from * 1000), - filterTo: new Date(to * 1000), + filterFrom: new Date(adjusted.from * 1000), + filterTo: new Date(adjusted.to * 1000), sort: 'timestamp', pageLimit: limit, }) @@ -101,11 +114,23 @@ export const createRumToolHandlers = ( request.params.arguments, ) + const adjusted = adjustTimestamps(from, to) + if (!adjusted.ok) { + return { + content: [ + { + type: 'text', + text: `RUM grouped event count: ${[]}`, + }, + ], + } + } + // For session counts, we need to use a query to count unique sessions const response = await apiInstance.listRUMEvents({ filterQuery: query !== '*' ? query : undefined, - filterFrom: new Date(from * 1000), - filterTo: new Date(to * 1000), + filterFrom: new Date(adjusted.from * 1000), + filterTo: new Date(adjusted.to * 1000), sort: 'timestamp', pageLimit: 2000, }) @@ -163,13 +188,25 @@ export const createRumToolHandlers = ( const { query, from, to, metricNames } = GetRumPagePerformanceZodSchema.parse(request.params.arguments) + const adjusted = adjustTimestamps(from, to) + if (!adjusted.ok) { + return { + content: [ + { + type: 'text', + text: `Page performance metrics: ${[]}`, + }, + ], + } + } + // Build a query that focuses on view events with performance metrics const viewQuery = query !== '*' ? `@type:view ${query}` : '@type:view' const response = await apiInstance.listRUMEvents({ filterQuery: viewQuery, - filterFrom: new Date(from * 1000), - filterTo: new Date(to * 1000), + filterFrom: new Date(adjusted.from * 1000), + filterTo: new Date(adjusted.to * 1000), sort: 'timestamp', pageLimit: 2000, }) diff --git a/src/utils/adjustTimestamps.ts b/src/utils/adjustTimestamps.ts new file mode 100644 index 0000000..72df197 --- /dev/null +++ b/src/utils/adjustTimestamps.ts @@ -0,0 +1,25 @@ +// adjustTimestamps set the timeframe to end at the evaluation timestamp. Return not OK if the resulting timeframe is invalid. +export const adjustTimestamps = ( + from: number, + to: number, +): { ok: false } | { ok: true; from: number; to: number } => { + const evalTimestamp = process.env.DATADOG_EVAL_TIMESTAMP + + if (evalTimestamp === undefined) { + throw new Error('[MCP Eval Version] DATADOG_EVAL_TIMESTAMP must be set') + } + + const evalSecs = Math.floor(new Date(evalTimestamp).getTime() / 1000) + + const adjustedTo = Math.min(to, evalSecs) + + if (adjustedTo < from) { + return { ok: false } + } + + return { + ok: true, + from, + to: adjustedTo, + } +} diff --git a/tests/setup.ts b/tests/setup.ts index c833a08..0069835 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -3,6 +3,7 @@ import { afterEach, vi } from 'vitest' process.env.DATADOG_API_KEY = 'test-api-key' process.env.DATADOG_APP_KEY = 'test-app-key' +process.env.DATADOG_EVAL_TIMESTAMP = new Date().toISOString() // Reset handlers after each test afterEach(() => { // server.resetHandlers() From 1d8d9ce3f70f3452cc250da30a30c0a223d54a5d Mon Sep 17 00:00:00 2001 From: PChol22 Date: Thu, 24 Jul 2025 12:10:55 +0200 Subject: [PATCH 5/6] v1.7.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 590382f..3660076 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@anyshift/datadog-mcp-server", - "version": "0.0.1", + "version": "1.7.4", "description": "MCP server for interacting with Datadog API", "repository": { "type": "git", From 0337f75c771bbd59193a9f28aa7231e1a289e195 Mon Sep 17 00:00:00 2001 From: safwentrabelsi Date: Wed, 30 Jul 2025 17:05:43 +0200 Subject: [PATCH 6/6] fix: keep interval between from and to --- src/utils/adjustTimestamps.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/utils/adjustTimestamps.ts b/src/utils/adjustTimestamps.ts index 72df197..949fd16 100644 --- a/src/utils/adjustTimestamps.ts +++ b/src/utils/adjustTimestamps.ts @@ -13,13 +13,30 @@ export const adjustTimestamps = ( const adjustedTo = Math.min(to, evalSecs) - if (adjustedTo < from) { + // Calculate the original interval + const interval = to - from + + // Adjust 'from' to maintain the same interval, but ensure it's not negative + const adjustedFrom = Math.max(0, adjustedTo - interval) + + // If the requested timeframe is entirely after the eval timestamp, + // adjust to show the last 'interval' seconds before eval timestamp + if (from > evalSecs) { + const cappedInterval = Math.min(interval, evalSecs) + return { + ok: true, + from: evalSecs - cappedInterval, + to: evalSecs, + } + } + + if (adjustedTo <= adjustedFrom) { return { ok: false } } return { ok: true, - from, + from: adjustedFrom, to: adjustedTo, } }