Skip to content

Commit 32d7a2d

Browse files
initial connection to Dynatrace Tenant Validation (#79)
* initial connection to Dynatrace Tenant Validation * Changes : 1) removed the tool for test connection and updated the get env tool subscription 2) added handleClientRequestError utility function to handle errors 3) added a rety logic to testconnection and avoid endless loop * format updated --------- Co-authored-by: Christian Kreuzberger <[email protected]>
1 parent e1b9fcf commit 32d7a2d

File tree

1 file changed

+82
-28
lines changed

1 file changed

+82
-28
lines changed

src/index.ts

Lines changed: 82 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,37 @@ let scopesBase = [
4646
'app-engine:functions:run', // needed for environmentInformationClient
4747
];
4848

49+
/**
50+
* Performs a connection test to the Dynatrace environment.
51+
* Throws an error if the connection or authentication fails.
52+
*/
53+
async function testDynatraceConnection(
54+
dtEnvironment: string,
55+
oauthClientId?: string,
56+
oauthClientSecret?: string,
57+
dtPlatformToken?: string,
58+
) {
59+
const dtClient = await createDtHttpClient(
60+
dtEnvironment,
61+
scopesBase,
62+
oauthClientId,
63+
oauthClientSecret,
64+
dtPlatformToken,
65+
);
66+
const environmentInformationClient = new EnvironmentInformationClient(dtClient);
67+
// This call will fail if authentication is incorrect.
68+
await environmentInformationClient.getEnvironmentInformation();
69+
}
70+
71+
function handleClientRequestError(error: ClientRequestError): string {
72+
let additionalErrorInformation = '';
73+
if (error.response.status === 403) {
74+
additionalErrorInformation =
75+
'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
76+
}
77+
return `Client Request Error: ${error.message} with HTTP status: ${error.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(error.body)})`;
78+
}
79+
4980
const main = async () => {
5081
// read Environment variables
5182
let dynatraceEnv: DynatraceEnv;
@@ -58,6 +89,35 @@ const main = async () => {
5889
console.error(`Initializing Dynatrace MCP Server v${VERSION}...`);
5990
const { oauthClientId, oauthClientSecret, dtEnvironment, dtPlatformToken, slackConnectionId } = dynatraceEnv;
6091

92+
// Test connection on startup
93+
let retryCount = 0;
94+
const maxRetries = 5;
95+
while (true) {
96+
try {
97+
console.error(
98+
`Testing connection to Dynatrace environment: ${dtEnvironment}... (Attempt ${retryCount + 1} of ${maxRetries})`,
99+
);
100+
await testDynatraceConnection(dtEnvironment, oauthClientId, oauthClientSecret, dtPlatformToken);
101+
console.error(`Successfully connected to the Dynatrace environment at ${dtEnvironment}.`);
102+
break;
103+
} catch (error: any) {
104+
console.error(`Error: Could not connect to the Dynatrace environment.`);
105+
if (isClientRequestError(error)) {
106+
console.error(handleClientRequestError(error));
107+
} else {
108+
console.error(`Error: ${error.message}`);
109+
}
110+
retryCount++;
111+
if (retryCount >= maxRetries) {
112+
console.error(`Fatal: Maximum number of connection retries (${maxRetries}) exceeded. Exiting.`);
113+
process.exit(1);
114+
}
115+
const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff
116+
console.error(`Retrying in ${delay / 1000} seconds...`);
117+
await new Promise((resolve) => setTimeout(resolve, delay));
118+
}
119+
}
120+
61121
console.error(`Starting Dynatrace MCP Server v${VERSION}...`);
62122
const server = new McpServer(
63123
{
@@ -87,19 +147,8 @@ const main = async () => {
87147
} catch (error: any) {
88148
// check if it's an error originating from the Dynatrace SDK / API Gateway and provide an appropriate message to the user
89149
if (isClientRequestError(error)) {
90-
const e: ClientRequestError = error;
91-
let additionalErrorInformation = '';
92-
if (e.response.status == 403) {
93-
additionalErrorInformation =
94-
'Note: Your user or service-user is most likely lacking the necessary permissions/scopes for this API Call.';
95-
}
96150
return {
97-
content: [
98-
{
99-
type: 'text',
100-
text: `Client Request Error: ${e.message} with HTTP status: ${e.response.status}. ${additionalErrorInformation} (body: ${JSON.stringify(e.body)})`,
101-
},
102-
],
151+
content: [{ type: 'text', text: handleClientRequestError(error) }],
103152
isError: true,
104153
};
105154
}
@@ -115,25 +164,30 @@ const main = async () => {
115164
server.tool(name, description, paramsSchema, (args) => wrappedCb(args));
116165
};
117166

118-
tool('get_environment_info', 'Get information about the connected Dynatrace Environment (Tenant)', {}, async ({}) => {
119-
// create an oauth-client
120-
const dtClient = await createDtHttpClient(
121-
dtEnvironment,
122-
scopesBase,
123-
oauthClientId,
124-
oauthClientSecret,
125-
dtPlatformToken,
126-
);
127-
const environmentInformationClient = new EnvironmentInformationClient(dtClient);
128-
129-
const environmentInfo = await environmentInformationClient.getEnvironmentInformation();
130-
let resp = `Environment Information (also referred to as tenant):
167+
tool(
168+
'get_environment_info',
169+
'Get information about the connected Dynatrace Environment (Tenant) and verify the connection and authentication.',
170+
{},
171+
async ({}) => {
172+
// create an oauth-client
173+
const dtClient = await createDtHttpClient(
174+
dtEnvironment,
175+
scopesBase,
176+
oauthClientId,
177+
oauthClientSecret,
178+
dtPlatformToken,
179+
);
180+
const environmentInformationClient = new EnvironmentInformationClient(dtClient);
181+
182+
const environmentInfo = await environmentInformationClient.getEnvironmentInformation();
183+
let resp = `Environment Information (also referred to as tenant):
131184
${JSON.stringify(environmentInfo)}\n`;
132185

133-
resp += `You can reach it via ${dtEnvironment}\n`;
186+
resp += `You can reach it via ${dtEnvironment}\n`;
134187

135-
return resp;
136-
});
188+
return resp;
189+
},
190+
);
137191

138192
tool(
139193
'list_vulnerabilities',

0 commit comments

Comments
 (0)