@dynatrace-oss/dynatrace-mcp-server 0.6.0-rc.2 → 0.6.1
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 +71 -54
- package/dist/index.js +67 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,12 +18,28 @@
|
|
|
18
18
|
</a>
|
|
19
19
|
</h4>
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
The local _Dynatrace MCP server_ allows AI Assistants to interact with the [Dynatrace](https://www.dynatrace.com/) observability platform,
|
|
22
|
+
bringing real-time observability data directly into your development workflow.
|
|
23
23
|
|
|
24
24
|
> Note: This product is not officially supported by Dynatrace.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
If you need help, please contact us via [GitHub Issues](https://github.com/dynatrace-oss/dynatrace-mcp/issues) if you have feature requests, questions, or need help.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
You can add this MCP server to your MCP Client like VSCode, Claude, Cursor, Amazon Q, Windsurf, ChatGPT, or Github Copilot via the npmjs package `@dynatrace-oss/dynatrace-mcp-server`, and type `stdio`.
|
|
31
|
+
You can find more details about the configuration for different AI Assistants, Agents and MCP Clients in the [Configuration section below](#configuration).
|
|
32
|
+
|
|
33
|
+
Furthermore, you need your Dynatrace environment URL, e.g., `https://abc12345.apps.dynatrace.com`, as well as a [Platform Token](https://docs.dynatrace.com/docs/manage/identity-access-management/access-tokens-and-oauth-clients/platform-tokens), e.g., `dt0s16.SAMPLE.abcd1234`, with [required scopes](#scopes-for-authentication).
|
|
34
|
+
|
|
35
|
+
Depending on your MCP Client, you need to configure these as environment variables or as settings in the UI:
|
|
36
|
+
|
|
37
|
+
- `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`)
|
|
38
|
+
- `DT_PLATFORM_TOKEN` (string, e.g., `dt0s16.SAMPLE.abcd1234`) - **Recommended**: Dynatrace Platform Token
|
|
39
|
+
|
|
40
|
+
Once you are done, we recommend looking into [example prompts](#-example-prompts-), like `Get all details of the entity 'my-service'` or `Show me error logs`. Please mind that these prompts lead to executing DQL statements which may incur [costs](#costs) in accordance to your licence.
|
|
41
|
+
|
|
42
|
+
## Architecture
|
|
27
43
|
|
|
28
44
|

|
|
29
45
|
|
|
@@ -184,7 +200,7 @@ rules/
|
|
|
184
200
|
|
|
185
201
|
For detailed information about the workshop rules, see the [Rules README](./dynatrace-agent-rules/rules/README.md).
|
|
186
202
|
|
|
187
|
-
##
|
|
203
|
+
## Configuration
|
|
188
204
|
|
|
189
205
|
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`.
|
|
190
206
|
|
|
@@ -261,6 +277,43 @@ The [Amazon Q Developer CLI](https://docs.aws.amazon.com/amazonq/latest/qdevelop
|
|
|
261
277
|
|
|
262
278
|
This configuration should be stored in `<your-repo>/.amazonq/mcp.json`.
|
|
263
279
|
|
|
280
|
+
**Google Gemini CLI**
|
|
281
|
+
|
|
282
|
+
The [Google Gemini CLI](https://github.com/google-gemini/gemini-cli) is Google's official command-line AI assistant that supports MCP server integration. You can add the Dynatrace MCP server using either the built-in management commands or manual configuration.
|
|
283
|
+
|
|
284
|
+
Using `gemini` CLI directly (recommended):
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
gemini extensions install https://github.com/dynatrace-oss/dynatrace-mcp
|
|
288
|
+
export DT_PLATFORM_TOKEN=...
|
|
289
|
+
export DT_ENVIRONMENT=https://...
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
and verify that the server is running via
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
gemini mcp list
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Or manually in your `~/.gemini/settings.json` or `.gemini/settings.json`:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"mcpServers": {
|
|
303
|
+
"dynatrace": {
|
|
304
|
+
"command": "npx",
|
|
305
|
+
"args": ["@dynatrace-oss/dynatrace-mcp-server@latest"],
|
|
306
|
+
"env": {
|
|
307
|
+
"DT_PLATFORM_TOKEN": "",
|
|
308
|
+
"DT_ENVIRONMENT": ""
|
|
309
|
+
},
|
|
310
|
+
"timeout": 30000,
|
|
311
|
+
"trust": false
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
264
317
|
### HTTP Server Mode (Alternative)
|
|
265
318
|
|
|
266
319
|
For scenarios where you need to run the MCP server as an HTTP service instead of using stdio (e.g., for stateful sessions, load balancing, or integration with web clients), you can use the HTTP server mode:
|
|
@@ -328,11 +381,11 @@ For fetching just error-logs, add `| filter loglevel == "ERROR"`.
|
|
|
328
381
|
|
|
329
382
|
You can set up authentication via **Platform Tokens** (recommended) or **OAuth Client** via the following environment variables:
|
|
330
383
|
|
|
331
|
-
- `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`)
|
|
384
|
+
- `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`)
|
|
332
385
|
- `DT_PLATFORM_TOKEN` (string, e.g., `dt0s16.SAMPLE.abcd1234`) - **Recommended**: Dynatrace Platform Token
|
|
333
386
|
- `OAUTH_CLIENT_ID` (string, e.g., `dt0s02.SAMPLE`) - Alternative: Dynatrace OAuth Client ID (for advanced use cases)
|
|
334
387
|
- `OAUTH_CLIENT_SECRET` (string, e.g., `dt0s02.SAMPLE.abcd1234`) - Alternative: Dynatrace OAuth Client Secret (for advanced use cases)
|
|
335
|
-
- `DT_GRAIL_QUERY_BUDGET_GB` (number, default: 1000) - Budget limit in GB (base 1000) for Grail query bytes scanned per session. The MCP server tracks your Grail usage and warns when approaching or exceeding this limit.
|
|
388
|
+
- `DT_GRAIL_QUERY_BUDGET_GB` (number, default: `1000`) - Budget limit in GB (base 1000) for Grail query bytes scanned per session. The MCP server tracks your Grail usage and warns when approaching or exceeding this limit.
|
|
336
389
|
|
|
337
390
|
**Platform Tokens are recommended** for most use cases as they provide a simpler authentication flow. OAuth Clients should only be used when specific OAuth features are required.
|
|
338
391
|
|
|
@@ -384,6 +437,18 @@ and extend them as needed. They're here to help you imagine how real-time observ
|
|
|
384
437
|
|
|
385
438
|
### **Basic Queries & AI Assistance**
|
|
386
439
|
|
|
440
|
+
**Find a monitored entity**
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
Get all details of the entity 'my-service'
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Find error logs**
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
Show me error logs
|
|
450
|
+
```
|
|
451
|
+
|
|
387
452
|
**Write a DQL query from natural language:**
|
|
388
453
|
|
|
389
454
|
```
|
|
@@ -608,51 +673,3 @@ To disable usage tracking, add this to your environment:
|
|
|
608
673
|
```bash
|
|
609
674
|
DT_MCP_DISABLE_TELEMETRY=true
|
|
610
675
|
```
|
|
611
|
-
|
|
612
|
-
## Development
|
|
613
|
-
|
|
614
|
-
For local development purposes, you can use VSCode and GitHub Copilot.
|
|
615
|
-
|
|
616
|
-
First, enable Copilot for your Workspace `.vscode/settings.json`:
|
|
617
|
-
|
|
618
|
-
```json
|
|
619
|
-
{
|
|
620
|
-
"github.copilot.enable": {
|
|
621
|
-
"*": true
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
and make sure that you are using Agent Mode in Copilot.
|
|
627
|
-
|
|
628
|
-
Second, add the MCP to `.vscode/mcp.json`:
|
|
629
|
-
|
|
630
|
-
```json
|
|
631
|
-
{
|
|
632
|
-
"servers": {
|
|
633
|
-
"my-dynatrace-mcp-server": {
|
|
634
|
-
"command": "node",
|
|
635
|
-
"args": ["--watch", "${workspaceFolder}/dist/index.js"],
|
|
636
|
-
"envFile": "${workspaceFolder}/.env"
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
Third, create a `.env` file in this repository (you can copy from `.env.template`) and configure environment variables as [described above](#environment-variables).
|
|
643
|
-
|
|
644
|
-
Finally, make changes to your code and compile it with `npm run build` or just run `npm run watch` and it auto-compiles.
|
|
645
|
-
|
|
646
|
-
## Releasing
|
|
647
|
-
|
|
648
|
-
When you are preparing for a release, you can use GitHub Copilot to guide you through the preparations.
|
|
649
|
-
|
|
650
|
-
In Visual Studio Code, you can use `/release` in the chat with Copilot in Agent Mode, which will execute [release.prompt.md](.github/prompts/release.prompt.md).
|
|
651
|
-
|
|
652
|
-
You may include additional information such as the version number. If not specified, you will be asked.
|
|
653
|
-
|
|
654
|
-
This will
|
|
655
|
-
|
|
656
|
-
- prepare the [changelog](CHANGELOG.md),
|
|
657
|
-
- update the version number in [package.json](package.json),
|
|
658
|
-
- commit the changes.
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,20 @@ const davis_copilot_1 = require("./capabilities/davis-copilot");
|
|
|
27
27
|
const getDynatraceEnv_1 = require("./getDynatraceEnv");
|
|
28
28
|
const telemetry_openkit_1 = require("./utils/telemetry-openkit");
|
|
29
29
|
const grail_budget_tracker_1 = require("./utils/grail-budget-tracker");
|
|
30
|
-
|
|
30
|
+
// Load environment variables from .env file if available, and suppress warnings/logging to stdio
|
|
31
|
+
// as it breaks MCP communication when using stdio transport
|
|
32
|
+
const dotEnvOutput = (0, dotenv_1.config)({ quiet: true });
|
|
33
|
+
if (dotEnvOutput.error) {
|
|
34
|
+
// Only log error if it's not about missing .env file
|
|
35
|
+
if (dotEnvOutput.error.code !== 'ENOENT') {
|
|
36
|
+
console.error('Error loading .env file:', dotEnvOutput.error);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Successfully loaded .env file
|
|
42
|
+
console.error(`.env file loaded successfully - loaded ${dotEnvOutput.parsed ? Object.keys(dotEnvOutput.parsed).length : 0} environment variables: ${Object.keys(dotEnvOutput.parsed || {}).join(', ')}`);
|
|
43
|
+
}
|
|
31
44
|
let scopesBase = [
|
|
32
45
|
'app-engine:apps:run', // needed for environmentInformationClient
|
|
33
46
|
'app-engine:functions:run', // needed for environmentInformationClient
|
|
@@ -129,7 +142,7 @@ const main = async () => {
|
|
|
129
142
|
},
|
|
130
143
|
});
|
|
131
144
|
// quick abstraction/wrapper to make it easier for tools to reply text instead of JSON
|
|
132
|
-
const tool = (name, description, paramsSchema, cb) => {
|
|
145
|
+
const tool = (name, description, paramsSchema, annotations, cb) => {
|
|
133
146
|
const wrappedCb = async (args) => {
|
|
134
147
|
// track starttime for telemetry
|
|
135
148
|
const startTime = Date.now();
|
|
@@ -168,10 +181,12 @@ const main = async () => {
|
|
|
168
181
|
.catch((e) => console.warn('Failed to track tool usage:', e));
|
|
169
182
|
}
|
|
170
183
|
};
|
|
171
|
-
server.tool(name, description, paramsSchema, (args) => wrappedCb(args));
|
|
184
|
+
server.tool(name, description, paramsSchema, annotations, (args) => wrappedCb(args));
|
|
172
185
|
};
|
|
173
186
|
/** Tool Definitions below */
|
|
174
|
-
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant) and verify the connection and authentication.', {},
|
|
187
|
+
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant) and verify the connection and authentication.', {}, {
|
|
188
|
+
readOnlyHint: true,
|
|
189
|
+
}, async ({}) => {
|
|
175
190
|
// create an oauth-client
|
|
176
191
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
177
192
|
const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
|
|
@@ -195,6 +210,8 @@ const main = async () => {
|
|
|
195
210
|
.number()
|
|
196
211
|
.default(25)
|
|
197
212
|
.describe('Maximum number of vulnerabilities to display in the response.'),
|
|
213
|
+
}, {
|
|
214
|
+
readOnlyHint: true,
|
|
198
215
|
}, async ({ riskScore, additionalFilter, maxVulnerabilitiesToDisplay }) => {
|
|
199
216
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read', 'storage:buckets:read', 'storage:security.events:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
200
217
|
const result = await (0, list_vulnerabilities_1.listVulnerabilities)(dtClient, additionalFilter, riskScore);
|
|
@@ -236,6 +253,8 @@ const main = async () => {
|
|
|
236
253
|
.optional()
|
|
237
254
|
.describe('Additional filter for DQL statement for dt.davis.problems, e.g., \'entity_tags == array("dt.owner:team-foobar", "tag:tag")\''),
|
|
238
255
|
maxProblemsToDisplay: zod_1.z.number().default(10).describe('Maximum number of problems to display in the response.'),
|
|
256
|
+
}, {
|
|
257
|
+
readOnlyHint: true,
|
|
239
258
|
}, async ({ additionalFilter, maxProblemsToDisplay }) => {
|
|
240
259
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read', 'storage:buckets:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
241
260
|
// get problems (uses fetch)
|
|
@@ -270,6 +289,8 @@ const main = async () => {
|
|
|
270
289
|
});
|
|
271
290
|
tool('find_entity_by_name', 'Get the entityId of a monitored entity (service, host, process-group, application, kubernetes-node, ...) within the topology based on the name of the entity on Dynatrace', {
|
|
272
291
|
entityName: zod_1.z.string().describe('Name of the entity to search for, e.g., "my-service" or "my-host"'),
|
|
292
|
+
}, {
|
|
293
|
+
readOnlyHint: true,
|
|
273
294
|
}, async ({ entityName }) => {
|
|
274
295
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
275
296
|
const entityResponse = await (0, find_monitored_entity_by_name_1.findMonitoredEntityByName)(dtClient, entityName);
|
|
@@ -277,6 +298,8 @@ const main = async () => {
|
|
|
277
298
|
});
|
|
278
299
|
tool('get_entity_details', 'Get details of a monitored entity based on the entityId on Dynatrace', {
|
|
279
300
|
entityId: zod_1.z.string().optional(),
|
|
301
|
+
}, {
|
|
302
|
+
readOnlyHint: true,
|
|
280
303
|
}, async ({ entityId }) => {
|
|
281
304
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:entities:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
282
305
|
const entityDetails = await (0, get_monitored_entity_details_1.getMonitoredEntityDetails)(dtClient, entityId);
|
|
@@ -314,6 +337,9 @@ const main = async () => {
|
|
|
314
337
|
tool('send_slack_message', 'Sends a Slack message to a dedicated Slack Channel via Slack Connector on Dynatrace', {
|
|
315
338
|
channel: zod_1.z.string().optional(),
|
|
316
339
|
message: zod_1.z.string().optional(),
|
|
340
|
+
}, {
|
|
341
|
+
// not read-only, not open-world, not destructive
|
|
342
|
+
readOnlyHint: false,
|
|
317
343
|
}, async ({ channel, message }) => {
|
|
318
344
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('app-settings:objects:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
319
345
|
const response = await (0, send_slack_message_1.sendSlackMessage)(dtClient, slackConnectionId, channel, message);
|
|
@@ -321,6 +347,9 @@ const main = async () => {
|
|
|
321
347
|
});
|
|
322
348
|
tool('verify_dql', 'Verify a Dynatrace Query Language (DQL) statement on Dynatrace GRAIL before executing it. This step is recommended for DQL statements that have been dynamically created by non-expert tools. For statements coming from the `generate_dql_from_natural_language` tool as well as from documentation, this step can be omitted.', {
|
|
323
349
|
dqlStatement: zod_1.z.string(),
|
|
350
|
+
}, {
|
|
351
|
+
readOnlyHint: true,
|
|
352
|
+
idempotentHint: true, // same input always yields same output
|
|
324
353
|
}, async ({ dqlStatement }) => {
|
|
325
354
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
326
355
|
const response = await (0, execute_dql_1.verifyDqlStatement)(dtClient, dqlStatement);
|
|
@@ -345,6 +374,12 @@ const main = async () => {
|
|
|
345
374
|
dqlStatement: zod_1.z
|
|
346
375
|
.string()
|
|
347
376
|
.describe('DQL Statement (Ex: "fetch [logs, spans, events] | filter <some-filter> | summarize count(), by:{some-fields}.", or for metrics: "timeseries { avg(<metric-name>), value.A = avg(<metric-name>, scalar: true) }")'),
|
|
377
|
+
}, {
|
|
378
|
+
// not readonly (DQL statements may modify things), not idempotent (may change over time)
|
|
379
|
+
readOnlyHint: false,
|
|
380
|
+
idempotentHint: false,
|
|
381
|
+
// while we are not strictly talking to the open world here, the response from execute DQL could interpreted as a web-search, which often is referred to open-world
|
|
382
|
+
openWorldHint: true,
|
|
348
383
|
}, async ({ dqlStatement }) => {
|
|
349
384
|
// Create a HTTP Client that has all storage:*:read scopes
|
|
350
385
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
|
|
@@ -404,6 +439,9 @@ const main = async () => {
|
|
|
404
439
|
text: zod_1.z
|
|
405
440
|
.string()
|
|
406
441
|
.describe('Natural language description of what you want to query. Be specific and include time ranges, entities, and metrics of interest.'),
|
|
442
|
+
}, {
|
|
443
|
+
readOnlyHint: true,
|
|
444
|
+
idempotentHint: true,
|
|
407
445
|
}, async ({ text }) => {
|
|
408
446
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:nl2dql:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
409
447
|
const response = await (0, davis_copilot_1.generateDqlFromNaturalLanguage)(dtClient, text);
|
|
@@ -430,6 +468,9 @@ const main = async () => {
|
|
|
430
468
|
});
|
|
431
469
|
tool('explain_dql_in_natural_language', 'Explain Dynatrace Query Language (DQL) statements in natural language using Davis CoPilot AI.', {
|
|
432
470
|
dql: zod_1.z.string().describe('The DQL statement to explain'),
|
|
471
|
+
}, {
|
|
472
|
+
readOnlyHint: true,
|
|
473
|
+
idempotentHint: true,
|
|
433
474
|
}, async ({ dql }) => {
|
|
434
475
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:dql2nl:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
435
476
|
const response = await (0, davis_copilot_1.explainDqlInNaturalLanguage)(dtClient, dql);
|
|
@@ -451,6 +492,10 @@ const main = async () => {
|
|
|
451
492
|
text: zod_1.z.string().describe('Your question or request for Davis CoPilot'),
|
|
452
493
|
context: zod_1.z.string().optional().describe('Optional context to provide additional information'),
|
|
453
494
|
instruction: zod_1.z.string().optional().describe('Optional instruction for how to format the response'),
|
|
495
|
+
}, {
|
|
496
|
+
readOnlyHint: true,
|
|
497
|
+
idempotentHint: true,
|
|
498
|
+
openWorldHint: true, // web-search like characteristics
|
|
454
499
|
}, async ({ text, context, instruction }) => {
|
|
455
500
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('davis-copilot:conversations:execute'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
456
501
|
const conversationContext = [];
|
|
@@ -500,6 +545,10 @@ const main = async () => {
|
|
|
500
545
|
teamName: zod_1.z.string().optional(),
|
|
501
546
|
channel: zod_1.z.string().optional(),
|
|
502
547
|
isPrivate: zod_1.z.boolean().optional().default(false),
|
|
548
|
+
}, {
|
|
549
|
+
// not read only, not idempotent
|
|
550
|
+
readOnlyHint: false,
|
|
551
|
+
idempotentHint: false, // creating the same workflow multiple times is possible
|
|
503
552
|
}, async ({ problemType, teamName, channel, isPrivate }) => {
|
|
504
553
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
505
554
|
const response = await (0, create_workflow_for_problem_notification_1.createWorkflowForProblemNotification)(dtClient, teamName, channel, problemType, isPrivate);
|
|
@@ -517,6 +566,10 @@ const main = async () => {
|
|
|
517
566
|
});
|
|
518
567
|
tool('make_workflow_public', 'Modify a workflow and make it publicly available to everyone on the Dynatrace Environment', {
|
|
519
568
|
workflowId: zod_1.z.string().optional(),
|
|
569
|
+
}, {
|
|
570
|
+
// not read only, but idempotent
|
|
571
|
+
readOnlyHint: false,
|
|
572
|
+
idempotentHint: true, // making the same workflow public multiple times yields the same result
|
|
520
573
|
}, async ({ workflowId }) => {
|
|
521
574
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('automation:workflows:write', 'automation:workflows:read', 'automation:workflows:run'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
522
575
|
const response = await (0, update_workflow_1.updateWorkflow)(dtClient, workflowId, {
|
|
@@ -529,6 +582,8 @@ const main = async () => {
|
|
|
529
582
|
.string()
|
|
530
583
|
.optional()
|
|
531
584
|
.describe(`The Kubernetes (K8s) Cluster Id, referred to as k8s.cluster.uid (this is NOT the Dynatrace environment)`),
|
|
585
|
+
}, {
|
|
586
|
+
readOnlyHint: true,
|
|
532
587
|
}, async ({ clusterId }) => {
|
|
533
588
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:events:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
534
589
|
const events = await (0, get_events_for_cluster_1.getEventsForCluster)(dtClient, clusterId);
|
|
@@ -536,6 +591,8 @@ const main = async () => {
|
|
|
536
591
|
});
|
|
537
592
|
tool('get_ownership', 'Get detailed Ownership information for one or multiple entities on Dynatrace', {
|
|
538
593
|
entityIds: zod_1.z.string().optional().describe('Comma separated list of entityIds'),
|
|
594
|
+
}, {
|
|
595
|
+
readOnlyHint: true,
|
|
539
596
|
}, async ({ entityIds }) => {
|
|
540
597
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('environment-api:entities:read', 'settings:objects:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
541
598
|
console.error(`Fetching ownership for ${entityIds}`);
|
|
@@ -545,7 +602,10 @@ const main = async () => {
|
|
|
545
602
|
resp += JSON.stringify(ownershipInformation);
|
|
546
603
|
return resp;
|
|
547
604
|
});
|
|
548
|
-
tool('reset_grail_budget', 'Reset the Grail query budget after it was exhausted, allowing new queries to be executed. This clears all tracked bytes scanned in the current session.', {},
|
|
605
|
+
tool('reset_grail_budget', 'Reset the Grail query budget after it was exhausted, allowing new queries to be executed. This clears all tracked bytes scanned in the current session.', {}, {
|
|
606
|
+
readonlyHint: false, // modifies state
|
|
607
|
+
idempotentHint: true, // multiple resets yield the same result
|
|
608
|
+
}, async ({}) => {
|
|
549
609
|
// Reset the global tracker
|
|
550
610
|
(0, grail_budget_tracker_1.resetGrailBudgetTracker)();
|
|
551
611
|
// Get a fresh tracker to show the reset state
|
|
@@ -572,6 +632,8 @@ You can now execute new Grail queries (DQL, etc.) again. If this happens more of
|
|
|
572
632
|
bccRecipients: zod_1.z.array(zod_1.z.string().email()).optional().describe('Array of email addresses for BCC recipients'),
|
|
573
633
|
subject: zod_1.z.string().describe('Subject line of the email'),
|
|
574
634
|
body: zod_1.z.string().describe('Body content of the email (plain text only)'),
|
|
635
|
+
}, {
|
|
636
|
+
openWorldHint: true, // email is as close to the open-world as we can get with our system
|
|
575
637
|
}, async ({ toRecipients, ccRecipients, bccRecipients, subject, body }) => {
|
|
576
638
|
// Validate total recipients limit (10 max across TO, CC, and BCC)
|
|
577
639
|
const totalRecipients = toRecipients.length + (ccRecipients?.length || 0) + (bccRecipients?.length || 0);
|
package/package.json
CHANGED