Skip to content

Commit cd1796e

Browse files
feat: Added timeframe and status parameter to list_problems tool
1 parent b195e09 commit cd1796e

File tree

3 files changed

+59
-11
lines changed

3 files changed

+59
-11
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @dynatrace-oss/dynatrace-mcp-server
22

3+
## Unreleased Changes
4+
5+
### Tools
6+
7+
- Added `status` parameter to `list_problems` tool to filter problems by status (ACTIVE, CLOSED, or ALL).
8+
- Added `timeframe` parameter to `list_problems` tool to support flexible time ranges (e.g., "12h", "24h", "7d", "30d"). Default: "24h".
9+
310
## 0.12.0
411

512
- Fixed OAuth callback URL to work in GitHub Codespaces by detecting the environment and using the forwarded URL instead of localhost

src/capabilities/list-problems.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
11
import { HttpClient } from '@dynatrace-sdk/http-client';
22
import { executeDql } from './execute-dql';
33

4-
export const listProblems = async (dtClient: HttpClient, additionalFilter?: string) => {
5-
// DQL Statement from Problems App to fetch all Davis Problems for the last 12 hours to now
6-
const dql = `fetch dt.davis.problems, from: now()-12h, to: now()
4+
/**
5+
* List all problems via dt.davis.problems
6+
* @param dtClient Dynatrace HTTP Client
7+
* @param additionalFilter Any additional DQL filter
8+
* @param status Status (ACTIVE, CLOSED, or ALL)
9+
* @param timeframe Timeframe to query problems (e.g., '24h', '7d', '30d'). Default: '24h'
10+
* @returns DQL query result
11+
*/
12+
export const listProblems = async (
13+
dtClient: HttpClient,
14+
additionalFilter?: string,
15+
status?: string,
16+
timeframe: string = '24h',
17+
) => {
18+
// DQL Statement from Problems App to fetch all Davis Problems
19+
let statusFilter = '';
20+
if (status === 'ACTIVE') {
21+
statusFilter = '| filter isNull(event.end)';
22+
} else if (status === 'CLOSED') {
23+
statusFilter = '| filter not(isNull(event.end))';
24+
}
25+
// For 'ALL' or undefined, no additional filter
26+
27+
const dql = `fetch dt.davis.problems, from: now()-${timeframe}, to: now()
728
| filter isNull(dt.davis.is_duplicate) OR not(dt.davis.is_duplicate)
29+
${statusFilter}
830
${additionalFilter ? `| filter ${additionalFilter}` : ''}
931
| fieldsAdd
10-
duration = coalesce(event.end, now()) - event.start,
32+
duration = (coalesce(event.end, now()) - event.start)/1000000000,
1133
affected_entities_count = arraySize(affected_entity_ids),
1234
event_count = arraySize(dt.davis.event_ids),
1335
affected_users_count = dt.davis.affected_users_count,
@@ -20,7 +42,7 @@ ${additionalFilter ? `| filter ${additionalFilter}` : ''}
2042
dt.cost.costcenter, dt.cost.product, dt.host_group.id, dt.security_context, gcp.project.id,
2143
host.name,
2244
k8s.cluster.name, k8s.cluster.uid, k8s.container.name, k8s.namespace.name, k8s.node.name, k8s.pod.name, k8s.service.name, k8s.workload.kind, k8s.workload.name
23-
| sort event.status asc, event.start desc
45+
| sort event.start desc
2446
`;
2547

2648
return await executeDql(dtClient, { query: dql, maxResultRecords: 5000, maxResultBytes: /* 5 MB */ 5000000 });

src/index.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -399,40 +399,59 @@ const main = async () => {
399399

400400
tool(
401401
'list_problems',
402-
'List all problems (dt.davis.problems) known on Dynatrace, sorted by their recency, for the last 12h. An additional DQL based filter, like filtering for specific entities, can be provided.',
402+
'List all problems (based on "fetch dt.davis.problems") known on Dynatrace, sorted by their recency.',
403403
{
404+
timeframe: z
405+
.string()
406+
.optional()
407+
.default('24h')
408+
.describe(
409+
'Timeframe to query problems (e.g., "12h", "24h", "7d", "30d"). Default: "24h". Supports hours (h) and days (d).',
410+
),
411+
status: z
412+
.enum(['ACTIVE', 'CLOSED', 'ALL'])
413+
.optional()
414+
.default('ALL')
415+
.describe(
416+
'Fitler problems by their status. "ACTIVE": only active problems (those without an end time set), "CLOSED": only closed problems (those with an end time set), "ALL": active and closed problems (default)',
417+
),
404418
additionalFilter: z
405419
.string()
406420
.optional()
407421
.describe(
408422
'Additional DQL filter for dt.davis.problems - filter by entity type (preferred), like \'dt.entity.<service|host|application|$type> == "<entity-id>"\', or by entity tags \'entity_tags == array("dt.owner:team-foobar", "tag:tag")\'',
409423
),
410-
maxProblemsToDisplay: z.number().default(10).describe('Maximum number of problems to display in the response.'),
424+
maxProblemsToDisplay: z
425+
.number()
426+
.min(1)
427+
.max(5000)
428+
.default(10)
429+
.describe('Maximum number of problems to display in the response.'),
411430
},
412431
{
413432
readOnlyHint: true,
414433
},
415-
async ({ additionalFilter, maxProblemsToDisplay }) => {
434+
async ({ timeframe, status, additionalFilter, maxProblemsToDisplay }) => {
416435
const dtClient = await createAuthenticatedHttpClient(
417436
scopesBase.concat('storage:events:read', 'storage:buckets:read'),
418437
);
419438
// get problems (uses fetch)
420-
const result = await listProblems(dtClient, additionalFilter);
439+
const result = await listProblems(dtClient, additionalFilter, status, timeframe);
421440
if (result && result.records && result.records.length > 0) {
422441
let resp = `Found ${result.records.length} problems! Displaying the top ${maxProblemsToDisplay} problems:\n`;
423442
// iterate over dqlResponse and create a string with the problem details, but only show the top maxProblemsToDisplay problems
424443
result.records.slice(0, maxProblemsToDisplay).forEach((problem) => {
425444
if (problem) {
426445
resp += `Problem ${problem['display_id']} (please refer to this problem with \`problemId\` or \`event.id\` ${problem['problem_id']}))
427446
with event.status ${problem['event.status']}, event.category ${problem['event.category']}: ${problem['event.name']} -
428-
affects ${problem['affected_users_count']} users and ${problem['affected_entity_count']} entities for a duration of ${problem['duration']}\n`;
447+
affects ${problem['affected_users_count']} users and ${problem['affected_entity_count']} entities for a duration of ${problem['duration']}s\n`;
429448
}
430449
});
431450

432451
resp +=
433452
`\nNext Steps:` +
434453
`\n1. Use "execute_dql" tool with the following query to get more details about a specific problem:
435-
"fetch dt.davis.problems, from: now()-10h, to: now() | filter event.id == \"<problem-id>\" | fields event.description, event.status, event.category, event.start, event.end,
454+
"fetch dt.davis.problems, from: now()-${timeframe}, to: now() | filter event.id == \"<problem-id>\" | fields event.description, event.status, event.category, event.start, event.end,
436455
root_cause_entity_id, root_cause_entity_name, duration, affected_entities_count,
437456
event_count, affected_users_count, problem_id, dt.davis.mute.status, dt.davis.mute.user,
438457
entity_tags, labels.alerting_profile, maintenance.is_under_maintenance,

0 commit comments

Comments
 (0)