#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
import http from "http";
const DD_API_KEY = process.env.DD_API_KEY;
const DD_APPLICATION_KEY = process.env.DD_APPLICATION_KEY;
if (!DD_API_KEY || !DD_APPLICATION_KEY) {
console.error("Error: DD_API_KEY and DD_APPLICATION_KEY environment variables must be set");
process.exit(1);
}
const server = new Server({
name: "datadog-logs-mcp-server",
version: "1.0.0",
}, {
capabilities: {
tools: {},
},
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search_logs",
description: "Search Datadog logs with a query and time range",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Log search query (e.g., 'env:prd AND service:pms-connectors')",
},
from: {
type: "string",
description: "Start time for log search (e.g., 'now-10m', '2024-01-01T00:00:00Z')",
},
to: {
type: "string",
description: "End time for log search (e.g., 'now', '2024-01-01T01:00:00Z')",
},
limit: {
type: "number",
description: "Maximum number of logs to return (default: 10)",
default: 10,
},
},
required: ["query", "from", "to"],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "search_logs") {
throw new Error(`Unknown tool: ${request.params.name}`);
}
const args = request.params.arguments;
if (!args.query || !args.from || !args.to) {
throw new Error("Missing required arguments: query, from, and to are required");
}
try {
const response = await fetch("https://api.datadoghq.com/api/v2/logs/events/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
"DD-APPLICATION-KEY": DD_APPLICATION_KEY,
"DD-API-KEY": DD_API_KEY,
},
body: JSON.stringify({
filter: {
from: args.from,
to: args.to,
query: args.query,
},
sort: "timestamp",
page: {
limit: args.limit || 10,
},
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Datadog API error (${response.status}): ${errorText}`);
}
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error searching logs: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
async function main() {
const PORT = parseInt(process.env.PORT || "4000");
const httpServer = http.createServer(async (req, res) => {
if (req.url === "/sse" && req.method === "GET") {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
}
else if (req.url === "/messages" && req.method === "POST") {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
const transport = server.transport;
if (transport) {
transport.handlePostMessage(body, res);
}
else {
res.writeHead(400);
res.end("No active SSE connection");
}
});
}
else {
res.writeHead(404);
res.end("Not found");
}
});
httpServer.listen(PORT, () => {
console.error(`Datadog Logs MCP Server running on http://0.0.0.0:${PORT}`);
});
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});