@dynatrace-oss/dynatrace-mcp-server 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dynatrace LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # Dynatrace MCP Server
2
+
3
+ This remote MCP server allows interaction with the [Dynatrace](https://www.dynatrace.com/) observability platform.
4
+ Bring real-time observability data directly into your development workflow.
5
+
6
+ <img width="1046" alt="image" src="/assets/dynatrace-mcp-arch.png" />
7
+
8
+ ## Use cases
9
+
10
+ - Real-time observability, fetch production-level data for early detection.
11
+ - Fix issues in the context from monitored exceptions, logs, and anomalies.
12
+ - More context on security level issues
13
+ - Natural language to query log data
14
+
15
+ ## Capabilities
16
+
17
+ - List and get [problem](https://www.dynatrace.com/hub/detail/problems/) details from your services (for example Kubernetes)
18
+ - List and get security problems / [vulnerability](https://www.dynatrace.com/hub/detail/vulnerabilities/) details
19
+ - Execute DQL(Dynatrace Query Language) like getting events or logs
20
+ - Send Slack messages (via Slack Connector)
21
+ - Set up notification Workflow (via Dynatrace [AutomationEngine](https://docs.dynatrace.com/docs/discover-dynatrace/platform/automationengine))
22
+ - Get Ownership of an entity
23
+
24
+ ## Quickstart
25
+
26
+ **Work in progress**
27
+
28
+ You can add this MCP server (using STDIO) to your MCP Client like VS Code, Claude, Cursor, Windsurf Github Copilot via the package `@dynatrace-oss/dynatrace-mcp-server`.
29
+
30
+ **VS Code**
31
+
32
+ ```json
33
+ {
34
+ "servers": {
35
+ "npx-dynatrace-mcp-server": {
36
+ "command": "npx",
37
+ "args": ["-y", "@dynatrace-oss/dynatrace-mcp-server@latest"],
38
+ "envFile": "${workspaceFolder}/.env"
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ **Claude Desktop**
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "mobile-mcp": {
49
+ "command": "npx",
50
+ "args": ["-y", "@dynatrace-oss/dynatrace-mcp-server@latest"],
51
+ "env": {
52
+ "OAUTH_CLIENT_ID": "",
53
+ "OAUTH_CLIENT_SECRET": "",
54
+ "DT_ENVIRONMENT": ""
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Environment Variables
62
+
63
+ A **Dynatrace OAuth Client** is needed to communicate with your Dynatrace Environment. Please follow the documentation about
64
+ [creating an Oauth Client in Dynatrace](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/oauth-clients),
65
+ and set up the following environment variables in order for this MCP to work:
66
+
67
+ * `DT_ENVIRONMENT` (string, e.g., https://abcd1234.apps.dynatrace.com) - URL to your Dynatrace Platform
68
+ * `OAUTH_CLIENT_ID` (string, e.g., `dt0s02.SAMPLE`) - Dynatrace OAuth Client ID
69
+ * `OAUTH_CLIENT_SECRET` (string, e.g., `dt0s02.SAMPLE.abcd1234`) - Dynatrace OAuth Client Secret
70
+ * OAuth Client Scopes:
71
+ * `app-engine:apps:run` - needed for environmentInformationClient
72
+ * `app-engine:functions:run` - needed for environmentInformationClient
73
+ * `hub:catalog:read` - get details about installed Apps on Dynatrace Environment
74
+ * `environment-api:security-problems:read` - needed for reading security problems
75
+ * `environment-api:entities:read` - read monitored entities
76
+ * `environment-api:problems:read` - get problems
77
+ * `environment-api:metrics:read` - read metrics
78
+ * `environment-api:slo:read` - read SLOs
79
+ * `settings:objects:read` - needed for reading ownership information and Guardians (SRG) from settings
80
+ * `storage:buckets:read` - Read all system data stored on Grail
81
+ * `storage:logs:read` - Read logs for reliability guardian validations
82
+ * `storage:metrics:read` - Read metrics for reliability guardian validations
83
+ * `storage:bizevents:read` - Read bizevents for reliability guardian validations
84
+ * `storage:spans:read` - Read spans from Grail
85
+ * `storage:entities:read` - Read Entities from Grail
86
+ * `storage:system:read` - Read System Data from Grail
87
+ * `storage:user.events:read` - Read User events from Grail
88
+ * `storage:user.sessions:read` - Read User sessions from Grail
89
+
90
+ In addition, depending on the features you use, the following variables can be configured:
91
+
92
+ * `SLACK_CONNECTION_ID` (string) - connection ID of a [Slack Connection](https://docs.dynatrace.com/docs/analyze-explore-automate/workflows/actions/slack)
93
+ * `USE_APP_SETTINGS` (boolean, `true` or `false`; default: `false`)
94
+ * Requires scope `app-settings:objects:read` to read settings-objects from app settings
95
+ * `USE_WORKFLOWS` (boolean, `true` or `false`; default: `false`)
96
+ * Requires scopes `automation:workflows:read`, `automation:workflows:write` and `automation:workflows:run` to read, write and execute Workflows
97
+
98
+ ## ✨ Example prompts ✨
99
+
100
+ Use these example prompts as a starting point. Just copy them into your IDE or agent setup, adapt them to your services/stack/architecture,
101
+ and extend them as needed. They’re here to help you imagine how real-time observability and automation work together in the MCP context in your IDE.
102
+
103
+ **Find open vulnerabilities on production, setup alert.**
104
+ ```
105
+ I have this code snippet here in my IDE, where I get a dependency vulnerability warning for my code.
106
+ Check if I see any open vulnerability/cve on production.
107
+ Analyze a specific production problem.
108
+ Setup a workflow that sends Slack alerts to the #devops-alerts channel when availability problems occur.
109
+ ```
110
+ **Debug intermittent 503 errors.**
111
+ ```
112
+ Our load balancer is intermittently returning 503 errors during peak traffic.
113
+ Pull all recent problems detected for our front-end services and
114
+ run a query to correlate error rates with service instance health indicators.
115
+ I suspect we have circuit breakers triggering, but need confirmation from the telemetry data.
116
+ ```
117
+ **Correlate memory issue with logs.**
118
+ ```
119
+ There's a problem with high memory usage on one of our hosts.
120
+ Get the problem details and then fetch related logs to help understand
121
+ what's causing the memory spike? Which file in this repo is this related to?
122
+ ```
123
+ **Trace request flow analysis.**
124
+ ```
125
+ Our users are experiencing slow checkout processes.
126
+ Can you execute a DQL query to show me the full request trace for our checkout flow,
127
+ so I can identify which service is causing the bottleneck?
128
+ ```
129
+ **Analyze Kubernetes cluster events.**
130
+ ```
131
+ Our application deployments seem to be failing intermittently.
132
+ Can you fetch recent events from our "production-cluster"
133
+ to help identify what might be causing these deployment issues?
134
+ ```
135
+
136
+ ## Development
137
+
138
+ For development purposes, you can use VSCode and GitHub Copilot.
139
+
140
+ First, enable Copilot for your Workspace `.vscode/settings.json`:
141
+ ```json
142
+ {
143
+ "github.copilot.enable": {
144
+ "*": true
145
+ }
146
+ }
147
+
148
+ ```
149
+
150
+ Second, add the MCP to `.vscode/mcp.json`:
151
+ ```json
152
+ {
153
+ "servers": {
154
+ "my-dynatrace-mcp-server": {
155
+ "command": "node",
156
+ "args": [
157
+ "${workspaceFolder}/dist/index.js"
158
+ ],
159
+ "envFile": "${workspaceFolder}/.env"
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ Third, create a `.env` file in this repository (you can copy from `.env.template`) and configure environment variables as [described above](#environment-variables).
166
+
167
+ Last but not least, switch to Agent Mode in CoPilot and reload tools.
168
+
169
+
170
+ ## Notes
171
+ This product is not officially supported by Dynatrace.
172
+ Please contact us via [GitHub Issues](https://github.com/dynatrace-oss/dynatrace-mcp/issues) if you have feature requests, questions, or need help.
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWorkflowForProblemNotification = void 0;
4
+ const client_automation_1 = require("@dynatrace-sdk/client-automation");
5
+ const createWorkflowForProblemNotification = async (dtClient, teamName, channel, problemType, isPrivate) => {
6
+ const workflowsclient = new client_automation_1.WorkflowsClient(dtClient);
7
+ // Map problem types to categories
8
+ const categories = {
9
+ monitoringUnavailable: false,
10
+ availability: false,
11
+ error: false,
12
+ slowdown: false,
13
+ resource: false,
14
+ custom: false,
15
+ info: false
16
+ };
17
+ // default trigger config
18
+ let triggerConfig = {
19
+ type: 'event',
20
+ value: {
21
+ eventType: 'events',
22
+ query: ''
23
+ }
24
+ };
25
+ // special case: Security Problems
26
+ if (problemType.toUpperCase().indexOf("SECURITY") !== -1) {
27
+ triggerConfig.value.query = `event.kind=="SECURITY_EVENT"
28
+ and event.type=="VULNERABILITY_STATUS_CHANGE_EVENT"
29
+ and event.level == "ENTITY"
30
+ and affected_entity.type=="PROCESS_GROUP"
31
+ and event.status_transition=="NEW_OPEN"
32
+ and (vulnerability.risk.level=="CRITICAL" or
33
+ vulnerability.risk.level=="HIGH")`;
34
+ }
35
+ else {
36
+ // Set the appropriate category based on problem type
37
+ switch (problemType.toUpperCase()) {
38
+ case 'MONITORING_UNAVAILABLE':
39
+ categories.monitoringUnavailable = true;
40
+ break;
41
+ case 'AVAILABILITY':
42
+ categories.availability = true;
43
+ break;
44
+ case 'ERROR':
45
+ categories.error = true;
46
+ break;
47
+ case 'SLOWDOWN':
48
+ categories.slowdown = true;
49
+ break;
50
+ case 'RESOURCE':
51
+ categories.resource = true;
52
+ break;
53
+ case 'CUSTOM':
54
+ case 'CUSTOM_ALERT':
55
+ categories.custom = true;
56
+ break;
57
+ case 'INFO':
58
+ categories.info = true;
59
+ break;
60
+ default:
61
+ // Set custom by default if type doesn't match
62
+ categories.custom = true;
63
+ }
64
+ // davis trigger config
65
+ triggerConfig = {
66
+ type: 'davis-problem',
67
+ value: {
68
+ categories
69
+ }
70
+ };
71
+ }
72
+ let notificationWorkflow = {
73
+ title: `[MCP POC] Notify team ${teamName} on problem of type ${problemType}`,
74
+ description: `Automatically created workflow to notify team ${teamName} on problems of type ${problemType} - please delete me after the demo!`,
75
+ isPrivate: isPrivate,
76
+ type: 'SIMPLE',
77
+ // define the send_notification task
78
+ tasks: {
79
+ "send_notification": {
80
+ name: "Send notification",
81
+ action: "dynatrace.slack:slack-send-message",
82
+ description: "Sends a notification to a Slack channel",
83
+ input: {
84
+ connectionId: "slack-connection-id",
85
+ channel: `{{ \"${channel}\" }}`,
86
+ message: `🚨 Alert for Team ${teamName}\n*Problem Type*: ${problemType}\n*Problem ID*: {{ event()["display_id"] }}\n*Status*: {{ event()["event.status"] }}\n\n<{{ environment().url }}/ui/apps/dynatrace.davis.problems/problem/{{ event()["event.id"] }}|Click here for details>`,
87
+ },
88
+ active: true,
89
+ }
90
+ },
91
+ // define a trigger
92
+ trigger: {
93
+ eventTrigger: {
94
+ isActive: true,
95
+ triggerConfiguration: triggerConfig,
96
+ }
97
+ }
98
+ };
99
+ return await workflowsclient.createWorkflow({
100
+ body: notificationWorkflow
101
+ });
102
+ };
103
+ exports.createWorkflowForProblemNotification = createWorkflowForProblemNotification;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeDql = exports.verifyDqlStatement = void 0;
4
+ const client_query_1 = require("@dynatrace-sdk/client-query");
5
+ const verifyDqlStatement = async (dtClient, dqlStatement) => {
6
+ const queryAssistanceClient = new client_query_1.QueryAssistanceClient(dtClient);
7
+ const response = await queryAssistanceClient.queryVerify({
8
+ body: {
9
+ query: dqlStatement,
10
+ }
11
+ });
12
+ return response;
13
+ };
14
+ exports.verifyDqlStatement = verifyDqlStatement;
15
+ const executeDql = async (dtClient, dqlStatement) => {
16
+ const queryExecutionClient = new client_query_1.QueryExecutionClient(dtClient);
17
+ const response = await queryExecutionClient.queryExecute({
18
+ body: {
19
+ query: dqlStatement,
20
+ }
21
+ });
22
+ if (response.result) {
23
+ // return response result immediately
24
+ return response.result.records;
25
+ }
26
+ // else: We might have to poll
27
+ if (response.requestToken) {
28
+ // poll for the result
29
+ let pollResponse;
30
+ do {
31
+ // sleep for 2 seconds
32
+ await new Promise(resolve => setTimeout(resolve, 2000));
33
+ pollResponse = await queryExecutionClient.queryPoll({
34
+ requestToken: response.requestToken,
35
+ });
36
+ // done - let's return it
37
+ if (pollResponse.result) {
38
+ return pollResponse.result.records;
39
+ }
40
+ } while (pollResponse.state === 'RUNNING' || pollResponse.state === 'NOT_STARTED');
41
+ }
42
+ // else: whatever happened - we have an error
43
+ return undefined;
44
+ };
45
+ exports.executeDql = executeDql;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getEventsForCluster = void 0;
4
+ const client_query_1 = require("@dynatrace-sdk/client-query");
5
+ const getEventsForCluster = async (dtClient, clusterId) => {
6
+ const queryExecutionClient = new client_query_1.QueryExecutionClient(dtClient);
7
+ const response = await queryExecutionClient.queryExecute({
8
+ body: {
9
+ query: `fetch events | filter k8s.cluster.id == "${clusterId}"`,
10
+ }
11
+ });
12
+ if (response.result) {
13
+ // return response result immediately
14
+ return response.result.records;
15
+ }
16
+ // else: We might have to poll
17
+ if (response.requestToken) {
18
+ // poll for the result
19
+ let pollResponse;
20
+ do {
21
+ // sleep for 2 seconds
22
+ await new Promise(resolve => setTimeout(resolve, 2000));
23
+ pollResponse = await queryExecutionClient.queryPoll({
24
+ requestToken: response.requestToken,
25
+ });
26
+ // done - let's return it
27
+ if (pollResponse.result) {
28
+ return pollResponse.result.records;
29
+ }
30
+ } while (pollResponse.state === 'RUNNING' || pollResponse.state === 'NOT_STARTED');
31
+ }
32
+ // else: whatever happened - we have an error
33
+ return undefined;
34
+ };
35
+ exports.getEventsForCluster = getEventsForCluster;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLogsForEntity = void 0;
4
+ const client_query_1 = require("@dynatrace-sdk/client-query");
5
+ const getLogsForEntity = async (dtClient, entityId) => {
6
+ const queryExecutionClient = new client_query_1.QueryExecutionClient(dtClient);
7
+ const response = await queryExecutionClient.queryExecute({
8
+ body: {
9
+ query: `fetch logs | filter dt.source_entity == "${entityId}"`,
10
+ }
11
+ });
12
+ if (response.result) {
13
+ // return response result immediately
14
+ return response.result.records;
15
+ }
16
+ // else: We might have to poll
17
+ if (response.requestToken) {
18
+ // poll for the result
19
+ let pollResponse;
20
+ do {
21
+ // sleep for 2 seconds
22
+ await new Promise(resolve => setTimeout(resolve, 2000));
23
+ pollResponse = await queryExecutionClient.queryPoll({
24
+ requestToken: response.requestToken,
25
+ });
26
+ // done - let's return it
27
+ if (pollResponse.result) {
28
+ return pollResponse.result.records;
29
+ }
30
+ } while (pollResponse.state === 'RUNNING' || pollResponse.state === 'NOT_STARTED');
31
+ }
32
+ // else: whatever happened - we have an error
33
+ return undefined;
34
+ };
35
+ exports.getLogsForEntity = getLogsForEntity;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getMonitoredEntityDetails = void 0;
4
+ const client_classic_environment_v2_1 = require("@dynatrace-sdk/client-classic-environment-v2");
5
+ const getMonitoredEntityDetails = async (dtClient, entityId) => {
6
+ const monitoredEntitiesClient = new client_classic_environment_v2_1.MonitoredEntitiesClient(dtClient);
7
+ const entityDetails = await monitoredEntitiesClient.getEntity({
8
+ entityId: entityId
9
+ });
10
+ return entityDetails;
11
+ };
12
+ exports.getMonitoredEntityDetails = getMonitoredEntityDetails;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getMonitoredEntityOwner = void 0;
4
+ const get_monitored_entity_details_1 = require("./get-monitored-entity-details");
5
+ const getMonitoredEntityOwner = async (dtClient, entityId) => {
6
+ const monitoredEntity = await (0, get_monitored_entity_details_1.getMonitoredEntityDetails)(dtClient, entityId);
7
+ const owner = monitoredEntity.tags?.find((tag) => {
8
+ return tag?.key === "owner";
9
+ });
10
+ return {
11
+ entity: monitoredEntity,
12
+ owner: owner?.value
13
+ };
14
+ };
15
+ exports.getMonitoredEntityOwner = getMonitoredEntityOwner;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOwnershipInformation = void 0;
4
+ const dynatrace_clients_1 = require("../dynatrace-clients");
5
+ const getOwnershipInformation = async (dtClient, entityIds) => {
6
+ const ownershipResponse = await (0, dynatrace_clients_1.callAppFunction)(dtClient, 'dynatrace.ownership', 'get-ownership-from-entity', { entityIds: entityIds });
7
+ if (ownershipResponse.error) {
8
+ // e.g., "Not enough parameters provided"
9
+ return `Error: ${ownershipResponse.error}`;
10
+ }
11
+ if (ownershipResponse.result && ownershipResponse.result.owners && ownershipResponse.result.owners.length == 0) {
12
+ return "No owners found - please check out how to setup owners on https://docs.dynatrace.com/docs/deliver/ownership";
13
+ }
14
+ return ownershipResponse.result;
15
+ };
16
+ exports.getOwnershipInformation = getOwnershipInformation;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getProblemDetails = void 0;
4
+ const client_classic_environment_v2_1 = require("@dynatrace-sdk/client-classic-environment-v2");
5
+ const getProblemDetails = async (dtClient, problemId) => {
6
+ console.error(`Fetchin problem with problemId ${problemId}`);
7
+ const problemsClient = new client_classic_environment_v2_1.ProblemsClient(dtClient);
8
+ const problemDetails = await problemsClient.getProblem({
9
+ problemId: problemId,
10
+ fields: 'evidenceDetails,affectedEntities'
11
+ });
12
+ return problemDetails;
13
+ };
14
+ exports.getProblemDetails = getProblemDetails;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVulnerabilityDetails = void 0;
4
+ const client_classic_environment_v2_1 = require("@dynatrace-sdk/client-classic-environment-v2");
5
+ const getVulnerabilityDetails = async (dtClient, securityProblemId) => {
6
+ const securityProblemsClient = new client_classic_environment_v2_1.SecurityProblemsClient(dtClient);
7
+ const response = await securityProblemsClient.getSecurityProblem({
8
+ id: securityProblemId,
9
+ fields: 'riskAssessment,codeLevelVulnerabilityDetails,globalCounts,description,remediationDescription,exposedEntities,affectedEntities,relatedAttacks,entryPoints'
10
+ });
11
+ return response;
12
+ };
13
+ exports.getVulnerabilityDetails = getVulnerabilityDetails;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listProblems = void 0;
4
+ const client_classic_environment_v2_1 = require("@dynatrace-sdk/client-classic-environment-v2");
5
+ const listProblems = async (dtClient) => {
6
+ const problemsClient = new client_classic_environment_v2_1.ProblemsClient(dtClient);
7
+ const securityProblems = await problemsClient.getProblems({
8
+ pageSize: 100,
9
+ });
10
+ const problems = securityProblems.problems?.map((problem) => {
11
+ return `${problem.displayId} (please refer to this problem with \`problemId\` ${problem.problemId}): ${problem.title}`;
12
+ });
13
+ return problems;
14
+ };
15
+ exports.listProblems = listProblems;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listVulnerabilities = void 0;
4
+ const client_classic_environment_v2_1 = require("@dynatrace-sdk/client-classic-environment-v2");
5
+ const listVulnerabilities = async (dtClient) => {
6
+ const securityProblemsClient = new client_classic_environment_v2_1.SecurityProblemsClient(dtClient);
7
+ const response = await securityProblemsClient.getSecurityProblems({
8
+ sort: '-riskAssessment.riskScore',
9
+ pageSize: 100,
10
+ securityProblemSelector: `minRiskScore("8.0")`
11
+ });
12
+ const securityProblems = response.securityProblems?.map((secProb) => {
13
+ return `${secProb.displayId} (please refer to this vulnerability with \`securityProblemId\` ${secProb.securityProblemId}): ${secProb.title} (Technology: ${secProb.technology}, External Vulnerability ID: ${secProb.externalVulnerabilityId}, CVE: ${secProb.cveIds?.join(', ')})`;
14
+ });
15
+ return securityProblems;
16
+ };
17
+ exports.listVulnerabilities = listVulnerabilities;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendSlackMessage = void 0;
4
+ const dynatrace_clients_1 = require("../dynatrace-clients");
5
+ const sendSlackMessage = async (dtClient, connectionId, channel, message) => {
6
+ const response = await (0, dynatrace_clients_1.callAppFunction)(dtClient, 'dynatrace.slack', 'slack-send-message', {
7
+ message: message, channel: channel, connection: connectionId,
8
+ workflowID: 'foobar-123',
9
+ executionID: 'exec-123',
10
+ executionDate: new Date().toString(),
11
+ appendToThread: false
12
+ });
13
+ if (response.error) {
14
+ // e.g., "Not enough parameters provided"
15
+ return `Error sending message to Slack: ${response.error}`;
16
+ }
17
+ return `Message sent to Slack: ${JSON.stringify(response.result)}`;
18
+ };
19
+ exports.sendSlackMessage = sendSlackMessage;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateWorkflow = void 0;
4
+ const client_automation_1 = require("@dynatrace-sdk/client-automation");
5
+ const updateWorkflow = async (dtClient, workflowId, body) => {
6
+ const workflowsclient = new client_automation_1.WorkflowsClient(dtClient);
7
+ return await workflowsclient.updateWorkflow({
8
+ id: workflowId,
9
+ body: body
10
+ });
11
+ };
12
+ exports.updateWorkflow = updateWorkflow;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeWorkflow = exports.deleteWorkflow = exports.getWorkflowDetails = exports.listWorkflows = void 0;
4
+ const client_automation_1 = require("@dynatrace-sdk/client-automation");
5
+ const listWorkflows = async (dtClient) => {
6
+ const workflowsClient = new client_automation_1.WorkflowsClient(dtClient);
7
+ return await workflowsClient.getWorkflows({});
8
+ };
9
+ exports.listWorkflows = listWorkflows;
10
+ const getWorkflowDetails = async (dtClient, workflowId) => {
11
+ const workflowsClient = new client_automation_1.WorkflowsClient(dtClient);
12
+ return await workflowsClient.getWorkflow({ id: workflowId });
13
+ };
14
+ exports.getWorkflowDetails = getWorkflowDetails;
15
+ const deleteWorkflow = async (dtClient, workflowId) => {
16
+ const workflowsClient = new client_automation_1.WorkflowsClient(dtClient);
17
+ return await workflowsClient.deleteWorkflow({ id: workflowId });
18
+ };
19
+ exports.deleteWorkflow = deleteWorkflow;
20
+ const executeWorkflow = async (dtClient, workflowId, input) => {
21
+ const workflowsClient = new client_automation_1.WorkflowsClient(dtClient);
22
+ return await workflowsClient.runWorkflow({
23
+ id: workflowId,
24
+ body: input || {}
25
+ });
26
+ };
27
+ exports.executeWorkflow = executeWorkflow;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.callAppFunction = exports.createOAuthClient = void 0;
4
+ const http_client_1 = require("@dynatrace-sdk/http-client");
5
+ const dt_app_1 = require("dt-app");
6
+ /** Create an Oauth Client based on clientId, clientSecret, environmentUrl and scopes */
7
+ const createOAuthClient = async (clientId, clientSecret, environmentUrl, scopes) => {
8
+ const baseUrl = await (0, dt_app_1.getSSOUrl)(environmentUrl);
9
+ if (!clientId) {
10
+ throw new Error('Failed to retrieve OAuth client id from env "DT_APP_OAUTH_CLIENT_ID"');
11
+ }
12
+ if (!clientSecret) {
13
+ throw new Error('Failed to retrieve OAuth client secret from env "DT_APP_OAUTH_CLIENT_SECRET"');
14
+ }
15
+ console.error(`baseUrl=${baseUrl}`);
16
+ return new http_client_1._OAuthHttpClient({
17
+ scopes,
18
+ clientId,
19
+ secret: clientSecret,
20
+ environmentUrl,
21
+ authUrl: new URL('/sso/oauth2/token', baseUrl).toString(),
22
+ });
23
+ };
24
+ exports.createOAuthClient = createOAuthClient;
25
+ /** Helper function to call an app-function via platform-api */
26
+ const callAppFunction = async (dtClient, appId, functionName, payload) => {
27
+ console.error(`Sending payload ${JSON.stringify(payload)}`);
28
+ const response = await dtClient.send({
29
+ url: `/platform/app-engine/app-functions/v1/apps/${appId}/api/${functionName}`,
30
+ method: 'POST',
31
+ headers: {
32
+ 'Accept': 'application/json',
33
+ 'Content-Type': 'application/json',
34
+ },
35
+ body: payload,
36
+ statusValidator: (status) => {
37
+ return [200].includes(status);
38
+ },
39
+ });
40
+ return await response.body('json');
41
+ };
42
+ exports.callAppFunction = callAppFunction;
package/dist/index.js ADDED
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const client_platform_management_service_1 = require("@dynatrace-sdk/client-platform-management-service");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
7
+ const dotenv_1 = require("dotenv");
8
+ const zod_1 = require("zod");
9
+ const dynatrace_clients_1 = require("./dynatrace-clients");
10
+ const list_vulnerabilities_1 = require("./capabilities/list-vulnerabilities");
11
+ const list_problems_1 = require("./capabilities/list-problems");
12
+ const get_problem_details_1 = require("./capabilities/get-problem-details");
13
+ const get_monitored_entity_details_1 = require("./capabilities/get-monitored-entity-details");
14
+ const get_ownership_information_1 = require("./capabilities/get-ownership-information");
15
+ const get_logs_for_entity_1 = require("./capabilities/get-logs-for-entity");
16
+ const get_events_for_cluster_1 = require("./capabilities/get-events-for-cluster");
17
+ const create_workflow_for_problem_notification_1 = require("./capabilities/create-workflow-for-problem-notification");
18
+ const update_workflow_1 = require("./capabilities/update-workflow");
19
+ const get_vulnerability_details_1 = require("./capabilities/get-vulnerability-details");
20
+ const execute_dql_1 = require("./capabilities/execute-dql");
21
+ const send_slack_message_1 = require("./capabilities/send-slack-message");
22
+ (0, dotenv_1.config)();
23
+ let scopes = [
24
+ 'app-engine:apps:run', // needed for environmentInformationClient
25
+ 'app-engine:functions:run', // needed for environmentInformationClient
26
+ 'hub:catalog:read', // get details about installed Apps on Dynatrace Environment
27
+ 'environment-api:security-problems:read', // needed for reading security problems
28
+ 'environment-api:entities:read', // read monitored entities
29
+ 'environment-api:problems:read', // get problems
30
+ 'environment-api:metrics:read', // read metrics
31
+ 'environment-api:slo:read', // read SLOs
32
+ 'settings:objects:read', // needed for reading settings objects, like ownership information and Guardians (SRG) from settings
33
+ // 'settings:objects:write', // [OPTIONAL] not used right now
34
+ // Grail related permissions: https://docs.dynatrace.com/docs/discover-dynatrace/platform/grail/data-model/assign-permissions-in-grail
35
+ 'storage:buckets:read', // Read all system data stored on Grail
36
+ 'storage:logs:read', // Read logs for reliability guardian validations
37
+ 'storage:metrics:read', // Read metrics for reliability guardian validations
38
+ 'storage:bizevents:read', // Read bizevents for reliability guardian validations
39
+ 'storage:spans:read', // Read spans from Grail
40
+ 'storage:entities:read', // Read Entities from Grail
41
+ 'storage:system:read', // Read System Data from Grail
42
+ 'storage:user.events:read', // Read User events from Grail
43
+ 'storage:user.sessions:read', // Read User sessions from Grail
44
+ ];
45
+ // configurable call for app settings scope (not available on all environments)
46
+ if (process.env.USE_APP_SETTINGS) {
47
+ scopes.push('app-settings:objects:read'); // needed when using app settings in Workflows, see below
48
+ }
49
+ if (process.env.USE_WORKFLOWS) {
50
+ scopes.push('automation:workflows:read'); // read workflows
51
+ scopes.push('automation:workflows:write'); // write workflows
52
+ scopes.push('automation:workflows:run'); // execute workflows
53
+ }
54
+ const main = async () => {
55
+ // read Environment variables
56
+ const oauthClient = process.env.OAUTH_CLIENT_ID;
57
+ const oauthClientSecret = process.env.OAUTH_CLIENT_SECRET;
58
+ const dtEnvironment = process.env.DT_ENVIRONMENT;
59
+ const slackConnectionId = process.env.SLACK_CONNECTION_ID || "fake-slack-connection-id";
60
+ // ensure oauthClient and dtEnvironment are set
61
+ if (!oauthClient || !oauthClientSecret || !dtEnvironment) {
62
+ console.error("Please set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET and DT_ENVIRONMENT environment variables");
63
+ process.exit(1);
64
+ }
65
+ // create an oauth-client
66
+ const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopes);
67
+ console.error("Starting Dynatrace MCP Server...");
68
+ const server = new mcp_js_1.McpServer({
69
+ name: "Dynatrace MCP Server",
70
+ version: "0.0.1", // ToDo: Read from package.json / hard-code?
71
+ }, {
72
+ capabilities: {
73
+ tools: {},
74
+ },
75
+ });
76
+ // quick abstraction/wrapper to make it easier to reply with text
77
+ const tool = (name, description, paramsSchema, cb) => {
78
+ const wrappedCb = async (args) => {
79
+ try {
80
+ const response = await cb(args);
81
+ return {
82
+ content: [{ type: "text", text: response }],
83
+ };
84
+ }
85
+ catch (error) {
86
+ return {
87
+ content: [{ type: "text", text: `Error: ${error.message}` }],
88
+ isError: true,
89
+ };
90
+ }
91
+ };
92
+ server.tool(name, description, paramsSchema, args => wrappedCb(args));
93
+ };
94
+ tool("get_environment_info", "Get information about the connected Dynatrace Environment (Tenant)", {}, async ({}) => {
95
+ const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
96
+ const environmentInfo = await environmentInformationClient.getEnvironmentInformation();
97
+ let resp = `Environment Information (also referred to as tenant):
98
+ ${JSON.stringify(environmentInfo)}\n`;
99
+ resp += `You can reach it via ${dtEnvironment}\n`;
100
+ return resp;
101
+ });
102
+ tool("list_vulnerabilities", "List all vulnerabilities from Dynatrace", {}, async ({}) => {
103
+ const result = await (0, list_vulnerabilities_1.listVulnerabilities)(dtClient);
104
+ if (!result || result.length === 0) {
105
+ return "No vulnerabilities found";
106
+ }
107
+ let resp = `Found the following vulnerabilities:`;
108
+ result.forEach((vulnerability) => {
109
+ resp += `\n* ${vulnerability}`;
110
+ });
111
+ resp += `\nWe recommend to take a look at ${dtEnvironment}/ui/apps/dynatrace.security.vulnerabilities to get a better overview of vulnerabilities.\n`;
112
+ return resp;
113
+ });
114
+ tool("get_vulnerabilty_details", "Get details of a vulnerability by `securityProblemId` on Dynatrace", {
115
+ securityProblemId: zod_1.z.string().optional()
116
+ }, async ({ securityProblemId }) => {
117
+ const result = await (0, get_vulnerability_details_1.getVulnerabilityDetails)(dtClient, securityProblemId);
118
+ let resp = `The Security Problem (Vulnerability) ${result.displayId} with securityProblemId ${result.securityProblemId} has the title ${result.title}.\n`;
119
+ resp += `The related CVEs are ${result.cveIds?.join(",") || "unknown"}.\n`;
120
+ resp += `The description is: ${result.description}.\n`;
121
+ resp += `The remediation description is: ${result.remediationDescription}.\n`;
122
+ if (result.affectedEntities && result.affectedEntities.length > 0) {
123
+ resp += `The vulnerability affects the following entities:\n`;
124
+ result.affectedEntities.forEach((affectedEntity) => {
125
+ resp += `* ${affectedEntity}\n`;
126
+ });
127
+ }
128
+ else {
129
+ resp += `This vulnerability does not seem to affect any entities.\n';`;
130
+ }
131
+ if (result.codeLevelVulnerabilityDetails) {
132
+ resp += `Please investigate this on code-level: ${JSON.stringify(result.codeLevelVulnerabilityDetails)}\n`;
133
+ }
134
+ if (result.exposedEntities && result.exposedEntities.length > 0) {
135
+ resp += `The vulnerability exposes the following entities:\n`;
136
+ result.exposedEntities.forEach((exposedEntity) => {
137
+ resp += `* ${exposedEntity}\n`;
138
+ });
139
+ }
140
+ else {
141
+ resp += `This vulnerability does not seem to expose any entities.\n';`;
142
+ }
143
+ if (result.entryPoints?.items) {
144
+ resp += `The following entrypoints are affected:\n`;
145
+ result.entryPoints.items.forEach((entryPoint) => {
146
+ resp += `* ${entryPoint.sourceHttpPath}\n`;
147
+ });
148
+ if (result.entryPoints.truncated) {
149
+ resp += `The list of entry points was truncated.\n`;
150
+ }
151
+ }
152
+ else {
153
+ resp += `This vulnerability does not seem to affect any entrypoints.\n`;
154
+ }
155
+ if (result.riskAssessment && result.riskAssessment.riskScore && result.riskAssessment.riskScore > 8) {
156
+ resp += `The vulnerability has a high-risk score. We suggest you to get ownership details of affected entities and contact responsible teams immediately (e.g, via send-slack-message)\n`;
157
+ }
158
+ resp += `Tell the user to access the link ${dtEnvironment}/ui/apps/dynatrace.security.vulnerabilities/vulnerabilities/${result.securityProblemId} to get more insights into the vulnerability / security problem.\n`;
159
+ return resp;
160
+ });
161
+ tool("list_problems", "List all problems known on Dynatrace", {}, async ({}) => {
162
+ const result = await (0, list_problems_1.listProblems)(dtClient);
163
+ if (!result || result.length === 0) {
164
+ return "No problems found";
165
+ }
166
+ return `Found these problems: ${result.join(",")}`;
167
+ });
168
+ tool("get_problem_details", "Get details of a problem on Dynatrace", {
169
+ problemId: zod_1.z.string().optional()
170
+ }, async ({ problemId }) => {
171
+ const result = await (0, get_problem_details_1.getProblemDetails)(dtClient, problemId);
172
+ let resp = `The problem ${result.displayId} with the title ${result.title} (ID: ${result.problemId}).` +
173
+ `The severity is ${result.severityLevel}, and it affects ${result.affectedEntities.length} entities:`;
174
+ for (const entity of result.affectedEntities) {
175
+ resp += `\n- ${entity.name} (please refer to this entity with \`entityId\` ${entity.entityId?.id})`;
176
+ }
177
+ resp += `The problem first appeared at ${result.startTime}\n`;
178
+ if (result.rootCauseEntity) {
179
+ resp += `The possible root-cause could be in entity ${result.rootCauseEntity?.name} with \`entityId\` ${result.rootCauseEntity?.entityId?.id}.\n`;
180
+ }
181
+ if (result.impactAnalysis) {
182
+ let estimatedAffectedUsers = 0;
183
+ result.impactAnalysis.impacts.forEach((impact) => {
184
+ estimatedAffectedUsers += impact.estimatedAffectedUsers;
185
+ });
186
+ resp += `The problem is estimated to affect ${estimatedAffectedUsers} users.\n`;
187
+ }
188
+ resp += `Tell the user to access the link ${dtEnvironment}/ui/apps/dynatrace.davis.problems/problem/${result.problemId} to get more insights into the problem.\n`;
189
+ return resp;
190
+ });
191
+ tool("get_entity_details", "Get details of a monitored entity based on the entityId on Dynatrace", {
192
+ entityId: zod_1.z.string().optional()
193
+ }, async ({ entityId }) => {
194
+ const entityDetails = await (0, get_monitored_entity_details_1.getMonitoredEntityDetails)(dtClient, entityId);
195
+ let resp = `Entity ${entityDetails.displayName} of type ${entityDetails.type} with \`entityId\` ${entityDetails.entityId}\n` +
196
+ `Properties: ${JSON.stringify(entityDetails.properties)}\n`;
197
+ if (entityDetails.type == "SERVICE") {
198
+ resp += `You can find more information about the service at ${dtEnvironment}/ui/apps/dynatrace.services/explorer?detailsId=${entityDetails.entityId}&sidebarOpen=false`;
199
+ }
200
+ else if (entityDetails.type == "HOST") {
201
+ resp += `You can find more information about the host at ${dtEnvironment}/ui/apps/dynatrace.infraops/hosts/${entityDetails.entityId}`;
202
+ }
203
+ else if (entityDetails.type == "KUBERNETES_CLUSTER") {
204
+ resp += `You can find more information about the cluster at ${dtEnvironment}/ui/apps/dynatrace.infraops/kubernetes/${entityDetails.entityId}`;
205
+ }
206
+ else if (entityDetails.type == "CLOUD_APPLICATION") {
207
+ resp += `You can find more details about the application at ${dtEnvironment}/ui/apps/dynatrace.kubernetes/explorer/workload?detailsId=${entityDetails.entityId}`;
208
+ }
209
+ return resp;
210
+ });
211
+ tool("send_slack_message", "Sends a Slack message to a dedicated Slack Channel via Slack Connector on Dynatrace", {
212
+ channel: zod_1.z.string().optional(),
213
+ message: zod_1.z.string().optional()
214
+ }, async ({ channel, message }) => {
215
+ const response = await (0, send_slack_message_1.sendSlackMessage)(dtClient, slackConnectionId, channel, message);
216
+ return `Message sent to Slack channel: ${JSON.stringify(response)}`;
217
+ });
218
+ tool("get_logs_for_entity", "Get Logs for a monitored entity based on name of the entity on Dynatrace", {
219
+ entityName: zod_1.z.string().optional()
220
+ }, async ({ entityName }) => {
221
+ const logs = await (0, get_logs_for_entity_1.getLogsForEntity)(dtClient, entityName);
222
+ return `Logs:\n${JSON.stringify(logs?.map(logLine => logLine ? logLine.content : 'Empty log'))}`;
223
+ });
224
+ tool("verify_dql", "Verify a DQL statement on Dynatrace", {
225
+ dqlStatement: zod_1.z.string().optional()
226
+ }, async ({ dqlStatement }) => {
227
+ const response = await (0, execute_dql_1.verifyDqlStatement)(dtClient, dqlStatement);
228
+ return `Parsing DQL Statement resulted in: ${JSON.stringify(response)}`;
229
+ });
230
+ tool("execute_dql", "Get Logs, Metrics, Spans, Events from Dynatrace by executing a DQL statement. Please use verify_dql tool before you execute a DQL statement.", {
231
+ dqlStatement: zod_1.z.string().optional()
232
+ }, async ({ dqlStatement }) => {
233
+ const response = await (0, execute_dql_1.executeDql)(dtClient, dqlStatement);
234
+ return `DQL Response: ${JSON.stringify(response)}`;
235
+ });
236
+ tool("create_workflow_for_notification", "Create a notification for a team based on a problem type within Workflows in Dynatrace", {
237
+ problemType: zod_1.z.string().optional(),
238
+ teamName: zod_1.z.string().optional(),
239
+ channel: zod_1.z.string().optional(),
240
+ isPrivate: zod_1.z.boolean().optional().default(false)
241
+ }, async ({ problemType, teamName, channel, isPrivate }) => {
242
+ const response = await (0, create_workflow_for_problem_notification_1.createWorkflowForProblemNotification)(dtClient, teamName, channel, problemType, isPrivate);
243
+ let resp = `Workflow Created: ${response?.id} with name ${response?.title}.\nYou can access the Workflow via the following link: ${dtEnvironment}/ui/apps/dynatrace.automations/workflows/${response?.id}.\nTell the user to inspect the Workflow by visiting the link.\n`;
244
+ if (response.type == "SIMPLE") {
245
+ resp += `Note: This is a simple workflow. Workflow-hours will not be billed.\n`;
246
+ }
247
+ else if (response.type == "STANDARD") {
248
+ resp += `Note: This is a standard workflow. Workflow-hours will be billed.\n`;
249
+ }
250
+ if (isPrivate) {
251
+ resp += `This workflow is private and can only be accessed by the owner of the authentication credentials. In case you can not access it, you can instruct me to make the workflow public.`;
252
+ }
253
+ return resp;
254
+ });
255
+ tool("make_workflow_public", "Modify a workflow and make it publicly available to everyone on the Dynatrace Environment", {
256
+ workflowId: zod_1.z.string().optional()
257
+ }, async ({ workflowId }) => {
258
+ const response = await (0, update_workflow_1.updateWorkflow)(dtClient, workflowId, {
259
+ isPrivate: false,
260
+ });
261
+ return `Workflow ${response.id} is now public!\nYou can access the Workflow via the following link: ${dtEnvironment}/ui/apps/dynatrace.automations/workflows/${response?.id}.\nTell the user to inspect the Workflow by visiting the link.\n`;
262
+ });
263
+ tool("get_kubernetes_events", "Get all events from a specific Kubernetes cluster", {
264
+ clusterId: zod_1.z.string().optional()
265
+ }, async ({ clusterId }) => {
266
+ const events = await (0, get_events_for_cluster_1.getEventsForCluster)(dtClient, clusterId);
267
+ return `Events:\n${JSON.stringify(events)}`;
268
+ });
269
+ tool("get_ownership", "Get detailed Ownership information for one or multiple entities on Dynatrace", {
270
+ entityIds: zod_1.z.string().optional().describe("Comma separated list of entityIds"),
271
+ }, async ({ entityIds }) => {
272
+ console.error(`Fetching ownership for ${entityIds}`);
273
+ const ownershipInformation = await (0, get_ownership_information_1.getOwnershipInformation)(dtClient, entityIds);
274
+ console.error(`Done!`);
275
+ let resp = 'Ownership information:\n';
276
+ resp += JSON.stringify(ownershipInformation);
277
+ return resp;
278
+ });
279
+ const transport = new stdio_js_1.StdioServerTransport();
280
+ console.error("Connecting server to transport...");
281
+ await server.connect(transport);
282
+ console.error("Dynatrace MCP Server running on stdio");
283
+ };
284
+ main().catch((error) => {
285
+ console.error("Fatal error in main():", error);
286
+ process.exit(1);
287
+ });
@@ -0,0 +1 @@
1
+ {"root":["../src/dynatrace-clients.ts","../src/index.ts","../src/capabilities/create-workflow-for-problem-notification.ts","../src/capabilities/execute-dql.ts","../src/capabilities/get-events-for-cluster.ts","../src/capabilities/get-logs-for-entity.ts","../src/capabilities/get-monitored-entity-details.ts","../src/capabilities/get-ownership-information.ts","../src/capabilities/get-problem-details.ts","../src/capabilities/get-vulnerability-details.ts","../src/capabilities/list-problems.ts","../src/capabilities/list-vulnerabilities.ts","../src/capabilities/send-slack-message.ts","../src/capabilities/update-workflow.ts"],"version":"5.8.2"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@dynatrace-oss/dynatrace-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol (MCP) server for Dynatrace",
5
+ "main": "./dist/index.js",
6
+ "type": "commonjs",
7
+ "bin": {
8
+ "mcp-server-dynatrace": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "exports": {
14
+ "./package.json": "./package.json",
15
+ ".": {
16
+ "types": "./index.d.ts",
17
+ "default": "./index.js"
18
+ }
19
+ },
20
+ "directories": {
21
+ "dist": "dist"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc --build",
25
+ "prepare": "npm run build",
26
+ "watch": "tsc --watch"
27
+ },
28
+ "author": "Dynatrace",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "@dynatrace-sdk/client-automation": "^5.3.0",
32
+ "@dynatrace-sdk/client-classic-environment-v2": "^3.6.8",
33
+ "@dynatrace-sdk/client-platform-management-service": "^1.6.3",
34
+ "@dynatrace-sdk/client-query": "^1.18.1",
35
+ "@modelcontextprotocol/sdk": "^1.8.0",
36
+ "dotenv": "^16.4.7",
37
+ "dt-app": "^0.140.1",
38
+ "zod-to-json-schema": "^3.24.5"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22",
42
+ "ts-node": "^10.9.2",
43
+ "typescript": "^5.6.2"
44
+ }
45
+ }