@dynatrace-oss/dynatrace-mcp-server 0.5.0-rc.4 → 0.5.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 +28 -0
- package/dist/authentication/dynatrace-clients.js +2 -2
- package/dist/authentication/dynatrace-clients.test.js +2 -2
- package/dist/capabilities/execute-dql.js +6 -1
- package/dist/index.js +7 -10
- package/dist/utils/user-agent.js +12 -0
- package/package.json +2 -2
- package/dist/capabilities/get-logs-for-entity.js +0 -9
package/README.md
CHANGED
|
@@ -27,6 +27,20 @@ Bring real-time observability data directly into your development workflow.
|
|
|
27
27
|
- Get more information about a monitored entity
|
|
28
28
|
- Get Ownership of an entity
|
|
29
29
|
|
|
30
|
+
## Costs
|
|
31
|
+
|
|
32
|
+
**Important:** While this local MCP server is provided for free, using it to access data in Dynatrace Grail may incur additional costs based
|
|
33
|
+
on your Dynatrace consumption model. This affects `execute_dql` tool and other capabilities that **query** Dynatrace Grail storage, and costs
|
|
34
|
+
depend on the volume (GB scanned/billed).
|
|
35
|
+
|
|
36
|
+
**Before using this MCP server extensively, please:**
|
|
37
|
+
|
|
38
|
+
1. Review your current Dynatrace consumption model and pricing
|
|
39
|
+
2. Understand the cost implications of the specific data you plan to query (logs, events, metrics) - see [Dynatrace Pricing and Rate Card](https://www.dynatrace.com/pricing/)
|
|
40
|
+
3. Start with smaller timeframes (e.g., 12h-24h) and make use of [buckets](https://docs.dynatrace.com/docs/discover-dynatrace/platform/grail/data-model#built-in-grail-buckets) to reduce the cost impact
|
|
41
|
+
|
|
42
|
+
**Note**: We will be providing a way to monitor Query Usage of the dynatrace-mcp-server in the future.
|
|
43
|
+
|
|
30
44
|
### AI-Powered Assistance (Preview)
|
|
31
45
|
|
|
32
46
|
- **Natural Language to DQL** - Convert plain English queries to Dynatrace Query Language
|
|
@@ -554,6 +568,20 @@ Third, create a `.env` file in this repository (you can copy from `.env.template
|
|
|
554
568
|
|
|
555
569
|
Finally, make changes to your code and compile it with `npm run build` or just run `npm run watch` and it auto-compiles.
|
|
556
570
|
|
|
571
|
+
## Releasing
|
|
572
|
+
|
|
573
|
+
When you are preparing for a release, you can use GitHub Copilot to guide you through the preparations.
|
|
574
|
+
|
|
575
|
+
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).
|
|
576
|
+
|
|
577
|
+
You may include additional information such as the version number. If not specified, you will be asked.
|
|
578
|
+
|
|
579
|
+
This will
|
|
580
|
+
|
|
581
|
+
- prepare the [changelog](CHANGELOG.md),
|
|
582
|
+
- update the version number in [package.json](package.json),
|
|
583
|
+
- commit the changes.
|
|
584
|
+
|
|
557
585
|
## Notes
|
|
558
586
|
|
|
559
587
|
This product is not officially supported by Dynatrace.
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createDtHttpClient = void 0;
|
|
4
4
|
const http_client_1 = require("@dynatrace-sdk/http-client");
|
|
5
5
|
const dt_app_1 = require("dt-app");
|
|
6
|
-
const
|
|
6
|
+
const user_agent_1 = require("../utils/user-agent");
|
|
7
7
|
/**
|
|
8
8
|
* Uses the provided oauth Client ID and Secret and requests a token via client-credentials flow
|
|
9
9
|
* @param clientId - OAuth Client ID for Dynatrace
|
|
@@ -60,7 +60,7 @@ const createBearerTokenHttpClient = async (environmentUrl, dtPlatformToken) => {
|
|
|
60
60
|
baseUrl: environmentUrl,
|
|
61
61
|
defaultHeaders: {
|
|
62
62
|
'Authorization': `Bearer ${dtPlatformToken}`,
|
|
63
|
-
'User-Agent':
|
|
63
|
+
'User-Agent': (0, user_agent_1.getUserAgent)(),
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
};
|
|
@@ -62,7 +62,7 @@ describe('dynatrace-clients', () => {
|
|
|
62
62
|
baseUrl: environmentUrl,
|
|
63
63
|
defaultHeaders: {
|
|
64
64
|
'Authorization': 'Bearer test-access-token',
|
|
65
|
-
'User-Agent':
|
|
65
|
+
'User-Agent': expect.stringMatching(/^dynatrace-mcp-server\/v1\.0\.0-test \(\w+-\w+\)$/),
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
68
|
expect(result).toBeInstanceOf(http_client_1.PlatformHttpClient);
|
|
@@ -131,7 +131,7 @@ describe('dynatrace-clients', () => {
|
|
|
131
131
|
baseUrl: environmentUrl,
|
|
132
132
|
defaultHeaders: {
|
|
133
133
|
'Authorization': `Bearer ${dtPlatformToken}`,
|
|
134
|
-
'User-Agent':
|
|
134
|
+
'User-Agent': expect.stringMatching(/^dynatrace-mcp-server\/v1\.0\.0-test \(\w+-\w+\)$/),
|
|
135
135
|
},
|
|
136
136
|
});
|
|
137
137
|
expect(result).toBeInstanceOf(http_client_1.PlatformHttpClient);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.executeDql = exports.verifyDqlStatement = void 0;
|
|
4
4
|
const client_query_1 = require("@dynatrace-sdk/client-query");
|
|
5
|
+
const user_agent_1 = require("../utils/user-agent");
|
|
5
6
|
const verifyDqlStatement = async (dtClient, dqlStatement) => {
|
|
6
7
|
const queryAssistanceClient = new client_query_1.QueryAssistanceClient(dtClient);
|
|
7
8
|
const response = await queryAssistanceClient.queryVerify({
|
|
@@ -22,7 +23,10 @@ exports.verifyDqlStatement = verifyDqlStatement;
|
|
|
22
23
|
*/
|
|
23
24
|
const executeDql = async (dtClient, body) => {
|
|
24
25
|
const queryExecutionClient = new client_query_1.QueryExecutionClient(dtClient);
|
|
25
|
-
const response = await queryExecutionClient.queryExecute({
|
|
26
|
+
const response = await queryExecutionClient.queryExecute({
|
|
27
|
+
body,
|
|
28
|
+
dtClientContext: (0, user_agent_1.getUserAgent)(),
|
|
29
|
+
});
|
|
26
30
|
if (response.result) {
|
|
27
31
|
// return response result immediately
|
|
28
32
|
return response.result.records;
|
|
@@ -36,6 +40,7 @@ const executeDql = async (dtClient, body) => {
|
|
|
36
40
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
37
41
|
pollResponse = await queryExecutionClient.queryPoll({
|
|
38
42
|
requestToken: response.requestToken,
|
|
43
|
+
dtClientContext: (0, user_agent_1.getUserAgent)(),
|
|
39
44
|
});
|
|
40
45
|
// done - let's return it
|
|
41
46
|
if (pollResponse.result) {
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,6 @@ const list_vulnerabilities_1 = require("./capabilities/list-vulnerabilities");
|
|
|
17
17
|
const list_problems_1 = require("./capabilities/list-problems");
|
|
18
18
|
const get_monitored_entity_details_1 = require("./capabilities/get-monitored-entity-details");
|
|
19
19
|
const get_ownership_information_1 = require("./capabilities/get-ownership-information");
|
|
20
|
-
const get_logs_for_entity_1 = require("./capabilities/get-logs-for-entity");
|
|
21
20
|
const get_events_for_cluster_1 = require("./capabilities/get-events-for-cluster");
|
|
22
21
|
const create_workflow_for_problem_notification_1 = require("./capabilities/create-workflow-for-problem-notification");
|
|
23
22
|
const update_workflow_1 = require("./capabilities/update-workflow");
|
|
@@ -261,13 +260,6 @@ const main = async () => {
|
|
|
261
260
|
const response = await (0, send_slack_message_1.sendSlackMessage)(dtClient, slackConnectionId, channel, message);
|
|
262
261
|
return `Message sent to Slack channel: ${JSON.stringify(response)}`;
|
|
263
262
|
});
|
|
264
|
-
tool('get_logs_for_entity', 'Get Logs for a monitored entity based on name of the entity on Dynatrace', {
|
|
265
|
-
entityName: zod_1.z.string().optional(),
|
|
266
|
-
}, async ({ entityName }) => {
|
|
267
|
-
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:logs:read'), oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
268
|
-
const logs = await (0, get_logs_for_entity_1.getLogsForEntity)(dtClient, entityName);
|
|
269
|
-
return `Logs:\n${JSON.stringify(logs?.map((logLine) => (logLine ? logLine.content : 'Empty log')))}`;
|
|
270
|
-
});
|
|
271
263
|
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.', {
|
|
272
264
|
dqlStatement: zod_1.z.string(),
|
|
273
265
|
}, async ({ dqlStatement }) => {
|
|
@@ -288,9 +280,14 @@ const main = async () => {
|
|
|
288
280
|
}
|
|
289
281
|
return resp;
|
|
290
282
|
});
|
|
291
|
-
tool('execute_dql', 'Get Logs, Metrics, Spans or Events from Dynatrace GRAIL by executing a Dynatrace Query Language (DQL) statement.
|
|
292
|
-
|
|
283
|
+
tool('execute_dql', 'Get Logs, Metrics, Spans or Events from Dynatrace GRAIL by executing a Dynatrace Query Language (DQL) statement. ' +
|
|
284
|
+
'You can also use "generate_dql_from_natural_language" to generate a DQL statement based on your request. ' +
|
|
285
|
+
'Note: For more information about available fields for filters and aggregation, use the query "fetch dt.semantic_dictionary.models | filter data_object == \"logs\""', {
|
|
286
|
+
dqlStatement: zod_1.z
|
|
287
|
+
.string()
|
|
288
|
+
.describe('DQL Statement (Ex: "fetch [logs, spans, events] | filter <some-filter> | summarize count(), by:{some-fields}.", "timeseries { avg(<metric-name>), value.A = avg(<metric-name>, scalar: true) }")'),
|
|
293
289
|
}, async ({ dqlStatement }) => {
|
|
290
|
+
// Create a HTTP Client that has all storage:*:read scopes
|
|
294
291
|
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
|
|
295
292
|
'storage:logs:read', // Read logs for reliability guardian validations
|
|
296
293
|
'storage:metrics:read', // Read metrics for reliability guardian validations
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUserAgent = void 0;
|
|
4
|
+
const package_json_1 = require("../../package.json");
|
|
5
|
+
/**
|
|
6
|
+
* Generate a user agent string for Dynatrace MCP Server
|
|
7
|
+
* @returns User agent string in format: dynatrace-mcp-server/vX.X.X (platform-arch)
|
|
8
|
+
*/
|
|
9
|
+
const getUserAgent = () => {
|
|
10
|
+
return `dynatrace-mcp-server/v${package_json_1.version} (${process.platform}-${process.arch})`;
|
|
11
|
+
};
|
|
12
|
+
exports.getUserAgent = getUserAgent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynatrace-oss/dynatrace-mcp-server",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server for Dynatrace",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Dynatrace",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@dynatrace-sdk/shared-errors": "^1.0.0",
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
54
54
|
"commander": "^14.0.0",
|
|
55
|
-
"dotenv": "^
|
|
55
|
+
"dotenv": "^17.2.1",
|
|
56
56
|
"dt-app": "^0.148.1",
|
|
57
57
|
"zod-to-json-schema": "^3.24.5"
|
|
58
58
|
},
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getLogsForEntity = void 0;
|
|
4
|
-
const execute_dql_1 = require("./execute-dql");
|
|
5
|
-
const getLogsForEntity = async (dtClient, entityId) => {
|
|
6
|
-
const dql = `fetch logs | filter dt.source_entity == "${entityId}"`;
|
|
7
|
-
return (0, execute_dql_1.executeDql)(dtClient, { query: dql });
|
|
8
|
-
};
|
|
9
|
-
exports.getLogsForEntity = getLogsForEntity;
|