@@ -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+
4980const 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