@dynatrace-oss/dynatrace-mcp-server 1.4.0 ā 1.5.0-beta.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/dist/capabilities/execute-dql.js +1 -0
- package/dist/index.js +74 -26
- package/dist/ui/execute-dql/index.html +253 -0
- package/package.json +17 -4
|
@@ -34,6 +34,7 @@ const createResultAndLog = (queryResult, logPrefix, budgetLimitGB) => {
|
|
|
34
34
|
const result = {
|
|
35
35
|
records: queryResult.records,
|
|
36
36
|
metadata: queryResult.metadata,
|
|
37
|
+
types: queryResult.types ?? [],
|
|
37
38
|
scannedBytes,
|
|
38
39
|
scannedRecords: queryResult.metadata?.grail?.scannedRecords,
|
|
39
40
|
executionTimeMilliseconds: queryResult.metadata?.grail?.executionTimeMilliseconds,
|
package/dist/index.js
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const client_platform_management_service_1 = require("@dynatrace-sdk/client-platform-management-service");
|
|
5
5
|
const shared_errors_1 = require("@dynatrace-sdk/shared-errors");
|
|
6
|
+
// Dynamically imported below (ESM-only package)
|
|
7
|
+
// import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
|
|
6
8
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
9
|
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
8
10
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9
11
|
const node_http_1 = require("node:http");
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
10
14
|
const commander_1 = require("commander");
|
|
11
15
|
const zod_1 = require("zod");
|
|
12
16
|
const version_1 = require("./utils/version");
|
|
@@ -76,6 +80,9 @@ const allRequiredScopes = scopesBase.concat([
|
|
|
76
80
|
'document:documents:write', // Create and update documents
|
|
77
81
|
]);
|
|
78
82
|
const main = async () => {
|
|
83
|
+
// Dynamic import: @modelcontextprotocol/ext-apps is ESM-only and can't be require()'d.
|
|
84
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
85
|
+
const { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } = await dynamicImport('@modelcontextprotocol/ext-apps/server');
|
|
79
86
|
console.error(`Initializing Dynatrace MCP Server v${(0, version_1.getPackageJsonVersion)()}...`);
|
|
80
87
|
// Configure proxy from environment variables early in the startup process
|
|
81
88
|
(0, proxy_config_1.configureProxyFromEnvironment)();
|
|
@@ -181,9 +188,9 @@ const main = async () => {
|
|
|
181
188
|
}
|
|
182
189
|
// Ready to start the server
|
|
183
190
|
console.error(`Starting Dynatrace MCP Server v${(0, version_1.getPackageJsonVersion)()}...`);
|
|
184
|
-
//
|
|
185
|
-
const
|
|
186
|
-
|
|
191
|
+
// Wraps a string-returning tool callback with rate limiting, telemetry, and error handling
|
|
192
|
+
const wrapToolCallback = (name, cb) => {
|
|
193
|
+
return async (args, _extra) => {
|
|
187
194
|
// Capture starttime for telemetry and rate limiting
|
|
188
195
|
const startTime = Date.now();
|
|
189
196
|
/**
|
|
@@ -210,9 +217,16 @@ const main = async () => {
|
|
|
210
217
|
// call the tool
|
|
211
218
|
const response = await cb(args);
|
|
212
219
|
toolCallSuccessful = true;
|
|
213
|
-
return {
|
|
214
|
-
|
|
220
|
+
// Support callbacks that return either a plain string or { text, _meta }
|
|
221
|
+
const text = typeof response === 'string' ? response : response.text;
|
|
222
|
+
const _meta = typeof response === 'string' ? undefined : response._meta;
|
|
223
|
+
const result = {
|
|
224
|
+
content: [{ type: 'text', text }],
|
|
215
225
|
};
|
|
226
|
+
if (_meta) {
|
|
227
|
+
result._meta = _meta;
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
216
230
|
}
|
|
217
231
|
catch (error) {
|
|
218
232
|
// Track error
|
|
@@ -239,12 +253,15 @@ const main = async () => {
|
|
|
239
253
|
.catch((e) => console.warn('Failed to track tool usage:', e));
|
|
240
254
|
}
|
|
241
255
|
};
|
|
256
|
+
};
|
|
257
|
+
// quick abstraction/wrapper to make it easier for tools to reply text instead of JSON
|
|
258
|
+
const tool = (name, title, description, paramsSchema, annotations, cb) => {
|
|
242
259
|
server.registerTool(name, {
|
|
243
260
|
title: title,
|
|
244
261
|
description: description,
|
|
245
262
|
inputSchema: zod_1.z.object(paramsSchema),
|
|
246
263
|
annotations: annotations,
|
|
247
|
-
}, (args) =>
|
|
264
|
+
}, (args) => wrapToolCallback(name, cb)(args));
|
|
248
265
|
};
|
|
249
266
|
/**
|
|
250
267
|
* Helper function to request human approval for potentially sensitive operations
|
|
@@ -498,26 +515,39 @@ const main = async () => {
|
|
|
498
515
|
}
|
|
499
516
|
return resp;
|
|
500
517
|
});
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
// MCP App: Define the resource URI for the execute_dql interactive UI
|
|
519
|
+
// The ui:// scheme tells hosts this is an MCP App resource.
|
|
520
|
+
const executeDqlResourceUri = 'ui://execute-dql/execute-dql.html';
|
|
521
|
+
// Register the execute_dql tool with MCP App UI support.
|
|
522
|
+
registerAppTool(server, 'execute_dql', {
|
|
523
|
+
title: 'Execute DQL',
|
|
524
|
+
description: 'Get data like Logs, Metrics, Spans, Events, or Entity Data from Dynatrace GRAIL by executing a Dynatrace Query Language (DQL) statement. ' +
|
|
525
|
+
'Use the "generate_dql_from_natural_language" tool upfront to generate or refine a DQL statement based on your request. ' +
|
|
526
|
+
'To learn about possible fields available for filtering, use the query "fetch dt.semantic_dictionary.models | filter data_object == \\"logs\\""',
|
|
527
|
+
inputSchema: {
|
|
528
|
+
dqlStatement: zod_1.z
|
|
529
|
+
.string()
|
|
530
|
+
.describe('DQL Statement (Ex: "fetch [logs, spans, events, metric.series, ...], from: now()-4h, to: now() [| filter <some-filter>] [| summarize count(), by:{some-fields}]", or for metrics: "timeseries { avg(<metric-name>), value.A = avg(<metric-name>, scalar: true) }", or for entities via smartscape: "smartscapeNodes \\"[*, HOST, PROCESS, ...]\\" [| filter id == \\"<ENTITY-ID>\\"]"). ' +
|
|
531
|
+
'When querying data for a specific entity, call the `find_entity_by_name` tool first to get an appropriate filter like `dt.entity.service == "SERVICE-1234"` or `dt.entity.host == "HOST-1234"` to be used in the DQL statement. '),
|
|
532
|
+
recordLimit: zod_1.z.number().optional().default(100).describe('Maximum number of records to return (default: 100)'),
|
|
533
|
+
recordSizeLimitMB: zod_1.z
|
|
534
|
+
.number()
|
|
535
|
+
.optional()
|
|
536
|
+
.default(1)
|
|
537
|
+
.describe('Maximum size of the returned records in MB (default: 1MB)'),
|
|
538
|
+
},
|
|
539
|
+
annotations: {
|
|
540
|
+
// not readonly (DQL statements may modify things), not idempotent (may change over time)
|
|
541
|
+
readOnlyHint: false,
|
|
542
|
+
idempotentHint: false,
|
|
543
|
+
// 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
|
|
544
|
+
openWorldHint: true,
|
|
545
|
+
},
|
|
546
|
+
_meta: {
|
|
547
|
+
// MCP App
|
|
548
|
+
ui: { resourceUri: executeDqlResourceUri },
|
|
549
|
+
},
|
|
550
|
+
}, wrapToolCallback('execute_dql', async ({ dqlStatement, recordLimit = 100, recordSizeLimitMB = 1 }) => {
|
|
521
551
|
// Create a HTTP Client that has all storage:*:read scopes
|
|
522
552
|
const dtClient = await createAuthenticatedHttpClient(scopesBase.concat('storage:buckets:read', // Read all system data stored on Grail
|
|
523
553
|
'storage:logs:read', // Read logs for reliability guardian validations
|
|
@@ -581,7 +611,25 @@ const main = async () => {
|
|
|
581
611
|
}
|
|
582
612
|
result += `\nš **Query Results**: (${response.records?.length || 0} records):\n\n`;
|
|
583
613
|
result += `\`\`\`json\n${JSON.stringify(response.records, null, 2)}\n\`\`\``;
|
|
614
|
+
// Include field type definitions for chart rendering in the UI
|
|
615
|
+
if (response.types && response.types.length > 0) {
|
|
616
|
+
result += `\n\nš **Field Types**:\n\n`;
|
|
617
|
+
result += `\`\`\`json:types\n${JSON.stringify(response.types)}\n\`\`\``;
|
|
618
|
+
}
|
|
619
|
+
// Include analysisTimeframe metadata for chart rendering in the UI.
|
|
620
|
+
// This is needed when timeseries results lack explicit timeframe/interval
|
|
621
|
+
// columns (e.g. timeseries queries with fieldsRemove or custom projections).
|
|
622
|
+
if (response.metadata?.grail?.analysisTimeframe) {
|
|
623
|
+
result += `\n\n\`\`\`json:analysisTimeframe\n${JSON.stringify(response.metadata.grail.analysisTimeframe)}\n\`\`\``;
|
|
624
|
+
}
|
|
584
625
|
return result;
|
|
626
|
+
}));
|
|
627
|
+
// MCP App: Register the HTML resource for the execute_dql interactive UI (MCP App)
|
|
628
|
+
registerAppResource(server, 'DQL Results Viewer', executeDqlResourceUri, {}, async () => {
|
|
629
|
+
const html = (0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, 'ui', 'execute-dql', 'index.html'), 'utf-8');
|
|
630
|
+
return {
|
|
631
|
+
contents: [{ uri: executeDqlResourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }],
|
|
632
|
+
};
|
|
585
633
|
});
|
|
586
634
|
tool('generate_dql_from_natural_language', 'Generate DQL from Natural Language', 'Convert natural language queries to Dynatrace Query Language (DQL) using Davis CoPilot AI. You can ask for problem events, security issues, logs, metrics, spans, and custom data.', {
|
|
587
635
|
text: zod_1.z
|