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