Skip to content

Commit 64d403d

Browse files
authored
feat: add monitors, logs, dashboards and metrics tools (#3)
1 parent 47fa6b9 commit 64d403d

File tree

17 files changed

+379
-15
lines changed

17 files changed

+379
-15
lines changed

README.md

+37-8
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ MCP server for the Datadog API, enabling incident management and more.
44

55
## Features
66

7-
- **Incident Management**: Enable listing and retrieving Datadog incidents through dedicated tools.
8-
- **Extensible Design**: Intended for future integrations with additional Datadog APIs.
7+
- **Observability Tools**: Provides a mechanism to leverage key Datadog monitoring features, such as incidents, monitors, logs, dashboards, and metrics, through the MCP server.
8+
- **Extensible Design**: Designed to easily integrate with additional Datadog APIs, allowing for seamless future feature expansion.
99

1010
## Tools
1111

@@ -24,7 +24,40 @@ MCP server for the Datadog API, enabling incident management and more.
2424
- `incident_id` (string): Incident ID to fetch details for.
2525
- **Returns**: Detailed incident information (title, status, timestamps, etc.).
2626

27-
3. _(Planned)_: Additional tools for creating, updating, or resolving incidents, as well as for managing other Datadog resources (e.g., dashboards, monitors).
27+
3. `get_monitors`
28+
29+
- Fetch the status of Datadog monitors.
30+
- **Inputs**:
31+
- `groupStates` (optional array): States to filter (e.g., alert, warn, no data, ok).
32+
- `name` (optional string): Filter by name.
33+
- `tags` (optional array): Filter by tags.
34+
- **Returns**: Monitors data and a summary of their statuses.
35+
36+
4. `get_logs`
37+
38+
- Search and retrieve logs from Datadog.
39+
- **Inputs**:
40+
- `query` (string): Datadog logs query string.
41+
- `from` (number): Start time in epoch seconds.
42+
- `to` (number): End time in epoch seconds.
43+
- `limit` (optional number): Maximum number of logs to return (defaults to 100).
44+
- **Returns**: Array of matching logs.
45+
46+
5. `list_dashboards`
47+
48+
- Get a list of dashboards from Datadog.
49+
- **Inputs**:
50+
- `name` (optional string): Filter dashboards by name.
51+
- `tags` (optional array): Filter dashboards by tags.
52+
- **Returns**: Array of dashboards with URL references.
53+
54+
6. `get_metrics`
55+
- Retrieve metrics data from Datadog.
56+
- **Inputs**:
57+
- `query` (string): Metrics query string.
58+
- `from` (number): Start time in epoch seconds.
59+
- `to` (number): End time in epoch seconds.
60+
- **Returns**: Metrics data for the queried timeframe.
2861

2962
## Setup
3063

@@ -122,8 +155,4 @@ Contributions are welcome! Feel free to open an issue or a pull request if you h
122155

123156
## License
124157

125-
This project is licensed under the [MIT License](./LICENSE).
126-
127-
```
128-
129-
```
158+
This project is licensed under the [Apache License, Version 2.0](./LICENSE).

src/index.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import {
1515
} from '@modelcontextprotocol/sdk/types.js'
1616
import { log, mcpDatadogVersion } from './utils/helper'
1717
import { INCIDENT_HANDLERS, INCIDENT_TOOLS } from './tools/incident'
18+
import { METRICS_TOOLS, METRICS_HANDLERS } from './tools/metrics'
19+
import { LOGS_TOOLS, LOGS_HANDLERS } from './tools/logs'
20+
import { MONITORS_TOOLS, MONITORS_HANDLERS } from './tools/monitors'
21+
import { DASHBOARDS_TOOLS, DASHBOARDS_HANDLERS } from './tools/dashboards'
1822
import { ToolHandlers } from './utils/types'
1923

2024
const server = new Server(
@@ -39,12 +43,22 @@ server.onerror = (error) => {
3943
*/
4044
server.setRequestHandler(ListToolsRequestSchema, async () => {
4145
return {
42-
tools: [...INCIDENT_TOOLS],
46+
tools: [
47+
...INCIDENT_TOOLS,
48+
...METRICS_TOOLS,
49+
...LOGS_TOOLS,
50+
...MONITORS_TOOLS,
51+
...DASHBOARDS_TOOLS,
52+
],
4353
}
4454
})
4555

4656
const TOOL_HANDLERS: ToolHandlers = {
4757
...INCIDENT_HANDLERS,
58+
...METRICS_HANDLERS,
59+
...LOGS_HANDLERS,
60+
...MONITORS_HANDLERS,
61+
...DASHBOARDS_HANDLERS,
4862
}
4963
/**
5064
* Handler for invoking Datadog-related tools in the mcp-server-datadog.

src/tools/dashboards/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { DASHBOARDS_TOOLS, DASHBOARDS_HANDLERS } from './tool'

src/tools/dashboards/schema.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { z } from 'zod'
2+
3+
export const ListDashboardsZodSchema = z.object({
4+
name: z.string().optional().describe('Filter dashboards by name'),
5+
tags: z.array(z.string()).optional().describe('Filter dashboards by tags'),
6+
})

src/tools/dashboards/tool.ts

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { ExtendedTool, ToolHandlers } from '../../utils/types'
2+
import { v1 } from '@datadog/datadog-api-client'
3+
import { createToolSchema } from '../../utils/tool'
4+
import { ListDashboardsZodSchema } from './schema'
5+
import { datadogConfig } from '../../utils/datadog'
6+
7+
type DashboardsToolName = 'list_dashboards'
8+
type DashboardsTool = ExtendedTool<DashboardsToolName>
9+
10+
export const DASHBOARDS_TOOLS: DashboardsTool[] = [
11+
createToolSchema(
12+
ListDashboardsZodSchema,
13+
'list_dashboards',
14+
'Get list of dashboards from Datadog',
15+
),
16+
] as const
17+
18+
const API_INSTANCE = new v1.DashboardsApi(datadogConfig)
19+
20+
type DashboardsToolHandlers = ToolHandlers<DashboardsToolName>
21+
22+
export const DASHBOARDS_HANDLERS: DashboardsToolHandlers = {
23+
list_dashboards: async (request) => {
24+
const { name, tags } = ListDashboardsZodSchema.parse(
25+
request.params.arguments,
26+
)
27+
28+
const response = await API_INSTANCE.listDashboards({
29+
filterShared: false,
30+
})
31+
32+
if (!response.dashboards) {
33+
throw new Error('No dashboards data returned')
34+
}
35+
36+
// Filter dashboards based on name and tags if provided
37+
let filteredDashboards = response.dashboards
38+
if (name) {
39+
const searchTerm = name.toLowerCase()
40+
filteredDashboards = filteredDashboards.filter((dashboard) =>
41+
dashboard.title?.toLowerCase().includes(searchTerm),
42+
)
43+
}
44+
if (tags && tags.length > 0) {
45+
filteredDashboards = filteredDashboards.filter((dashboard) => {
46+
const dashboardTags = dashboard.description?.split(',') || []
47+
return tags.every((tag) => dashboardTags.includes(tag))
48+
})
49+
}
50+
51+
const dashboards = filteredDashboards.map((dashboard) => ({
52+
...dashboard,
53+
url: `https://app.datadoghq.com/dashboard/${dashboard.id}`,
54+
}))
55+
56+
return {
57+
content: [
58+
{
59+
type: 'text',
60+
text: `Dashboards: ${JSON.stringify(dashboards)}`,
61+
},
62+
],
63+
}
64+
},
65+
} as const

src/tools/incident/schema.ts

-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,3 @@ export const ListIncidentsZodSchema = z.object({
88
export const GetIncidentZodSchema = z.object({
99
incidentId: z.string().nonempty(),
1010
})
11-
12-
export type ListIncidentsArgs = z.infer<typeof ListIncidentsZodSchema>
13-
export type GetIncidentArgs = z.infer<typeof GetIncidentZodSchema>

src/tools/incident/tool.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ type IncidentToolHandlers = ToolHandlers<IncidentToolName>
2626

2727
export const INCIDENT_HANDLERS: IncidentToolHandlers = {
2828
list_incidents: async (request) => {
29-
const pageSize = Number(request.params.arguments?.pageSize) || 10
30-
const pageOffset = Number(request.params.arguments?.pageOffset) || 0
29+
const { pageSize, pageOffset } = ListIncidentsZodSchema.parse(
30+
request.params.arguments,
31+
)
32+
3133
const res = await API_INSTANCE.listIncidents({
3234
pageSize: pageSize,
3335
pageOffset: pageOffset,
@@ -44,7 +46,7 @@ export const INCIDENT_HANDLERS: IncidentToolHandlers = {
4446
}
4547
},
4648
get_incident: async (request) => {
47-
const incidentId = request.params.arguments?.incidentId as string
49+
const { incidentId } = GetIncidentZodSchema.parse(request.params.arguments)
4850
const res = await API_INSTANCE.getIncident({
4951
incidentId: incidentId,
5052
})

src/tools/logs/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { LOGS_TOOLS, LOGS_HANDLERS } from './tool'

src/tools/logs/schema.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { z } from 'zod'
2+
3+
export const GetLogsZodSchema = z.object({
4+
query: z.string().default('').describe('Datadog logs query string'),
5+
from: z.number().describe('Start time in epoch seconds'),
6+
to: z.number().describe('End time in epoch seconds'),
7+
limit: z
8+
.number()
9+
.optional()
10+
.default(100)
11+
.describe('Maximum number of logs to return. Default is 100.'),
12+
})

src/tools/logs/tool.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { ExtendedTool, ToolHandlers } from '../../utils/types'
2+
import { v2 } from '@datadog/datadog-api-client'
3+
import { createToolSchema } from '../../utils/tool'
4+
import { GetLogsZodSchema } from './schema'
5+
import { datadogConfig } from '../../utils/datadog'
6+
7+
type LogsToolName = 'get_logs'
8+
type LogsTool = ExtendedTool<LogsToolName>
9+
10+
export const LOGS_TOOLS: LogsTool[] = [
11+
createToolSchema(
12+
GetLogsZodSchema,
13+
'get_logs',
14+
'Search and retrieve logs from Datadog',
15+
),
16+
] as const
17+
18+
const API_INSTANCE = new v2.LogsApi(datadogConfig)
19+
20+
type LogsToolHandlers = ToolHandlers<LogsToolName>
21+
22+
export const LOGS_HANDLERS: LogsToolHandlers = {
23+
get_logs: async (request) => {
24+
const { query, from, to, limit } = GetLogsZodSchema.parse(
25+
request.params.arguments,
26+
)
27+
28+
const response = await API_INSTANCE.listLogs({
29+
body: {
30+
filter: {
31+
query,
32+
// `from` and `to` are in epoch seconds, but the Datadog API expects milliseconds
33+
from: `${from * 1000}`,
34+
to: `${to * 1000}`,
35+
},
36+
page: {
37+
limit,
38+
},
39+
sort: '-timestamp',
40+
},
41+
})
42+
43+
if (response.data == null) {
44+
throw new Error('No logs data returned')
45+
}
46+
47+
return {
48+
content: [
49+
{
50+
type: 'text',
51+
text: `Logs data: ${JSON.stringify(response.data)}`,
52+
},
53+
],
54+
}
55+
},
56+
} as const

src/tools/metrics/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { METRICS_TOOLS, METRICS_HANDLERS } from './tool'

src/tools/metrics/schema.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { z } from 'zod'
2+
3+
export const GetMetricsZodSchema = z.object({
4+
from: z.number().describe('Start time in epoch seconds'),
5+
to: z.number().describe('End time in epoch seconds'),
6+
query: z
7+
.string()
8+
.describe('Datadog metrics query string. e.g. "avg:system.cpu.user{*}'),
9+
})
10+
11+
export type GetMetricsArgs = z.infer<typeof GetMetricsZodSchema>

src/tools/metrics/tool.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ExtendedTool, ToolHandlers } from '../../utils/types'
2+
import { v1 } from '@datadog/datadog-api-client'
3+
import { createToolSchema } from '../../utils/tool'
4+
import { GetMetricsZodSchema } from './schema'
5+
import { datadogConfig } from '../../utils/datadog'
6+
7+
type MetricsToolName = 'get_metrics'
8+
type MetricsTool = ExtendedTool<MetricsToolName>
9+
10+
export const METRICS_TOOLS: MetricsTool[] = [
11+
createToolSchema(
12+
GetMetricsZodSchema,
13+
'get_metrics',
14+
'Get metrics data from Datadog',
15+
),
16+
] as const
17+
18+
const API_INSTANCE = new v1.MetricsApi(datadogConfig)
19+
20+
type MetricsToolHandlers = ToolHandlers<MetricsToolName>
21+
22+
export const METRICS_HANDLERS: MetricsToolHandlers = {
23+
get_metrics: async (request) => {
24+
const { from, to, query } = GetMetricsZodSchema.parse(
25+
request.params.arguments,
26+
)
27+
28+
const response = await API_INSTANCE.queryMetrics({
29+
from,
30+
to,
31+
query,
32+
})
33+
34+
return {
35+
content: [
36+
{
37+
type: 'text',
38+
text: `Metrics data: ${JSON.stringify({ response })}`,
39+
},
40+
],
41+
}
42+
},
43+
} as const

src/tools/monitors/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { MONITORS_TOOLS, MONITORS_HANDLERS } from './tool'

src/tools/monitors/schema.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { z } from 'zod'
2+
3+
export const GetMonitorsZodSchema = z.object({
4+
groupStates: z
5+
.array(z.enum(['alert', 'warn', 'no data', 'ok']))
6+
.optional()
7+
.describe('Filter monitors by their states'),
8+
name: z.string().optional().describe('Filter monitors by name'),
9+
tags: z.array(z.string()).optional().describe('Filter monitors by tags'),
10+
})

0 commit comments

Comments
 (0)