mcp-instana 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

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.
Files changed (37) hide show
  1. mcp_instana-0.1.1.dist-info/METADATA +908 -0
  2. mcp_instana-0.1.1.dist-info/RECORD +30 -0
  3. {mcp_instana-0.1.0.dist-info → mcp_instana-0.1.1.dist-info}/WHEEL +1 -1
  4. mcp_instana-0.1.1.dist-info/entry_points.txt +4 -0
  5. mcp_instana-0.1.0.dist-info/LICENSE → mcp_instana-0.1.1.dist-info/licenses/LICENSE.md +3 -3
  6. src/application/__init__.py +1 -0
  7. src/{client/application_alert_config_mcp_tools.py → application/application_alert_config.py} +251 -273
  8. src/application/application_analyze.py +415 -0
  9. src/application/application_catalog.py +153 -0
  10. src/{client/application_metrics_mcp_tools.py → application/application_metrics.py} +107 -129
  11. src/{client/application_resources_mcp_tools.py → application/application_resources.py} +128 -150
  12. src/application/application_settings.py +1135 -0
  13. src/application/application_topology.py +107 -0
  14. src/core/__init__.py +1 -0
  15. src/core/server.py +436 -0
  16. src/core/utils.py +213 -0
  17. src/event/__init__.py +1 -0
  18. src/{client/events_mcp_tools.py → event/events_tools.py} +128 -136
  19. src/infrastructure/__init__.py +1 -0
  20. src/{client/infrastructure_analyze_mcp_tools.py → infrastructure/infrastructure_analyze.py} +200 -203
  21. src/{client/infrastructure_catalog_mcp_tools.py → infrastructure/infrastructure_catalog.py} +194 -264
  22. src/infrastructure/infrastructure_metrics.py +167 -0
  23. src/{client/infrastructure_resources_mcp_tools.py → infrastructure/infrastructure_resources.py} +192 -223
  24. src/{client/infrastructure_topology_mcp_tools.py → infrastructure/infrastructure_topology.py} +105 -106
  25. src/log/__init__.py +1 -0
  26. src/log/log_alert_configuration.py +331 -0
  27. src/prompts/mcp_prompts.py +900 -0
  28. src/prompts/prompt_loader.py +29 -0
  29. src/prompts/prompt_registry.json +21 -0
  30. mcp_instana-0.1.0.dist-info/METADATA +0 -649
  31. mcp_instana-0.1.0.dist-info/RECORD +0 -19
  32. mcp_instana-0.1.0.dist-info/entry_points.txt +0 -3
  33. src/client/What is the sum of queue depth for all q +0 -55
  34. src/client/instana_client_base.py +0 -93
  35. src/client/log_alert_configuration_mcp_tools.py +0 -316
  36. src/client/show the top 5 services with the highest +0 -28
  37. src/mcp_server.py +0 -343
@@ -4,26 +4,35 @@ Infrastructure Analyze MCP Tools Module
4
4
  This module provides infrastructure analysis-specific MCP tools for Instana monitoring.
