datadog-mcp 1.0.2 → 1.0.5
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/README.md +21 -1
- package/dist/index.js +55 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Datadog MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://sonarcloud.io/summary/new_code?id=TANTIOPE_datadog-mcp-server)
|
|
3
4
|
[](https://github.com/tantiope/datadog-mcp-server/actions/workflows/ci.yml)
|
|
4
5
|
[](https://www.npmjs.com/package/datadog-mcp)
|
|
5
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
|
-
[](https://sonarcloud.io/summary/new_code?id=TANTIOPE_datadog-mcp-server)
|
|
7
7
|
[](https://sonarcloud.io/summary/new_code?id=TANTIOPE_datadog-mcp-server)
|
|
8
8
|
|
|
9
9
|
> **DISCLAIMER**: This is a community-maintained project and is not officially affiliated with, endorsed by, or supported by Datadog, Inc. This MCP server utilizes the Datadog API but is developed independently.
|
|
@@ -80,6 +80,26 @@ DD_SITE=datadoghq.com # Default. Use datadoghq.eu for EU, etc.
|
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
### Kubernetes
|
|
84
|
+
|
|
85
|
+
**Use environment variables instead of container args:**
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
env:
|
|
89
|
+
- name: DD_API_KEY
|
|
90
|
+
value: "your-api-key"
|
|
91
|
+
- name: DD_APP_KEY
|
|
92
|
+
value: "your-app-key"
|
|
93
|
+
- name: MCP_TRANSPORT
|
|
94
|
+
value: "http"
|
|
95
|
+
- name: MCP_PORT
|
|
96
|
+
value: "3000"
|
|
97
|
+
- name: MCP_HOST
|
|
98
|
+
value: "0.0.0.0"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> **Note:** Kubernetes `args:` replaces the entire Dockerfile CMD, causing Node.js to receive the flags instead of your application. Environment variables avoid this issue.
|
|
102
|
+
|
|
83
103
|
### HTTP Transport
|
|
84
104
|
|
|
85
105
|
When running with `--transport=http`:
|
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
|
-
|
|
64
|
-
|
|
65
|
-
const key =
|
|
66
|
-
|
|
67
|
-
|
|
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 += 1;
|
|
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;
|
|
79
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;
|
|
91
|
+
}
|
|
92
|
+
booleans.add(parseBooleanFlag(arg));
|
|
80
93
|
}
|
|
81
94
|
}
|
|
82
95
|
return { strings, booleans };
|
|
@@ -261,7 +274,7 @@ function formatResponse(data) {
|
|
|
261
274
|
return [
|
|
262
275
|
{
|
|
263
276
|
type: "text",
|
|
264
|
-
text: JSON.stringify(data, null, 2)
|
|
277
|
+
text: JSON.stringify(data, null, 2) ?? "null"
|
|
265
278
|
}
|
|
266
279
|
];
|
|
267
280
|
}
|
|
@@ -1136,7 +1149,7 @@ TOKEN TIP: Use compact:true to reduce payload size (strips heavy fields) when qu
|
|
|
1136
1149
|
);
|
|
1137
1150
|
}
|
|
1138
1151
|
case "aggregate": {
|
|
1139
|
-
const aggregateQuery =
|
|
1152
|
+
const aggregateQuery = query ?? "*";
|
|
1140
1153
|
return toolResult(
|
|
1141
1154
|
await aggregateLogs(
|
|
1142
1155
|
api,
|
|
@@ -1399,6 +1412,18 @@ function formatSpan(span) {
|
|
|
1399
1412
|
tags
|
|
1400
1413
|
};
|
|
1401
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
|
+
}
|
|
1402
1427
|
function buildTraceQuery(params) {
|
|
1403
1428
|
const parts = [];
|
|
1404
1429
|
if (params.query) {
|
|
@@ -1432,21 +1457,7 @@ function buildTraceQuery(params) {
|
|
|
1432
1457
|
}
|
|
1433
1458
|
}
|
|
1434
1459
|
if (params.httpStatus) {
|
|
1435
|
-
|
|
1436
|
-
if (status.endsWith("xx")) {
|
|
1437
|
-
const base = Number.parseInt(status[0] ?? "0", 10) * 100;
|
|
1438
|
-
parts.push(`@http.status_code:[${base} TO ${base + 99}]`);
|
|
1439
|
-
} else if (status.startsWith(">=")) {
|
|
1440
|
-
parts.push(`@http.status_code:>=${status.slice(2)}`);
|
|
1441
|
-
} else if (status.startsWith(">")) {
|
|
1442
|
-
parts.push(`@http.status_code:>${status.slice(1)}`);
|
|
1443
|
-
} else if (status.startsWith("<=")) {
|
|
1444
|
-
parts.push(`@http.status_code:<=${status.slice(2)}`);
|
|
1445
|
-
} else if (status.startsWith("<")) {
|
|
1446
|
-
parts.push(`@http.status_code:<${status.slice(1)}`);
|
|
1447
|
-
} else {
|
|
1448
|
-
parts.push(`@http.status_code:${params.httpStatus}`);
|
|
1449
|
-
}
|
|
1460
|
+
parts.push(buildHttpStatusFilter(params.httpStatus));
|
|
1450
1461
|
}
|
|
1451
1462
|
if (params.errorType) {
|
|
1452
1463
|
const escaped = params.errorType.replace(/"/g, '\\"');
|
|
@@ -1751,7 +1762,7 @@ function extractTitleFromMessage(message) {
|
|
|
1751
1762
|
if (!message) return "";
|
|
1752
1763
|
const content = message.replace(/^%%%\s*\n?/, "").trim();
|
|
1753
1764
|
const firstLine = content.split("\n")[0]?.trim() ?? "";
|
|
1754
|
-
return firstLine.replace(/\s
|
|
1765
|
+
return firstLine.replace(/\s*!?\s*$/, "").trim();
|
|
1755
1766
|
}
|
|
1756
1767
|
function extractMonitorIdFromMessage(message) {
|
|
1757
1768
|
if (!message) return void 0;
|
|
@@ -2086,7 +2097,7 @@ async function timeseriesEventsV2(api, params, limits, site) {
|
|
|
2086
2097
|
const fullQuery = buildEventQuery({
|
|
2087
2098
|
query: params.query ?? "source:alert",
|
|
2088
2099
|
sources: params.sources,
|
|
2089
|
-
tags: params.tags
|
|
2100
|
+
tags: params.tags
|
|
2090
2101
|
});
|
|
2091
2102
|
const intervalMs = parseIntervalToMs(params.interval);
|
|
2092
2103
|
const groupByFields = params.groupBy ?? ["monitor_name"];
|
|
@@ -2171,7 +2182,7 @@ async function incidentsEventsV2(api, params, limits, site) {
|
|
|
2171
2182
|
const fullQuery = buildEventQuery({
|
|
2172
2183
|
query: params.query ?? "source:alert",
|
|
2173
2184
|
sources: params.sources,
|
|
2174
|
-
tags: params.tags
|
|
2185
|
+
tags: params.tags
|
|
2175
2186
|
});
|
|
2176
2187
|
const dedupeWindowNs = parseDurationToNs(params.dedupeWindow ?? "5m");
|
|
2177
2188
|
const dedupeWindowMs = dedupeWindowNs ? Math.floor(dedupeWindowNs / 1e6) : 3e5;
|
|
@@ -4804,8 +4815,9 @@ import { randomUUID } from "crypto";
|
|
|
4804
4815
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4805
4816
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
4806
4817
|
var transports = {};
|
|
4807
|
-
|
|
4818
|
+
function createExpressApp(server, config) {
|
|
4808
4819
|
const app = express();
|
|
4820
|
+
app.disable("x-powered-by");
|
|
4809
4821
|
app.use(express.json());
|
|
4810
4822
|
app.get("/health", (_req, res) => {
|
|
4811
4823
|
res.json({ status: "ok", name: config.name, version: config.version });
|
|
@@ -4858,6 +4870,10 @@ async function connectHttp(server, config) {
|
|
|
4858
4870
|
res.status(400).json({ error: "Invalid session" });
|
|
4859
4871
|
}
|
|
4860
4872
|
});
|
|
4873
|
+
return app;
|
|
4874
|
+
}
|
|
4875
|
+
async function connectHttp(server, config) {
|
|
4876
|
+
const app = createExpressApp(server, config);
|
|
4861
4877
|
app.listen(config.port, config.host, () => {
|
|
4862
4878
|
console.error(`[MCP] Datadog MCP server running on http://${config.host}:${config.port}/mcp`);
|
|
4863
4879
|
console.error(`[MCP] Health check available at http://${config.host}:${config.port}/health`);
|