datadog-mcp 1.0.0 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ var configSchema = z.object({
35
35
  transport: z.enum(["stdio", "http"]).default("stdio"),
36
36
  port: z.number().default(3e3),
37
37
  host: z.string().default("localhost")
38
- }),
38
+ }).default({}),
39
39
  limits: z.object({
40
40
  maxResults: z.number().default(100),
41
41
  maxLogLines: z.number().default(100),
@@ -44,7 +44,7 @@ var configSchema = z.object({
44
44
  // Default limit for initial queries
45
45
  maxMetricDataPoints: z.number().default(1e3),
46
46
  defaultTimeRangeHours: z.number().default(24)
47
- }),
47
+ }).default({}),
48
48
  features: z.object({
49
49
  readOnly: z.boolean().default(false),
50
50
  disabledTools: z.array(z.string()).default([])
@@ -52,6 +52,22 @@ var configSchema = z.object({
52
52
  });
53
53
 
54
54
  // src/config/index.ts
55
+ function parseEqualsFormat(arg) {
56
+ if (!arg.includes("=")) return null;
57
+ const parts = arg.slice(2).split("=");
58
+ const key = parts[0];
59
+ const value = parts.slice(1).join("=");
60
+ return key && value !== void 0 ? [key, value] : null;
61
+ }
62
+ function parseSpacedFormat(arg, nextArg) {
63
+ if (nextArg && !nextArg.startsWith("--")) {
64
+ return [arg.slice(2), nextArg];
65
+ }
66
+ return null;
67
+ }
68
+ function parseBooleanFlag(arg) {
69
+ return arg.slice(2);
70
+ }
55
71
  function parseArgs() {
56
72
  const strings = {};
57
73
  const booleans = /* @__PURE__ */ new Set();
@@ -60,23 +76,20 @@ function parseArgs() {
60
76
  const arg = argv[i];
61
77
  if (!arg) continue;
62
78
  if (arg.startsWith("--")) {
63
- if (arg.includes("=")) {
64
- const parts = arg.slice(2).split("=");
65
- const key = parts[0];
66
- const value = parts.slice(1).join("=");
67
- if (key && value !== void 0) {
68
- strings[key] = value;
69
- }
70
- } else {
71
- const argName = arg.slice(2);
72
- const nextArg = argv[i + 1];
73
- if (nextArg && !nextArg.startsWith("--")) {
74
- strings[argName] = nextArg;
75
- i++;
76
- } else {
77
- booleans.add(argName);
78
- }
79
+ const equalsResult = parseEqualsFormat(arg);
80
+ if (equalsResult) {
81
+ const [key, value] = equalsResult;
82
+ strings[key] = value;
83
+ continue;
84
+ }
85
+ const spacedResult = parseSpacedFormat(arg, argv[i + 1]);
86
+ if (spacedResult) {
87
+ const [key, value] = spacedResult;
88
+ strings[key] = value;
89
+ i += 1;
90
+ continue;
79
91
  }
92
+ booleans.add(parseBooleanFlag(arg));
80
93
  }
81
94
  }
82
95
  return { strings, booleans };
@@ -98,18 +111,20 @@ function loadConfig() {
98
111
  name: "datadog-mcp",
99
112
  version: "1.0.0",
100
113
  transport: args.strings.transport ?? process.env.MCP_TRANSPORT ?? "stdio",
101
- port: parseInt(args.strings.port ?? process.env.MCP_PORT ?? "3000", 10),
114
+ port: Number.parseInt(args.strings.port ?? process.env.MCP_PORT ?? "3000", 10),
102
115
  host: args.strings.host ?? process.env.MCP_HOST ?? "localhost"
103
116
  },
104
117
  limits: {
105
- maxResults: parseInt(process.env.MCP_MAX_RESULTS ?? "100", 10),
106
- maxLogLines: parseInt(process.env.MCP_MAX_LOG_LINES ?? "500", 10),
107
- maxMetricDataPoints: parseInt(process.env.MCP_MAX_METRIC_POINTS ?? "1000", 10),
108
- defaultTimeRangeHours: parseInt(process.env.MCP_DEFAULT_TIME_RANGE ?? "24", 10)
118
+ maxResults: Number.parseInt(process.env.MCP_MAX_RESULTS ?? "100", 10),
119
+ maxLogLines: Number.parseInt(process.env.MCP_MAX_LOG_LINES ?? "500", 10),
120
+ maxMetricDataPoints: Number.parseInt(process.env.MCP_MAX_METRIC_POINTS ?? "1000", 10),
121
+ defaultTimeRangeHours: Number.parseInt(process.env.MCP_DEFAULT_TIME_RANGE ?? "24", 10)
109
122
  },
110
123
  features: {
111
124
  readOnly: args.booleans.has("read-only") || process.env.MCP_READ_ONLY === "true",
112
- disabledTools: parseDisabledTools(args.strings["disable-tools"] ?? process.env.MCP_DISABLE_TOOLS)
125
+ disabledTools: parseDisabledTools(
126
+ args.strings["disable-tools"] ?? process.env.MCP_DISABLE_TOOLS
127
+ )
113
128
  }
114
129
  };
115
130
  return configSchema.parse(raw);
@@ -194,19 +209,31 @@ function handleDatadogError(error) {
194
209
  case 400:
195
210
  throw new McpError(ErrorCode.InvalidRequest, `Invalid request: ${message}`);
196
211
  case 401:
197
- throw new McpError(DatadogErrorCode.Unauthorized, `Authentication failed: Invalid Datadog API key or APP key`);
212
+ throw new McpError(
213
+ DatadogErrorCode.Unauthorized,
214
+ `Authentication failed: Invalid Datadog API key or APP key`
215
+ );
198
216
  case 403:
199
217
  throw new McpError(DatadogErrorCode.Forbidden, `Authorization denied: ${message}`);
200
218
  case 404:
201
219
  throw new McpError(DatadogErrorCode.NotFound, `Resource not found: ${message}`);
202
220
  case 429:
203
- throw new McpError(DatadogErrorCode.RateLimited, "Rate limit exceeded. Retry after a short delay.");
221
+ throw new McpError(
222
+ DatadogErrorCode.RateLimited,
223
+ "Rate limit exceeded. Retry after a short delay."
224
+ );
204
225
  case 500:
205
226
  case 502:
206
227
  case 503:
207
- throw new McpError(DatadogErrorCode.ServiceUnavailable, "Datadog service temporarily unavailable. Retry later.");
228
+ throw new McpError(
229
+ DatadogErrorCode.ServiceUnavailable,
230
+ "Datadog service temporarily unavailable. Retry later."
231
+ );
208
232
  default:
209
- throw new McpError(ErrorCode.InternalError, `Datadog API error (${apiError.code}): ${message}`);
233
+ throw new McpError(
234
+ ErrorCode.InternalError,
235
+ `Datadog API error (${apiError.code}): ${message}`
236
+ );
210
237
  }
211
238
  }
212
239
  throw new McpError(
@@ -247,7 +274,7 @@ function formatResponse(data) {
247
274
  return [
248
275
  {
249
276
  type: "text",
250
- text: JSON.stringify(data, null, 2)
277
+ text: JSON.stringify(data, null, 2) ?? "null"
251
278
  }
252
279
  ];
253
280
  }
@@ -341,7 +368,16 @@ function buildRumSessionUrl(applicationId, sessionId, site = "datadoghq.com") {
341
368
  }
342
369
 
343
370
  // src/tools/monitors.ts
344
- var ActionSchema = z2.enum(["list", "get", "search", "create", "update", "delete", "mute", "unmute"]);
371
+ var ActionSchema = z2.enum([
372
+ "list",
373
+ "get",
374
+ "search",
375
+ "create",
376
+ "update",
377
+ "delete",
378
+ "mute",
379
+ "unmute"
380
+ ]);
345
381
  var InputSchema = {
346
382
  action: ActionSchema.describe("Action to perform"),
347
383
  id: z2.string().optional().describe("Monitor ID (required for get/update/delete/mute/unmute)"),
@@ -389,8 +425,8 @@ async function listMonitors(api, params, limits, site) {
389
425
  };
390
426
  }
391
427
  async function getMonitor(api, id, site) {
392
- const monitorId = parseInt(id, 10);
393
- if (isNaN(monitorId)) {
428
+ const monitorId = Number.parseInt(id, 10);
429
+ if (Number.isNaN(monitorId)) {
394
430
  throw new Error(`Invalid monitor ID: ${id}`);
395
431
  }
396
432
  const monitor = await api.getMonitor({ monitorId });
@@ -478,7 +514,7 @@ async function createMonitor(api, config) {
478
514
  };
479
515
  }
480
516
  async function updateMonitor(api, id, config) {
481
- const monitorId = parseInt(id, 10);
517
+ const monitorId = Number.parseInt(id, 10);
482
518
  const body = normalizeMonitorConfig(config);
483
519
  const monitor = await api.updateMonitor({ monitorId, body });
484
520
  return {
@@ -487,12 +523,12 @@ async function updateMonitor(api, id, config) {
487
523
  };
488
524
  }
489
525
  async function deleteMonitor(api, id) {
490
- const monitorId = parseInt(id, 10);
526
+ const monitorId = Number.parseInt(id, 10);
491
527
  await api.deleteMonitor({ monitorId });
492
528
  return { success: true, message: `Monitor ${id} deleted` };
493
529
  }
494
530
  async function muteMonitor(api, id, params) {
495
- const monitorId = parseInt(id, 10);
531
+ const monitorId = Number.parseInt(id, 10);
496
532
  const monitor = await api.getMonitor({ monitorId });
497
533
  await api.updateMonitor({
498
534
  monitorId,
@@ -506,7 +542,7 @@ async function muteMonitor(api, id, params) {
506
542
  return { success: true, message: `Monitor ${id} muted` };
507
543
  }
508
544
  async function unmuteMonitor(api, id) {
509
- const monitorId = parseInt(id, 10);
545
+ const monitorId = Number.parseInt(id, 10);
510
546
  const monitor = await api.getMonitor({ monitorId });
511
547
  await api.updateMonitor({
512
548
  monitorId,
@@ -531,7 +567,9 @@ TIP: For alert HISTORY (which monitors triggered), use the events tool with tags
531
567
  checkReadOnly(action, readOnly);
532
568
  switch (action) {
533
569
  case "list":
534
- return toolResult(await listMonitors(api, { name, tags, groupStates, limit }, limits, site));
570
+ return toolResult(
571
+ await listMonitors(api, { name, tags, groupStates, limit }, limits, site)
572
+ );
535
573
  case "get": {
536
574
  const monitorId = requireParam(id, "id", "get");
537
575
  return toolResult(await getMonitor(api, monitorId, site));
@@ -602,9 +640,7 @@ async function listDashboards(api, params, limits) {
602
640
  let dashboards = response.dashboards ?? [];
603
641
  if (params.name) {
604
642
  const lowerName = params.name.toLowerCase();
605
- dashboards = dashboards.filter(
606
- (d) => d.title?.toLowerCase().includes(lowerName)
607
- );
643
+ dashboards = dashboards.filter((d) => d.title?.toLowerCase().includes(lowerName));
608
644
  }
609
645
  const result = dashboards.slice(0, effectiveLimit).map(formatDashboardSummary);
610
646
  return {
@@ -667,7 +703,7 @@ async function deleteDashboard(api, id) {
667
703
  await api.deleteDashboard({ dashboardId: id });
668
704
  return { success: true, message: `Dashboard ${id} deleted` };
669
705
  }
670
- function registerDashboardsTool(server, api, limits, readOnly = false) {
706
+ function registerDashboardsTool(server, api, limits, readOnly = false, _site = "datadoghq.com") {
671
707
  server.tool(
672
708
  "dashboards",
673
709
  "Access Datadog dashboards and visualizations. Actions: list (filter by name/tags), get, create, update, delete. Use for: finding existing views, team dashboards, understanding what is monitored.",
@@ -729,9 +765,9 @@ function parseTime(input, defaultValue) {
729
765
  return input;
730
766
  }
731
767
  const trimmed = input.trim();
732
- const simpleRelativeMatch = trimmed.match(/^(\d+)(s|m|h|d)$/);
768
+ const simpleRelativeMatch = trimmed.match(/^(\d+)([smhd])$/);
733
769
  if (simpleRelativeMatch) {
734
- const value = parseInt(simpleRelativeMatch[1] ?? "0", 10);
770
+ const value = Number.parseInt(simpleRelativeMatch[1] ?? "0", 10);
735
771
  const unit = simpleRelativeMatch[2];
736
772
  const nowTs = now();
737
773
  switch (unit) {
@@ -747,13 +783,13 @@ function parseTime(input, defaultValue) {
747
783
  return defaultValue;
748
784
  }
749
785
  }
750
- const relativeWithTimeMatch = trimmed.match(/^(\d+)(d|h)[@\s](\d{1,2}):(\d{2})(?::(\d{2}))?$/);
786
+ const relativeWithTimeMatch = trimmed.match(/^(\d+)([dh])[@\s](\d{1,2}):(\d{2})(?::(\d{2}))?$/);
751
787
  if (relativeWithTimeMatch) {
752
- const value = parseInt(relativeWithTimeMatch[1] ?? "0", 10);
788
+ const value = Number.parseInt(relativeWithTimeMatch[1] ?? "0", 10);
753
789
  const unit = relativeWithTimeMatch[2];
754
- const hours = parseInt(relativeWithTimeMatch[3] ?? "0", 10);
755
- const minutes = parseInt(relativeWithTimeMatch[4] ?? "0", 10);
756
- const seconds = parseInt(relativeWithTimeMatch[5] ?? "0", 10);
790
+ const hours = Number.parseInt(relativeWithTimeMatch[3] ?? "0", 10);
791
+ const minutes = Number.parseInt(relativeWithTimeMatch[4] ?? "0", 10);
792
+ const seconds = Number.parseInt(relativeWithTimeMatch[5] ?? "0", 10);
757
793
  if (unit === "d") {
758
794
  const date3 = startOfDayAgo(value);
759
795
  date3.setHours(hours, minutes, seconds, 0);
@@ -767,26 +803,27 @@ function parseTime(input, defaultValue) {
767
803
  const keywordMatch = trimmed.match(/^(today|yesterday)[@\s](\d{1,2}):(\d{2})(?::(\d{2}))?$/i);
768
804
  if (keywordMatch) {
769
805
  const keyword = keywordMatch[1]?.toLowerCase();
770
- const hours = parseInt(keywordMatch[2] ?? "0", 10);
771
- const minutes = parseInt(keywordMatch[3] ?? "0", 10);
772
- const seconds = parseInt(keywordMatch[4] ?? "0", 10);
806
+ const hours = Number.parseInt(keywordMatch[2] ?? "0", 10);
807
+ const minutes = Number.parseInt(keywordMatch[3] ?? "0", 10);
808
+ const seconds = Number.parseInt(keywordMatch[4] ?? "0", 10);
773
809
  const daysAgo = keyword === "yesterday" ? 1 : 0;
774
810
  const date2 = startOfDayAgo(daysAgo);
775
811
  date2.setHours(hours, minutes, seconds, 0);
776
812
  return Math.floor(date2.getTime() / 1e3);
777
813
  }
778
814
  const date = new Date(trimmed);
779
- if (!isNaN(date.getTime())) {
815
+ if (!Number.isNaN(date.getTime())) {
780
816
  return Math.floor(date.getTime() / 1e3);
781
817
  }
782
- const ts = parseInt(trimmed, 10);
783
- if (!isNaN(ts)) {
818
+ const ts = Number.parseInt(trimmed, 10);
819
+ if (!Number.isNaN(ts)) {
784
820
  return ts;
785
821
  }
786
822
  return defaultValue;
787
823
  }
788
824
  function ensureValidTimeRange(from, to, minRangeSeconds = 60) {
789
825
  if (from > to) {
826
+ ;
790
827
  [from, to] = [to, from];
791
828
  }
792
829
  if (to - from < minRangeSeconds) {
@@ -804,21 +841,21 @@ function parseDurationToNs(input) {
804
841
  const trimmed = input.trim().toLowerCase();
805
842
  const match = trimmed.match(/^(\d+(?:\.\d+)?)(ns|µs|us|ms|s|m|h|d|w)?$/);
806
843
  if (!match) {
807
- const raw = parseInt(trimmed, 10);
808
- return isNaN(raw) ? void 0 : raw;
844
+ const raw = Number.parseInt(trimmed, 10);
845
+ return Number.isNaN(raw) ? void 0 : raw;
809
846
  }
810
- const value = parseFloat(match[1] ?? "0");
847
+ const value = Number.parseFloat(match[1] ?? "0");
811
848
  const unit = match[2] ?? "ns";
812
849
  const multipliers = {
813
- "ns": 1,
814
- "\xB5s": 1e3,
815
- "us": 1e3,
816
- "ms": 1e6,
817
- "s": 1e9,
818
- "m": 6e10,
819
- "h": 36e11,
820
- "d": 864e11,
821
- "w": 6048e11
850
+ ns: 1,
851
+ \u00B5s: 1e3,
852
+ us: 1e3,
853
+ ms: 1e6,
854
+ s: 1e9,
855
+ m: 6e10,
856
+ h: 36e11,
857
+ d: 864e11,
858
+ w: 6048e11
822
859
  };
823
860
  return Math.floor(value * (multipliers[unit] ?? 1));
824
861
  }
@@ -834,10 +871,18 @@ function formatDurationNs(ns) {
834
871
  var ActionSchema3 = z4.enum(["search", "aggregate"]);
835
872
  var InputSchema3 = {
836
873
  action: ActionSchema3.describe("Action to perform"),
837
- query: z4.string().optional().describe('Log search query (Datadog syntax). Examples: "error", "service:my-service status:error", "error AND timeout"'),
838
- keyword: z4.string().optional().describe("Simple text search - finds logs containing this text (grep-like). Merged with query using AND"),
839
- pattern: z4.string().optional().describe('Regex pattern to match in log message (grep -E style). Example: "ERROR.*timeout|connection refused"'),
840
- from: z4.string().optional().describe("Start time. Formats: ISO 8601, relative (30s, 15m, 2h, 7d), precise (3d@11:45:23, yesterday@14:00)"),
874
+ query: z4.string().optional().describe(
875
+ 'Log search query (Datadog syntax). Examples: "error", "service:my-service status:error", "error AND timeout"'
876
+ ),
877
+ keyword: z4.string().optional().describe(
878
+ "Simple text search - finds logs containing this text (grep-like). Merged with query using AND"
879
+ ),
880
+ pattern: z4.string().optional().describe(
881
+ 'Regex pattern to match in log message (grep -E style). Example: "ERROR.*timeout|connection refused"'
882
+ ),
883
+ from: z4.string().optional().describe(
884
+ "Start time. Formats: ISO 8601, relative (30s, 15m, 2h, 7d), precise (3d@11:45:23, yesterday@14:00)"
885
+ ),
841
886
  to: z4.string().optional().describe('End time. Same formats as "from". Example: from="3d@11:45:23" to="3d@12:55:34"'),
842
887
  service: z4.string().optional().describe("Filter by service name"),
843
888
  host: z4.string().optional().describe("Filter by host"),
@@ -845,8 +890,12 @@ var InputSchema3 = {
845
890
  indexes: z4.array(z4.string()).optional().describe("Log indexes to search"),
846
891
  limit: z4.number().optional().describe("Maximum number of logs to return"),
847
892
  sort: z4.enum(["timestamp", "-timestamp"]).optional().describe("Sort order"),
848
- sample: z4.enum(["first", "spread", "diverse"]).optional().describe("Sampling mode: first (chronological, default), spread (evenly across time range), diverse (distinct message patterns)"),
849
- compact: z4.boolean().optional().describe("Strip custom attributes for token efficiency. Keeps: id, timestamp, service, status, message (truncated), dd.trace_id, error info"),
893
+ sample: z4.enum(["first", "spread", "diverse"]).optional().describe(
894
+ "Sampling mode: first (chronological, default), spread (evenly across time range), diverse (distinct message patterns)"
895
+ ),
896
+ compact: z4.boolean().optional().describe(
897
+ "Strip custom attributes for token efficiency. Keeps: id, timestamp, service, status, message (truncated), dd.trace_id, error info"
898
+ ),
850
899
  groupBy: z4.array(z4.string()).optional().describe("Fields to group by (for aggregate)"),
851
900
  compute: z4.record(z4.unknown()).optional().describe("Compute operations (for aggregate)")
852
901
  };
@@ -876,8 +925,9 @@ function formatLogCompact(log) {
876
925
  const ts = attrs.timestamp;
877
926
  timestamp = ts instanceof Date ? ts.toISOString() : new Date(String(ts)).toISOString();
878
927
  }
879
- const traceId = nestedAttrs["dd.trace_id"] ?? nestedAttrs["trace_id"] ?? attrs["dd.trace_id"] ?? "";
880
- const spanId = nestedAttrs["dd.span_id"] ?? nestedAttrs["span_id"] ?? attrs["dd.span_id"] ?? "";
928
+ const attrsAny = attrs;
929
+ const traceId = nestedAttrs["dd.trace_id"] ?? nestedAttrs["trace_id"] ?? attrsAny["dd.trace_id"] ?? "";
930
+ const spanId = nestedAttrs["dd.span_id"] ?? nestedAttrs["span_id"] ?? attrsAny["dd.span_id"] ?? "";
881
931
  const errorType = nestedAttrs["error.type"] ?? nestedAttrs["error.kind"] ?? "";
882
932
  const errorMessage = nestedAttrs["error.message"] ?? nestedAttrs["error.msg"] ?? "";
883
933
  const fullMessage = attrs.message ?? "";
@@ -1054,35 +1104,66 @@ CORRELATION: Logs contain dd.trace_id in attributes for linking to traces and AP
1054
1104
  SAMPLING: Use sample:"diverse" for error investigation (dedupes by message pattern), sample:"spread" for time distribution.
1055
1105
  TOKEN TIP: Use compact:true to reduce payload size (strips heavy fields) when querying large volumes.`,
1056
1106
  InputSchema3,
1057
- async ({ action, query, keyword, pattern, service, host, status, from, to, indexes, limit, sort, sample, compact, groupBy, compute }) => {
1107
+ async ({
1108
+ action,
1109
+ query,
1110
+ keyword,
1111
+ pattern,
1112
+ service,
1113
+ host,
1114
+ status,
1115
+ from,
1116
+ to,
1117
+ indexes,
1118
+ limit,
1119
+ sort,
1120
+ sample,
1121
+ compact,
1122
+ groupBy,
1123
+ compute
1124
+ }) => {
1058
1125
  try {
1059
1126
  switch (action) {
1060
1127
  case "search": {
1061
- return toolResult(await searchLogs(api, {
1062
- query,
1063
- keyword,
1064
- pattern,
1065
- service,
1066
- host,
1067
- status,
1068
- from,
1069
- to,
1070
- indexes,
1071
- limit,
1072
- sort,
1073
- sample,
1074
- compact
1075
- }, limits, site));
1128
+ return toolResult(
1129
+ await searchLogs(
1130
+ api,
1131
+ {
1132
+ query,
1133
+ keyword,
1134
+ pattern,
1135
+ service,
1136
+ host,
1137
+ status,
1138
+ from,
1139
+ to,
1140
+ indexes,
1141
+ limit,
1142
+ sort,
1143
+ sample,
1144
+ compact
1145
+ },
1146
+ limits,
1147
+ site
1148
+ )
1149
+ );
1076
1150
  }
1077
1151
  case "aggregate": {
1078
- const aggregateQuery = requireParam(query, "query", "aggregate");
1079
- return toolResult(await aggregateLogs(api, {
1080
- query: aggregateQuery,
1081
- from,
1082
- to,
1083
- groupBy,
1084
- compute
1085
- }, limits, site));
1152
+ const aggregateQuery = query ?? "*";
1153
+ return toolResult(
1154
+ await aggregateLogs(
1155
+ api,
1156
+ {
1157
+ query: aggregateQuery,
1158
+ from,
1159
+ to,
1160
+ groupBy,
1161
+ compute
1162
+ },
1163
+ limits,
1164
+ site
1165
+ )
1166
+ );
1086
1167
  }
1087
1168
  default:
1088
1169
  throw new Error(`Unknown action: ${action}`);
@@ -1099,8 +1180,12 @@ import { z as z5 } from "zod";
1099
1180
  var ActionSchema4 = z5.enum(["query", "search", "list", "metadata"]);
1100
1181
  var InputSchema4 = {
1101
1182
  action: ActionSchema4.describe("Action to perform"),
1102
- query: z5.string().optional().describe('For query: PromQL expression (e.g., "avg:system.cpu.user{*}"). For search: grep-like filter on metric names. For list: tag filter.'),
1103
- from: z5.string().optional().describe("Start time (ONLY for query action). Formats: ISO 8601, relative (30s, 15m, 2h, 7d), precise (3d@11:45:23)"),
1183
+ query: z5.string().optional().describe(
1184
+ 'For query: PromQL expression (e.g., "avg:system.cpu.user{*}"). For search: grep-like filter on metric names. For list: tag filter.'
1185
+ ),
1186
+ from: z5.string().optional().describe(
1187
+ "Start time (ONLY for query action). Formats: ISO 8601, relative (30s, 15m, 2h, 7d), precise (3d@11:45:23)"
1188
+ ),
1104
1189
  to: z5.string().optional().describe('End time (ONLY for query action). Same formats as "from".'),
1105
1190
  metric: z5.string().optional().describe("Metric name (for metadata action)"),
1106
1191
  tag: z5.string().optional().describe("Filter by tag"),
@@ -1198,18 +1283,31 @@ Example: max:trace.{service}.request.duration{*}`,
1198
1283
  switch (action) {
1199
1284
  case "query": {
1200
1285
  const metricsQuery = requireParam(query, "query", "query");
1201
- return toolResult(await queryMetrics(metricsV1Api, {
1202
- query: metricsQuery,
1203
- from,
1204
- to
1205
- }, limits, site));
1286
+ return toolResult(
1287
+ await queryMetrics(
1288
+ metricsV1Api,
1289
+ {
1290
+ query: metricsQuery,
1291
+ from,
1292
+ to
1293
+ },
1294
+ limits,
1295
+ site
1296
+ )
1297
+ );
1206
1298
  }
1207
1299
  case "search": {
1208
1300
  const searchQuery = requireParam(query, "query", "search");
1209
- return toolResult(await searchMetrics(metricsV1Api, {
1210
- query: searchQuery,
1211
- limit
1212
- }, limits));
1301
+ return toolResult(
1302
+ await searchMetrics(
1303
+ metricsV1Api,
1304
+ {
1305
+ query: searchQuery,
1306
+ limit
1307
+ },
1308
+ limits
1309
+ )
1310
+ );
1213
1311
  }
1214
1312
  case "list":
1215
1313
  return toolResult(await listMetrics(metricsV1Api, { query }, limits));
@@ -1245,17 +1343,25 @@ var RESERVED_SPAN_FACETS = /* @__PURE__ */ new Set([
1245
1343
  ]);
1246
1344
  var InputSchema5 = {
1247
1345
  action: ActionSchema5.describe("Action to perform"),
1248
- query: z6.string().optional().describe('APM trace search query (Datadog syntax). Example: "@http.status_code:500", "service:my-service status:error"'),
1249
- from: z6.string().optional().describe("Start time. Formats: ISO 8601, relative (30s, 15m, 2h, 7d), precise (3d@11:45:23, yesterday@14:00)"),
1346
+ query: z6.string().optional().describe(
1347
+ 'APM trace search query (Datadog syntax). Example: "@http.status_code:500", "service:my-service status:error"'
1348
+ ),
1349
+ from: z6.string().optional().describe(
1350
+ "Start time. Formats: ISO 8601, relative (30s, 15m, 2h, 7d), precise (3d@11:45:23, yesterday@14:00)"
1351
+ ),
1250
1352
  to: z6.string().optional().describe('End time. Same formats as "from". Example: from="3d@11:45" to="3d@12:55"'),
1251
1353
  service: z6.string().optional().describe('Filter by service name. Example: "my-service", "postgres"'),
1252
1354
  operation: z6.string().optional().describe('Filter by operation name. Example: "express.request", "mongodb.query"'),
1253
- resource: z6.string().optional().describe('Filter by resource name (endpoint/query). Supports wildcards. Example: "GET /api/*", "*orders*"'),
1355
+ resource: z6.string().optional().describe(
1356
+ 'Filter by resource name (endpoint/query). Supports wildcards. Example: "GET /api/*", "*orders*"'
1357
+ ),
1254
1358
  status: z6.enum(["ok", "error"]).optional().describe('Filter by span status - "ok" for successful, "error" for failed spans'),
1255
1359
  env: z6.string().optional().describe('Filter by environment. Example: "production", "staging"'),
1256
1360
  minDuration: z6.string().optional().describe('Minimum span duration (find slow spans). Examples: "1s", "500ms", "100ms"'),
1257
1361
  maxDuration: z6.string().optional().describe('Maximum span duration. Examples: "5s", "1000ms"'),
1258
- httpStatus: z6.string().optional().describe('HTTP status code filter. Examples: "500", "5xx" (500-599), "4xx" (400-499), ">=400"'),
1362
+ httpStatus: z6.string().optional().describe(
1363
+ 'HTTP status code filter. Examples: "500", "5xx" (500-599), "4xx" (400-499), ">=400"'
1364
+ ),
1259
1365
  errorType: z6.string().optional().describe('Filter by error type (grep-like). Example: "TimeoutError", "ConnectionRefused"'),
1260
1366
  errorMessage: z6.string().optional().describe('Filter by error message (grep-like). Example: "timeout", "connection refused"'),
1261
1367
  limit: z6.number().optional().describe("Maximum number of results"),
@@ -1306,6 +1412,18 @@ function formatSpan(span) {
1306
1412
  tags
1307
1413
  };
1308
1414
  }
1415
+ function buildHttpStatusFilter(httpStatus) {
1416
+ const status = httpStatus.toLowerCase();
1417
+ if (status.endsWith("xx")) {
1418
+ const base = Number.parseInt(status[0] ?? "0", 10) * 100;
1419
+ return `@http.status_code:[${base} TO ${base + 99}]`;
1420
+ }
1421
+ if (status.startsWith(">=")) return `@http.status_code:>=${status.slice(2)}`;
1422
+ if (status.startsWith(">")) return `@http.status_code:>${status.slice(1)}`;
1423
+ if (status.startsWith("<=")) return `@http.status_code:<=${status.slice(2)}`;
1424
+ if (status.startsWith("<")) return `@http.status_code:<${status.slice(1)}`;
1425
+ return `@http.status_code:${httpStatus}`;
1426
+ }
1309
1427
  function buildTraceQuery(params) {
1310
1428
  const parts = [];
1311
1429
  if (params.query) {
@@ -1318,11 +1436,7 @@ function buildTraceQuery(params) {
1318
1436
  parts.push(`operation_name:${params.operation}`);
1319
1437
  }
1320
1438
  if (params.resource) {
1321
- if (params.resource.includes("*") || params.resource.includes(" ")) {
1322
- parts.push(`resource_name:${params.resource}`);
1323
- } else {
1324
- parts.push(`resource_name:${params.resource}`);
1325
- }
1439
+ parts.push(`resource_name:${params.resource}`);
1326
1440
  }
1327
1441
  if (params.status) {
1328
1442
  parts.push(`status:${params.status}`);
@@ -1343,21 +1457,7 @@ function buildTraceQuery(params) {
1343
1457
  }
1344
1458
  }
1345
1459
  if (params.httpStatus) {
1346
- const status = params.httpStatus.toLowerCase();
1347
- if (status.endsWith("xx")) {
1348
- const base = parseInt(status[0] ?? "0", 10) * 100;
1349
- parts.push(`@http.status_code:[${base} TO ${base + 99}]`);
1350
- } else if (status.startsWith(">=")) {
1351
- parts.push(`@http.status_code:>=${status.slice(2)}`);
1352
- } else if (status.startsWith(">")) {
1353
- parts.push(`@http.status_code:>${status.slice(1)}`);
1354
- } else if (status.startsWith("<=")) {
1355
- parts.push(`@http.status_code:<=${status.slice(2)}`);
1356
- } else if (status.startsWith("<")) {
1357
- parts.push(`@http.status_code:<${status.slice(1)}`);
1358
- } else {
1359
- parts.push(`@http.status_code:${params.httpStatus}`);
1360
- }
1460
+ parts.push(buildHttpStatusFilter(params.httpStatus));
1361
1461
  }
1362
1462
  if (params.errorType) {
1363
1463
  const escaped = params.errorType.replace(/"/g, '\\"');
@@ -1491,10 +1591,12 @@ async function listApmServices(api, params, limits) {
1491
1591
  to: toTime
1492
1592
  },
1493
1593
  compute: [{ aggregation: "count", type: "total" }],
1494
- groupBy: [{
1495
- facet: "service",
1496
- limit: limits.maxResults
1497
- }]
1594
+ groupBy: [
1595
+ {
1596
+ facet: "service",
1597
+ limit: limits.maxResults
1598
+ }
1599
+ ]
1498
1600
  }
1499
1601
  }
1500
1602
  };
@@ -1521,45 +1623,77 @@ function registerTracesTool(server, spansApi, _servicesApi, limits, site = "data
1521
1623
  `Analyze APM traces for request flow and latency debugging. Actions: search (find spans), aggregate (group stats), services (list APM services). Key filters: minDuration/maxDuration ("500ms", "2s"), httpStatus ("5xx", ">=400"), status (ok/error), errorMessage (grep).
1522
1624
  APM METRICS: Traces auto-generate metrics in trace.{service}.* namespace. Use metrics tool to query: avg:trace.{service}.request.duration{*}`,
1523
1625
  InputSchema5,
1524
- async ({ action, query, from, to, service, operation, resource, status, env, minDuration, maxDuration, httpStatus, errorType, errorMessage, limit, sort, groupBy }) => {
1626
+ async ({
1627
+ action,
1628
+ query,
1629
+ from,
1630
+ to,
1631
+ service,
1632
+ operation,
1633
+ resource,
1634
+ status,
1635
+ env,
1636
+ minDuration,
1637
+ maxDuration,
1638
+ httpStatus,
1639
+ errorType,
1640
+ errorMessage,
1641
+ limit,
1642
+ sort,
1643
+ groupBy
1644
+ }) => {
1525
1645
  try {
1526
1646
  switch (action) {
1527
1647
  case "search": {
1528
- return toolResult(await searchTraces(spansApi, {
1529
- query,
1530
- from,
1531
- to,
1532
- service,
1533
- operation,
1534
- resource,
1535
- status,
1536
- env,
1537
- minDuration,
1538
- maxDuration,
1539
- httpStatus,
1540
- errorType,
1541
- errorMessage,
1542
- limit,
1543
- sort
1544
- }, limits, site));
1648
+ return toolResult(
1649
+ await searchTraces(
1650
+ spansApi,
1651
+ {
1652
+ query,
1653
+ from,
1654
+ to,
1655
+ service,
1656
+ operation,
1657
+ resource,
1658
+ status,
1659
+ env,
1660
+ minDuration,
1661
+ maxDuration,
1662
+ httpStatus,
1663
+ errorType,
1664
+ errorMessage,
1665
+ limit,
1666
+ sort
1667
+ },
1668
+ limits,
1669
+ site
1670
+ )
1671
+ );
1545
1672
  }
1546
1673
  case "aggregate": {
1547
- return toolResult(await aggregateTraces(spansApi, {
1548
- query,
1549
- from,
1550
- to,
1551
- service,
1552
- operation,
1553
- resource,
1554
- status,
1555
- env,
1556
- minDuration,
1557
- maxDuration,
1558
- httpStatus,
1559
- errorType,
1560
- errorMessage,
1561
- groupBy
1562
- }, limits, site));
1674
+ return toolResult(
1675
+ await aggregateTraces(
1676
+ spansApi,
1677
+ {
1678
+ query,
1679
+ from,
1680
+ to,
1681
+ service,
1682
+ operation,
1683
+ resource,
1684
+ status,
1685
+ env,
1686
+ minDuration,
1687
+ maxDuration,
1688
+ httpStatus,
1689
+ errorType,
1690
+ errorMessage,
1691
+ groupBy
1692
+ },
1693
+ limits,
1694
+ site
1695
+ )
1696
+ );
1563
1697
  }
1564
1698
  case "services":
1565
1699
  return toolResult(await listApmServices(spansApi, { env, from, to }, limits));
@@ -1575,7 +1709,16 @@ APM METRICS: Traces auto-generate metrics in trace.{service}.* namespace. Use me
1575
1709
 
1576
1710
  // src/tools/events.ts
1577
1711
  import { z as z7 } from "zod";
1578
- var ActionSchema6 = z7.enum(["list", "get", "create", "search", "aggregate", "top", "timeseries", "incidents"]);
1712
+ var ActionSchema6 = z7.enum([
1713
+ "list",
1714
+ "get",
1715
+ "create",
1716
+ "search",
1717
+ "aggregate",
1718
+ "top",
1719
+ "timeseries",
1720
+ "incidents"
1721
+ ]);
1579
1722
  var InputSchema6 = {
1580
1723
  action: ActionSchema6.describe("Action to perform"),
1581
1724
  id: z7.string().optional().describe("Event ID (for get action)"),
@@ -1619,14 +1762,14 @@ function extractTitleFromMessage(message) {
1619
1762
  if (!message) return "";
1620
1763
  const content = message.replace(/^%%%\s*\n?/, "").trim();
1621
1764
  const firstLine = content.split("\n")[0]?.trim() ?? "";
1622
- return firstLine.replace(/\s+!?\s*$/, "").trim();
1765
+ return firstLine.replace(/\s*!?\s*$/, "").trim();
1623
1766
  }
1624
1767
  function extractMonitorIdFromMessage(message) {
1625
1768
  if (!message) return void 0;
1626
1769
  const match = message.match(/\/monitors\/(\d+)/);
1627
- if (match && match[1]) {
1628
- const id = parseInt(match[1], 10);
1629
- return isNaN(id) ? void 0 : id;
1770
+ if (match?.[1]) {
1771
+ const id = Number.parseInt(match[1], 10);
1772
+ return Number.isNaN(id) ? void 0 : id;
1630
1773
  }
1631
1774
  return void 0;
1632
1775
  }
@@ -1745,8 +1888,8 @@ async function listEventsV1(api, params, limits) {
1745
1888
  };
1746
1889
  }
1747
1890
  async function getEventV1(api, id) {
1748
- const eventId = parseInt(id, 10);
1749
- if (isNaN(eventId)) {
1891
+ const eventId = Number.parseInt(id, 10);
1892
+ if (Number.isNaN(eventId)) {
1750
1893
  throw new Error(`Invalid event ID: ${id}`);
1751
1894
  }
1752
1895
  const response = await api.getEvent({ eventId });
@@ -1910,13 +2053,18 @@ async function aggregateEventsV2(api, params, limits, site) {
1910
2053
  async function topEventsV2(api, params, limits, site) {
1911
2054
  const effectiveQuery = params.query ?? "source:alert";
1912
2055
  const effectiveTags = params.tags ?? ["source:alert"];
1913
- const result = await aggregateEventsV2(api, {
1914
- ...params,
1915
- query: effectiveQuery,
1916
- tags: effectiveTags,
1917
- groupBy: params.groupBy ?? ["monitor_name"],
1918
- limit: params.limit ?? 10
1919
- }, limits, site);
2056
+ const result = await aggregateEventsV2(
2057
+ api,
2058
+ {
2059
+ ...params,
2060
+ query: effectiveQuery,
2061
+ tags: effectiveTags,
2062
+ groupBy: params.groupBy ?? ["monitor_name"],
2063
+ limit: params.limit ?? 10
2064
+ },
2065
+ limits,
2066
+ site
2067
+ );
1920
2068
  return {
1921
2069
  top: result.buckets.map((bucket, index) => ({
1922
2070
  rank: index + 1,
@@ -1949,7 +2097,7 @@ async function timeseriesEventsV2(api, params, limits, site) {
1949
2097
  const fullQuery = buildEventQuery({
1950
2098
  query: params.query ?? "source:alert",
1951
2099
  sources: params.sources,
1952
- tags: params.tags ?? ["source:alert"]
2100
+ tags: params.tags
1953
2101
  });
1954
2102
  const intervalMs = parseIntervalToMs(params.interval);
1955
2103
  const groupByFields = params.groupBy ?? ["monitor_name"];
@@ -2034,7 +2182,7 @@ async function incidentsEventsV2(api, params, limits, site) {
2034
2182
  const fullQuery = buildEventQuery({
2035
2183
  query: params.query ?? "source:alert",
2036
2184
  sources: params.sources,
2037
- tags: params.tags ?? ["source:alert"]
2185
+ tags: params.tags
2038
2186
  });
2039
2187
  const dedupeWindowNs = parseDurationToNs(params.dedupeWindow ?? "5m");
2040
2188
  const dedupeWindowMs = dedupeWindowNs ? Math.floor(dedupeWindowNs / 1e6) : 3e5;
@@ -2149,7 +2297,9 @@ async function incidentsEventsV2(api, params, limits, site) {
2149
2297
  sample: inc.sample
2150
2298
  };
2151
2299
  });
2152
- incidentList.sort((a, b) => new Date(b.firstTrigger).getTime() - new Date(a.firstTrigger).getTime());
2300
+ incidentList.sort(
2301
+ (a, b) => new Date(b.firstTrigger).getTime() - new Date(a.firstTrigger).getTime()
2302
+ );
2153
2303
  const effectiveLimit = Math.min(params.limit ?? 100, 500);
2154
2304
  return {
2155
2305
  incidents: incidentList.slice(0, effectiveLimit),
@@ -2227,20 +2377,44 @@ Use action:"timeseries" with interval:"1h" to see alert trends over time.
2227
2377
  Use action:"incidents" with dedupeWindow:"5m" to deduplicate alerts into incidents.
2228
2378
  Use enrich:true with search to get monitor metadata (slower).`,
2229
2379
  InputSchema6,
2230
- async ({ action, id, query, from, to, priority, sources, tags, limit, title, text, alertType, groupBy, cursor, interval, dedupeWindow, enrich }) => {
2380
+ async ({
2381
+ action,
2382
+ id,
2383
+ query,
2384
+ from,
2385
+ to,
2386
+ priority,
2387
+ sources,
2388
+ tags,
2389
+ limit,
2390
+ title,
2391
+ text,
2392
+ alertType,
2393
+ groupBy,
2394
+ cursor,
2395
+ interval,
2396
+ dedupeWindow,
2397
+ enrich
2398
+ }) => {
2231
2399
  try {
2232
2400
  checkReadOnly(action, readOnly);
2233
2401
  switch (action) {
2234
2402
  case "list":
2235
- return toolResult(await listEventsV1(apiV1, {
2236
- query,
2237
- from,
2238
- to,
2239
- priority,
2240
- sources,
2241
- tags,
2242
- limit
2243
- }, limits));
2403
+ return toolResult(
2404
+ await listEventsV1(
2405
+ apiV1,
2406
+ {
2407
+ query,
2408
+ from,
2409
+ to,
2410
+ priority,
2411
+ sources,
2412
+ tags,
2413
+ limit
2414
+ },
2415
+ limits
2416
+ )
2417
+ );
2244
2418
  case "get": {
2245
2419
  const eventId = requireParam(id, "id", "get");
2246
2420
  return toolResult(await getEventV1(apiV1, eventId));
@@ -2248,25 +2422,32 @@ Use enrich:true with search to get monitor metadata (slower).`,
2248
2422
  case "create": {
2249
2423
  const eventTitle = requireParam(title, "title", "create");
2250
2424
  const eventText = requireParam(text, "text", "create");
2251
- return toolResult(await createEventV1(apiV1, {
2252
- title: eventTitle,
2253
- text: eventText,
2254
- priority,
2255
- tags,
2256
- alertType
2257
- }));
2425
+ return toolResult(
2426
+ await createEventV1(apiV1, {
2427
+ title: eventTitle,
2428
+ text: eventText,
2429
+ priority,
2430
+ tags,
2431
+ alertType
2432
+ })
2433
+ );
2258
2434
  }
2259
2435
  case "search": {
2260
- const result = await searchEventsV2(apiV2, {
2261
- query,
2262
- from,
2263
- to,
2264
- sources,
2265
- tags,
2266
- priority,
2267
- limit,
2268
- cursor
2269
- }, limits, site);
2436
+ const result = await searchEventsV2(
2437
+ apiV2,
2438
+ {
2439
+ query,
2440
+ from,
2441
+ to,
2442
+ sources,
2443
+ tags,
2444
+ priority,
2445
+ limit,
2446
+ cursor
2447
+ },
2448
+ limits,
2449
+ site
2450
+ );
2270
2451
  if (enrich && result.events.length > 0) {
2271
2452
  const enrichedEvents = await enrichWithMonitorMetadata(result.events, monitorsApi);
2272
2453
  return toolResult({ ...result, events: enrichedEvents });
@@ -2274,46 +2455,74 @@ Use enrich:true with search to get monitor metadata (slower).`,
2274
2455
  return toolResult(result);
2275
2456
  }
2276
2457
  case "aggregate":
2277
- return toolResult(await aggregateEventsV2(apiV2, {
2278
- query,
2279
- from,
2280
- to,
2281
- sources,
2282
- tags,
2283
- groupBy,
2284
- limit
2285
- }, limits, site));
2458
+ return toolResult(
2459
+ await aggregateEventsV2(
2460
+ apiV2,
2461
+ {
2462
+ query,
2463
+ from,
2464
+ to,
2465
+ sources,
2466
+ tags,
2467
+ groupBy,
2468
+ limit
2469
+ },
2470
+ limits,
2471
+ site
2472
+ )
2473
+ );
2286
2474
  case "top":
2287
- return toolResult(await topEventsV2(apiV2, {
2288
- query,
2289
- from,
2290
- to,
2291
- sources,
2292
- tags,
2293
- groupBy,
2294
- limit
2295
- }, limits, site));
2475
+ return toolResult(
2476
+ await topEventsV2(
2477
+ apiV2,
2478
+ {
2479
+ query,
2480
+ from,
2481
+ to,
2482
+ sources,
2483
+ tags,
2484
+ groupBy,
2485
+ limit
2486
+ },
2487
+ limits,
2488
+ site
2489
+ )
2490
+ );
2296
2491
  case "timeseries":
2297
- return toolResult(await timeseriesEventsV2(apiV2, {
2298
- query,
2299
- from,
2300
- to,
2301
- sources,
2302
- tags,
2303
- groupBy,
2304
- interval,
2305
- limit
2306
- }, limits, site));
2492
+ return toolResult(
2493
+ await timeseriesEventsV2(
2494
+ apiV2,
2495
+ {
2496
+ query,
2497
+ from,
2498
+ to,
2499
+ sources,
2500
+ tags,
2501
+ groupBy,
2502
+ interval,
2503
+ limit
2504
+ },
2505
+ limits,
2506
+ site
2507
+ )
2508
+ );
2307
2509
  case "incidents":
2308
- return toolResult(await incidentsEventsV2(apiV2, {
2309
- query,
2310
- from,
2311
- to,
2312
- sources,
2313
- tags,
2314
- dedupeWindow,
2315
- limit
2316
- }, limits, site));
2510
+ return toolResult(
2511
+ await incidentsEventsV2(
2512
+ apiV2,
2513
+ {
2514
+ query,
2515
+ from,
2516
+ to,
2517
+ sources,
2518
+ tags,
2519
+ dedupeWindow,
2520
+ limit
2521
+ },
2522
+ limits,
2523
+ site
2524
+ )
2525
+ );
2317
2526
  default:
2318
2527
  throw new Error(`Unknown action: ${action}`);
2319
2528
  }
@@ -2333,7 +2542,9 @@ var InputSchema7 = {
2333
2542
  query: z8.string().optional().describe("Search query (for search action)"),
2334
2543
  status: z8.enum(["active", "stable", "resolved"]).optional().describe("Filter by status (for list)"),
2335
2544
  limit: z8.number().optional().describe("Maximum number of incidents to return"),
2336
- config: z8.record(z8.unknown()).optional().describe("Incident configuration (for create/update). Create requires: title. Update can modify: title, status, severity, fields.")
2545
+ config: z8.record(z8.unknown()).optional().describe(
2546
+ "Incident configuration (for create/update). Create requires: title. Update can modify: title, status, severity, fields."
2547
+ )
2337
2548
  };
2338
2549
  function formatIncident(i) {
2339
2550
  const attrs = i.attributes;
@@ -2385,11 +2596,13 @@ async function searchIncidents(api, query, limits) {
2385
2596
  query,
2386
2597
  pageSize: limits.maxResults
2387
2598
  });
2388
- const incidents = (response.data?.attributes?.incidents ?? []).map((i) => ({
2389
- id: i.data?.id ?? "",
2390
- title: i.data?.attributes?.title ?? "",
2391
- state: i.data?.attributes?.state ?? "unknown"
2392
- }));
2599
+ const incidents = (response.data?.attributes?.incidents ?? []).map(
2600
+ (i) => ({
2601
+ id: i.data?.id ?? "",
2602
+ title: i.data?.attributes?.title ?? "",
2603
+ state: i.data?.attributes?.state ?? "unknown"
2604
+ })
2605
+ );
2393
2606
  return {
2394
2607
  incidents,
2395
2608
  total: response.data?.attributes?.total ?? incidents.length
@@ -2429,7 +2642,7 @@ async function deleteIncident(api, id) {
2429
2642
  message: `Incident ${id} deleted`
2430
2643
  };
2431
2644
  }
2432
- function registerIncidentsTool(server, api, limits, readOnly = false) {
2645
+ function registerIncidentsTool(server, api, limits, readOnly = false, _site = "datadoghq.com") {
2433
2646
  server.tool(
2434
2647
  "incidents",
2435
2648
  "Manage Datadog incidents for incident response. Actions: list, get, search, create, update, delete. Use for: incident management, on-call response, postmortems, tracking MTTR/MTTD.",
@@ -2606,7 +2819,7 @@ async function getSloHistory(api, id, params) {
2606
2819
  }
2607
2820
  };
2608
2821
  }
2609
- function registerSlosTool(server, api, limits, readOnly = false) {
2822
+ function registerSlosTool(server, api, limits, readOnly = false, _site = "datadoghq.com") {
2610
2823
  server.tool(
2611
2824
  "slos",
2612
2825
  "Manage Datadog Service Level Objectives. Actions: list, get, create, update, delete, history. SLO types: metric-based, monitor-based. Use for: reliability tracking, error budgets, SLA compliance, performance targets.",
@@ -2659,7 +2872,9 @@ var InputSchema9 = {
2659
2872
  locations: z10.array(z10.string()).optional().describe("Filter by locations (for list)"),
2660
2873
  tags: z10.array(z10.string()).optional().describe("Filter by tags (for list)"),
2661
2874
  limit: z10.number().optional().describe("Maximum number of tests to return"),
2662
- config: z10.record(z10.unknown()).optional().describe("Test configuration (for create/update). Includes: name, type, config, options, locations, message.")
2875
+ config: z10.record(z10.unknown()).optional().describe(
2876
+ "Test configuration (for create/update). Includes: name, type, config, options, locations, message."
2877
+ )
2663
2878
  };
2664
2879
  function formatTest(t) {
2665
2880
  return {
@@ -2750,7 +2965,7 @@ async function createTest(api, config, testType) {
2750
2965
  }
2751
2966
  async function updateTest(api, id, config) {
2752
2967
  const normalizedConfig = normalizeConfigKeys2(config);
2753
- let testType = "api";
2968
+ let testType;
2754
2969
  try {
2755
2970
  await api.getAPITest({ publicId: id });
2756
2971
  testType = "api";
@@ -2820,7 +3035,7 @@ async function getTestResults(api, id) {
2820
3035
  return { results, testType: "browser" };
2821
3036
  }
2822
3037
  }
2823
- function registerSyntheticsTool(server, api, limits, readOnly = false) {
3038
+ function registerSyntheticsTool(server, api, limits, readOnly = false, _site = "datadoghq.com") {
2824
3039
  server.tool(
2825
3040
  "synthetics",
2826
3041
  "Manage Datadog Synthetic tests (API and Browser). Actions: list, get, create, update, delete, trigger, results. Use for: uptime monitoring, API testing, user journey testing, performance testing, canary deployments.",
@@ -2933,9 +3148,10 @@ async function muteHost(api, hostName, params) {
2933
3148
  override: params.override
2934
3149
  }
2935
3150
  });
3151
+ const muteEndMessage = params.end ? ` until ${new Date(params.end * 1e3).toISOString()}` : " indefinitely";
2936
3152
  return {
2937
3153
  success: true,
2938
- message: `Host ${hostName} muted${params.end ? ` until ${new Date(params.end * 1e3).toISOString()}` : " indefinitely"}`
3154
+ message: `Host ${hostName} muted${muteEndMessage}`
2939
3155
  };
2940
3156
  }
2941
3157
  async function unmuteHost(api, hostName) {
@@ -2950,12 +3166,25 @@ function registerHostsTool(server, api, limits, readOnly = false) {
2950
3166
  "hosts",
2951
3167
  "Manage Datadog infrastructure hosts. Actions: list (with filters), totals (counts), mute (silence alerts), unmute. Use for: infrastructure inventory, host health, silencing noisy hosts during maintenance.",
2952
3168
  InputSchema10,
2953
- async ({ action, filter, from, count, sortField, sortDir, hostName, message, end, override }) => {
3169
+ async ({
3170
+ action,
3171
+ filter,
3172
+ from,
3173
+ count,
3174
+ sortField,
3175
+ sortDir,
3176
+ hostName,
3177
+ message,
3178
+ end,
3179
+ override
3180
+ }) => {
2954
3181
  try {
2955
3182
  checkReadOnly(action, readOnly);
2956
3183
  switch (action) {
2957
3184
  case "list":
2958
- return toolResult(await listHosts(api, { filter, from, count, sortField, sortDir }, limits));
3185
+ return toolResult(
3186
+ await listHosts(api, { filter, from, count, sortField, sortDir }, limits)
3187
+ );
2959
3188
  case "totals":
2960
3189
  return toolResult(await getHostTotals(api));
2961
3190
  case "mute": {
@@ -3141,7 +3370,9 @@ var InputSchema12 = {
3141
3370
  interval: z13.string().optional()
3142
3371
  }).optional().describe("Compute configuration for aggregation"),
3143
3372
  // Performance action parameters
3144
- metrics: z13.array(z13.enum(["lcp", "fcp", "cls", "fid", "inp", "loading_time"])).optional().describe("Core Web Vitals metrics to retrieve (default: all). lcp=Largest Contentful Paint, fcp=First Contentful Paint, cls=Cumulative Layout Shift, fid=First Input Delay, inp=Interaction to Next Paint, loading_time=View loading time"),
3373
+ metrics: z13.array(z13.enum(["lcp", "fcp", "cls", "fid", "inp", "loading_time"])).optional().describe(
3374
+ "Core Web Vitals metrics to retrieve (default: all). lcp=Largest Contentful Paint, fcp=First Contentful Paint, cls=Cumulative Layout Shift, fid=First Input Delay, inp=Interaction to Next Paint, loading_time=View loading time"
3375
+ ),
3145
3376
  // Waterfall action parameters
3146
3377
  applicationId: z13.string().optional().describe("Application ID for waterfall action"),
3147
3378
  sessionId: z13.string().optional().describe("Session ID for waterfall action"),
@@ -3443,10 +3674,7 @@ function formatWaterfallEvent(event) {
3443
3674
  };
3444
3675
  }
3445
3676
  async function getSessionWaterfall(api, params, limits, site) {
3446
- const queryParts = [
3447
- `@application.id:${params.applicationId}`,
3448
- `@session.id:${params.sessionId}`
3449
- ];
3677
+ const queryParts = [`@application.id:${params.applicationId}`, `@session.id:${params.sessionId}`];
3450
3678
  if (params.viewId) {
3451
3679
  queryParts.push(`@view.id:${params.viewId}`);
3452
3680
  }
@@ -3480,22 +3708,44 @@ function registerRumTool(server, api, limits, site = "datadoghq.com") {
3480
3708
  "rum",
3481
3709
  "Query Datadog Real User Monitoring (RUM) data. Actions: applications (list RUM apps), events (search RUM events), aggregate (group and count events), performance (Core Web Vitals: LCP, FCP, CLS, FID, INP), waterfall (session timeline with resources/actions/errors). Use for: frontend performance, user sessions, page views, errors, resource loading.",
3482
3710
  InputSchema12,
3483
- async ({ action, query, from, to, type, sort, limit, groupBy, compute, metrics, applicationId, sessionId, viewId }) => {
3711
+ async ({
3712
+ action,
3713
+ query,
3714
+ from,
3715
+ to,
3716
+ type,
3717
+ sort,
3718
+ limit,
3719
+ groupBy,
3720
+ compute,
3721
+ metrics,
3722
+ applicationId,
3723
+ sessionId,
3724
+ viewId
3725
+ }) => {
3484
3726
  try {
3485
3727
  switch (action) {
3486
3728
  case "applications":
3487
3729
  return toolResult(await listApplications(api));
3488
3730
  case "events":
3489
- return toolResult(await searchEvents(api, { query, from, to, type, sort, limit }, limits, site));
3731
+ return toolResult(
3732
+ await searchEvents(api, { query, from, to, type, sort, limit }, limits, site)
3733
+ );
3490
3734
  case "aggregate":
3491
- return toolResult(await aggregateEvents(api, { query, from, to, groupBy, compute }, limits, site));
3735
+ return toolResult(
3736
+ await aggregateEvents(api, { query, from, to, groupBy, compute }, limits, site)
3737
+ );
3492
3738
  case "performance":
3493
- return toolResult(await getPerformanceMetrics(api, { query, from, to, groupBy, metrics }, limits, site));
3739
+ return toolResult(
3740
+ await getPerformanceMetrics(api, { query, from, to, groupBy, metrics }, limits, site)
3741
+ );
3494
3742
  case "waterfall":
3495
3743
  if (!applicationId || !sessionId) {
3496
3744
  throw new Error("waterfall action requires applicationId and sessionId parameters");
3497
3745
  }
3498
- return toolResult(await getSessionWaterfall(api, { applicationId, sessionId, viewId }, limits, site));
3746
+ return toolResult(
3747
+ await getSessionWaterfall(api, { applicationId, sessionId, viewId }, limits, site)
3748
+ );
3499
3749
  default:
3500
3750
  throw new Error(`Unknown action: ${action}`);
3501
3751
  }
@@ -3653,7 +3903,13 @@ function registerSecurityTool(server, api, limits) {
3653
3903
  }
3654
3904
  return toolResult(await listRules(api, { pageSize, pageCursor }, limits));
3655
3905
  case "signals":
3656
- return toolResult(await searchSignals(api, { query, from, to, severity, status, pageSize, pageCursor }, limits));
3906
+ return toolResult(
3907
+ await searchSignals(
3908
+ api,
3909
+ { query, from, to, severity, status, pageSize, pageCursor },
3910
+ limits
3911
+ )
3912
+ );
3657
3913
  case "findings":
3658
3914
  return toolResult(await listFindings(api, { query, pageSize, pageCursor }, limits));
3659
3915
  default:
@@ -3677,10 +3933,19 @@ var InputSchema14 = {
3677
3933
  excludeAuthorHandle: z15.string().optional().describe("Exclude notebooks by author handle"),
3678
3934
  includeCells: z15.boolean().optional().describe("Include cell content in response (default: true for get)"),
3679
3935
  name: z15.string().optional().describe("Notebook name (for create/update)"),
3680
- cells: z15.array(z15.object({
3681
- type: z15.enum(["markdown", "timeseries", "toplist", "heatmap", "distribution", "log_stream"]),
3682
- content: z15.unknown()
3683
- })).optional().describe("Notebook cells (for create/update)"),
3936
+ cells: z15.array(
3937
+ z15.object({
3938
+ type: z15.enum([
3939
+ "markdown",
3940
+ "timeseries",
3941
+ "toplist",
3942
+ "heatmap",
3943
+ "distribution",
3944
+ "log_stream"
3945
+ ]),
3946
+ content: z15.unknown()
3947
+ })
3948
+ ).optional().describe("Notebook cells (for create/update)"),
3684
3949
  time: z15.object({
3685
3950
  liveSpan: z15.string().optional(),
3686
3951
  start: z15.number().optional(),
@@ -3845,7 +4110,9 @@ async function updateNotebook(api, notebookId, params) {
3845
4110
  };
3846
4111
  });
3847
4112
  }
3848
- const timeConfig = params.time?.liveSpan ? { liveSpan: params.time.liveSpan } : void 0;
4113
+ const timeConfig = params.time?.liveSpan ? {
4114
+ liveSpan: params.time.liveSpan
4115
+ } : void 0;
3849
4116
  const response = await api.updateNotebook({
3850
4117
  notebookId,
3851
4118
  body: {
@@ -3880,38 +4147,61 @@ async function deleteNotebook(api, notebookId) {
3880
4147
  message: `Notebook ${notebookId} deleted successfully`
3881
4148
  };
3882
4149
  }
3883
- function registerNotebooksTool(server, api, limits, readOnly = false) {
4150
+ function registerNotebooksTool(server, api, limits, readOnly = false, _site = "datadoghq.com") {
3884
4151
  server.tool(
3885
4152
  "notebooks",
3886
4153
  "Manage Datadog Notebooks. Actions: list (search notebooks), get (by ID with cells), create (new notebook), update (modify notebook), delete (remove notebook). Use for: runbooks, incident documentation, investigation notes, dashboards as code.",
3887
4154
  InputSchema14,
3888
- async ({ action, id, query, authorHandle, excludeAuthorHandle, includeCells, name, cells, time, status, pageSize, pageNumber }) => {
4155
+ async ({
4156
+ action,
4157
+ id,
4158
+ query,
4159
+ authorHandle,
4160
+ excludeAuthorHandle,
4161
+ includeCells,
4162
+ name,
4163
+ cells,
4164
+ time,
4165
+ status,
4166
+ pageSize,
4167
+ pageNumber
4168
+ }) => {
3889
4169
  try {
3890
4170
  checkReadOnly(action, readOnly);
3891
4171
  switch (action) {
3892
4172
  case "list":
3893
- return toolResult(await listNotebooks(api, { query, authorHandle, excludeAuthorHandle, includeCells, pageSize, pageNumber }, limits));
4173
+ return toolResult(
4174
+ await listNotebooks(
4175
+ api,
4176
+ { query, authorHandle, excludeAuthorHandle, includeCells, pageSize, pageNumber },
4177
+ limits
4178
+ )
4179
+ );
3894
4180
  case "get": {
3895
4181
  const notebookId = requireParam(id, "id", "get");
3896
4182
  return toolResult(await getNotebook(api, notebookId));
3897
4183
  }
3898
4184
  case "create": {
3899
4185
  const notebookName = requireParam(name, "name", "create");
3900
- return toolResult(await createNotebook(api, {
3901
- name: notebookName,
3902
- cells,
3903
- time,
3904
- status
3905
- }));
4186
+ return toolResult(
4187
+ await createNotebook(api, {
4188
+ name: notebookName,
4189
+ cells,
4190
+ time,
4191
+ status
4192
+ })
4193
+ );
3906
4194
  }
3907
4195
  case "update": {
3908
4196
  const notebookId = requireParam(id, "id", "update");
3909
- return toolResult(await updateNotebook(api, notebookId, {
3910
- name,
3911
- cells,
3912
- time,
3913
- status
3914
- }));
4197
+ return toolResult(
4198
+ await updateNotebook(api, notebookId, {
4199
+ name,
4200
+ cells,
4201
+ time,
4202
+ status
4203
+ })
4204
+ );
3915
4205
  }
3916
4206
  case "delete": {
3917
4207
  const notebookId = requireParam(id, "id", "delete");
@@ -3993,7 +4283,9 @@ function registerUsersTool(server, api, limits) {
3993
4283
  try {
3994
4284
  switch (action) {
3995
4285
  case "list":
3996
- return toolResult(await listUsers(api, { filter, status, pageSize, pageNumber }, limits));
4286
+ return toolResult(
4287
+ await listUsers(api, { filter, status, pageSize, pageNumber }, limits)
4288
+ );
3997
4289
  case "get": {
3998
4290
  const userId = requireParam(id, "id", "get");
3999
4291
  return toolResult(await getUser(api, userId));
@@ -4224,9 +4516,18 @@ function registerTagsTool(server, api, _limits, readOnly = false) {
4224
4516
 
4225
4517
  // src/tools/usage.ts
4226
4518
  import { z as z19 } from "zod";
4227
- var ActionSchema18 = z19.enum(["summary", "hosts", "logs", "custom_metrics", "indexed_spans", "ingested_spans"]);
4519
+ var ActionSchema18 = z19.enum([
4520
+ "summary",
4521
+ "hosts",
4522
+ "logs",
4523
+ "custom_metrics",
4524
+ "indexed_spans",
4525
+ "ingested_spans"
4526
+ ]);
4228
4527
  var InputSchema18 = {
4229
- action: ActionSchema18.describe("Action to perform: summary (overall usage), hosts, logs, custom_metrics, indexed_spans, ingested_spans"),
4528
+ action: ActionSchema18.describe(
4529
+ "Action to perform: summary (overall usage), hosts, logs, custom_metrics, indexed_spans, ingested_spans"
4530
+ ),
4230
4531
  from: z19.string().optional().describe('Start time (ISO 8601 date like "2024-01-01", or relative like "30d")'),
4231
4532
  to: z19.string().optional().describe('End time (ISO 8601 date like "2024-01-31", or relative like "now")'),
4232
4533
  includeOrgDetails: z19.boolean().optional().describe("Include usage breakdown by organization (for multi-org accounts)")
@@ -4238,7 +4539,7 @@ function parseDate(dateStr, defaultDate) {
4238
4539
  return new Date(seconds * 1e3);
4239
4540
  }
4240
4541
  const parsed = new Date(dateStr);
4241
- if (!isNaN(parsed.getTime())) {
4542
+ if (!Number.isNaN(parsed.getTime())) {
4242
4543
  return parsed;
4243
4544
  }
4244
4545
  return defaultDate;
@@ -4395,7 +4696,9 @@ function registerUsageTool(server, api, _limits) {
4395
4696
  import { z as z20 } from "zod";
4396
4697
  var ActionSchema19 = z20.enum(["validate"]);
4397
4698
  var InputSchema19 = {
4398
- action: ActionSchema19.describe("Action to perform: validate - test if API key and App key are valid")
4699
+ action: ActionSchema19.describe(
4700
+ "Action to perform: validate - test if API key and App key are valid"
4701
+ )
4399
4702
  };
4400
4703
  function registerAuthTool(server, clients) {
4401
4704
  server.tool(
@@ -4455,14 +4758,26 @@ function registerAllTools(server, clients, limits, features, site = "datadoghq.c
4455
4758
  const { readOnly, disabledTools } = features;
4456
4759
  const enabled = (tool) => !disabledTools.includes(tool);
4457
4760
  if (enabled("monitors")) registerMonitorsTool(server, clients.monitors, limits, readOnly, site);
4458
- if (enabled("dashboards")) registerDashboardsTool(server, clients.dashboards, limits, readOnly, site);
4761
+ if (enabled("dashboards"))
4762
+ registerDashboardsTool(server, clients.dashboards, limits, readOnly, site);
4459
4763
  if (enabled("logs")) registerLogsTool(server, clients.logs, limits, site);
4460
- if (enabled("metrics")) registerMetricsTool(server, clients.metricsV1, clients.metricsV2, limits, site);
4764
+ if (enabled("metrics"))
4765
+ registerMetricsTool(server, clients.metricsV1, clients.metricsV2, limits, site);
4461
4766
  if (enabled("traces")) registerTracesTool(server, clients.spans, clients.services, limits, site);
4462
- if (enabled("events")) registerEventsTool(server, clients.eventsV1, clients.eventsV2, clients.monitors, limits, readOnly, site);
4767
+ if (enabled("events"))
4768
+ registerEventsTool(
4769
+ server,
4770
+ clients.eventsV1,
4771
+ clients.eventsV2,
4772
+ clients.monitors,
4773
+ limits,
4774
+ readOnly,
4775
+ site
4776
+ );
4463
4777
  if (enabled("incidents")) registerIncidentsTool(server, clients.incidents, limits, readOnly, site);
4464
4778
  if (enabled("slos")) registerSlosTool(server, clients.slo, limits, readOnly, site);
4465
- if (enabled("synthetics")) registerSyntheticsTool(server, clients.synthetics, limits, readOnly, site);
4779
+ if (enabled("synthetics"))
4780
+ registerSyntheticsTool(server, clients.synthetics, limits, readOnly, site);
4466
4781
  if (enabled("hosts")) registerHostsTool(server, clients.hosts, limits, readOnly);
4467
4782
  if (enabled("downtimes")) registerDowntimesTool(server, clients.downtimes, limits, readOnly);
4468
4783
  if (enabled("rum")) registerRumTool(server, clients.rum, limits, site);
@@ -4500,8 +4815,9 @@ import { randomUUID } from "crypto";
4500
4815
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4501
4816
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4502
4817
  var transports = {};
4503
- async function connectHttp(server, config) {
4818
+ function createExpressApp(server, config) {
4504
4819
  const app = express();
4820
+ app.disable("x-powered-by");
4505
4821
  app.use(express.json());
4506
4822
  app.get("/health", (_req, res) => {
4507
4823
  res.json({ status: "ok", name: config.name, version: config.version });
@@ -4554,6 +4870,10 @@ async function connectHttp(server, config) {
4554
4870
  res.status(400).json({ error: "Invalid session" });
4555
4871
  }
4556
4872
  });
4873
+ return app;
4874
+ }
4875
+ async function connectHttp(server, config) {
4876
+ const app = createExpressApp(server, config);
4557
4877
  app.listen(config.port, config.host, () => {
4558
4878
  console.error(`[MCP] Datadog MCP server running on http://${config.host}:${config.port}/mcp`);
4559
4879
  console.error(`[MCP] Health check available at http://${config.host}:${config.port}/health`);
@@ -4561,19 +4881,16 @@ async function connectHttp(server, config) {
4561
4881
  }
4562
4882
 
4563
4883
  // src/index.ts
4564
- async function main() {
4565
- try {
4566
- const config = loadConfig();
4567
- const server = createServer(config);
4568
- if (config.server.transport === "http") {
4569
- await connectHttp(server, config.server);
4570
- } else {
4571
- await connectStdio(server);
4572
- }
4573
- } catch (error) {
4574
- console.error("[MCP] Failed to start server:", error);
4575
- process.exit(1);
4884
+ try {
4885
+ const config = loadConfig();
4886
+ const server = createServer(config);
4887
+ if (config.server.transport === "http") {
4888
+ await connectHttp(server, config.server);
4889
+ } else {
4890
+ await connectStdio(server);
4576
4891
  }
4892
+ } catch (error) {
4893
+ console.error("[MCP] Failed to start server:", error);
4894
+ process.exit(1);
4577
4895
  }
4578
- main();
4579
4896
  //# sourceMappingURL=index.js.map