5
5
  """
6
6
 
7
+ import logging
7
8
  import sys
8
- import traceback
9
- from typing import Dict, Any, Optional, List, Union
10
- from datetime import datetime
9
+ from typing import Any, Dict, Optional, Union
11
10
 
12
11
  # Import the necessary classes from the SDK
13
12
  try:
14
13
  from instana_client.api.infrastructure_analyze_api import InfrastructureAnalyzeApi
15
14
  from instana_client.api_client import ApiClient
16
15
  from instana_client.configuration import Configuration
17
- from instana_client.models.get_available_metrics_query import GetAvailableMetricsQuery
18
- from instana_client.models.get_available_plugins_query import GetAvailablePluginsQuery
16
+ from instana_client.models.get_available_metrics_query import (
17
+ GetAvailableMetricsQuery,
18
+ )
19
+ from instana_client.models.get_available_plugins_query import (
20
+ GetAvailablePluginsQuery,
21
+ )
22
+ from instana_client.models.get_infrastructure_groups_query import (
23
+ GetInfrastructureGroupsQuery,
24
+ )
19
25
  from instana_client.models.get_infrastructure_query import GetInfrastructureQuery
20
- from instana_client.models.get_infrastructure_groups_query import GetInfrastructureGroupsQuery
21
26
  except ImportError as e:
22
- print(f"Error importing Instana SDK: {e}", file=sys.stderr)
23
- traceback.print_exc(file=sys.stderr)
27
+ import logging
28
+ logger = logging.getLogger(__name__)
29
+ logger.error(f"Error importing Instana SDK: {e}", exc_info=True)
24
30
  raise
25
31
 
26
- from .instana_client_base import BaseInstanaClient, register_as_tool
32
+ from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
33
+
34
+ # Configure logger for this module
35
+ logger = logging.getLogger(__name__)
27
36
 
28
37
  # Helper function for debug printing
29
38
  def debug_print(*args, **kwargs):
@@ -32,39 +41,22 @@ def debug_print(*args, **kwargs):
32
41
 
33
42
  class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
34
43
  """Tools for infrastructure analysis in Instana MCP."""
35
-
44
+
36
45
  def __init__(self, read_token: str, base_url: str):
37
46
  """Initialize the Infrastructure Analyze MCP tools client."""
38
47
  super().__init__(read_token=read_token, base_url=base_url)
39
-
40
- try:
41
-
42
- # Configure the API client with the correct base URL and authentication
43
- configuration = Configuration()
44
- configuration.host = base_url
45
- configuration.api_key['ApiKeyAuth'] = read_token
46
- configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
47
-
48
- # Create an API client with this configuration
49
- api_client = ApiClient(configuration=configuration)
50
-
51
- # Initialize the Instana SDK's InfrastructureAnalyzeApi with our configured client
52
- self.analyze_api = InfrastructureAnalyzeApi(api_client=api_client)
53
- except Exception as e:
54
- debug_print(f"Error initializing InfrastructureAnalyzeApi: {e}")
55
- traceback.print_exc(file=sys.stderr)
56
- raise
57
-
48
+
58
49
  @register_as_tool
59
- async def get_available_metrics(self,
60
- payload: Optional[Union[Dict[str, Any], str]] = None,
61
- ctx=None) -> Dict[str, Any]:
50
+ @with_header_auth(InfrastructureAnalyzeApi)
51
+ async def get_available_metrics(self,
52
+ payload: Optional[Union[Dict[str, Any], str]] = None,
53
+ ctx=None, api_client=None) -> Dict[str, Any]:
62
54
  """
63
55
  Get available metrics for infrastructure monitoring.
64
-
56
+
65
57
  This tool retrieves information about available metrics for a specific entity type.
66
58
  You can use this to discover what metrics are available for monitoring different components in your environment.
67
-
59
+
68
60
  Sample payload:
