@dynatrace-oss/dynatrace-mcp-server 0.9.1 → 0.9.2
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/index.js +51 -68
- package/dist/utils/dynatrace-connection-utils.js +11 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ const getDynatraceEnv_1 = require("./getDynatraceEnv");
|
|
|
27
27
|
const telemetry_openkit_1 = require("./utils/telemetry-openkit");
|
|
28
28
|
const dynatrace_entity_types_1 = require("./utils/dynatrace-entity-types");
|
|
29
29
|
const grail_budget_tracker_1 = require("./utils/grail-budget-tracker");
|
|
30
|
+
const dynatrace_connection_utils_1 = require("./utils/dynatrace-connection-utils");
|
|
30
31
|
// Load environment variables from .env file if available, and suppress warnings/logging to stdio
|
|
31
32
|
// as it breaks MCP communication when using stdio transport
|
|
32
33
|
const dotEnvOutput = (0, dotenv_1.config)({ quiet: true });
|
|
@@ -42,6 +43,7 @@ else {
|
|
|
42
43
|
console.error(`.env file loaded successfully - loaded ${dotEnvOutput.parsed ? Object.keys(dotEnvOutput.parsed).length : 0} environment variables: ${Object.keys(dotEnvOutput.parsed || {}).join(', ')}`);
|
|
43
44
|
}
|
|
44
45
|
const DT_MCP_AUTH_CODE_FLOW_OAUTH_CLIENT_ID = 'dt0s08.dt-app-local'; // ToDo: Register our own oauth client
|
|
46
|
+
// Base Scopes for MCP Server tools
|
|
45
47
|
let scopesBase = [
|
|
46
48
|
'app-engine:apps:run', // needed for environmentInformationClient
|
|
47
49
|
'app-engine:functions:run', // needed for environmentInformationClient
|
|
@@ -53,7 +55,8 @@ const allRequiredScopes = scopesBase.concat([
|
|
|
53
55
|
'storage:events:read', // Read events from Grail
|
|
54
56
|
'storage:buckets:read', // Read all system data stored on Grail
|
|
55
57
|
'storage:security.events:read', // Read Security events from Grail
|
|
56
|
-
'storage:entities:read', // Read Entities
|
|
58
|
+
'storage:entities:read', // Read classic Entities
|
|
59
|
+
'storage:smartscape:read', // Read Smartscape Entities from Grail
|
|
57
60
|
'storage:logs:read', // Read logs for reliability guardian validations
|
|
58
61
|
'storage:metrics:read', // Read metrics for reliability guardian validations
|
|
59
62
|
'storage:bizevents:read', // Read bizevents for reliability guardian validations
|
|
@@ -74,57 +77,6 @@ const allRequiredScopes = scopesBase.concat([
|
|
|
74
77
|
// Communication scopes
|
|
75
78
|
'email:emails:send', // Send emails
|
|
76
79
|
]);
|
|
77
|
-
/**
|
|
78
|
-
* Performs a connection test to the Dynatrace environment.
|
|
79
|
-
* Throws an error if the connection or authentication fails.
|
|
80
|
-
*/
|
|
81
|
-
async function testDynatraceConnection(dtEnvironment, oauthClientId, oauthClientSecret, dtPlatformToken) {
|
|
82
|
-
const dtClient = await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, oauthClientId && !oauthClientSecret ? allRequiredScopes : scopesBase, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
83
|
-
const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
|
|
84
|
-
// This call will fail if authentication is incorrect.
|
|
85
|
-
await environmentInformationClient.getEnvironmentInformation();
|
|
86
|
-
}
|
|
87
|
-
function handleClientRequestError(error) {
|
|
88
|
-
let additionalErrorInformation = '';
|
|
89
|
-
if (error.response.status === 403) {
|
|
90
|
-
additionalErrorInformation =
|
|
91
|
-
'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
|
|
92
|
-
}
|
|
93
|
-
return `Client Request Error: ${error.message} with HTTP status: ${error.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(error.body)})`;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Try to connect to Dynatrace environment with retries and exponential backoff.
|
|
97
|
-
*/
|
|
98
|
-
async function retryTestDynatraceConnection(dtEnvironment, oauthClientId, oauthClientSecret, dtPlatformToken) {
|
|
99
|
-
let retryCount = 0;
|
|
100
|
-
const maxRetries = 3; // Max retries
|
|
101
|
-
const delayMs = 2000; // Initial delay of 2 seconds
|
|
102
|
-
while (true) {
|
|
103
|
-
try {
|
|
104
|
-
console.error(`Testing connection to Dynatrace environment: ${dtEnvironment}... (Attempt ${retryCount + 1} of ${maxRetries})`);
|
|
105
|
-
await testDynatraceConnection(dtEnvironment, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
106
|
-
console.error(`Successfully connected to the Dynatrace environment at ${dtEnvironment}.`);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
console.error(`Error: Could not connect to the Dynatrace environment at ${dtEnvironment}.`);
|
|
111
|
-
if ((0, shared_errors_1.isClientRequestError)(error)) {
|
|
112
|
-
console.error(handleClientRequestError(error));
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
console.error(`Error: ${error.message}`);
|
|
116
|
-
}
|
|
117
|
-
retryCount++;
|
|
118
|
-
if (retryCount >= maxRetries) {
|
|
119
|
-
console.error(`Fatal: Maximum number of connection retries (${maxRetries}) exceeded. Exiting.`);
|
|
120
|
-
throw new Error(`Failed to connect to Dynatrace environment ${dtEnvironment} after ${maxRetries} attempts. Most likely your configuration is incorrect. Last error: ${error.message}`, { cause: error });
|
|
121
|
-
}
|
|
122
|
-
const delay = Math.pow(2, retryCount) * delayMs; // Exponential backoff
|
|
123
|
-
console.error(`Retrying in ${delay / 1000} seconds...`);
|
|
124
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
80
|
const main = async () => {
|
|
129
81
|
console.error(`Initializing Dynatrace MCP Server v${(0, version_1.getPackageJsonVersion)()}...`);
|
|
130
82
|
// read Environment variables
|
|
@@ -144,20 +96,6 @@ const main = async () => {
|
|
|
144
96
|
console.error('No OAuth credentials or platform token provided - switching to OAuth authorization code flow.');
|
|
145
97
|
oauthClientId = DT_MCP_AUTH_CODE_FLOW_OAUTH_CLIENT_ID; // Default OAuth client ID for auth code flow
|
|
146
98
|
}
|
|
147
|
-
// Test connection on startup
|
|
148
|
-
try {
|
|
149
|
-
// Depending on the authentication type, there are multiple pitfalls
|
|
150
|
-
// * For Platform Tokens, we can just try to access "get environment info" and we will know whether it works
|
|
151
|
-
// * For Oauth Client Credentials flow, we can also try to request an access token upfront with limited scopes, and verify whether everything works
|
|
152
|
-
// * for Oauth Auth Code flow, we can only verify whether the client ID is valid and the OAuth verifier call works, but we can't verify whether the user will be able to authenticate successfully
|
|
153
|
-
await retryTestDynatraceConnection(dtEnvironment, oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
154
|
-
}
|
|
155
|
-
catch (err) {
|
|
156
|
-
console.error(err.message);
|
|
157
|
-
process.exit(2);
|
|
158
|
-
}
|
|
159
|
-
// Ready to start the server
|
|
160
|
-
console.error(`Starting Dynatrace MCP Server v${(0, version_1.getPackageJsonVersion)()}...`);
|
|
161
99
|
// Initialize usage tracking
|
|
162
100
|
const telemetry = (0, telemetry_openkit_1.createTelemetry)();
|
|
163
101
|
await telemetry.trackMcpServerStart();
|
|
@@ -189,6 +127,51 @@ const main = async () => {
|
|
|
189
127
|
return await (0, dynatrace_clients_1.createDtHttpClient)(dtEnvironment, oauthClientId && !oauthClientSecret ? allRequiredScopes : scopes, // Always use all scopes for maximum reusability
|
|
190
128
|
oauthClientId, oauthClientSecret, dtPlatformToken);
|
|
191
129
|
};
|
|
130
|
+
// Try to establish a Dynatrace connection upfront, to see if everything is configured properly
|
|
131
|
+
console.error(`Testing connection to Dynatrace environment: ${dtEnvironment}...`);
|
|
132
|
+
// First, we will try a simple "fetch" to connect to dtEnvironment, without authentication
|
|
133
|
+
// This should help to see if DNS lookup works, TCP connection can be established, and TLS handshake works
|
|
134
|
+
try {
|
|
135
|
+
const response = await fetch(`${dtEnvironment}`).then((response) => response.text());
|
|
136
|
+
// check response
|
|
137
|
+
if (response && response.length > 0) {
|
|
138
|
+
if (response.includes('Authentication required')) {
|
|
139
|
+
// all good - we reached the environment and authentication is required, which is going to be the next step
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.error(`⚠️ Tried to contact ${dtEnvironment}, got the following response: ${response}`);
|
|
143
|
+
// Note: We won't error out yet, but this information could already be helpful for troubleshooting
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
throw new Error('No response received');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error(`❌ Failed to connect to Dynatrace environment ${dtEnvironment}:`, error.message);
|
|
152
|
+
console.error(error);
|
|
153
|
+
process.exit(3);
|
|
154
|
+
}
|
|
155
|
+
// Second, we will try with proper authentication
|
|
156
|
+
try {
|
|
157
|
+
const dtClient = await createAuthenticatedHttpClient(scopesBase);
|
|
158
|
+
const environmentInformationClient = new client_platform_management_service_1.EnvironmentInformationClient(dtClient);
|
|
159
|
+
await environmentInformationClient.getEnvironmentInformation();
|
|
160
|
+
console.error(`✅ Successfully connected to the Dynatrace environment at ${dtEnvironment}.`);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if ((0, shared_errors_1.isClientRequestError)(error)) {
|
|
164
|
+
console.error(`❌ Failed to connect to Dynatrace environment ${dtEnvironment}:`, (0, dynatrace_connection_utils_1.handleClientRequestError)(error));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.error(`❌ Failed to connect to Dynatrace environment ${dtEnvironment}:`, error.message);
|
|
168
|
+
// Logging more exhaustive error details for troubleshooting
|
|
169
|
+
console.error(error);
|
|
170
|
+
}
|
|
171
|
+
process.exit(2);
|
|
172
|
+
}
|
|
173
|
+
// Ready to start the server
|
|
174
|
+
console.error(`Starting Dynatrace MCP Server v${(0, version_1.getPackageJsonVersion)()}...`);
|
|
192
175
|
// quick abstraction/wrapper to make it easier for tools to reply text instead of JSON
|
|
193
176
|
const tool = (name, description, paramsSchema, annotations, cb) => {
|
|
194
177
|
const wrappedCb = async (args) => {
|
|
@@ -210,11 +193,11 @@ const main = async () => {
|
|
|
210
193
|
// check if it's an error originating from the Dynatrace SDK / API Gateway and provide an appropriate message to the user
|
|
211
194
|
if ((0, shared_errors_1.isClientRequestError)(error)) {
|
|
212
195
|
return {
|
|
213
|
-
content: [{ type: 'text', text: handleClientRequestError(error) }],
|
|
196
|
+
content: [{ type: 'text', text: (0, dynatrace_connection_utils_1.handleClientRequestError)(error) }],
|
|
214
197
|
isError: true,
|
|
215
198
|
};
|
|
216
199
|
}
|
|
217
|
-
// else: We don't know what kind of error happened - best
|
|
200
|
+
// else: We don't know what kind of error happened - best case we can log the error and provide error.message as a tool response
|
|
218
201
|
console.log(error);
|
|
219
202
|
return {
|
|
220
203
|
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleClientRequestError = handleClientRequestError;
|
|
4
|
+
function handleClientRequestError(error) {
|
|
5
|
+
let additionalErrorInformation = '';
|
|
6
|
+
if (error.response.status === 403) {
|
|
7
|
+
additionalErrorInformation =
|
|
8
|
+
'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
|
|
9
|
+
}
|
|
10
|
+
return `Client Request Error: ${error.message} with HTTP status: ${error.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(error.body)})`;
|
|
11
|
+
}
|