@dynatrace-oss/dynatrace-mcp-server 0.1.3 → 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 +51 -5
- package/dist/capabilities/find-monitored-entity-by-name.js +26 -0
- package/dist/dynatrace-clients.js +14 -3
- package/dist/getDynatraceEnv.js +23 -0
- package/dist/getDynatraceEnv.test.js +58 -0
- package/dist/index.js +51 -11
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Bring real-time observability data directly into your development workflow.
|
|
|
25
25
|
|
|
26
26
|
**Work in progress**
|
|
27
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`.
|
|
28
|
+
You can add this MCP server (using STDIO) to your MCP Client like VS Code, Claude, Cursor, Amazon Q Developer CLI, Windsurf Github Copilot via the package `@dynatrace-oss/dynatrace-mcp-server`.
|
|
29
29
|
|
|
30
30
|
**VS Code**
|
|
31
31
|
|
|
@@ -79,7 +79,7 @@ This only works if the config is stored in the current workspaces, e.g., `<your-
|
|
|
79
79
|
|
|
80
80
|
**Amazon Q Developer CLI**
|
|
81
81
|
|
|
82
|
-
The [Amazon Q Developer CLI](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-
|
|
82
|
+
The [Amazon Q Developer CLI](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html) provides an interactive chat experience directly in your terminal. You can ask questions, get help with AWS services, troubleshoot issues, and generate code snippets without leaving your command line environment.
|
|
83
83
|
```json
|
|
84
84
|
{
|
|
85
85
|
"mcpServers": {
|
|
@@ -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;
|
|
@@ -3,7 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.callAppFunction = exports.createOAuthClient = void 0;
|
|
4
4
|
const http_client_1 = require("@dynatrace-sdk/http-client");
|
|
5
5
|
const dt_app_1 = require("dt-app");
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Uses the provided oauth Client ID and Secret and requests a token
|
|
8
|
+
* @param clientId - OAuth Client ID for Dynatrace
|
|
9
|
+
* @param clientSecret - Oauth Client Secret for Dynatrace
|
|
10
|
+
* @param authUrl - SSO Authentication URL
|
|
11
|
+
* @param scopes - List of requested scopes
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
7
14
|
const requestToken = async (clientId, clientSecret, authUrl, scopes) => {
|
|
8
15
|
const res = await fetch(authUrl, {
|
|
9
16
|
method: 'POST',
|
|
@@ -17,9 +24,12 @@ const requestToken = async (clientId, clientSecret, authUrl, scopes) => {
|
|
|
17
24
|
scope: scopes.join(' '),
|
|
18
25
|
}),
|
|
19
26
|
});
|
|
27
|
+
// check if the response was okay (HTTP 2xx) or not (HTTP 4xx or 5xx)
|
|
20
28
|
if (!res.ok) {
|
|
29
|
+
// log the error
|
|
21
30
|
console.error(`Failed to fetch token: ${res.status} ${res.statusText}`);
|
|
22
31
|
}
|
|
32
|
+
// and return the JSON result, as it contains additional information
|
|
23
33
|
return await res.json();
|
|
24
34
|
};
|
|
25
35
|
/** Create an Oauth Client based on clientId, clientSecret, environmentUrl and scopes */
|
|
@@ -39,8 +49,9 @@ const createOAuthClient = async (clientId, clientSecret, environmentUrl, scopes)
|
|
|
39
49
|
console.error(`Using SSO auth URL: ${ssoAuthUrl}`);
|
|
40
50
|
// try to request a token, just to verify that everything is set up correctly
|
|
41
51
|
const tokenResponse = await requestToken(clientId, clientSecret, ssoAuthUrl, scopes);
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
// in case we didn't get a token, or error / error_description / issueId is set, we throw an error
|
|
53
|
+
if (!tokenResponse.access_token || tokenResponse.error || tokenResponse.error_description || tokenResponse.issueId) {
|
|
54
|
+
throw new Error(`Failed to retrieve OAuth token (IssueId: ${tokenResponse.issueId}): ${tokenResponse.error} - ${tokenResponse.error_description}. Note: Your OAuth client is most likely not configured correctly.`);
|
|
44
55
|
}
|
|
45
56
|
console.error(`Successfully retrieved token from SSO!`);
|
|
46
57
|
return new http_client_1._OAuthHttpClient({
|
|
@@ -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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const client_platform_management_service_1 = require("@dynatrace-sdk/client-platform-management-service");
|
|
5
|
+
const shared_errors_1 = require("@dynatrace-sdk/shared-errors");
|
|
5
6
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
7
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
7
8
|
const dotenv_1 = require("dotenv");
|
|
@@ -19,6 +20,8 @@ const update_workflow_1 = require("./capabilities/update-workflow");
|
|
|
19
20
|
const get_vulnerability_details_1 = require("./capabilities/get-vulnerability-details");
|
|
20
21
|
const execute_dql_1 = require("./capabilities/execute-dql");
|
|
21
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");
|
|
22
25
|
(0, dotenv_1.config)();
|
|
23
26
|
let scopes = [
|
|
24
27
|
'app-engine:apps:run', // needed for environmentInformationClient
|
|
@@ -54,15 +57,15 @@ if (process.env.USE_WORKFLOWS) {
|
|
|
54
57
|
}
|
|
55
58
|
const main = async () => {
|
|
56
59
|
// read Environment variables
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
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);
|
|
64
66
|
process.exit(1);
|
|
65
67
|
}
|
|
68
|
+
const { oauthClient, oauthClientSecret, dtEnvironment, slackConnectionId } = dynatraceEnv;
|
|
66
69
|
// create an oauth-client
|
|
67
70
|
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopes);
|
|
68
71
|
console.error("Starting Dynatrace MCP Server...");
|
|
@@ -74,7 +77,7 @@ const main = async () => {
|
|
|
74
77
|
tools: {},
|
|
75
78
|
},
|
|
76
79
|
});
|
|
77
|
-
// quick abstraction/wrapper to make it easier to reply
|
|
80
|
+
// quick abstraction/wrapper to make it easier for tools to reply text instead of JSON
|
|
78
81
|
const tool = (name, description, paramsSchema, cb) => {
|
|
79
82
|
const wrappedCb = async (args) => {
|
|
80
83
|
try {
|
|
@@ -84,6 +87,24 @@ const main = async () => {
|
|
|
84
87
|
};
|
|
85
88
|
}
|
|
86
89
|
catch (error) {
|
|
90
|
+
// check if it's an error originating from the Dynatrace SDK / API Gateway and provide an appropriate message to the user
|
|
91
|
+
if ((0, shared_errors_1.isClientRequestError)(error)) {
|
|
92
|
+
const e = error;
|
|
93
|
+
let additionalErrorInformation = '';
|
|
94
|
+
if (e.response.status == 403) {
|
|
95
|
+
additionalErrorInformation = 'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `Client Request Error: ${e.message} with HTTP status: ${e.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(e.body)})`
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// else: We don't know what kind of error happened - best-case we can provide error.message
|
|
107
|
+
console.log(error);
|
|
87
108
|
return {
|
|
88
109
|
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
89
110
|
isError: true,
|
|
@@ -189,6 +210,12 @@ const main = async () => {
|
|
|
189
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`;
|
|
190
211
|
return resp;
|
|
191
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
|
+
});
|
|
192
219
|
tool("get_entity_details", "Get details of a monitored entity based on the entityId on Dynatrace", {
|
|
193
220
|
entityId: zod_1.z.string().optional()
|
|
194
221
|
}, async ({ entityId }) => {
|
|
@@ -222,13 +249,26 @@ const main = async () => {
|
|
|
222
249
|
const logs = await (0, get_logs_for_entity_1.getLogsForEntity)(dtClient, entityName);
|
|
223
250
|
return `Logs:\n${JSON.stringify(logs?.map(logLine => logLine ? logLine.content : 'Empty log'))}`;
|
|
224
251
|
});
|
|
225
|
-
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.", {
|
|
226
253
|
dqlStatement: zod_1.z.string()
|
|
227
254
|
}, async ({ dqlStatement }) => {
|
|
228
255
|
const response = await (0, execute_dql_1.verifyDqlStatement)(dtClient, dqlStatement);
|
|
229
|
-
|
|
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;
|
|
230
270
|
});
|
|
231
|
-
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`.', {
|
|
232
272
|
dqlStatement: zod_1.z.string()
|
|
233
273
|
}, async ({ dqlStatement }) => {
|
|
234
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",
|
|
@@ -45,13 +46,17 @@
|
|
|
45
46
|
"@dynatrace-sdk/client-classic-environment-v2": "^3.6.8",
|
|
46
47
|
"@dynatrace-sdk/client-platform-management-service": "^1.6.3",
|
|
47
48
|
"@dynatrace-sdk/client-query": "^1.18.1",
|
|
49
|
+
"@dynatrace-sdk/shared-errors": "^1.0.0",
|
|
48
50
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
49
51
|
"dotenv": "^16.4.7",
|
|
50
52
|
"dt-app": "^0.140.1",
|
|
51
53
|
"zod-to-json-schema": "^3.24.5"
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
56
|
+
"@types/jest": "^30.0.0",
|
|
54
57
|
"@types/node": "^22",
|
|
58
|
+
"jest": "^30.0.0",
|
|
59
|
+
"ts-jest": "^29.4.0",
|
|
55
60
|
"ts-node": "^10.9.2",
|
|
56
61
|
"typescript": "^5.6.2"
|
|
57
62
|
}
|