diff --git a/README.md b/README.md index 4b7ff34..ac7ad52 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ MCP server for the Datadog API, enabling incident management and more. - `groupStates` (optional array): States to filter (e.g., alert, warn, no data, ok). - `name` (optional string): Filter by name. - `tags` (optional array): Filter by tags. + - `idOffset` (optional number): Start listing from this monitor ID. Use with pageSize=1 to get a specific monitor. + - `page` (optional number): Page number for pagination (0-based). + - `pageSize` (optional number): Number of monitors per page (default: 100). - **Returns**: Monitors data and a summary of their statuses. 4. `get_logs` diff --git a/src/tools/monitors/schema.ts b/src/tools/monitors/schema.ts index e811b4c..a98fbc9 100644 --- a/src/tools/monitors/schema.ts +++ b/src/tools/monitors/schema.ts @@ -4,7 +4,37 @@ export const GetMonitorsZodSchema = z.object({ groupStates: z .array(z.enum(['alert', 'warn', 'no data', 'ok'])) .optional() - .describe('Filter monitors by their states'), - name: z.string().optional().describe('Filter monitors by name'), - tags: z.array(z.string()).optional().describe('Filter monitors by tags'), + .describe( + 'Filter monitors by their states (e.g., alert, warn, no data, ok)', + ), + name: z + .string() + .optional() + .describe('Filter monitors by name (case-sensitive)'), + tags: z + .array(z.string()) + .optional() + .describe('Filter monitors by tags (e.g., ["env:prod", "service:api"])'), + idOffset: z + .number() + .int() + .optional() + .describe( + 'Start listing from this monitor ID. Use with pageSize=1 to get a specific monitor. For pagination, use the last monitor ID from previous response.', + ), + page: z + .number() + .int() + .optional() + .describe( + 'Page number for pagination (0-based). Use with pageSize parameter.', + ), + pageSize: z + .number() + .int() + .optional() + .default(100) + .describe( + 'Number of monitors per page (default: 100). Set to 1 with idOffset to get a specific monitor.', + ), }) diff --git a/src/tools/monitors/tool.ts b/src/tools/monitors/tool.ts index 59fd468..afff62a 100644 --- a/src/tools/monitors/tool.ts +++ b/src/tools/monitors/tool.ts @@ -23,14 +23,16 @@ export const createMonitorsToolHandlers = ( ): MonitorsToolHandlers => { return { get_monitors: async (request) => { - const { groupStates, name, tags } = GetMonitorsZodSchema.parse( - request.params.arguments, - ) + const { groupStates, name, tags, idOffset, page, pageSize } = + GetMonitorsZodSchema.parse(request.params.arguments) const response = await apiInstance.listMonitors({ groupStates: groupStates?.join(','), name, tags: tags?.join(','), + page, + pageSize, + idOffset, }) if (response == null) { @@ -105,6 +107,14 @@ export const createMonitorsToolHandlers = ( type: 'text', text: `Summary of monitors: ${JSON.stringify(summary)}`, }, + ...(monitors.length > 0 + ? [ + { + type: 'text', + text: `Last monitor ID: ${monitors[monitors.length - 1].id}`, + }, + ] + : []), ], } }, diff --git a/tests/tools/monitors.test.ts b/tests/tools/monitors.test.ts index 05cace1..eeea572 100644 --- a/tests/tools/monitors.test.ts +++ b/tests/tools/monitors.test.ts @@ -192,6 +192,90 @@ describe('Monitors Tool', () => { server.close() }) + it('should handle pagination', async () => { + const mockHandler = http.get(monitorsEndpoint, async (info) => { + const url = new URL(info.request.url) + const page = parseInt(url.searchParams.get('page') || '0') + const pageSize = parseInt(url.searchParams.get('page_size') || '100') + + // Generate mock data based on pagination parameters + const monitors = Array.from({ length: pageSize }, (_, i) => ({ + id: page * pageSize + i + 1, + name: `Monitor ${page * pageSize + i + 1}`, + type: 'metric alert', + overall_state: 'OK', + tags: ['env:test'], + query: 'avg(last_5m):avg:system.cpu.user{*} > 80', + })) + + return HttpResponse.json(monitors) + }) + + const server = setupServer(mockHandler) + + await server.boundary(async () => { + const request = createMockToolRequest('get_monitors', { + page: 1, + pageSize: 50, + }) + const response = (await toolHandlers.get_monitors( + request, + )) as unknown as DatadogToolResponse + + // Check pagination results + const monitors = JSON.parse( + response.content[0].text.replace('Monitors: ', ''), + ) + expect(monitors.length).toBe(50) + expect(monitors[0].id).toBe(51) // First ID on page 1 with pageSize 50 + expect(monitors[49].id).toBe(100) // Last ID on page 1 with pageSize 50 + expect(response.content[2].text).toBe('Last monitor ID: 100') + })() + + server.close() + }) + + it('should handle id offset', async () => { + const mockHandler = http.get(monitorsEndpoint, async (info) => { + const url = new URL(info.request.url) + const idOffset = parseInt(url.searchParams.get('id_offset') || '0') + + // Generate mock data starting from id_offset + const monitors = Array.from({ length: 3 }, (_, i) => ({ + id: idOffset + i + 1, + name: `Monitor ${idOffset + i + 1}`, + type: 'metric alert', + overall_state: 'OK', + tags: ['env:test'], + query: 'avg(last_5m):avg:system.cpu.user{*} > 80', + })) + + return HttpResponse.json(monitors) + }) + + const server = setupServer(mockHandler) + + await server.boundary(async () => { + const request = createMockToolRequest('get_monitors', { + idOffset: 100, + }) + const response = (await toolHandlers.get_monitors( + request, + )) as unknown as DatadogToolResponse + + // Check id offset results + const monitors = JSON.parse( + response.content[0].text.replace('Monitors: ', ''), + ) + expect(monitors.length).toBe(3) + expect(monitors[0].id).toBe(101) // First ID after offset 100 + expect(monitors[2].id).toBe(103) // Last ID + expect(response.content[2].text).toBe('Last monitor ID: 103') + })() + + server.close() + }) + it('should handle null response', async () => { const mockHandler = http.get(monitorsEndpoint, async () => { return HttpResponse.json(null)