@dynatrace-oss/dynatrace-mcp-server 0.2.0 → 0.4.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 -38
- package/dist/authentication/dynatrace-clients.js +89 -0
- package/dist/authentication/types.js +2 -0
- package/dist/capabilities/call-app-function.js +21 -0
- package/dist/capabilities/create-workflow-for-problem-notification.js +15 -15
- package/dist/capabilities/execute-dql.js +3 -3
- package/dist/capabilities/get-monitored-entity-details.js +1 -1
- package/dist/capabilities/get-ownership-information.js +5 -3
- package/dist/capabilities/get-problem-details.js +1 -1
- package/dist/capabilities/get-vulnerability-details.js +1 -1
- package/dist/capabilities/list-vulnerabilities.js +1 -1
- package/dist/capabilities/send-slack-message.js +6 -4
- package/dist/capabilities/update-workflow.js +1 -1
- package/dist/dynatrace-clients.js +0 -82
- package/dist/getDynatraceEnv.js +6 -6
- package/dist/getDynatraceEnv.test.js +34 -22
- package/dist/index.js +86 -84
- package/dist/types/http-client-types.js +1 -0
- package/package.json +5 -2
- package/dist/capabilities/get-monitored-entity-owner.js +0 -15
- package/dist/capabilities/workflow-operations.js +0 -27
- package/dist/dynatrace-oauth-client.js +0 -55
- package/dist/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -16,9 +16,10 @@ Bring real-time observability data directly into your development workflow.
|
|
|
16
16
|
|
|
17
17
|
- List and get [problem](https://www.dynatrace.com/hub/detail/problems/) details from your services (for example Kubernetes)
|
|
18
18
|
- List and get security problems / [vulnerability](https://www.dynatrace.com/hub/detail/vulnerabilities/) details
|
|
19
|
-
- Execute DQL(Dynatrace Query Language)
|
|
19
|
+
- Execute DQL (Dynatrace Query Language) and retrieve logs, events, spans and metrics
|
|
20
20
|
- Send Slack messages (via Slack Connector)
|
|
21
21
|
- Set up notification Workflow (via Dynatrace [AutomationEngine](https://docs.dynatrace.com/docs/discover-dynatrace/platform/automationengine))
|
|
22
|
+
- Get more information about a monitored entity
|
|
22
23
|
- Get Ownership of an entity
|
|
23
24
|
|
|
24
25
|
## Quickstart
|
|
@@ -27,6 +28,8 @@ Bring real-time observability data directly into your development workflow.
|
|
|
27
28
|
|
|
28
29
|
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
30
|
|
|
31
|
+
We recommend to always set it up for your current workspace instead of using it globally.
|
|
32
|
+
|
|
30
33
|
**VS Code**
|
|
31
34
|
|
|
32
35
|
```json
|
|
@@ -34,6 +37,7 @@ You can add this MCP server (using STDIO) to your MCP Client like VS Code, Claud
|
|
|
34
37
|
"servers": {
|
|
35
38
|
"npx-dynatrace-mcp-server": {
|
|
36
39
|
"command": "npx",
|
|
40
|
+
"cwd": "${workspaceFolder}",
|
|
37
41
|
"args": ["-y", "@dynatrace-oss/dynatrace-mcp-server@latest"],
|
|
38
42
|
"envFile": "${workspaceFolder}/.env"
|
|
39
43
|
}
|
|
@@ -61,6 +65,7 @@ This only works if the config is stored in the current workspaces, e.g., `<your-
|
|
|
61
65
|
```
|
|
62
66
|
|
|
63
67
|
**Claude Desktop**
|
|
68
|
+
|
|
64
69
|
```json
|
|
65
70
|
{
|
|
66
71
|
"mcpServers": {
|
|
@@ -80,6 +85,7 @@ This only works if the config is stored in the current workspaces, e.g., `<your-
|
|
|
80
85
|
**Amazon Q Developer CLI**
|
|
81
86
|
|
|
82
87
|
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.
|
|
88
|
+
|
|
83
89
|
```json
|
|
84
90
|
{
|
|
85
91
|
"mcpServers": {
|
|
@@ -96,45 +102,43 @@ The [Amazon Q Developer CLI](https://docs.aws.amazon.com/amazonq/latest/qdevelop
|
|
|
96
102
|
}
|
|
97
103
|
```
|
|
98
104
|
|
|
105
|
+
This configuration should be stored in `<your-repo>/.amazonq/mcp.json`.
|
|
106
|
+
|
|
99
107
|
## Environment Variables
|
|
100
108
|
|
|
101
109
|
A **Dynatrace OAuth Client** is needed to communicate with your Dynatrace Environment. Please follow the documentation about
|
|
102
110
|
[creating an Oauth Client in Dynatrace](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/oauth-clients),
|
|
103
111
|
and set up the following environment variables in order for this MCP to work:
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
**Note**: Please ensure that `settings:objects:read` is used, and
|
|
113
|
+
- `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`)
|
|
114
|
+
- `OAUTH_CLIENT_ID` (string, e.g., `dt0s02.SAMPLE`) - Dynatrace OAuth Client ID
|
|
115
|
+
- `OAUTH_CLIENT_SECRET` (string, e.g., `dt0s02.SAMPLE.abcd1234`) - Dynatrace OAuth Client Secret
|
|
116
|
+
- OAuth Client Scopes:
|
|
117
|
+
- `app-engine:apps:run` - needed for environmentInformationClient
|
|
118
|
+
- `app-engine:functions:run` - needed for environmentInformationClient
|
|
119
|
+
- `hub:catalog:read` - get details about installed Apps on Dynatrace Environment
|
|
120
|
+
- `environment-api:security-problems:read` - needed for reading security problems
|
|
121
|
+
- `environment-api:entities:read` - read monitored entities
|
|
122
|
+
- `environment-api:problems:read` - get problems
|
|
123
|
+
- `environment-api:metrics:read` - read metrics
|
|
124
|
+
- `environment-api:slo:read` - read SLOs
|
|
125
|
+
- `storage:buckets:read` - Read all system data stored on Grail
|
|
126
|
+
- `storage:logs:read` - Read logs for reliability guardian validations
|
|
127
|
+
- `storage:metrics:read` - Read metrics for reliability guardian validations
|
|
128
|
+
- `storage:bizevents:read` - Read bizevents for reliability guardian validations
|
|
129
|
+
- `storage:spans:read` - Read spans from Grail
|
|
130
|
+
- `storage:entities:read` - Read Entities from Grail
|
|
131
|
+
- `storage:events:read` - Read Events from Grail
|
|
132
|
+
- `storage:system:read` - Read System Data from Grail
|
|
133
|
+
- `storage:user.events:read` - Read User events from Grail
|
|
134
|
+
- `storage:user.sessions:read` - Read User sessions from Grail
|
|
135
|
+
- `settings:objects:read` - needed for reading ownership information and Guardians (SRG) from settings
|
|
136
|
+
|
|
137
|
+
**Note**: Please ensure that `settings:objects:read` is used, and _not_ the similarly named scope `app-settings:objects:read`.
|
|
130
138
|
|
|
131
139
|
In addition, depending on the features you use, the following variables can be configured:
|
|
132
140
|
|
|
133
|
-
|
|
134
|
-
* `USE_APP_SETTINGS` (boolean, `true` or `false`; default: `false`)
|
|
135
|
-
* Requires scope `app-settings:objects:read` to read settings-objects from app settings
|
|
136
|
-
* `USE_WORKFLOWS` (boolean, `true` or `false`; default: `false`)
|
|
137
|
-
* Requires scopes `automation:workflows:read`, `automation:workflows:write` and `automation:workflows:run` to read, write and execute Workflows
|
|
141
|
+
- `SLACK_CONNECTION_ID` (string) - connection ID of a [Slack Connection](https://docs.dynatrace.com/docs/analyze-explore-automate/workflows/actions/slack)
|
|
138
142
|
|
|
139
143
|
## ✨ Example prompts ✨
|
|
140
144
|
|
|
@@ -142,32 +146,41 @@ Use these example prompts as a starting point. Just copy them into your IDE or a
|
|
|
142
146
|
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.
|
|
143
147
|
|
|
144
148
|
**Find open vulnerabilities on production, setup alert.**
|
|
149
|
+
|
|
145
150
|
```
|
|
146
151
|
I have this code snippet here in my IDE, where I get a dependency vulnerability warning for my code.
|
|
147
152
|
Check if I see any open vulnerability/cve on production.
|
|
148
153
|
Analyze a specific production problem.
|
|
149
154
|
Setup a workflow that sends Slack alerts to the #devops-alerts channel when availability problems occur.
|
|
150
155
|
```
|
|
156
|
+
|
|
151
157
|
**Debug intermittent 503 errors.**
|
|
158
|
+
|
|
152
159
|
```
|
|
153
160
|
Our load balancer is intermittently returning 503 errors during peak traffic.
|
|
154
161
|
Pull all recent problems detected for our front-end services and
|
|
155
162
|
run a query to correlate error rates with service instance health indicators.
|
|
156
163
|
I suspect we have circuit breakers triggering, but need confirmation from the telemetry data.
|
|
157
164
|
```
|
|
165
|
+
|
|
158
166
|
**Correlate memory issue with logs.**
|
|
167
|
+
|
|
159
168
|
```
|
|
160
169
|
There's a problem with high memory usage on one of our hosts.
|
|
161
170
|
Get the problem details and then fetch related logs to help understand
|
|
162
171
|
what's causing the memory spike? Which file in this repo is this related to?
|
|
163
172
|
```
|
|
173
|
+
|
|
164
174
|
**Trace request flow analysis.**
|
|
175
|
+
|
|
165
176
|
```
|
|
166
177
|
Our users are experiencing slow checkout processes.
|
|
167
178
|
Can you execute a DQL query to show me the full request trace for our checkout flow,
|
|
168
179
|
so I can identify which service is causing the bottleneck?
|
|
169
180
|
```
|
|
181
|
+
|
|
170
182
|
**Analyze Kubernetes cluster events.**
|
|
183
|
+
|
|
171
184
|
```
|
|
172
185
|
Our application deployments seem to be failing intermittently.
|
|
173
186
|
Can you fetch recent events from our "production-cluster"
|
|
@@ -186,6 +199,7 @@ In case of any problems, you can troubleshoot SSO/OAuth issues based on our [Dyn
|
|
|
186
199
|
It is recommended to try access the following API (which requires minimal scopes `app-engine:apps:run` and `app-engine:functions:run`):
|
|
187
200
|
|
|
188
201
|
1. Use OAuth Client ID and Secret to retrieve a Bearer Token (only valid for a couple of minutes):
|
|
202
|
+
|
|
189
203
|
```bash
|
|
190
204
|
curl --request POST 'https://sso.dynatrace.com/sso/oauth2/token' \
|
|
191
205
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
|
@@ -196,6 +210,7 @@ curl --request POST 'https://sso.dynatrace.com/sso/oauth2/token' \
|
|
|
196
210
|
```
|
|
197
211
|
|
|
198
212
|
2. Use `access_token` from the response of the above call as the bearer-token in the next call:
|
|
213
|
+
|
|
199
214
|
```bash
|
|
200
215
|
curl -X GET https://abc12345.apps.dynatrace.com/platform/management/v1/environment \
|
|
201
216
|
-H 'accept: application/json' \
|
|
@@ -203,6 +218,7 @@ curl -X GET https://abc12345.apps.dynatrace.com/platform/management/v1/environme
|
|
|
203
218
|
```
|
|
204
219
|
|
|
205
220
|
3. You should retrieve a result like this:
|
|
221
|
+
|
|
206
222
|
```json
|
|
207
223
|
{
|
|
208
224
|
"environmentId": "abc12345",
|
|
@@ -212,35 +228,32 @@ curl -X GET https://abc12345.apps.dynatrace.com/platform/management/v1/environme
|
|
|
212
228
|
}
|
|
213
229
|
```
|
|
214
230
|
|
|
215
|
-
|
|
216
231
|
### Problem accessing data on Grail
|
|
217
232
|
|
|
218
233
|
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
234
|
|
|
220
|
-
|
|
221
235
|
## Development
|
|
222
236
|
|
|
223
237
|
For local development purposes, you can use VSCode and GitHub Copilot.
|
|
224
238
|
|
|
225
239
|
First, enable Copilot for your Workspace `.vscode/settings.json`:
|
|
240
|
+
|
|
226
241
|
```json
|
|
227
242
|
{
|
|
228
243
|
"github.copilot.enable": {
|
|
229
244
|
"*": true
|
|
230
245
|
}
|
|
231
246
|
}
|
|
232
|
-
|
|
233
247
|
```
|
|
234
248
|
|
|
235
249
|
Second, add the MCP to `.vscode/mcp.json`:
|
|
250
|
+
|
|
236
251
|
```json
|
|
237
252
|
{
|
|
238
253
|
"servers": {
|
|
239
254
|
"my-dynatrace-mcp-server": {
|
|
240
255
|
"command": "node",
|
|
241
|
-
"args": [
|
|
242
|
-
"${workspaceFolder}/dist/index.js"
|
|
243
|
-
],
|
|
256
|
+
"args": ["${workspaceFolder}/dist/index.js"],
|
|
244
257
|
"envFile": "${workspaceFolder}/.env"
|
|
245
258
|
}
|
|
246
259
|
}
|
|
@@ -251,7 +264,7 @@ Third, create a `.env` file in this repository (you can copy from `.env.template
|
|
|
251
264
|
|
|
252
265
|
Last but not least, switch to Agent Mode in CoPilot and reload tools.
|
|
253
266
|
|
|
254
|
-
|
|
255
267
|
## Notes
|
|
268
|
+
|
|
256
269
|
This product is not officially supported by Dynatrace.
|
|
257
270
|
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,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOAuthClient = exports.ExtendedOauthClient = void 0;
|
|
4
|
+
const http_client_1 = require("@dynatrace-sdk/http-client");
|
|
5
|
+
const dt_app_1 = require("dt-app");
|
|
6
|
+
const package_json_1 = require("../../package.json");
|
|
7
|
+
/**
|
|
8
|
+
* Uses the provided oauth Client ID and Secret and requests a token via client-credentials flow
|
|
9
|
+
* @param clientId - OAuth Client ID for Dynatrace
|
|
10
|
+
* @param clientSecret - Oauth Client Secret for Dynatrace
|
|
11
|
+
* @param ssoAuthUrl - SSO Authentication URL
|
|
12
|
+
* @param scopes - List of requested scopes
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
const requestToken = async (clientId, clientSecret, ssoAuthUrl, scopes) => {
|
|
16
|
+
const res = await fetch(ssoAuthUrl, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
20
|
+
},
|
|
21
|
+
body: new URLSearchParams({
|
|
22
|
+
grant_type: 'client_credentials',
|
|
23
|
+
client_id: clientId,
|
|
24
|
+
client_secret: clientSecret,
|
|
25
|
+
scope: scopes.join(' '),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
// check if the response was okay (HTTP 2xx) or not (HTTP 4xx or 5xx)
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
// log the error
|
|
31
|
+
console.error(`Failed to fetch token: ${res.status} ${res.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
// and return the JSON result, as it contains additional information
|
|
34
|
+
return await res.json();
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* ExtendedOAuthClient that takes parameters for clientId, secret, scopes, environmentUrl, authUrl, and the version of the dynatrace-mcp-server
|
|
38
|
+
*/
|
|
39
|
+
class ExtendedOauthClient extends http_client_1._OAuthHttpClient {
|
|
40
|
+
userAgent;
|
|
41
|
+
constructor(config, userAgent) {
|
|
42
|
+
super(config);
|
|
43
|
+
this.userAgent = userAgent;
|
|
44
|
+
}
|
|
45
|
+
send(options) {
|
|
46
|
+
// add the user-agent header to the request
|
|
47
|
+
options.headers = {
|
|
48
|
+
...options.headers,
|
|
49
|
+
'User-Agent': this.userAgent,
|
|
50
|
+
};
|
|
51
|
+
// call the parent send method
|
|
52
|
+
return super.send(options);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.ExtendedOauthClient = ExtendedOauthClient;
|
|
56
|
+
/** Create an Oauth Client based on clientId, clientSecret, environmentUrl and scopes
|
|
57
|
+
* This uses a client-credentials flow to request a token from the SSO endpoint.
|
|
58
|
+
*/
|
|
59
|
+
const createOAuthClient = async (clientId, clientSecret, environmentUrl, scopes) => {
|
|
60
|
+
if (!clientId) {
|
|
61
|
+
throw new Error('Failed to retrieve OAuth client id from env "DT_APP_OAUTH_CLIENT_ID"');
|
|
62
|
+
}
|
|
63
|
+
if (!clientSecret) {
|
|
64
|
+
throw new Error('Failed to retrieve OAuth client secret from env "DT_APP_OAUTH_CLIENT_SECRET"');
|
|
65
|
+
}
|
|
66
|
+
if (!environmentUrl) {
|
|
67
|
+
throw new Error('Failed to retrieve environment URL from env "DT_ENVIRONMENT"');
|
|
68
|
+
}
|
|
69
|
+
console.error(`Trying to authenticate API Calls to ${environmentUrl} via OAuthClientId ${clientId} with the following scopes: ${scopes.join(', ')}`);
|
|
70
|
+
const ssoBaseUrl = await (0, dt_app_1.getSSOUrl)(environmentUrl);
|
|
71
|
+
const ssoAuthUrl = new URL('/sso/oauth2/token', ssoBaseUrl).toString();
|
|
72
|
+
console.error(`Using SSO auth URL: ${ssoAuthUrl}`);
|
|
73
|
+
// try to request a token, just to verify that everything is set up correctly
|
|
74
|
+
const tokenResponse = await requestToken(clientId, clientSecret, ssoAuthUrl, scopes);
|
|
75
|
+
// in case we didn't get a token, or error / error_description / issueId is set, we throw an error
|
|
76
|
+
if (!tokenResponse.access_token || tokenResponse.error || tokenResponse.error_description || tokenResponse.issueId) {
|
|
77
|
+
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 and/or is missing scopes.`);
|
|
78
|
+
}
|
|
79
|
+
console.error(`Successfully retrieved token from SSO!`);
|
|
80
|
+
const userAgent = `dynatrace-mcp-server/v${package_json_1.version} (${process.platform}-${process.arch})`;
|
|
81
|
+
return new ExtendedOauthClient({
|
|
82
|
+
scopes,
|
|
83
|
+
clientId,
|
|
84
|
+
secret: clientSecret,
|
|
85
|
+
environmentUrl,
|
|
86
|
+
authUrl: ssoAuthUrl,
|
|
87
|
+
}, userAgent);
|
|
88
|
+
};
|
|
89
|
+
exports.createOAuthClient = createOAuthClient;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.callAppFunction = void 0;
|
|
4
|
+
/** Helper function to call an app-function via platform-api */
|
|
5
|
+
const callAppFunction = async (dtClient, appId, functionName, payload) => {
|
|
6
|
+
console.error(`Sending payload ${JSON.stringify(payload)}`);
|
|
7
|
+
const response = await dtClient.send({
|
|
8
|
+
url: `/platform/app-engine/app-functions/v1/apps/${appId}/api/${functionName}`,
|
|
9
|
+
method: 'POST',
|
|
10
|
+
headers: {
|
|
11
|
+
'Accept': 'application/json',
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
},
|
|
14
|
+
body: payload,
|
|
15
|
+
statusValidator: (status) => {
|
|
16
|
+
return [200].includes(status);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
return await response.body('json');
|
|
20
|
+
};
|
|
21
|
+
exports.callAppFunction = callAppFunction;
|
|
@@ -12,18 +12,18 @@ const createWorkflowForProblemNotification = async (dtClient, teamName, channel,
|
|
|
12
12
|
slowdown: false,
|
|
13
13
|
resource: false,
|
|
14
14
|
custom: false,
|
|
15
|
-
info: false
|
|
15
|
+
info: false,
|
|
16
16
|
};
|
|
17
17
|
// default trigger config
|
|
18
18
|
let triggerConfig = {
|
|
19
19
|
type: 'event',
|
|
20
20
|
value: {
|
|
21
21
|
eventType: 'events',
|
|
22
|
-
query: ''
|
|
23
|
-
}
|
|
22
|
+
query: '',
|
|
23
|
+
},
|
|
24
24
|
};
|
|
25
25
|
// special case: Security Problems
|
|
26
|
-
if (problemType.toUpperCase().indexOf(
|
|
26
|
+
if (problemType.toUpperCase().indexOf('SECURITY') !== -1) {
|
|
27
27
|
triggerConfig.value.query = `event.kind=="SECURITY_EVENT"
|
|
28
28
|
and event.type=="VULNERABILITY_STATUS_CHANGE_EVENT"
|
|
29
29
|
and event.level == "ENTITY"
|
|
@@ -65,8 +65,8 @@ const createWorkflowForProblemNotification = async (dtClient, teamName, channel,
|
|
|
65
65
|
triggerConfig = {
|
|
66
66
|
type: 'davis-problem',
|
|
67
67
|
value: {
|
|
68
|
-
categories
|
|
69
|
-
}
|
|
68
|
+
categories,
|
|
69
|
+
},
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
let notificationWorkflow = {
|
|
@@ -76,28 +76,28 @@ const createWorkflowForProblemNotification = async (dtClient, teamName, channel,
|
|
|
76
76
|
type: 'SIMPLE',
|
|
77
77
|
// define the send_notification task
|
|
78
78
|
tasks: {
|
|
79
|
-
|
|
80
|
-
name:
|
|
81
|
-
action:
|
|
82
|
-
description:
|
|
79
|
+
send_notification: {
|
|
80
|
+
name: 'Send notification',
|
|
81
|
+
action: 'dynatrace.slack:slack-send-message',
|
|
82
|
+
description: 'Sends a notification to a Slack channel',
|
|
83
83
|
input: {
|
|
84
|
-
connectionId:
|
|
84
|
+
connectionId: 'slack-connection-id',
|
|
85
85
|
channel: `{{ \"${channel}\" }}`,
|
|
86
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
87
|
},
|
|
88
88
|
active: true,
|
|
89
|
-
}
|
|
89
|
+
},
|
|
90
90
|
},
|
|
91
91
|
// define a trigger
|
|
92
92
|
trigger: {
|
|
93
93
|
eventTrigger: {
|
|
94
94
|
isActive: true,
|
|
95
95
|
triggerConfiguration: triggerConfig,
|
|
96
|
-
}
|
|
97
|
-
}
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
98
|
};
|
|
99
99
|
return await workflowsclient.createWorkflow({
|
|
100
|
-
body: notificationWorkflow
|
|
100
|
+
body: notificationWorkflow,
|
|
101
101
|
});
|
|
102
102
|
};
|
|
103
103
|
exports.createWorkflowForProblemNotification = createWorkflowForProblemNotification;
|
|
@@ -7,7 +7,7 @@ const verifyDqlStatement = async (dtClient, dqlStatement) => {
|
|
|
7
7
|
const response = await queryAssistanceClient.queryVerify({
|
|
8
8
|
body: {
|
|
9
9
|
query: dqlStatement,
|
|
10
|
-
}
|
|
10
|
+
},
|
|
11
11
|
});
|
|
12
12
|
return response;
|
|
13
13
|
};
|
|
@@ -17,7 +17,7 @@ const executeDql = async (dtClient, dqlStatement) => {
|
|
|
17
17
|
const response = await queryExecutionClient.queryExecute({
|
|
18
18
|
body: {
|
|
19
19
|
query: dqlStatement,
|
|
20
|
-
}
|
|
20
|
+
},
|
|
21
21
|
});
|
|
22
22
|
if (response.result) {
|
|
23
23
|
// return response result immediately
|
|
@@ -29,7 +29,7 @@ const executeDql = async (dtClient, dqlStatement) => {
|
|
|
29
29
|
let pollResponse;
|
|
30
30
|
do {
|
|
31
31
|
// sleep for 2 seconds
|
|
32
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
32
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
33
33
|
pollResponse = await queryExecutionClient.queryPoll({
|
|
34
34
|
requestToken: response.requestToken,
|
|
35
35
|
});
|
|
@@ -5,7 +5,7 @@ const client_classic_environment_v2_1 = require("@dynatrace-sdk/client-classic-e
|
|
|
5
5
|
const getMonitoredEntityDetails = async (dtClient, entityId) => {
|
|
6
6
|
const monitoredEntitiesClient = new client_classic_environment_v2_1.MonitoredEntitiesClient(dtClient);
|
|
7
7
|
const entityDetails = await monitoredEntitiesClient.getEntity({
|
|
8
|
-
entityId: entityId
|
|
8
|
+
entityId: entityId,
|
|
9
9
|
});
|
|
10
10
|
return entityDetails;
|
|
11
11
|
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getOwnershipInformation = void 0;
|
|
4
|
-
const
|
|
4
|
+
const call_app_function_1 = require("./call-app-function");
|
|
5
5
|
const getOwnershipInformation = async (dtClient, entityIds) => {
|
|
6
|
-
const ownershipResponse = await (0,
|
|
6
|
+
const ownershipResponse = await (0, call_app_function_1.callAppFunction)(dtClient, 'dynatrace.ownership', 'get-ownership-from-entity', {
|
|
7
|
+
entityIds: entityIds,
|
|
8
|
+
});
|
|
7
9
|
if (ownershipResponse.error) {
|
|
8
10
|
// e.g., "Not enough parameters provided"
|
|
9
11
|
return `Error: ${ownershipResponse.error}`;
|
|
10
12
|
}
|
|
11
13
|
if (ownershipResponse.result && ownershipResponse.result.owners && ownershipResponse.result.owners.length == 0) {
|
|
12
|
-
return
|
|
14
|
+
return 'No owners found - please check out how to setup owners on https://docs.dynatrace.com/docs/deliver/ownership';
|
|
13
15
|
}
|
|
14
16
|
return ownershipResponse.result;
|
|
15
17
|
};
|
|
@@ -7,7 +7,7 @@ const getProblemDetails = async (dtClient, problemId) => {
|
|
|
7
7
|
const problemsClient = new client_classic_environment_v2_1.ProblemsClient(dtClient);
|
|
8
8
|
const problemDetails = await problemsClient.getProblem({
|
|
9
9
|
problemId: problemId,
|
|
10
|
-
fields: 'evidenceDetails,affectedEntities'
|
|
10
|
+
fields: 'evidenceDetails,affectedEntities',
|
|
11
11
|
});
|
|
12
12
|
return problemDetails;
|
|
13
13
|
};
|
|
@@ -6,7 +6,7 @@ const getVulnerabilityDetails = async (dtClient, securityProblemId) => {
|
|
|
6
6
|
const securityProblemsClient = new client_classic_environment_v2_1.SecurityProblemsClient(dtClient);
|
|
7
7
|
const response = await securityProblemsClient.getSecurityProblem({
|
|
8
8
|
id: securityProblemId,
|
|
9
|
-
fields: 'riskAssessment,codeLevelVulnerabilityDetails,globalCounts,description,remediationDescription,exposedEntities,affectedEntities,relatedAttacks,entryPoints'
|
|
9
|
+
fields: 'riskAssessment,codeLevelVulnerabilityDetails,globalCounts,description,remediationDescription,exposedEntities,affectedEntities,relatedAttacks,entryPoints',
|
|
10
10
|
});
|
|
11
11
|
return response;
|
|
12
12
|
};
|
|
@@ -7,7 +7,7 @@ const listVulnerabilities = async (dtClient) => {
|
|
|
7
7
|
const response = await securityProblemsClient.getSecurityProblems({
|
|
8
8
|
sort: '-riskAssessment.riskScore',
|
|
9
9
|
pageSize: 100,
|
|
10
|
-
securityProblemSelector: `minRiskScore("8.0")
|
|
10
|
+
securityProblemSelector: `minRiskScore("8.0")`,
|
|
11
11
|
});
|
|
12
12
|
const securityProblems = response.securityProblems?.map((secProb) => {
|
|
13
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(', ')})`;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sendSlackMessage = void 0;
|
|
4
|
-
const
|
|
4
|
+
const call_app_function_1 = require("./call-app-function");
|
|
5
5
|
const sendSlackMessage = async (dtClient, connectionId, channel, message) => {
|
|
6
|
-
const response = await (0,
|
|
7
|
-
message: message,
|
|
6
|
+
const response = await (0, call_app_function_1.callAppFunction)(dtClient, 'dynatrace.slack', 'slack-send-message', {
|
|
7
|
+
message: message,
|
|
8
|
+
channel: channel,
|
|
9
|
+
connection: connectionId,
|
|
8
10
|
workflowID: 'foobar-123',
|
|
9
11
|
executionID: 'exec-123',
|
|
10
12
|
executionDate: new Date().toString(),
|
|
11
|
-
appendToThread: false
|
|
13
|
+
appendToThread: false,
|
|
12
14
|
});
|
|
13
15
|
if (response.error) {
|
|
14
16
|
// e.g., "Not enough parameters provided"
|
|
@@ -6,7 +6,7 @@ const updateWorkflow = async (dtClient, workflowId, body) => {
|
|
|
6
6
|
const workflowsclient = new client_automation_1.WorkflowsClient(dtClient);
|
|
7
7
|
return await workflowsclient.updateWorkflow({
|
|
8
8
|
id: workflowId,
|
|
9
|
-
body: body
|
|
9
|
+
body: body,
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
12
|
exports.updateWorkflow = updateWorkflow;
|
|
@@ -1,83 +1 @@
|
|
|
1
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
|
-
/**
|
|
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
|
-
*/
|
|
14
|
-
const requestToken = async (clientId, clientSecret, authUrl, scopes) => {
|
|
15
|
-
const res = await fetch(authUrl, {
|
|
16
|
-
method: 'POST',
|
|
17
|
-
headers: {
|
|
18
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
19
|
-
},
|
|
20
|
-
body: new URLSearchParams({
|
|
21
|
-
grant_type: 'client_credentials',
|
|
22
|
-
client_id: clientId,
|
|
23
|
-
client_secret: clientSecret,
|
|
24
|
-
scope: scopes.join(' '),
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
// check if the response was okay (HTTP 2xx) or not (HTTP 4xx or 5xx)
|
|
28
|
-
if (!res.ok) {
|
|
29
|
-
// log the error
|
|
30
|
-
console.error(`Failed to fetch token: ${res.status} ${res.statusText}`);
|
|
31
|
-
}
|
|
32
|
-
// and return the JSON result, as it contains additional information
|
|
33
|
-
return await res.json();
|
|
34
|
-
};
|
|
35
|
-
/** Create an Oauth Client based on clientId, clientSecret, environmentUrl and scopes */
|
|
36
|
-
const createOAuthClient = async (clientId, clientSecret, environmentUrl, scopes) => {
|
|
37
|
-
if (!clientId) {
|
|
38
|
-
throw new Error('Failed to retrieve OAuth client id from env "DT_APP_OAUTH_CLIENT_ID"');
|
|
39
|
-
}
|
|
40
|
-
if (!clientSecret) {
|
|
41
|
-
throw new Error('Failed to retrieve OAuth client secret from env "DT_APP_OAUTH_CLIENT_SECRET"');
|
|
42
|
-
}
|
|
43
|
-
if (!environmentUrl) {
|
|
44
|
-
throw new Error('Failed to retrieve environment URL from env "DT_ENVIRONMENT"');
|
|
45
|
-
}
|
|
46
|
-
console.error(`Trying to authenticate API Calls to ${environmentUrl} via OAuthClientId ${clientId}`);
|
|
47
|
-
const ssoBaseUrl = await (0, dt_app_1.getSSOUrl)(environmentUrl);
|
|
48
|
-
const ssoAuthUrl = new URL('/sso/oauth2/token', ssoBaseUrl).toString();
|
|
49
|
-
console.error(`Using SSO auth URL: ${ssoAuthUrl}`);
|
|
50
|
-
// try to request a token, just to verify that everything is set up correctly
|
|
51
|
-
const tokenResponse = await requestToken(clientId, clientSecret, ssoAuthUrl, scopes);
|
|
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.`);
|
|
55
|
-
}
|
|
56
|
-
console.error(`Successfully retrieved token from SSO!`);
|
|
57
|
-
return new http_client_1._OAuthHttpClient({
|
|
58
|
-
scopes,
|
|
59
|
-
clientId,
|
|
60
|
-
secret: clientSecret,
|
|
61
|
-
environmentUrl,
|
|
62
|
-
authUrl: ssoAuthUrl,
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
exports.createOAuthClient = createOAuthClient;
|
|
66
|
-
/** Helper function to call an app-function via platform-api */
|
|
67
|
-
const callAppFunction = async (dtClient, appId, functionName, payload) => {
|
|
68
|
-
console.error(`Sending payload ${JSON.stringify(payload)}`);
|
|
69
|
-
const response = await dtClient.send({
|
|
70
|
-
url: `/platform/app-engine/app-functions/v1/apps/${appId}/api/${functionName}`,
|
|
71
|
-
method: 'POST',
|
|
72
|
-
headers: {
|
|
73
|
-
'Accept': 'application/json',
|
|
74
|
-
'Content-Type': 'application/json',
|
|
75
|
-
},
|
|
76
|
-
body: payload,
|
|
77
|
-
statusValidator: (status) => {
|
|
78
|
-
return [200].includes(status);
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
return await response.body('json');
|
|
82
|
-
};
|
|
83
|
-
exports.callAppFunction = callAppFunction;
|
package/dist/getDynatraceEnv.js
CHANGED
|
@@ -9,15 +9,15 @@ function getDynatraceEnv(env = process.env) {
|
|
|
9
9
|
const oauthClient = env.OAUTH_CLIENT_ID;
|
|
10
10
|
const oauthClientSecret = env.OAUTH_CLIENT_SECRET;
|
|
11
11
|
const dtEnvironment = env.DT_ENVIRONMENT;
|
|
12
|
-
const slackConnectionId = env.SLACK_CONNECTION_ID ||
|
|
12
|
+
const slackConnectionId = env.SLACK_CONNECTION_ID || 'fake-slack-connection-id';
|
|
13
13
|
if (!oauthClient || !oauthClientSecret || !dtEnvironment) {
|
|
14
|
-
throw new Error(
|
|
14
|
+
throw new Error('Please set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET and DT_ENVIRONMENT environment variables');
|
|
15
15
|
}
|
|
16
|
-
if (!dtEnvironment.startsWith(
|
|
17
|
-
throw new Error(
|
|
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
18
|
}
|
|
19
|
-
if (!dtEnvironment.includes(
|
|
20
|
-
throw new Error(
|
|
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
21
|
}
|
|
22
22
|
return { oauthClient, oauthClientSecret, dtEnvironment, slackConnectionId };
|
|
23
23
|
}
|
|
@@ -1,58 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const getDynatraceEnv_1 = require("./getDynatraceEnv");
|
|
4
|
-
describe(
|
|
4
|
+
describe('getDynatraceEnv', () => {
|
|
5
5
|
const baseEnv = {
|
|
6
|
-
OAUTH_CLIENT_ID:
|
|
7
|
-
OAUTH_CLIENT_SECRET:
|
|
8
|
-
DT_ENVIRONMENT:
|
|
9
|
-
SLACK_CONNECTION_ID:
|
|
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
10
|
};
|
|
11
|
-
it(
|
|
11
|
+
it('returns all required values when environment is valid', () => {
|
|
12
12
|
const env = { ...baseEnv };
|
|
13
13
|
const result = (0, getDynatraceEnv_1.getDynatraceEnv)(env);
|
|
14
14
|
expect(result).toEqual({
|
|
15
15
|
oauthClient: env.OAUTH_CLIENT_ID,
|
|
16
16
|
oauthClientSecret: env.OAUTH_CLIENT_SECRET,
|
|
17
17
|
dtEnvironment: env.DT_ENVIRONMENT,
|
|
18
|
-
slackConnectionId: env.SLACK_CONNECTION_ID
|
|
18
|
+
slackConnectionId: env.SLACK_CONNECTION_ID,
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
|
-
it(
|
|
21
|
+
it('uses default slackConnectionId if not set', () => {
|
|
22
22
|
const env = { ...baseEnv, SLACK_CONNECTION_ID: undefined };
|
|
23
23
|
const result = (0, getDynatraceEnv_1.getDynatraceEnv)(env);
|
|
24
|
-
expect(result.slackConnectionId).toBe(
|
|
24
|
+
expect(result.slackConnectionId).toBe('fake-slack-connection-id');
|
|
25
25
|
});
|
|
26
|
-
it(
|
|
26
|
+
it('throws if OAUTH_CLIENT_ID is missing', () => {
|
|
27
27
|
const env = { ...baseEnv, OAUTH_CLIENT_ID: undefined };
|
|
28
28
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/OAUTH_CLIENT_ID/);
|
|
29
29
|
});
|
|
30
|
-
it(
|
|
30
|
+
it('throws if OAUTH_CLIENT_SECRET is missing', () => {
|
|
31
31
|
const env = { ...baseEnv, OAUTH_CLIENT_SECRET: undefined };
|
|
32
32
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/OAUTH_CLIENT_SECRET/);
|
|
33
33
|
});
|
|
34
|
-
it(
|
|
34
|
+
it('throws if DT_ENVIRONMENT is missing', () => {
|
|
35
35
|
const env = { ...baseEnv, DT_ENVIRONMENT: undefined };
|
|
36
36
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/DT_ENVIRONMENT/);
|
|
37
37
|
});
|
|
38
|
-
it(
|
|
39
|
-
const env = {
|
|
38
|
+
it('throws if DT_ENVIRONMENT does not start with https://', () => {
|
|
39
|
+
const env = {
|
|
40
|
+
...baseEnv,
|
|
41
|
+
DT_ENVIRONMENT: 'http://abc123.apps.dynatrace.com',
|
|
42
|
+
};
|
|
40
43
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/https:\/\//);
|
|
41
44
|
});
|
|
42
|
-
it(
|
|
43
|
-
const env = { ...baseEnv, DT_ENVIRONMENT:
|
|
45
|
+
it('throws if DT_ENVIRONMENT is not a Dynatrace Platform URL (any URL)', () => {
|
|
46
|
+
const env = { ...baseEnv, DT_ENVIRONMENT: 'https://abc123.example.com' };
|
|
44
47
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/Dynatrace Platform Environment URL/);
|
|
45
48
|
});
|
|
46
|
-
it(
|
|
47
|
-
const env = {
|
|
49
|
+
it('throws if DT_ENVIRONMENT is not a Dynatrace Platform URL (contains live)', () => {
|
|
50
|
+
const env = {
|
|
51
|
+
...baseEnv,
|
|
52
|
+
DT_ENVIRONMENT: 'https://abc123.live.dynatrace.com',
|
|
53
|
+
};
|
|
48
54
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).toThrow(/Dynatrace Platform Environment URL/);
|
|
49
55
|
});
|
|
50
|
-
it(
|
|
51
|
-
const env = {
|
|
56
|
+
it('accepts DT_ENVIRONMENT with apps.dynatracelabs.com', () => {
|
|
57
|
+
const env = {
|
|
58
|
+
...baseEnv,
|
|
59
|
+
DT_ENVIRONMENT: 'https://xyz789.apps.dynatracelabs.com',
|
|
60
|
+
};
|
|
52
61
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).not.toThrow();
|
|
53
62
|
});
|
|
54
|
-
it(
|
|
55
|
-
const env = {
|
|
63
|
+
it('accepts DT_ENVIRONMENT with apps.dynatrace.com', () => {
|
|
64
|
+
const env = {
|
|
65
|
+
...baseEnv,
|
|
66
|
+
DT_ENVIRONMENT: 'https://env123.apps.dynatrace.com',
|
|
67
|
+
};
|
|
56
68
|
expect(() => (0, getDynatraceEnv_1.getDynatraceEnv)(env)).not.toThrow();
|
|
57
69
|
});
|
|
58
70
|
});
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
7
7
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
8
8
|
const dotenv_1 = require("dotenv");
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
|
-
const
|
|
10
|
+
const package_json_1 = require("../package.json");
|
|
11
|
+
const dynatrace_clients_1 = require("./authentication/dynatrace-clients");
|
|
11
12
|
const list_vulnerabilities_1 = require("./capabilities/list-vulnerabilities");
|
|
12
13
|
const list_problems_1 = require("./capabilities/list-problems");
|
|
13
14
|
const get_problem_details_1 = require("./capabilities/get-problem-details");
|
|
@@ -23,38 +24,10 @@ const send_slack_message_1 = require("./capabilities/send-slack-message");
|
|
|
23
24
|
const find_monitored_entity_by_name_1 = require("./capabilities/find-monitored-entity-by-name");
|
|
24
25
|
const getDynatraceEnv_1 = require("./getDynatraceEnv");
|
|
25
26
|
(0, dotenv_1.config)();
|
|
26
|
-
let
|
|
27
|
+
let scopesBase = [
|
|
27
28
|
'app-engine:apps:run', // needed for environmentInformationClient
|
|
28
29
|
'app-engine:functions:run', // needed for environmentInformationClient
|
|
29
|
-
'hub:catalog:read', // get details about installed Apps on Dynatrace Environment
|
|
30
|
-
'environment-api:security-problems:read', // needed for reading security problems
|
|
31
|
-
'environment-api:entities:read', // read monitored entities
|
|
32
|
-
'environment-api:problems:read', // get problems
|
|
33
|
-
'environment-api:metrics:read', // read metrics
|
|
34
|
-
'environment-api:slo:read', // read SLOs
|
|
35
|
-
'settings:objects:read', // needed for reading settings objects, like ownership information and Guardians (SRG) from settings
|
|
36
|
-
// 'settings:objects:write', // [OPTIONAL] not used right now
|
|
37
|
-
// Grail related permissions: https://docs.dynatrace.com/docs/discover-dynatrace/platform/grail/data-model/assign-permissions-in-grail
|
|
38
|
-
'storage:buckets:read', // Read all system data stored on Grail
|
|
39
|
-
'storage:logs:read', // Read logs for reliability guardian validations
|
|
40
|
-
'storage:metrics:read', // Read metrics for reliability guardian validations
|
|
41
|
-
'storage:bizevents:read', // Read bizevents for reliability guardian validations
|
|
42
|
-
'storage:spans:read', // Read spans from Grail
|
|
43
|
-
'storage:entities:read', // Read Entities from Grail
|
|
44
|
-
'storage:events:read', // Read events from Grail
|
|
45
|
-
'storage:system:read', // Read System Data from Grail
|
|
46
|
-
'storage:user.events:read', // Read User events from Grail
|
|
47
|
-
'storage:user.sessions:read', // Read User sessions from Grail
|
|
48
30
|
];
|
|
49
|
-
// configurable call for app settings scope (not available on all environments)
|
|
50
|
-
if (process.env.USE_APP_SETTINGS) {
|
|
51
|
-
scopes.push('app-settings:objects:read'); // needed when using app settings in Workflows, see below
|
|
52
|
-
}
|
|
53
|
-
if (process.env.USE_WORKFLOWS) {
|
|
54
|
-
scopes.push('automation:workflows:read'); // read workflows
|
|
55
|
-
scopes.push('automation:workflows:write'); // write workflows
|
|
56
|
-
scopes.push('automation:workflows:run'); // execute workflows
|
|
57
|
-
}
|
|
58
31
|
const main = async () => {
|
|
59
32
|
// read Environment variables
|
|
60
33
|
let dynatraceEnv;
|
|
@@ -66,12 +39,10 @@ const main = async () => {
|
|
|
66
39
|
process.exit(1);
|
|
67
40
|
}
|
|
68
41
|
const { oauthClient, oauthClientSecret, dtEnvironment, slackConnectionId } = dynatraceEnv;
|
|
69
|
-
|
|
70
|
-
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopes);
|
|
71
|
-
console.error("Starting Dynatrace MCP Server...");
|
|
42
|
+
console.error(`Starting Dynatrace MCP Server v${package_json_1.version}...`);
|
|
72
43
|
const server = new mcp_js_1.McpServer({
|
|
73
|
-
name:
|
|
74
|
-
version:
|
|
44
|
+
name: 'Dynatrace MCP Server',
|
|
45
|
+
version: package_json_1.version,
|
|
75
46
|
}, {
|
|
76
47
|
capabilities: {
|
|
77
48
|
tools: {},
|
|
@@ -83,7 +54,7 @@ const main = async () => {
|
|
|
83
54
|
try {
|
|
84
55
|
const response = await cb(args);
|
|
85
56
|
return {
|
|
86
|
-
content: [{ type:
|
|
57
|
+
content: [{ type: 'text', text: response }],
|
|
87
58
|
};
|
|
88
59
|
}
|
|
89
60
|
catch (error) {
|
|
@@ -92,13 +63,15 @@ const main = async () => {
|
|
|
92
63
|
const e = error;
|
|
93
64
|
let additionalErrorInformation = '';
|
|
94
65
|
if (e.response.status == 403) {
|
|
95
|
-
additionalErrorInformation =
|
|
66
|
+
additionalErrorInformation =
|
|
67
|
+
'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
|
|
96
68
|
}
|
|
97
69
|
return {
|
|
98
|
-
content: [
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: `Client Request Error: ${e.message} with HTTP status: ${e.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(e.body)})`,
|
|
74
|
+
},
|
|
102
75
|
],
|
|
103
76
|
isError: true,
|
|
104
77
|
};
|
|
@@ -106,14 +79,16 @@ const main = async () => {
|
|
|
106
79
|
// else: We don't know what kind of error happened - best-case we can provide error.message
|
|
107
80
|
console.log(error);
|
|
108
81
|
return {
|
|
109
|
-
content: [{ type:
|
|
82
|
+
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
110
83
|
isError: true,
|
|
111
84
|
};
|
|
112
85
|
}
|
|
113
86
|
};
|
|
114
|
-
server.tool(name, description, paramsSchema, args => wrappedCb(args));
|
|
87
|
+
server.tool(name, description, paramsSchema, (args) => wrappedCb(args));
|
|
115
88
|
};
|
|
116
|
-
tool(
|
|
89
|
+
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant)', {}, async ({}) => {
|
|
90
|
+
// create an oauth-client
|
|
91
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase);
|
|
117
92
|
const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
|
|
118
93
|
const environmentInfo = await environmentInformationClient.getEnvironmentInformation();
|
|
119
94
|
let resp = `Environment Information (also referred to as tenant):
|
|
@@ -121,10 +96,11 @@ const main = async () => {
|
|
|
121
96
|
resp += `You can reach it via ${dtEnvironment}\n`;
|
|
122
97
|
return resp;
|
|
123
98
|
});
|
|
124
|
-
tool(
|
|
99
|
+
tool('list_vulnerabilities', 'List all vulnerabilities from Dynatrace', {}, async ({}) => {
|
|
100
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:security-problems:read'));
|
|
125
101
|
const result = await (0, list_vulnerabilities_1.listVulnerabilities)(dtClient);
|
|
126
102
|
if (!result || result.length === 0) {
|
|
127
|
-
return
|
|
103
|
+
return 'No vulnerabilities found';
|
|
128
104
|
}
|
|
129
105
|
let resp = `Found the following vulnerabilities:`;
|
|
130
106
|
result.forEach((vulnerability) => {
|
|
@@ -133,12 +109,13 @@ const main = async () => {
|
|
|
133
109
|
resp += `\nWe recommend to take a look at ${dtEnvironment}/ui/apps/dynatrace.security.vulnerabilities to get a better overview of vulnerabilities.\n`;
|
|
134
110
|
return resp;
|
|
135
111
|
});
|
|
136
|
-
tool(
|
|
137
|
-
securityProblemId: zod_1.z.string().optional()
|
|
112
|
+
tool('get_vulnerabilty_details', 'Get details of a vulnerability by `securityProblemId` on Dynatrace', {
|
|
113
|
+
securityProblemId: zod_1.z.string().optional(),
|
|
138
114
|
}, async ({ securityProblemId }) => {
|
|
115
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:security-problems:read'));
|
|
139
116
|
const result = await (0, get_vulnerability_details_1.getVulnerabilityDetails)(dtClient, securityProblemId);
|
|
140
117
|
let resp = `The Security Problem (Vulnerability) ${result.displayId} with securityProblemId ${result.securityProblemId} has the title ${result.title}.\n`;
|
|
141
|
-
resp += `The related CVEs are ${result.cveIds?.join(
|
|
118
|
+
resp += `The related CVEs are ${result.cveIds?.join(',') || 'unknown'}.\n`;
|
|
142
119
|
resp += `The description is: ${result.description}.\n`;
|
|
143
120
|
resp += `The remediation description is: ${result.remediationDescription}.\n`;
|
|
144
121
|
if (result.affectedEntities && result.affectedEntities.length > 0) {
|
|
@@ -180,16 +157,18 @@ const main = async () => {
|
|
|
180
157
|
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`;
|
|
181
158
|
return resp;
|
|
182
159
|
});
|
|
183
|
-
tool(
|
|
160
|
+
tool('list_problems', 'List all problems known on Dynatrace', {}, async ({}) => {
|
|
161
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:problems:read'));
|
|
184
162
|
const result = await (0, list_problems_1.listProblems)(dtClient);
|
|
185
163
|
if (!result || result.length === 0) {
|
|
186
|
-
return
|
|
164
|
+
return 'No problems found';
|
|
187
165
|
}
|
|
188
|
-
return `Found these problems: ${result.join(
|
|
166
|
+
return `Found these problems: ${result.join(',')}`;
|
|
189
167
|
});
|
|
190
|
-
tool(
|
|
191
|
-
problemId: zod_1.z.string().optional()
|
|
168
|
+
tool('get_problem_details', 'Get details of a problem on Dynatrace', {
|
|
169
|
+
problemId: zod_1.z.string().optional(),
|
|
192
170
|
}, async ({ problemId }) => {
|
|
171
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:problems:read'));
|
|
193
172
|
const result = await (0, get_problem_details_1.getProblemDetails)(dtClient, problemId);
|
|
194
173
|
let resp = `The problem ${result.displayId} with the title ${result.title} (ID: ${result.problemId}).` +
|
|
195
174
|
`The severity is ${result.severityLevel}, and it affects ${result.affectedEntities.length} entities:`;
|
|
@@ -210,48 +189,53 @@ const main = async () => {
|
|
|
210
189
|
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`;
|
|
211
190
|
return resp;
|
|
212
191
|
});
|
|
213
|
-
tool(
|
|
214
|
-
entityName: zod_1.z.string()
|
|
192
|
+
tool('find_entity_by_name', 'Get the entityId of a monitored entity based on the name of the entity on Dynatrace', {
|
|
193
|
+
entityName: zod_1.z.string(),
|
|
215
194
|
}, async ({ entityName }) => {
|
|
195
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:entities:read', 'storage:entities:read'));
|
|
216
196
|
const entityResponse = await (0, find_monitored_entity_by_name_1.findMonitoredEntityByName)(dtClient, entityName);
|
|
217
197
|
return entityResponse;
|
|
218
198
|
});
|
|
219
|
-
tool(
|
|
220
|
-
entityId: zod_1.z.string().optional()
|
|
199
|
+
tool('get_entity_details', 'Get details of a monitored entity based on the entityId on Dynatrace', {
|
|
200
|
+
entityId: zod_1.z.string().optional(),
|
|
221
201
|
}, async ({ entityId }) => {
|
|
202
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:entities:read'));
|
|
222
203
|
const entityDetails = await (0, get_monitored_entity_details_1.getMonitoredEntityDetails)(dtClient, entityId);
|
|
223
204
|
let resp = `Entity ${entityDetails.displayName} of type ${entityDetails.type} with \`entityId\` ${entityDetails.entityId}\n` +
|
|
224
205
|
`Properties: ${JSON.stringify(entityDetails.properties)}\n`;
|
|
225
|
-
if (entityDetails.type ==
|
|
206
|
+
if (entityDetails.type == 'SERVICE') {
|
|
226
207
|
resp += `You can find more information about the service at ${dtEnvironment}/ui/apps/dynatrace.services/explorer?detailsId=${entityDetails.entityId}&sidebarOpen=false`;
|
|
227
208
|
}
|
|
228
|
-
else if (entityDetails.type ==
|
|
209
|
+
else if (entityDetails.type == 'HOST') {
|
|
229
210
|
resp += `You can find more information about the host at ${dtEnvironment}/ui/apps/dynatrace.infraops/hosts/${entityDetails.entityId}`;
|
|
230
211
|
}
|
|
231
|
-
else if (entityDetails.type ==
|
|
212
|
+
else if (entityDetails.type == 'KUBERNETES_CLUSTER') {
|
|
232
213
|
resp += `You can find more information about the cluster at ${dtEnvironment}/ui/apps/dynatrace.infraops/kubernetes/${entityDetails.entityId}`;
|
|
233
214
|
}
|
|
234
|
-
else if (entityDetails.type ==
|
|
215
|
+
else if (entityDetails.type == 'CLOUD_APPLICATION') {
|
|
235
216
|
resp += `You can find more details about the application at ${dtEnvironment}/ui/apps/dynatrace.kubernetes/explorer/workload?detailsId=${entityDetails.entityId}`;
|
|
236
217
|
}
|
|
237
218
|
return resp;
|
|
238
219
|
});
|
|
239
|
-
tool(
|
|
220
|
+
tool('send_slack_message', 'Sends a Slack message to a dedicated Slack Channel via Slack Connector on Dynatrace', {
|
|
240
221
|
channel: zod_1.z.string().optional(),
|
|
241
|
-
message: zod_1.z.string().optional()
|
|
222
|
+
message: zod_1.z.string().optional(),
|
|
242
223
|
}, async ({ channel, message }) => {
|
|
224
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('app-settings:objects:read'));
|
|
243
225
|
const response = await (0, send_slack_message_1.sendSlackMessage)(dtClient, slackConnectionId, channel, message);
|
|
244
226
|
return `Message sent to Slack channel: ${JSON.stringify(response)}`;
|
|
245
227
|
});
|
|
246
|
-
tool(
|
|
247
|
-
entityName: zod_1.z.string().optional()
|
|
228
|
+
tool('get_logs_for_entity', 'Get Logs for a monitored entity based on name of the entity on Dynatrace', {
|
|
229
|
+
entityName: zod_1.z.string().optional(),
|
|
248
230
|
}, async ({ entityName }) => {
|
|
231
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('storage:logs:read'));
|
|
249
232
|
const logs = await (0, get_logs_for_entity_1.getLogsForEntity)(dtClient, entityName);
|
|
250
|
-
return `Logs:\n${JSON.stringify(logs?.map(logLine => logLine ? logLine.content : 'Empty log'))}`;
|
|
233
|
+
return `Logs:\n${JSON.stringify(logs?.map((logLine) => (logLine ? logLine.content : 'Empty log')))}`;
|
|
251
234
|
});
|
|
252
|
-
tool(
|
|
253
|
-
dqlStatement: zod_1.z.string()
|
|
235
|
+
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.', {
|
|
236
|
+
dqlStatement: zod_1.z.string(),
|
|
254
237
|
}, async ({ dqlStatement }) => {
|
|
238
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase);
|
|
255
239
|
const response = await (0, execute_dql_1.verifyDqlStatement)(dtClient, dqlStatement);
|
|
256
240
|
let resp = 'DQL Statement Verification:\n';
|
|
257
241
|
if (response.notifications && response.notifications.length > 0) {
|
|
@@ -268,24 +252,36 @@ const main = async () => {
|
|
|
268
252
|
}
|
|
269
253
|
return resp;
|
|
270
254
|
});
|
|
271
|
-
tool(
|
|
272
|
-
dqlStatement: zod_1.z.string()
|
|
255
|
+
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`.', {
|
|
256
|
+
dqlStatement: zod_1.z.string(),
|
|
273
257
|
}, async ({ dqlStatement }) => {
|
|
258
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
|
|
259
|
+
'storage:logs:read', // Read logs for reliability guardian validations
|
|
260
|
+
'storage:metrics:read', // Read metrics for reliability guardian validations
|
|
261
|
+
'storage:bizevents:read', // Read bizevents for reliability guardian validations
|
|
262
|
+
'storage:spans:read', // Read spans from Grail
|
|
263
|
+
'storage:entities:read', // Read Entities from Grail
|
|
264
|
+
'storage:events:read', // Read events from Grail
|
|
265
|
+
'storage:system:read', // Read System Data from Grail
|
|
266
|
+
'storage:user.events:read', // Read User events from Grail
|
|
267
|
+
'storage:user.sessions:read', // Read User sessions from Grail
|
|
268
|
+
'storage:security.events:read'));
|
|
274
269
|
const response = await (0, execute_dql_1.executeDql)(dtClient, dqlStatement);
|
|
275
270
|
return `DQL Response: ${JSON.stringify(response)}`;
|
|
276
271
|
});
|
|
277
|
-
tool(
|
|
272
|
+
tool('create_workflow_for_notification', 'Create a notification for a team based on a problem type within Workflows in Dynatrace', {
|
|
278
273
|
problemType: zod_1.z.string().optional(),
|
|
279
274
|
teamName: zod_1.z.string().optional(),
|
|
280
275
|
channel: zod_1.z.string().optional(),
|
|
281
|
-
isPrivate: zod_1.z.boolean().optional().default(false)
|
|
276
|
+
isPrivate: zod_1.z.boolean().optional().default(false),
|
|
282
277
|
}, async ({ problemType, teamName, channel, isPrivate }) => {
|
|
278
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'));
|
|
283
279
|
const response = await (0, create_workflow_for_problem_notification_1.createWorkflowForProblemNotification)(dtClient, teamName, channel, problemType, isPrivate);
|
|
284
280
|
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`;
|
|
285
|
-
if (response.type ==
|
|
281
|
+
if (response.type == 'SIMPLE') {
|
|
286
282
|
resp += `Note: This is a simple workflow. Workflow-hours will not be billed.\n`;
|
|
287
283
|
}
|
|
288
|
-
else if (response.type ==
|
|
284
|
+
else if (response.type == 'STANDARD') {
|
|
289
285
|
resp += `Note: This is a standard workflow. Workflow-hours will be billed.\n`;
|
|
290
286
|
}
|
|
291
287
|
if (isPrivate) {
|
|
@@ -293,23 +289,29 @@ const main = async () => {
|
|
|
293
289
|
}
|
|
294
290
|
return resp;
|
|
295
291
|
});
|
|
296
|
-
tool(
|
|
297
|
-
workflowId: zod_1.z.string().optional()
|
|
292
|
+
tool('make_workflow_public', 'Modify a workflow and make it publicly available to everyone on the Dynatrace Environment', {
|
|
293
|
+
workflowId: zod_1.z.string().optional(),
|
|
298
294
|
}, async ({ workflowId }) => {
|
|
295
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'));
|
|
299
296
|
const response = await (0, update_workflow_1.updateWorkflow)(dtClient, workflowId, {
|
|
300
297
|
isPrivate: false,
|
|
301
298
|
});
|
|
302
299
|
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`;
|
|
303
300
|
});
|
|
304
|
-
tool(
|
|
305
|
-
clusterId: zod_1.z
|
|
301
|
+
tool('get_kubernetes_events', 'Get all events from a specific Kubernetes (K8s) cluster', {
|
|
302
|
+
clusterId: zod_1.z
|
|
303
|
+
.string()
|
|
304
|
+
.optional()
|
|
305
|
+
.describe(`The Kubernetes (K8s) Cluster Id, referred to as k8s.cluster.uid (this is NOT the Dynatrace environment)`),
|
|
306
306
|
}, async ({ clusterId }) => {
|
|
307
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('storage:events:read'));
|
|
307
308
|
const events = await (0, get_events_for_cluster_1.getEventsForCluster)(dtClient, clusterId);
|
|
308
309
|
return `Kubernetes Events:\n${JSON.stringify(events)}`;
|
|
309
310
|
});
|
|
310
|
-
tool(
|
|
311
|
-
entityIds: zod_1.z.string().optional().describe(
|
|
311
|
+
tool('get_ownership', 'Get detailed Ownership information for one or multiple entities on Dynatrace', {
|
|
312
|
+
entityIds: zod_1.z.string().optional().describe('Comma separated list of entityIds'),
|
|
312
313
|
}, async ({ entityIds }) => {
|
|
314
|
+
const dtClient = await (0, dynatrace_clients_1.createOAuthClient)(oauthClient, oauthClientSecret, dtEnvironment, scopesBase.concat('environment-api:entities:read', 'settings:objects:read'));
|
|
313
315
|
console.error(`Fetching ownership for ${entityIds}`);
|
|
314
316
|
const ownershipInformation = await (0, get_ownership_information_1.getOwnershipInformation)(dtClient, entityIds);
|
|
315
317
|
console.error(`Done!`);
|
|
@@ -318,11 +320,11 @@ const main = async () => {
|
|
|
318
320
|
return resp;
|
|
319
321
|
});
|
|
320
322
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
321
|
-
console.error(
|
|
323
|
+
console.error('Connecting server to transport...');
|
|
322
324
|
await server.connect(transport);
|
|
323
|
-
console.error(
|
|
325
|
+
console.error('Dynatrace MCP Server running on stdio');
|
|
324
326
|
};
|
|
325
327
|
main().catch((error) => {
|
|
326
|
-
console.error(
|
|
328
|
+
console.error('Fatal error in main():', error);
|
|
327
329
|
process.exit(1);
|
|
328
330
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynatrace-oss/dynatrace-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for Dynatrace",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Dynatrace",
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
"build": "tsc --build",
|
|
38
38
|
"prepare": "npm run build",
|
|
39
39
|
"watch": "tsc --watch",
|
|
40
|
-
"test": "jest"
|
|
40
|
+
"test": "jest",
|
|
41
|
+
"prettier": "prettier --check .",
|
|
42
|
+
"prettier:fix": "prettier --write ."
|
|
41
43
|
},
|
|
42
44
|
"author": "Dynatrace",
|
|
43
45
|
"license": "MIT",
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
"@types/jest": "^30.0.0",
|
|
57
59
|
"@types/node": "^22",
|
|
58
60
|
"jest": "^30.0.0",
|
|
61
|
+
"prettier": "^3.6.2",
|
|
59
62
|
"ts-jest": "^29.4.0",
|
|
60
63
|
"ts-node": "^10.9.2",
|
|
61
64
|
"typescript": "^5.6.2"
|
|
@@ -1,15 +0,0 @@
|
|
|
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;
|
|
@@ -1,27 +0,0 @@
|
|
|
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;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OAuthHttpClient = void 0;
|
|
4
|
-
class OAuthHttpClient {
|
|
5
|
-
config;
|
|
6
|
-
oauthToken;
|
|
7
|
-
constructor(config) {
|
|
8
|
-
this.config = config;
|
|
9
|
-
}
|
|
10
|
-
async send(options) {
|
|
11
|
-
const { statusValidator = defaultStatusValidator } = options;
|
|
12
|
-
if (!this.oauthToken || this.oauthToken.isExpired()) {
|
|
13
|
-
this.oauthToken = await this.requestToken();
|
|
14
|
-
}
|
|
15
|
-
const requestBodyType = options.requestBodyType ?? 'json';
|
|
16
|
-
const url = new URL(options.url, this.config.environmentUrl);
|
|
17
|
-
const body = options.body !== undefined
|
|
18
|
-
? await encodeRequestBody(options.body, requestBodyType)
|
|
19
|
-
: undefined;
|
|
20
|
-
const headers = {
|
|
21
|
-
...applyContentTypeHeader(options.headers, requestBodyType),
|
|
22
|
-
Authorization: `${this.oauthToken.tokenType} ${this.oauthToken.accessToken}`,
|
|
23
|
-
};
|
|
24
|
-
const response = await send(url.toString(), {
|
|
25
|
-
method: options.method,
|
|
26
|
-
signal: options.abortSignal,
|
|
27
|
-
body,
|
|
28
|
-
headers,
|
|
29
|
-
});
|
|
30
|
-
const ok = statusValidator(response.status);
|
|
31
|
-
if (ok) {
|
|
32
|
-
return new HttpClientResponse(response);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
throw new HttpClientResponseError(response, { requestMethod: options.method });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async requestToken() {
|
|
39
|
-
const res = await send(this.config.authUrl, {
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: {
|
|
42
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
43
|
-
},
|
|
44
|
-
body: new URLSearchParams({
|
|
45
|
-
grant_type: 'client_credentials',
|
|
46
|
-
client_id: this.config.clientId,
|
|
47
|
-
client_secret: this.config.secret,
|
|
48
|
-
scope: this.config.scopes.join(' '),
|
|
49
|
-
}),
|
|
50
|
-
});
|
|
51
|
-
const data = await res.json();
|
|
52
|
-
return new OAuthToken(data);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
exports.OAuthHttpClient = OAuthHttpClient;
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|