@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 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 from Grail
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-case we can provide error.message
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynatrace-oss/dynatrace-mcp-server",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "mcpName": "io.github.dynatrace-oss/Dynatrace-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Dynatrace",
6
6
  "keywords": [