@dynatrace-oss/dynatrace-mcp-server 0.1.4 → 0.2.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 +49 -3
- package/dist/capabilities/find-monitored-entity-by-name.js +26 -0
- package/dist/getDynatraceEnv.js +23 -0
- package/dist/getDynatraceEnv.test.js +58 -0
- package/dist/index.js +31 -10
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ A **Dynatrace OAuth Client** is needed to communicate with your Dynatrace Enviro
|
|
|
102
102
|
[creating an Oauth Client in Dynatrace](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/oauth-clients),
|
|
103
103
|
and set up the following environment variables in order for this MCP to work:
|
|
104
104
|
|
|
105
|
-
* `DT_ENVIRONMENT` (string, e.g., https://
|
|
105
|
+
* `DT_ENVIRONMENT` (string, e.g., https://abc12345.apps.dynatrace.com) - URL to your Dynatrace Platform (do not use Dynatrace classic URLs like `abc12345.live.dynatrace.com`)
|
|
106
106
|
* `OAUTH_CLIENT_ID` (string, e.g., `dt0s02.SAMPLE`) - Dynatrace OAuth Client ID
|
|
107
107
|
* `OAUTH_CLIENT_SECRET` (string, e.g., `dt0s02.SAMPLE.abcd1234`) - Dynatrace OAuth Client Secret
|
|
108
108
|
* OAuth Client Scopes:
|
|
@@ -114,7 +114,6 @@ and set up the following environment variables in order for this MCP to work:
|
|
|
114
114
|
* `environment-api:problems:read` - get problems
|
|
115
115
|
* `environment-api:metrics:read` - read metrics
|
|
116
116
|
* `environment-api:slo:read` - read SLOs
|
|
117
|
-
* `settings:objects:read` - needed for reading ownership information and Guardians (SRG) from settings
|
|
118
117
|
* `storage:buckets:read` - Read all system data stored on Grail
|
|
119
118
|
* `storage:logs:read` - Read logs for reliability guardian validations
|
|
120
119
|
* `storage:metrics:read` - Read metrics for reliability guardian validations
|
|
@@ -125,6 +124,9 @@ and set up the following environment variables in order for this MCP to work:
|
|
|
125
124
|
* `storage:system:read` - Read System Data from Grail
|
|
126
125
|
* `storage:user.events:read` - Read User events from Grail
|
|
127
126
|
* `storage:user.sessions:read` - Read User sessions from Grail
|
|
127
|
+
* `settings:objects:read` - needed for reading ownership information and Guardians (SRG) from settings
|
|
128
|
+
|
|
129
|
+
**Note**: Please ensure that `settings:objects:read` is used, and *not* the similarly named scope `app-settings:objects:read`.
|
|
128
130
|
|
|
129
131
|
In addition, depending on the features you use, the following variables can be configured:
|
|
130
132
|
|
|
@@ -172,9 +174,53 @@ Can you fetch recent events from our "production-cluster"
|
|
|
172
174
|
to help identify what might be causing these deployment issues?
|
|
173
175
|
```
|
|
174
176
|
|
|
177
|
+
## Troubleshooting
|
|
178
|
+
|
|
179
|
+
### Authentication Issues
|
|
180
|
+
|
|
181
|
+
In most cases, something is wrong with the OAuth Client. Please ensure that you have added all scopes as requested above.
|
|
182
|
+
In addition, please ensure that your user also has all necessary permissions on your Dynatrace Environment.
|
|
183
|
+
|
|
184
|
+
In case of any problems, you can troubleshoot SSO/OAuth issues based on our [Dynatrace Developer Documentation](https://developer.dynatrace.com/develop/access-platform-apis-from-outside/#get-bearer-token-and-call-app-function) and providing the list of scopes.
|
|
185
|
+
|
|
186
|
+
It is recommended to try access the following API (which requires minimal scopes `app-engine:apps:run` and `app-engine:functions:run`):
|
|
187
|
+
|
|
188
|
+
1. Use OAuth Client ID and Secret to retrieve a Bearer Token (only valid for a couple of minutes):
|
|
189
|
+
```bash
|
|
190
|
+
curl --request POST 'https://sso.dynatrace.com/sso/oauth2/token' \
|
|
191
|
+
--header 'Content-Type: application/x-www-form-urlencoded' \
|
|
192
|
+
--data-urlencode 'grant_type=client_credentials' \
|
|
193
|
+
--data-urlencode 'client_id={your-client-id}' \
|
|
194
|
+
--data-urlencode 'client_secret={your-client-secret}' \
|
|
195
|
+
--data-urlencode 'scope=app-engine:apps:run app-engine:functions:run'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
2. Use `access_token` from the response of the above call as the bearer-token in the next call:
|
|
199
|
+
```bash
|
|
200
|
+
curl -X GET https://abc12345.apps.dynatrace.com/platform/management/v1/environment \
|
|
201
|
+
-H 'accept: application/json' \
|
|
202
|
+
-H 'Authorization: Bearer {your-bearer-token}'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
3. You should retrieve a result like this:
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"environmentId": "abc12345",
|
|
209
|
+
"createTime": "2023-01-01T00:10:57.123Z",
|
|
210
|
+
"blockTime": "2025-12-07T00:00:00Z",
|
|
211
|
+
"state": "ACTIVE"
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
### Problem accessing data on Grail
|
|
217
|
+
|
|
218
|
+
Grail has a dedicated section about permissions in the Dynatrace Docs. Please refer to https://docs.dynatrace.com/docs/discover-dynatrace/platform/grail/data-model/assign-permissions-in-grail for more details.
|
|
219
|
+
|
|
220
|
+
|
|
175
221
|
## Development
|
|
176
222
|
|
|
177
|
-
For development purposes, you can use VSCode and GitHub Copilot.
|
|
223
|
+
For local development purposes, you can use VSCode and GitHub Copilot.
|
|
178
224
|
|
|
179
225
|
First, enable Copilot for your Workspace `.vscode/settings.json`:
|
|
180
226
|
```json
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findMonitoredEntityByName = void 0;
|
|
4
|
+
const execute_dql_1 = require("./execute-dql");
|
|
5
|
+
const findMonitoredEntityByName = async (dtClient, entityName) => {
|
|
6
|
+
const dql = `fetch dt.entity.application | search "*${entityName}*" | fieldsAdd entity.type
|
|
7
|
+
| append [fetch dt.entity.service | search "*${entityName}*" | fieldsAdd entity.type]
|
|
8
|
+
| append [fetch dt.entity.host | search "*${entityName}*" | fieldsAdd entity.type]
|
|
9
|
+
| append [fetch dt.entity.process_group | search "*${entityName}*" | fieldsAdd entity.type]
|
|
10
|
+
| append [fetch dt.entity.cloud_application | search "*${entityName}*" | fieldsAdd entity.type]`;
|
|
11
|
+
const dqlResponse = await (0, execute_dql_1.executeDql)(dtClient, dql);
|
|
12
|
+
if (dqlResponse && dqlResponse.length > 0) {
|
|
13
|
+
let resp = 'The following monitored entities were found:\n';
|
|
14
|
+
// iterate over dqlResponse and create a string with the entity names
|
|
15
|
+
dqlResponse.forEach((entity) => {
|
|
16
|
+
if (entity) {
|
|
17
|
+
resp += `- Entity '${entity['entity.name']}' of type '${entity['entity.type']} has entity id '${entity.id}'\n`;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return resp;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return 'No monitored entity found with the specified name.';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.findMonitoredEntityByName = findMonitoredEntityByName;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDynatraceEnv = getDynatraceEnv;
|
|
4
|
+
/**
|
|
5
|
+
* Reads and validates required environment variables for Dynatrace MCP.
|
|
6
|
+
* Throws an Error if validation fails.
|
|
7
|
+
*/
|
|
8
|
+
function getDynatraceEnv(env = process.env) {
|
|
9
|
+
const oauthClient = env.OAUTH_CLIENT_ID;
|
|
10
|
+
const oauthClientSecret = env.OAUTH_CLIENT_SECRET;
|
|
11
|
+
const dtEnvironment = env.DT_ENVIRONMENT;
|
|
12
|
+
const slackConnectionId = env.SLACK_CONNECTION_ID || "fake-slack-connection-id";
|
|
13
|
+
if (!oauthClient || !oauthClientSecret || !dtEnvironment) {
|
|
14
|
+
throw new Error("Please set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET and DT_ENVIRONMENT environment variables");
|
|
15
|
+
}
|
|
16
|
+
if (!dtEnvironment.startsWith("https://")) {
|
|
17
|
+
throw new Error("Please set DT_ENVIRONMENT to a valid Dynatrace Environment URL (e.g., https://<environment-id>.apps.dynatrace.com)");
|
|
18
|
+
}
|
|
19
|
+
if (!dtEnvironment.includes("apps.dynatrace.com") && !dtEnvironment.includes("apps.dynatracelabs.com")) {
|
|
20
|
+
throw new Error("Please set DT_ENVIRONMENT to a valid Dynatrace Platform Environment URL (e.g., https://<environment-id>.apps.dynatrace.com)");
|
|
21
|
+
}
|
|
22
|
+
return { oauthClient, oauthClientSecret, dtEnvironment, slackConnectionId };
|
|
23
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const getDynatraceEnv_1 = require("./getDynatraceEnv");
|
|
4
|
+
describe("getDynatraceEnv", () => {
|
|
5
|
+
const baseEnv = {
|
|
6
|
+
OAUTH_CLIENT_ID: "dt0s02.SAMPLE",
|
|
7
|
+
OAUTH_CLIENT_SECRET: "dt0s02.SAMPLE.abcd1234",
|
|
8
|
+
DT_ENVIRONMENT: "https://abc123.apps.dynatrace.com",
|
|
9
|
+
SLACK_CONNECTION_ID: "slack-conn-id"
|
|
10
|
+
};
|
|
11
|
+
it("returns all required values when environment is valid", () => {
|
|
12
|
+
const env = { ...baseEnv };
|
|
13
|
+
const result = (0, getDynatraceEnv_1.getDynatraceEnv)(env);
|
|
14
|
+
expect(result).toEqual({
|
|
15
|
+
oauthClient: env.OAUTH_CLIENT_ID,
|
|
16
|
+
oauthClientSecret: env.OAUTH_CLIENT_SECRET,
|
|
17
|
+
dtEnvironment: env.DT_ENVIRONMENT,
|
|
18
|
+
slackConnectionId: env.SLACK_CONNECTION_ID
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it("uses default slackConnectionId if not set", () => {
|
|
22
|
+
const env = { ...baseEnv, SLACK_CONNECTION_ID: undefined };
|
|
23
|
+
const result = (0, getDynatraceEnv_1.getDynatraceEnv)(env);
|
|
24
|
+
expect(result.slackConnectionId).toBe("fake-slack-connection-id");
|
|
25
|
+
});
|
|
26
|
+
it("throws if OAUTH_CLIENT_ID is missing", () => {
|
|
27
|
+
const env = { ...baseEnv, OAUTH_CLIENT_ID: undefined };
|
|
28
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/OAUTH_CLIENT_ID/);
|
|
29
|
+
});
|
|
30
|
+
it("throws if OAUTH_CLIENT_SECRET is missing", () => {
|
|
31
|
+
const env = { ...baseEnv, OAUTH_CLIENT_SECRET: undefined };
|
|
32
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/OAUTH_CLIENT_SECRET/);
|
|
33
|
+
});
|
|
34
|
+
it("throws if DT_ENVIRONMENT is missing", () => {
|
|
35
|
+
const env = { ...baseEnv, DT_ENVIRONMENT: undefined };
|
|
36
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/DT_ENVIRONMENT/);
|
|
37
|
+
});
|
|
38
|
+
it("throws if DT_ENVIRONMENT does not start with https://", () => {
|
|
39
|
+
const env = { ...baseEnv, DT_ENVIRONMENT: "http://abc123.apps.dynatrace.com" };
|
|
40
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/https:\/\//);
|
|
41
|
+
});
|
|
42
|
+
it("throws if DT_ENVIRONMENT is not a Dynatrace Platform URL (any URL)", () => {
|
|
43
|
+
const env = { ...baseEnv, DT_ENVIRONMENT: "https://abc123.example.com" };
|
|
44
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/Dynatrace Platform Environment URL/);
|
|
45
|
+
});
|
|
46
|
+
it("throws if DT_ENVIRONMENT is not a Dynatrace Platform URL (contains live)", () => {
|
|
47
|
+
const env = { ...baseEnv, DT_ENVIRONMENT: "https://abc123.live.dynatrace.com" };
|
|
48
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/Dynatrace Platform Environment URL/);
|
|
49
|
+
});
|
|
50
|
+
it("accepts DT_ENVIRONMENT with apps.dynatracelabs.com", () => {
|
|
51
|
+
const env = { ...baseEnv, DT_ENVIRONMENT: "https://xyz789.apps.dynatracelabs.com" };
|
|
52
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).not.toThrow();
|
|
53
|
+
});
|
|
54
|
+
it("accepts DT_ENVIRONMENT with apps.dynatrace.com", () => {
|
|
55
|
+
const env = { ...baseEnv, DT_ENVIRONMENT: "https://env123.apps.dynatrace.com" };
|
|
56
|
+
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).not.toThrow();
|
|
57
|
+
});
|
|
58
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,8 @@ const update_workflow_1 = require("./capabilities/update-workflow");
|
|
|
20
20
|
const get_vulnerability_details_1 = require("./capabilities/get-vulnerability-details");
|
|
21
21
|
const execute_dql_1 = require("./capabilities/execute-dql");
|
|
22
22
|
const send_slack_message_1 = require("./capabilities/send-slack-message");
|
|
23
|
+
const find_monitored_entity_by_name_1 = require("./capabilities/find-monitored-entity-by-name");
|
|
24
|
+
const getDynatraceEnv_1 = require("./getDynatraceEnv");
|
|
23
25
|
(0, dotenv_1.config)();
|
|
24
26
|
let scopes = [
|
|
25
27
|
'app-engine:apps:run', // needed for environmentInformationClient
|
|
@@ -55,15 +57,15 @@ if (process.env.USE_WORKFLOWS) {
|
|
|
55
57
|
}
|
|
56
58
|
const main = async () => {
|
|
57
59
|
// read Environment variables
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.error("Please set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET and DT_ENVIRONMENT environment variables");
|
|
60
|
+
let dynatraceEnv;
|
|
61
|
+
try {
|
|
62
|
+
dynatraceEnv = (0, getDynatraceEnv_1.getDynatraceEnv)();
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error(err.message);
|
|
65
66
|
process.exit(1);
|
|
66
67
|
}
|
|
68
|
+
const { oauthClient, oauthClientSecret, dtEnvironment, slackConnectionId } = dynatraceEnv;
|
|
67
69
|
// create an oauth-client
|
|
68
70
|
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopes);
|
|
69
71
|
console.error("Starting Dynatrace MCP Server...");
|
|
@@ -208,6 +210,12 @@ const main = async () => {
|
|
|
208
210
|
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`;
|
|
209
211
|
return resp;
|
|
210
212
|
});
|
|
213
|
+
tool("find_entity_by_name", "Get the entityId of a monitored entity based on the name of the entity on Dynatrace", {
|
|
214
|
+
entityName: zod_1.z.string()
|
|
215
|
+
}, async ({ entityName }) => {
|
|
216
|
+
const entityResponse = await (0, find_monitored_entity_by_name_1.findMonitoredEntityByName)(dtClient, entityName);
|
|
217
|
+
return entityResponse;
|
|
218
|
+
});
|
|
211
219
|
tool("get_entity_details", "Get details of a monitored entity based on the entityId on Dynatrace", {
|
|
212
220
|
entityId: zod_1.z.string().optional()
|
|
213
221
|
}, async ({ entityId }) => {
|
|
@@ -241,13 +249,26 @@ const main = async () => {
|
|
|
241
249
|
const logs = await (0, get_logs_for_entity_1.getLogsForEntity)(dtClient, entityName);
|
|
242
250
|
return `Logs:\n${JSON.stringify(logs?.map(logLine => logLine ? logLine.content : 'Empty log'))}`;
|
|
243
251
|
});
|
|
244
|
-
tool("verify_dql", "Verify a DQL statement on Dynatrace", {
|
|
252
|
+
tool("verify_dql", "Verify a Dynatrace Query Language (DQL) statement on Dynatrace GRAIL before executing it. This is useful to ensure that the DQL statement is valid and can be executed without errors.", {
|
|
245
253
|
dqlStatement: zod_1.z.string()
|
|
246
254
|
}, async ({ dqlStatement }) => {
|
|
247
255
|
const response = await (0, execute_dql_1.verifyDqlStatement)(dtClient, dqlStatement);
|
|
248
|
-
|
|
256
|
+
let resp = 'DQL Statement Verification:\n';
|
|
257
|
+
if (response.notifications && response.notifications.length > 0) {
|
|
258
|
+
resp += `Please consider the following notifications for adapting the your DQL statement:\n`;
|
|
259
|
+
response.notifications.forEach((notification) => {
|
|
260
|
+
resp += `* ${notification.severity}: ${notification.message}\n`;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (response.valid) {
|
|
264
|
+
resp += `The DQL statement is valid - you can use the "execute_dql" tool.\n`;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
resp += `The DQL statement is invalid. Please adapt your statement.\n`;
|
|
268
|
+
}
|
|
269
|
+
return resp;
|
|
249
270
|
});
|
|
250
|
-
tool("execute_dql",
|
|
271
|
+
tool("execute_dql", 'Get Logs, Metrics, Spans or Events from Dynatrace GRAIL by executing a Dynatrace Query Language (DQL) statement. Always use "verify_dql" tool before you execute a DQL statement. A valid statement looks like this: "fetch [logs, metrics, spans, events] | filter <some-filter> | summarize count(), by:{some-fields}. Adapt filters for certain attributes: `traceId` could be `trace_id` or `trace.id`.', {
|
|
251
272
|
dqlStatement: zod_1.z.string()
|
|
252
273
|
}, async ({ dqlStatement }) => {
|
|
253
274
|
const response = await (0, execute_dql_1.executeDql)(dtClient, dqlStatement);
|
|
@@ -1 +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"}
|
|
1
|
+
{"root":["../src/dynatrace-clients.ts","../src/getDynatraceEnv.test.ts","../src/getDynatraceEnv.ts","../src/index.ts","../src/capabilities/create-workflow-for-problem-notification.ts","../src/capabilities/execute-dql.ts","../src/capabilities/find-monitored-entity-by-name.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynatrace-oss/dynatrace-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for Dynatrace",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Dynatrace",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc --build",
|
|
38
38
|
"prepare": "npm run build",
|
|
39
|
-
"watch": "tsc --watch"
|
|
39
|
+
"watch": "tsc --watch",
|
|
40
|
+
"test": "jest"
|
|
40
41
|
},
|
|
41
42
|
"author": "Dynatrace",
|
|
42
43
|
"license": "MIT",
|
|
@@ -52,7 +53,10 @@
|
|
|
52
53
|
"zod-to-json-schema": "^3.24.5"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
56
|
+
"@types/jest": "^30.0.0",
|
|
55
57
|
"@types/node": "^22",
|
|
58
|
+
"jest": "^30.0.0",
|
|
59
|
+
"ts-jest": "^29.4.0",
|
|
56
60
|
"ts-node": "^10.9.2",
|
|
57
61
|
"typescript": "^5.6.2"
|
|
58
62
|
}
|