69
61
  {
70
62
  "timeFrame": {
@@ -80,68 +72,71 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
80
72
  "query": "",
81
73
  "type": "jvmRuntimePlatform"
82
74
  }
83
-
75
+
84
76
  Args:
85
77
  payload: Complete request payload as a dictionary or a JSON string
86
78
  ctx: The MCP context (optional)
87
-
79
+
88
80
  Returns:
89
81
  Dictionary containing available metrics or error information
90
82
  """
91
83
  try:
92
- debug_print(f"get_available_metrics called with payload={payload}")
93
-
84
+ logger.debug(f"get_available_metrics called with payload={payload}")
85
+
94
86
  # If payload is a string, try to parse it as JSON
95
87
  if isinstance(payload, str):
96
- debug_print(f"Payload is a string, attempting to parse")
88
+ logger.debug("Payload is a string, attempting to parse")
97
89
  try:
98
90
  import json
99
91
  try:
100
92
  parsed_payload = json.loads(payload)
101
- debug_print(f"Successfully parsed payload as JSON")
93
+ logger.debug("Successfully parsed payload as JSON")
102
94
  request_body = parsed_payload
103
95
  except json.JSONDecodeError as e:
104
- debug_print(f"JSON parsing failed: {e}, trying with quotes replaced")
105
-
96
+ logger.error(f"JSON parsing failed: {e}, trying with quotes replaced")
97
+
106
98
  # Try replacing single quotes with double quotes
107
99
  fixed_payload = payload.replace("'", "\"")
108
100
  try:
109
101
  parsed_payload = json.loads(fixed_payload)
110
- debug_print(f"Successfully parsed fixed JSON")
102
+ logger.debug("Successfully parsed fixed JSON")
111
103
  request_body = parsed_payload
112
104
  except json.JSONDecodeError:
113
105
  # Try as Python literal
114
106
  import ast
115
107
  try:
116
108
  parsed_payload = ast.literal_eval(payload)
117
- debug_print(f"Successfully parsed payload as Python literal")
109
+ logger.debug("Successfully parsed payload as Python literal")
118
110
  request_body = parsed_payload
119
111
  except (SyntaxError, ValueError) as e2:
120
- debug_print(f"Failed to parse payload string: {e2}")
112
+ logger.error(f"Failed to parse payload string: {e2}")
121
113
  return {"error": f"Invalid payload format: {e2}", "payload": payload}
122
114
  except Exception as e:
123
- debug_print(f"Error parsing payload string: {e}")
115
+ logger.error(f"Error parsing payload string: {e}")
124
116
  return {"error": f"Failed to parse payload: {e}", "payload": payload}
125
117
  else:
126
118
  # If payload is already a dictionary, use it directly
127
- debug_print(f"Using provided payload dictionary")
119
+ logger.debug("Using provided payload dictionary")
128
120
  request_body = payload
129
-
130
- debug_print(f"Final request body: {request_body}")
131
-
121
+
122
+ logger.debug(f"Final request body: {request_body}")
123
+
132
124
  # Import the GetAvailableMetricsQuery class
133
125
  try:
134
- from instana_client.models.get_available_metrics_query import GetAvailableMetricsQuery
135
- debug_print("Successfully imported GetAvailableMetricsQuery")
126
+ from instana_client.models.get_available_metrics_query import (
127
+ GetAvailableMetricsQuery,
128
+ )
129
+ logger.debug("Successfully imported GetAvailableMetricsQuery")
136
130
  except ImportError as e:
137
- debug_print(f"Error importing GetAvailableMetricsQuery: {e}")
138
- return {"error": f"Failed to import GetAvailableMetricsQuery: {str(e)}"}
139
-
131
+ error_msg = f"Error importing GetAvailableMetricsQuery: {e!s}"
132
+ logger.error(error_msg)
133
+ return {"error": error_msg}
134
+
140
135
  # Create a GetAvailableMetricsQuery object from the request body
141
136
  try:
142
137
  # Extract parameters from the request body
143
138
  query_params = {}
144
-
139
+
145
140
  # Handle timeFrame
146
141
  if request_body and "timeFrame" in request_body:
147
142
  time_frame = {}
@@ -152,54 +147,54 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
152
147
  if "windowSize" in request_body["timeFrame"]:
153
148
  time_frame["windowSize"] = request_body["timeFrame"]["windowSize"]
154
149
  query_params["timeFrame"] = time_frame
155
-
150
+
156
151
  # Handle other parameters
157
152
  if request_body and "query" in request_body:
158
153
  query_params["query"] = request_body["query"]
159
-
154
+
160
155
  if request_body and "type" in request_body:
161
156
  query_params["type"] = request_body["type"]
162
-
157
+
163
158
  if request_body and "tagFilterExpression" in request_body:
164
159
  query_params["tagFilterExpression"] = request_body["tagFilterExpression"]
165
-
166
- debug_print(f"Creating GetAvailableMetricsQuery with params: {query_params}")
160
+
161
+ logger.debug(f"Creating GetAvailableMetricsQuery with params: {query_params}")
167
162
  query_object = GetAvailableMetricsQuery(**query_params)
168
- debug_print(f"Successfully created query object: {query_object}")
163
+ logger.debug(f"Successfully created query object: {query_object}")
169
164
  except Exception as e:
170
- debug_print(f"Error creating GetAvailableMetricsQuery: {e}")
171
- return {"error": f"Failed to create query object: {str(e)}"}
172
-
165
+ logger.error(f"Error creating GetAvailableMetricsQuery: {e}")
166
+ return {"error": f"Failed to create query object: {e!s}"}
167
+
173
168
  # Call the get_available_metrics method from the SDK with the query object
174
- debug_print("Calling get_available_metrics with query object")
175
- result = self.analyze_api.get_available_metrics(get_available_metrics_query=query_object)
176
-
169
+ logger.debug("Calling get_available_metrics with query object")
170
+ result = api_client.get_available_metrics(get_available_metrics_query=query_object)
171
+
177
172
  # Convert the result to a dictionary
178
173
  if hasattr(result, 'to_dict'):
179
174
  result_dict = result.to_dict()
180
175
  else:
181
176
  # If it's already a dict or another format, use it as is
182
177
  result_dict = result
183
-
184
- debug_print(f"Result from get_available_metrics: {result_dict}")
178
+
179
+ logger.debug(f"Result from get_available_metrics: {result_dict}")
185
180
  return result_dict
186
181
  except Exception as e:
187
- debug_print(f"Error in get_available_metrics: {e}")
188
- traceback.print_exc(file=sys.stderr)
189
- return {"error": f"Failed to get available metrics: {str(e)}"}
182
+ logger.error(f"Error in get_available_metrics: {e}", exc_info=True)
183
+ return {"error": f"Failed to get available metrics: {e!s}"}
190
184
 
191
185
  @register_as_tool
192
- async def get_entities(self,
193
- payload: Optional[Union[Dict[str, Any], str]] = None,
194
- ctx=None) -> Dict[str, Any]:
186
+ @with_header_auth(InfrastructureAnalyzeApi)
187
+ async def get_entities(self,
188
+ payload: Optional[Union[Dict[str, Any], str]] = None,
189
+ ctx=None, api_client=None) -> Dict[str, Any]:
195
190
  """
196
191
  Get infrastructure entities for a given entity type along with requested metrics.
197
-
192
+
198
193
  We want to know the memory used and no of blocked threads for entity named JVMruntimeplatform for last 1 hour in Instana. can you help us get the details ?
199
-
194
+
200
195
  This tool retrieves entities of a specific type (e.g., hosts, processes, containers) along with
201
196
  their metrics. You can filter the results using tag filters and paginate through large result sets.
202
-
197
+
203
198
  Sample payload:
204
199
  {
205
200
  "tagFilterExpression": {
@@ -224,95 +219,95 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
224
219
  {"metric": "threads.blocked", "granularity": 600000, "aggregation": "MEAN"}
225
220
  ]
226
221
  }
227
-
222
+
228
223
  Args:
229
224
  payload: Complete request payload as a dictionary or a JSON string
230
225
  ctx: The MCP context (optional)
231
-
226
+
232
227
  Returns:
233
228
  Dictionary containing infrastructure entities and their metrics or error information
234
229
  """
235
230
  try:
236
- debug_print(f"get_entities called with payload={payload}")
237
-
231
+ logger.debug(f"get_entities called with payload={payload}")
232
+
238
233
  # If payload is a string, try to parse it as JSON
239
234
  if isinstance(payload, str):
240
- debug_print(f"Payload is a string, attempting to parse")
235
+ logger.debug("Payload is a string, attempting to parse")
241
236
  try:
242
237
  import json
243
238
  try:
244
239
  parsed_payload = json.loads(payload)
245
- debug_print(f"Successfully parsed payload as JSON")
240
+ logger.debug("Successfully parsed payload as JSON")
246
241
  request_body = parsed_payload
247
242
  except json.JSONDecodeError as e:
248
- debug_print(f"JSON parsing failed: {e}, trying with quotes replaced")
249
-
243
+ logger.warning(f"JSON parsing failed: {e}, trying with quotes replaced")
244
+
250
245
  # Try replacing single quotes with double quotes
251
246
  fixed_payload = payload.replace("'", "\"")
252
247
  try:
253
248
  parsed_payload = json.loads(fixed_payload)
254
- debug_print(f"Successfully parsed fixed JSON")
249
+ logger.debug("Successfully parsed fixed JSON")
255
250
  request_body = parsed_payload
256
251
  except json.JSONDecodeError:
257
252
  # Try as Python literal
258
253
  import ast
259
254
  try:
260
255
  parsed_payload = ast.literal_eval(payload)
261
- debug_print(f"Successfully parsed payload as Python literal")
256
+ logger.debug("Successfully parsed payload as Python literal")
262
257
  request_body = parsed_payload
263
258
  except (SyntaxError, ValueError) as e2:
264
- debug_print(f"Failed to parse payload string: {e2}")
259
+ logger.error(f"Failed to parse payload string: {e2}")
265
260
  return {"error": f"Invalid payload format: {e2}", "payload": payload}
266
261
  except Exception as e:
267
- debug_print(f"Error parsing payload string: {e}")
262
+ logger.error(f"Error parsing payload string: {e}")
268
263
  return {"error": f"Failed to parse payload: {e}", "payload": payload}
269
264
  else:
270
265
  # If payload is already a dictionary, use it directly
271
- debug_print(f"Using provided payload dictionary")
266
+ logger.debug("Using provided payload dictionary")
272
267
  request_body = payload
273
-
274
- debug_print(f"Final request body: {request_body}")
275
-
268
+
269
+ logger.debug(f"Final request body: {request_body}")
270
+
276
271
  # Create the GetInfrastructureQuery object
277
272
  try:
278
273
  # Create the query object directly from the request body
279
274
  get_infra_query = GetInfrastructureQuery(**request_body)
280
- debug_print("Successfully created GetInfrastructureQuery object")
275
+ logger.debug("Successfully created GetInfrastructureQuery object")
281
276
  except Exception as model_error:
282
277
  error_msg = f"Failed to create GetInfrastructureQuery object: {model_error}"
283
- debug_print(error_msg)
278
+ logger.error(error_msg)
284
279
  return {"error": error_msg, "request_body": request_body}
285
-
280
+
286
281
  # Call the get_entities method from the SDK
287
- debug_print("Calling API method get_entities")
288
- result = self.analyze_api.get_entities(
282
+ logger.debug("Calling API method get_entities")
283
+ result = api_client.get_entities(
289
284
  get_infrastructure_query=get_infra_query
290
285
  )
291
-
286
+
292
287
  # Convert the result to a dictionary
293
288
  if hasattr(result, 'to_dict'):
294
289
  result_dict = result.to_dict()
295
290
  else:
296
291
  # If it's already a dict or another format, use it as is
297
292
  result_dict = result
298
-
299
- debug_print(f"Result from get_entities: {result_dict}")
293
+
294
+ logger.debug(f"Result from get_entities: {result_dict}")
300
295
  return result_dict
301
296
  except Exception as e:
302
- debug_print(f"Error in get_entities: {e}")
303
- traceback.print_exc(file=sys.stderr)
304
- return {"error": f"Failed to get entities: {str(e)}"}
297
+ logger.error(f"Error in get_entities: {e}", exc_info=True)
298
+ return {"error": f"Failed to get entities: {e!s}"}
305
299
 
306
300
  @register_as_tool
307
- async def get_aggregated_entity_groups(self,
308
- payload: Optional[Union[Dict[str, Any], str]] = None,
309
- ctx=None) -> Dict[str, Any]:
301
+ @with_header_auth(InfrastructureAnalyzeApi)
302
+ async def get_aggregated_entity_groups(self,
303
+ payload: Optional[Union[Dict[str, Any], str]] = None,
304
+ ctx=None, api_client=None) -> Dict[str, Any]:
310
305
  """
311
306
  Get grouped infrastructure entities with aggregated metrics.
312
-
307
+
313
308
  This tool groups entities of a specific type by specified tags and aggregates metrics for these groups.
314
309
  For example, you can group hosts by their region and get average CPU usage per region.
315
-
310
+
316
311
  Sample payload:
317
312
  {
318
313
  "timeFrame": {
@@ -340,115 +335,116 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
340
335
  "direction": "ASC"
341
336
  }
342
337
  }
343
-
338
+
344
339
  Args:
345
340
  payload: Complete request payload as a dictionary or a JSON string
346
341
  ctx: The MCP context (optional)
347
-
342
+
348
343
  Returns:
349
344
  Dictionary containing grouped entities and their aggregated metrics or error information
350
345
  """
351
346
  try:
352
- debug_print(f"get_aggregated_entity_groups called with payload={payload}")
353
-
347
+ logger.debug(f"get_aggregated_entity_groups called with payload={payload}")
348
+
354
349
  # If no payload is provided, return an error
355
350
  if not payload:
356
351
  return {"error": "Payload is required for this operation"}
357
-
352
+
358
353
  # If payload is a string, try to parse it as JSON
359
354
  if isinstance(payload, str):
360
- debug_print(f"Payload is a string, attempting to parse")
355
+ logger.debug("Payload is a string, attempting to parse")
361
356
  try:
362
357
  import json
363
358
  try:
364
359
  parsed_payload = json.loads(payload)
365
- debug_print(f"Successfully parsed payload as JSON")
360
+ logger.debug("Successfully parsed payload as JSON")
366
361
  request_body = parsed_payload
367
362
  except json.JSONDecodeError as e:
368
- debug_print(f"JSON parsing failed: {e}, trying with quotes replaced")
369
-
363
+ logger.warning(f"JSON parsing failed: {e}, trying with quotes replaced")
364
+
370
365
  # Try replacing single quotes with double quotes
371
366
  fixed_payload = payload.replace("'", "\"")
372
367
  try:
373
368
  parsed_payload = json.loads(fixed_payload)
374
- debug_print(f"Successfully parsed fixed JSON")
369
+ logger.debug("Successfully parsed fixed JSON")
375
370
  request_body = parsed_payload
376
371
  except json.JSONDecodeError:
377
372
  # Try as Python literal
378
373
  import ast
379
374
  try:
380
375
  parsed_payload = ast.literal_eval(payload)
381
- debug_print(f"Successfully parsed payload as Python literal")
376
+ logger.debug("Successfully parsed payload as Python literal")
382
377
  request_body = parsed_payload
383
378
  except (SyntaxError, ValueError) as e2:
384
- debug_print(f"Failed to parse payload string: {e2}")
379
+ logger.error(f"Failed to parse payload string: {e2}")
385
380
  return {"error": f"Invalid payload format: {e2}", "payload": payload}
386
381
  except Exception as e:
387
- debug_print(f"Error parsing payload string: {e}")
382
+ logger.error(f"Error parsing payload string: {e}")
388
383
  return {"error": f"Failed to parse payload: {e}", "payload": payload}
389
384
  else:
390
385
  # If payload is already a dictionary, use it directly
391
- debug_print(f"Using provided payload dictionary")
386
+ logger.debug("Using provided payload dictionary")
392
387
  request_body = payload
393
-
394
- debug_print(f"Final request body: {request_body}")
395
-
388
+
389
+ logger.debug(f"Final request body: {request_body}")
390
+
396
391
  # Create the GetInfrastructureGroupsQuery object
397
392
  try:
398
393
  # Import the model class
399
- from instana_client.models.get_infrastructure_groups_query import GetInfrastructureGroupsQuery
400
-
394
+ from instana_client.models.get_infrastructure_groups_query import (
395
+ GetInfrastructureGroupsQuery,
396
+ )
397
+
401
398
  # Create the query object
402
399
  get_groups_query = GetInfrastructureGroupsQuery(**request_body)
403
- debug_print("Successfully created GetInfrastructureGroupsQuery object")
400
+ logger.debug("Successfully created GetInfrastructureGroupsQuery object")
404
401
  except Exception as model_error:
405
402
  error_msg = f"Failed to create GetInfrastructureGroupsQuery object: {model_error}"
406
- debug_print(error_msg)
403
+ logger.debug(error_msg)
407
404
  return {"error": error_msg, "request_body": request_body}
408
-
405
+
409
406
  # Call the get_entity_groups method from the SDK
410
- debug_print("Calling API method get_entity_groups")
407
+ logger.debug("Calling API method get_entity_groups")
411
408
  try:
412
409
  # Use the without_preload_content version to get the raw response
413
- response = self.analyze_api.get_entity_groups_without_preload_content(
410
+ response = api_client.get_entity_groups_without_preload_content(
414
411
  get_infrastructure_groups_query=get_groups_query
415
412
  )
416
-
413
+
417
414
  # Check if the response was successful
418
415
  if response.status != 200:
419
416
  error_message = f"Failed to get entity groups: HTTP {response.status}"
420
- debug_print(error_message)
417
+ logger.debug(error_message)
421
418
  return {"error": error_message}
422
-
419
+
423
420
  # Read the response content
424
421
  response_text = response.data.decode('utf-8')
425
-
422
+
426
423
  # Parse the response as JSON
427
424
  import json
428
425
  result_dict = json.loads(response_text)
429
-
430
- debug_print(f"Successfully parsed raw response")
431
-
426
+
427
+ logger.debug("Successfully parsed raw response")
428
+
432
429
  # Create a summarized version of the results
433
430
  return self._summarize_entity_groups_result(result_dict, request_body)
434
431
  except Exception as api_error:
435
432
  error_msg = f"API call failed: {api_error}"
436
- debug_print(error_msg)
433
+ logger.error(error_msg)
437
434
  return {"error": error_msg}
438
-
435
+
439
436
  except Exception as e:
440
- debug_print(f"Error in get_aggregated_entity_groups: {e}")
441
- traceback.print_exc(file=sys.stderr)
442
- return {"error": f"Failed to get aggregated entity groups: {str(e)}"}
437
+ logger.error(f"Error in get_aggregated_entity_groups: {e}", exc_info=True)
438
+ return {"error": f"Failed to get aggregated entity groups: {e!s}"}
443
439
 
444
440
  def _summarize_entity_groups_result(self, result_dict, query_body):
445
441
  """
446
442
  Create a summarized version of the entity groups result.
447
-
443
+
448
444
  Args:
449
445
  result_dict: The full API response
450
446
  query_body: The query body used to make the request
451
-
447
+
452
448
  Returns:
453
449
  A summarized version of the results
454
450
  """
@@ -456,15 +452,15 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
456
452
  # Check if there's an error in the result
457
453
  if isinstance(result_dict, dict) and "error" in result_dict:
458
454
  return result_dict
459
-
455
+
460
456
  # Extract the group by tag
461
457
  group_by_tag = None
462
458
  if "groupBy" in query_body and isinstance(query_body["groupBy"], list) and len(query_body["groupBy"]) > 0:
463
459
  group_by_tag = query_body["groupBy"][0]
464
-
460
+
465
461
  # Extract host names if available
466
462
  host_names = []
467
-
463
+
468
464
  # Process each item in the results
469
465
  if "items" in result_dict and isinstance(result_dict["items"], list):
470
466
  for item in result_dict["items"]:
@@ -480,39 +476,39 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
480
476
  else:
481
477
  # Convert other types to string
482
478
  host_name = str(tag_value)
483
-
479
+
484
480
  if host_name not in host_names:
485
481
  host_names.append(host_name)
486
-
482
+
487
483
  # Sort host names alphabetically
488
484
  host_names.sort()
489
-
485
+
490
486
  # Create the exact format requested
491
487
  summary = {
492
488
  "hosts": host_names,
493
489
  "count": len(host_names),
494
490
  "summary": f"Found {len(host_names)} hosts: {', '.join(host_names)}"
495
491
  }
496
-
492
+
497
493
  return summary
498
494
  except Exception as e:
499
- debug_print(f"Error in _summarize_entity_groups_result: {e}")
500
- traceback.print_exc(file=sys.stderr)
495
+ logger.error(f"Error in _summarize_entity_groups_result: {e}", exc_info=True)
501
496
  # If summarization fails, return an error message
502
497
  return {
503
- "error": f"Failed to summarize results: {str(e)}"
498
+ "error": f"Failed to summarize results: {e!s}"
504
499
  }
505
500
 
506
501
  @register_as_tool
507
- async def get_available_plugins(self,
508
- payload: Optional[Union[Dict[str, Any], str]] = None,
509
- ctx=None) -> Dict[str, Any]:
502
+ @with_header_auth(InfrastructureAnalyzeApi)
503
+ async def get_available_plugins(self,
504
+ payload: Optional[Union[Dict[str, Any], str]] = None,
505
+ ctx=None, api_client=None) -> Dict[str, Any]:
510
506
  """
511
507
  Get available plugins for infrastructure monitoring.
512
-
508
+
513
509
  This tool retrieves information about available plugins for infrastructure monitoring.
514
510
  You can use this to discover what types of entities can be monitored in your environment.
515
-
511
+
516
512
  Sample payload:
517
513
  {
518
514
  "timeFrame": {
@@ -527,68 +523,70 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
527
523
  "query": "java",
528
524
  "offline": false
529
525
  }
530
-
526
+
531
527
  Args:
532
528
  payload: Complete request payload as a dictionary or a JSON string
533
529
  ctx: The MCP context (optional)
534
-
530
+
535
531
  Returns:
536
532
  Dictionary containing available plugins or error information
537
533
  """
538
534
  try:
539
- debug_print(f"get_available_plugins called with payload={payload}")
540
-
535
+ logger.debug(f"get_available_plugins called with payload={payload}")
536
+
541
537
  # If payload is a string, try to parse it as JSON
542
538
  if isinstance(payload, str):
543
- debug_print(f"Payload is a string, attempting to parse")
539
+ logger.debug("Payload is a string, attempting to parse")
544
540
  try:
545
541
  import json
546
542
  try:
547
543
  parsed_payload = json.loads(payload)
548
- debug_print(f"Successfully parsed payload as JSON")
544
+ logger.debug("Successfully parsed payload as JSON")
549
545
  request_body = parsed_payload
550
546
  except json.JSONDecodeError as e:
551
- debug_print(f"JSON parsing failed: {e}, trying with quotes replaced")
552
-
547
+ logger.warning(f"JSON parsing failed: {e}, trying with quotes replaced")
548
+
553
549
  # Try replacing single quotes with double quotes
554
550
  fixed_payload = payload.replace("'", "\"")
555
551
  try:
556
552
  parsed_payload = json.loads(fixed_payload)
557
- debug_print(f"Successfully parsed fixed JSON")
553
+ logger.debug("Successfully parsed fixed JSON")
558
554
  request_body = parsed_payload
559
555
  except json.JSONDecodeError:
560
556
  # Try as Python literal
561
557
  import ast
562
558
  try:
563
559
  parsed_payload = ast.literal_eval(payload)
564
- debug_print(f"Successfully parsed payload as Python literal")
560
+ logger.debug("Successfully parsed payload as Python literal")
565
561
  request_body = parsed_payload
566
562
  except (SyntaxError, ValueError) as e2:
567
- debug_print(f"Failed to parse payload string: {e2}")
563
+ logger.error(f"Failed to parse payload string: {e2}")
568
564
  return {"error": f"Invalid payload format: {e2}", "payload": payload}
569
565
  except Exception as e:
570
- debug_print(f"Error parsing payload string: {e}")
566
+ logger.error(f"Error parsing payload string: {e}")
571
567
  return {"error": f"Failed to parse payload: {e}", "payload": payload}
572
568
  else:
573
569
  # If payload is already a dictionary, use it directly
574
- debug_print(f"Using provided payload dictionary")
570
+ logger.debug("Using provided payload dictionary")
575
571
  request_body = payload
576
-
577
- debug_print(f"Final request body: {request_body}")
578
-
572
+
573
+ logger.debug(f"Final request body: {request_body}")
574
+
579
575
  # Import the GetAvailablePluginsQuery class
580
576
  try:
581
- from instana_client.models.get_available_plugins_query import GetAvailablePluginsQuery
582
- debug_print("Successfully imported GetAvailablePluginsQuery")
577
+ from instana_client.models.get_available_plugins_query import (
578
+ GetAvailablePluginsQuery,
579
+ )
580
+ logger.debug("Successfully imported GetAvailablePluginsQuery")
583
581
  except ImportError as e:
584
- debug_print(f"Error importing GetAvailablePluginsQuery: {e}")
585
- return {"error": f"Failed to import GetAvailablePluginsQuery: {str(e)}"}
586
-
582
+ logger.error(f"Error importing GetAvailablePluginsQuery: {e}")
583
+ return {"error": f"Failed to import GetAvailablePluginsQuery: {e!s}"}
584
+
587
585
  # Create a GetAvailablePluginsQuery object from the request body
588
586
  try:
589
587
  # Extract parameters from the request body
590
588
  query_params = {}
591
-
589
+
592
590
  # Handle timeFrame
593
591
  if request_body and "timeFrame" in request_body:
594
592
  time_frame = {}
@@ -597,38 +595,37 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
597
595
  if "windowSize" in request_body["timeFrame"]:
598
596
  time_frame["windowSize"] = request_body["timeFrame"]["windowSize"]
599
597
  query_params["timeFrame"] = time_frame
600
-
598
+
601
599
  # Handle other parameters
602
600
  if request_body and "query" in request_body:
603
601
  query_params["query"] = request_body["query"]
604
-
602
+
605
603
  if request_body and "offline" in request_body:
606
604
  query_params["offline"] = request_body["offline"]
607
-
605
+
608
606
  if request_body and "tagFilterExpression" in request_body:
609
607
  query_params["tagFilterExpression"] = request_body["tagFilterExpression"]
610
-
611
- debug_print(f"Creating GetAvailablePluginsQuery with params: {query_params}")
608
+
609
+ logger.debug(f"Creating GetAvailablePluginsQuery with params: {query_params}")
612
610
  query_object = GetAvailablePluginsQuery(**query_params)
613
- debug_print(f"Successfully created query object: {query_object}")
611
+ logger.debug(f"Successfully created query object: {query_object}")
614
612
  except Exception as e:
615
- debug_print(f"Error creating GetAvailablePluginsQuery: {e}")
616
- return {"error": f"Failed to create query object: {str(e)}"}
617
-
613
+ logger.error(f"Error creating GetAvailablePluginsQuery: {e}")
614
+ return {"error": f"Failed to create query object: {e!s}"}
615
+
618
616
  # Call the get_available_plugins method from the SDK with the query object
619
- debug_print("Calling get_available_plugins with query object")
620
- result = self.analyze_api.get_available_plugins(get_available_plugins_query=query_object)
621
-
617
+ logger.debug("Calling get_available_plugins with query object")
618
+ result = api_client.get_available_plugins(get_available_plugins_query=query_object)
619
+
622
620
  # Convert the result to a dictionary
623
621
  if hasattr(result, 'to_dict'):
624
622
  result_dict = result.to_dict()
625
623
  else:
626
624
  # If it's already a dict or another format, use it as is
627
625
  result_dict = result
628
-
629
- debug_print(f"Result from get_available_plugins: {result_dict}")
626
+
627
+ logger.debug(f"Result from get_available_plugins: {result_dict}")
630
628
  return result_dict
631
629
  except Exception as e:
632
- debug_print(f"Error in get_available_plugins: {e}")
633
- traceback.print_exc(file=sys.stderr)
634
- return {"error": f"Failed to get available plugins: {str(e)}"}
630
+ logger.error(f"Error in get_available_plugins: {e}", exc_info=True)
631
+ return {"error": f"Failed to get available plugins: {e!s}"}