@dynatrace-oss/dynatrace-mcp-server 0.7.0 → 0.8.0

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 CHANGED
@@ -25,6 +25,8 @@ bringing real-time observability data directly into your development workflow.
25
25
 
26
26
  If you need help, please contact us via [GitHub Issues](https://github.com/dynatrace-oss/dynatrace-mcp/issues) if you have feature requests, questions, or need help.
27
27
 
28
+ https://github.com/user-attachments/assets/25c05db1-8e09-4a7f-add2-ed486ffd4b5a
29
+
28
30
  ## Quickstart
29
31
 
30
32
  You can add this MCP server to your MCP Client like VSCode, Claude, Cursor, Amazon Q, Windsurf, ChatGPT, or Github Copilot via the npmjs package `@dynatrace-oss/dynatrace-mcp-server`, and type `stdio`.
@@ -34,7 +34,7 @@ const requestToken = async (clientId, clientSecret, ssoAuthUrl, scopes) => {
34
34
  return await res.json();
35
35
  };
36
36
  /**
37
- * Create a Dynatrace Http Client (from the http-client SDK) based on the provided authentication credentails
37
+ * Create a Dynatrace Http Client (from the http-client SDK) based on the provided authentication credentials
38
38
  * @param environmentUrl
39
39
  * @param scopes
40
40
  * @param clientId
@@ -2,12 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getEventsForCluster = void 0;
4
4
  const execute_dql_1 = require("./execute-dql");
5
- const getEventsForCluster = async (dtClient, clusterId) => {
6
- let dql = `fetch events | filter k8s.cluster.uid == "${clusterId}"`;
7
- if (!clusterId) {
8
- // if no clusterId is provided, we need to fetch all events
9
- dql = `fetch events | filter isNotNull(k8s.cluster.uid)`;
5
+ const getEventsForCluster = async (dtClient, clusterId, kubernetesEntityId, eventType) => {
6
+ let dql = 'fetch events';
7
+ if (!clusterId && !kubernetesEntityId) {
8
+ // If no clusterId or kubernetesEntityId is provided, return all kubernetes related events
9
+ dql += ` | filter isNotNull(k8s.cluster.uid)`;
10
10
  }
11
+ else if (clusterId || kubernetesEntityId) {
12
+ // filter by clusterId or kubernetesEntityId if provided
13
+ dql += `| filter k8s.cluster.uid == "${clusterId}" or dt.entity.kubernetes_cluster == "${kubernetesEntityId}"`;
14
+ }
15
+ // filter by eventType if provided
16
+ if (eventType) {
17
+ dql += ` | filter eventType == "${eventType}"`;
18
+ }
19
+ // sort by timestamp
20
+ dql += ' | sort timestamp desc';
11
21
  return (0, execute_dql_1.executeDql)(dtClient, { query: dql });
12
22
  };
13
23
  exports.getEventsForCluster = getEventsForCluster;
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.updateWorkflow = void 0;
4
4
  const client_automation_1 = require("@dynatrace-sdk/client-automation");
5
5
  const updateWorkflow = async (dtClient, workflowId, body) => {
6
- const workflowsclient = new client_automation_1.WorkflowsClient(dtClient);
7
- return await workflowsclient.updateWorkflow({
6
+ const workflowsClient = new client_automation_1.WorkflowsClient(dtClient);
7
+ return await workflowsClient.updateWorkflow({
8
8
  id: workflowId,
9
9
  body: body,
10
10
  });
package/dist/index.js CHANGED
@@ -321,7 +321,7 @@ const main = async () => {
321
321
  return 'No problems found';
322
322
  }
323
323
  });
324
- tool('find_entity_by_name', 'Find the entityId and type of a monitored entity (service, host, process-group, application, kubernetes-node, custom-app, ...) within the topology on Dynatrace, based on the name of the entity. Run this before querying data like logs, metrics, problems, events. If no entity name is known, make an educated guess with common identifiers like package.json `id`/`name`, helm chart names, kubernetes manfiest names, and alike.', {
324
+ tool('find_entity_by_name', 'Find the entityId and type of a monitored entity (service, host, process-group, application, kubernetes-node, custom-app, ...) within the topology on Dynatrace, based on the name of the entity. Run this before querying data like logs, metrics, problems, events. If no entity name is known, make an educated guess with common identifiers like package.json `id`/`name`, helm chart names, kubernetes manifest names, and alike.', {
325
325
  entityNames: zod_1.z
326
326
  .array(zod_1.z.string())
327
327
  .describe('Names of the entities to search for - try with one name at first (identifiers like package.json id), and only try with multiple names if the first search was unsuccessful'),
@@ -637,13 +637,50 @@ const main = async () => {
637
637
  clusterId: zod_1.z
638
638
  .string()
639
639
  .optional()
640
- .describe(`The Kubernetes (K8s) Cluster Id, referred to as k8s.cluster.uid (this is NOT the Dynatrace environment)`),
640
+ .describe(`The Kubernetes Cluster Id, referred to as k8s.cluster.uid, usually seen when using "kubectl" - this is NOT the Dynatrace environment and not the Dynatrace Kubernetes Entity Id. Leave empty if you don't know the Cluster Id.`),
641
+ kubernetesEntityId: zod_1.z
642
+ .string()
643
+ .optional()
644
+ .describe(`The Dynatrace Kubernetes Entity Id, referred to as dt.entity.kubernetes_cluster. Leave empty if you don't know the Entity Id, or use the "find_entity_by_name" tool to find the cluster by name.`),
645
+ eventType: zod_1.z
646
+ .enum([
647
+ 'OMPLIANCE_FINDING',
648
+ 'COMPLIANCE_SCAN_COMPLETED',
649
+ 'CUSTOM_INFO',
650
+ 'DETECTION_FINDING',
651
+ 'ERROR_EVENT',
652
+ 'OSI_UNEXPECTEDLY_UNAVAILABLE',
653
+ 'PROCESS_RESTART',
654
+ 'RESOURCE_CONTENTION_EVENT',
655
+ 'SERVICE_CLIENT_ERROR_RATE_INCREASED',
656
+ 'SERVICE_CLIENT_SLOWDOWN',
657
+ 'SERVICE_ERROR_RATE_INCREASED',
658
+ 'SERVICE_SLOWDOWN',
659
+ 'SERVICE_UNEXPECTED_HIGH_LOAD',
660
+ 'SERVICE_UNEXPECTED_LOW_LOAD',
661
+ ])
662
+ .optional(),
663
+ maxEventsToDisplay: zod_1.z.number().default(10).describe('Maximum number of events to display in the response.'),
641
664
  }, {
642
665
  readOnlyHint: true,
643
- }, async ({ clusterId }) => {
666
+ }, async ({ clusterId, kubernetesEntityId, eventType, maxEventsToDisplay }) => {
644
667
  const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
645
- const events = await (0, get_events_for_cluster_1.getEventsForCluster)(dtClient, clusterId);
646
- return `Kubernetes Events:\n${JSON.stringify(events)}`;
668
+ const result = await (0, get_events_for_cluster_1.getEventsForCluster)(dtClient, clusterId, kubernetesEntityId, eventType);
669
+ if (result && result.records && result.records.length > 0) {
670
+ let resp = `Found ${result.records.length} events! Displaying the top ${maxEventsToDisplay} events:\n`;
671
+ // iterate over dqlResponse and create a string with the problem details, but only show the top maxEntitiesToDisplay problems
672
+ result.records.slice(0, maxEventsToDisplay).forEach((event) => {
673
+ if (event) {
674
+ resp += `- Event ${event['event.id']} (${event['event.type']}) on Kubernetes Entity ID ${event['dt.entity.kubernetes_cluster']} with status ${event['event.status']}: ${event['event.name']} - started at ${event['event.start']}, ended at ${event['event.end']}, duration: ${event['duration']}\n`;
675
+ }
676
+ });
677
+ resp +=
678
+ `\nNext Steps:` +
679
+ `\n1. Consider filtering by \`eventType\` to find specific events of interest.` +
680
+ `\n2. Use "execute_dql" tool with the following query to get more details about a specific event: "fetch events | filter event.id == \"<event-id>\""`;
681
+ return resp;
682
+ }
683
+ return 'No events found for the specified Kubernetes cluster. Try to leave clusterId and kubernetesEntityId empty to get events from all clusters.';
647
684
  });
648
685
  tool('get_ownership', 'Get detailed Ownership information for one or multiple entities on Dynatrace', {
649
686
  entityIds: zod_1.z.string().optional().describe('Comma separated list of entityIds'),
@@ -774,7 +811,8 @@ You can now execute new Grail queries (DQL, etc.) again. If this happens more of
774
811
  }
775
812
  catch (error) {
776
813
  res.writeHead(400, { 'Content-Type': 'application/json' });
777
- res.end(JSON.stringify({ error: 'Invalid JSON' }));
814
+ // Respond with a JSON-RPC Parse error
815
+ res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }));
778
816
  return;
779
817
  }
780
818
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynatrace-oss/dynatrace-mcp-server",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "mcpName": "io.github.dynatrace-oss/Dynatrace-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Dynatrace",
6
6
  "keywords": [