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,170 +4,150 @@ Infrastructure Resources MCP Tools Module
4
4
  This module provides infrastructure resources-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
9
  from datetime import datetime
10
+ from typing import Any, Dict, List, Optional, Union
11
11
 
12
12
  # Import the necessary classes from the SDK
13
13
  try:
14
- from instana_client.api.infrastructure_resources_api import InfrastructureResourcesApi
15
- from instana_client.api_client import ApiClient
16
- from instana_client.configuration import Configuration
14
+ from instana_client.api.infrastructure_resources_api import ( #type: ignore
15
+ InfrastructureResourcesApi,
16
+ )
17
17
  # Check if GetSnapshotsQuery exists, otherwise we'll handle it differently
18
18
  try:
19
- from instana_client.models.get_snapshots_query import GetSnapshotsQuery
20
19
  has_get_snapshots_query = True
21
20
  except ImportError:
22
21
  has_get_snapshots_query = False
23
- except ImportError as e:
24
- traceback.print_exc(file=sys.stderr)
22
+ except ImportError:
23
+ import logging
24
+ logger = logging.getLogger(__name__)
25
+ logger.error("Failed to import infrastructure resources API", exc_info=True)
25
26
  raise
26
27
 
27
- from .instana_client_base import BaseInstanaClient, register_as_tool
28
+ from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
28
29
 
29
- # Helper function for debug printing
30
- def debug_print(*args, **kwargs):
31
- """Print debug information to stderr instead of stdout"""
32
- print(*args, file=sys.stderr, **kwargs)
30
+ # Configure logger for this module
31
+ logger = logging.getLogger(__name__)
33
32
 
34
33
  class InfrastructureResourcesMCPTools(BaseInstanaClient):
35
34
  """Tools for infrastructure resources in Instana MCP."""
36
-
35
+
37
36
  def __init__(self, read_token: str, base_url: str):
38
37
  """Initialize the Infrastructure Resources MCP tools client."""
39
38
  super().__init__(read_token=read_token, base_url=base_url)
40
-
41
- try:
42
-
43
- # Configure the API client with the correct base URL and authentication
44
- configuration = Configuration()
45
- configuration.host = base_url
46
- configuration.api_key['ApiKeyAuth'] = read_token
47
- configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
48
-
49
- # Create an API client with this configuration
50
- api_client = ApiClient(configuration=configuration)
51
-
52
- # Initialize the Instana SDK's InfrastructureResourcesApi with our configured client
53
- self.infra_api = InfrastructureResourcesApi(api_client=api_client)
54
- except Exception as e:
55
- debug_print(f"Error initializing InfrastructureResourcesApi: {e}")
56
- traceback.print_exc(file=sys.stderr)
57
- raise
58
-
39
+
59
40
  @register_as_tool
60
- async def get_monitoring_state(self, ctx=None) -> Dict[str, Any]:
41
+ @with_header_auth(InfrastructureResourcesApi)
42
+ async def get_monitoring_state(self, ctx=None, api_client=None) -> Dict[str, Any]:
61
43
  """
62
44
  Get the current monitoring state of the Instana system. This tool retrieves details about the number of monitored hosts and serverless entities in your environment.
63
- Use this when you need an overview of your monitoring coverage, want to check how many hosts are being monitored, or need to verify the scale of your Instana deployment.
45
+ Use this when you need an overview of your monitoring coverage, want to check how many hosts are being monitored, or need to verify the scale of your Instana deployment.
64
46
  Use this tool when asked about 'monitoring status', 'how many hosts are monitored', 'monitoring coverage', or when someone wants to 'check the monitoring state'.
65
-
47
+
66
48
  Args:
67
49
  ctx: The MCP context (optional)
68
-
50
+
69
51
  Returns:
70
52
  Dictionary containing monitoring state information or error information
71
53
  """
72
54
  try:
73
- print("get_monitoring_state called", file=sys.stderr)
74
-
55
+ logger.info("get_monitoring_state called")
56
+
75
57
  # Call the get_monitoring_state method from the SDK
76
- result = self.infra_api.get_monitoring_state()
77
-
78
- print(f"Result from get_monitoring_state: {result}", file=sys.stderr)
58
+ result = api_client.get_monitoring_state()
59
+
60
+ logger.debug(f"Result from get_monitoring_state: {result}")
79
61
  return result
80
62
  except Exception as e:
81
- print(f"Error in get_monitoring_state: {e}", file=sys.stderr)
82
- import traceback
83
- traceback.print_exc(file=sys.stderr)
84
- return {"error": f"Failed to get monitoring state: {str(e)}"}
85
-
63
+ logger.error(f"Error in get_monitoring_state: {e}", exc_info=True)
64
+ return {"error": f"Failed to get monitoring state: {e!s}"}
65
+
86
66
  @register_as_tool
87
- async def get_plugin_payload(self,
88
- snapshot_id: str,
89
- payload_key: str,
90
- to_time: Optional[int] = None,
91
- window_size: Optional[int] = None,
92
- ctx=None) -> Dict[str, Any]:
67
+ @with_header_auth(InfrastructureResourcesApi)
68
+ async def get_plugin_payload(self,
69
+ snapshot_id: str,
70
+ payload_key: str,
71
+ to_time: Optional[int] = None,
72
+ window_size: Optional[int] = None,
73
+ ctx=None, api_client=None) -> Dict[str, Any]:
93
74
  """
94
- Get detailed payload data for a specific snapshot in Instana. This tool retrieves raw monitoring data for a particular entity snapshot using its ID and a specific payload key.
95
- Use this when you need to access detailed, low-level monitoring information that isn't available through other APIs.
96
- This is particularly useful for deep troubleshooting, accessing specific metrics or configuration details, or when you need the complete raw data for a monitored
75
+ Get detailed payload data for a specific snapshot in Instana. This tool retrieves raw monitoring data for a particular entity snapshot using its ID and a specific payload key.
76
+ Use this when you need to access detailed, low-level monitoring information that isn't available through other APIs.
77
+ This is particularly useful for deep troubleshooting, accessing specific metrics or configuration details, or when you need the complete raw data for a monitored
97
78
  entity. For example, use this tool when asked about 'detailed snapshot data', 'raw monitoring information', 'plugin payload details', or when someone wants to 'get the complete data for a specific entity'.
98
-
79
+
99
80
  Args:
100
81
  snapshot_id: The ID of the snapshot
101
82
  payload_key: The key of the payload to retrieve
102
83
  to_time: End timestamp in milliseconds (optional)
103
84
  window_size: Window size in milliseconds (optional)
104
85
  ctx: The MCP context (optional)
105
-
86
+
106
87
  Returns:
107
88
  Dictionary containing the payload data or error information
108
89
  """
109
90
  try:
110
- print(f"get_plugin_payload called with snapshot_id={snapshot_id}, payload_key={payload_key}", file=sys.stderr)
111
-
91
+ logger.debug(f"get_plugin_payload called with snapshot_id={snapshot_id}, payload_key={payload_key}")
92
+
112
93
  # Call the get_plugin_payload method from the SDK
113
- result = self.infra_api.get_plugin_payload(
94
+ result = api_client.get_plugin_payload(
114
95
  snapshot_id=snapshot_id,
115
96
  payload_key=payload_key,
116
97
  to=to_time,
117
98
  window_size=window_size
118
99
  )
119
-
120
- print(f"Result from get_plugin_payload: {result}", file=sys.stderr)
100
+
101
+ logger.debug(f"Result from get_plugin_payload: {result}")
121
102
  return result
122
103
  except Exception as e:
123
- print(f"Error in get_plugin_payload: {e}", file=sys.stderr)
124
- import traceback
125
- traceback.print_exc(file=sys.stderr)
126
- return {"error": f"Failed to get plugin payload: {str(e)}"}
127
-
104
+ logger.error(f"Error in get_plugin_payload: {e}", exc_info=True)
105
+ return {"error": f"Failed to get plugin payload: {e!s}"}
106
+
128
107
  @register_as_tool
129
- async def get_snapshot(self,
130
- snapshot_id: str,
131
- to_time: Optional[int] = None,
132
- window_size: Optional[int] = None,
133
- ctx=None) -> Dict[str, Any]:
108
+ @with_header_auth(InfrastructureResourcesApi)
109
+ async def get_snapshot(self,
110
+ snapshot_id: str,
111
+ to_time: Optional[int] = None,
112
+ window_size: Optional[int] = None,
113
+ ctx=None, api_client=None) -> Dict[str, Any]:
134
114
  """
135
115
  Get detailed information about a specific snapshot in Instana using its ID.
136
-
116
+
137
117
  This tool retrieves comprehensive data for a single, specific entity snapshot that you already have the ID for.
138
- Use this when you need to examine one particular entity in depth, such as investigating a specific host, container,
118
+ Use this when you need to examine one particular entity in depth, such as investigating a specific host, container,
139
119
  or process that you've already identified. This is NOT for searching or discovering entities - use get_snapshots for that purpose.
140
-
120
+
141
121
  For example, use this tool when:
142
122
  - You already have a specific snapshot ID and need its details
143
123
  - You want to examine one particular entity's configuration and metrics
144
124
  - You need to troubleshoot a specific component that's already been identified
145
125
  - Someone asks for "details about this specific entity" or "information about snapshot 12345"
146
-
126
+
147
127
  Args:
148
128
  snapshot_id: The ID of the snapshot to retrieve
149
129
  to_time: End timestamp in milliseconds (optional)
150
130
  window_size: Window size in milliseconds (optional)
151
131
  ctx: The MCP context (optional)
152
-
132
+
153
133
  Returns:
154
134
  Dictionary containing snapshot details or error information
155
135
  """
156
136
  try:
157
- print(f"get_snapshot called with snapshot_id={snapshot_id}", file=sys.stderr)
158
-
137
+ logger.debug(f"get_snapshot called with snapshot_id={snapshot_id}")
138
+
159
139
  if not snapshot_id:
160
140
  return {"error": "snapshot_id parameter is required"}
161
-
141
+
162
142
  # Try using the standard SDK method
163
143
  try:
164
144
  # Call the get_snapshot method from the SDK
165
- result = self.infra_api.get_snapshot(
145
+ result = api_client.get_snapshot(
166
146
  id=snapshot_id,
167
147
  to=to_time,
168
148
  window_size=window_size
169
149
  )
170
-
150
+
171
151
  # Convert the result to a dictionary
172
152
  if hasattr(result, 'to_dict'):
173
153
  result_dict = result.to_dict()
@@ -176,13 +156,13 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
176
156
  else:
177
157
  # For any other type, convert to string representation
178
158
  result_dict = {"data": str(result), "snapshot_id": snapshot_id}
179
-
180
- print(f"Result from get_snapshot: {result_dict}", file=sys.stderr)
159
+
160
+ logger.debug(f"Result from get_snapshot: {result_dict}")
181
161
  return result_dict
182
-
162
+
183
163
  except Exception as sdk_error:
184
- print(f"SDK method failed: {sdk_error}, trying fallback", file=sys.stderr)
185
-
164
+ logger.warning(f"SDK method failed: {sdk_error}, trying fallback")
165
+
186
166
  # Check if it's a "not found" error
187
167
  error_str = str(sdk_error).lower()
188
168
  if "not exist" in error_str or "not found" in error_str or "not available" in error_str:
@@ -190,66 +170,65 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
190
170
  "error": f"Snapshot with ID '{snapshot_id}' does not exist or is not available.",
191
171
  "details": str(sdk_error)
192
172
  }
193
-
173
+
194
174
  # Check if it's a validation error
195
175
  if "validation error" in error_str:
196
176
  # Try using the without_preload_content version to get the raw response
197
177
  try:
198
- response_data = self.infra_api.get_snapshot_without_preload_content(
178
+ response_data = api_client.get_snapshot_without_preload_content(
199
179
  id=snapshot_id,
200
180
  to=to_time,
201
181
  window_size=window_size
202
182
  )
203
-
183
+
204
184
  # Check if the response was successful
205
185
  if response_data.status != 200:
206
186
  error_message = f"Failed to get snapshot: HTTP {response_data.status}"
207
- print(error_message, file=sys.stderr)
187
+ logger.error(error_message)
208
188
  return {"error": error_message}
209
-
189
+
210
190
  # Read the response content
211
191
  response_text = response_data.data.decode('utf-8')
212
-
192
+
213
193
  # Try to parse as JSON
214
194
  import json
215
195
  try:
216
196
  result_dict = json.loads(response_text)
217
- print(f"Result from fallback method: {result_dict}", file=sys.stderr)
197
+ logger.info(f"Result from fallback method: {result_dict}")
218
198
  return result_dict
219
199
  except json.JSONDecodeError:
220
200
  # If not valid JSON, return as string
221
- print(f"Result from fallback method (string): {response_text}", file=sys.stderr)
201
+ logger.error(f"Result from fallback method (string): {response_text}")
222
202
  return {"message": response_text, "snapshot_id": snapshot_id}
223
-
203
+
224
204
  except Exception as fallback_error:
225
- print(f"Fallback method failed: {fallback_error}", file=sys.stderr)
205
+ logger.error(f"Fallback method failed: {fallback_error}")
226
206
  # Continue to the general error handling
227
-
207
+
228
208
  # Re-raise if we couldn't handle it specifically
229
209
  raise
230
-
210
+
231
211
  except Exception as e:
232
- print(f"Error in get_snapshot: {e}", file=sys.stderr)
233
- import traceback
234
- traceback.print_exc(file=sys.stderr)
235
- return {"error": f"Failed to get snapshot: {str(e)}"}
212
+ logger.error(f"Error in get_snapshot: {e}", exc_info=True)
213
+ return {"error": f"Failed to get snapshot: {e!s}"}
236
214
 
237
215
  @register_as_tool
238
- async def get_snapshots(self,
239
- query: Optional[str] = None,
240
- from_time: Optional[int] = None,
241
- to_time: Optional[int] = None,
242
- size: Optional[int] = 100,
243
- plugin: Optional[str] = None,
244
- offline: Optional[bool] = False,
245
- detailed: Optional[bool] = False,
246
- ctx=None) -> Dict[str, Any]:
216
+ @with_header_auth(InfrastructureResourcesApi)
217
+ async def get_snapshots(self,
218
+ query: Optional[str] = None,
219
+ from_time: Optional[int] = None,
220
+ to_time: Optional[int] = None,
221
+ size: Optional[int] = 100,
222
+ plugin: Optional[str] = None,
223
+ offline: Optional[bool] = False,
224
+ detailed: Optional[bool] = False,
225
+ ctx=None, api_client=None) -> Dict[str, Any]:
247
226
  """
248
227
  Search and discover snapshots based on search criteria.
249
228
 
250
229
  This tool is for finding and retrieving MULTIPLE entities that match your search parameters.
251
230
  Use this when you need to discover entities, search across your infrastructure, or find components
252
- matching certain criteria. This is NOT for retrieving details about a specific entity you already know -
231
+ matching certain criteria. This is NOT for retrieving details about a specific entity you already know -
253
232
  use get_snapshot (singular) for that purpose.
254
233
 
255
234
  For example, use this tool when:
@@ -267,22 +246,22 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
267
246
  offline: Whether to include offline snapshots (optional, default False)
268
247
  detailed: If True, returns full raw data. If False (default), returns summarized data
269
248
  ctx: The MCP context (optional)
270
-
249
+
271
250
  Returns:
272
251
  Dictionary containing matching snapshots (summarized by default) or error information
273
252
  """
274
253
  try:
275
- debug_print(f"get_snapshots called with query={query}, from_time={from_time}, to_time={to_time}, size={size}, detailed={detailed}")
276
-
254
+ logger.debug(f"get_snapshots called with query={query}, from_time={from_time}, to_time={to_time}, size={size}, detailed={detailed}")
255
+
277
256
  # Set default time range if not provided
278
257
  if not to_time:
279
258
  to_time = int(datetime.now().timestamp() * 1000)
280
-
259
+
281
260
  if not from_time:
282
261
  from_time = to_time - (60 * 60 * 1000) # Default to 1 hour
283
-
262
+
284
263
  # Call the get_snapshots method from the SDK
285
- result = self.infra_api.get_snapshots(
264
+ result = api_client.get_snapshots(
286
265
  query=query,
287
266
  to=to_time,
288
267
  window_size=to_time - from_time if from_time else None,
@@ -290,9 +269,9 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
290
269
  plugin=plugin,
291
270
  offline=offline
292
271
  )
293
-
294
- debug_print(f"SDK returned result type: {type(result)}")
295
-
272
+
273
+ logger.debug(f"SDK returned result type: {type(result)}")
274
+
296
275
  # Convert result to dictionary if needed
297
276
  if hasattr(result, 'to_dict'):
298
277
  result_dict = result.to_dict()
@@ -300,48 +279,46 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
300
279
  result_dict = result
301
280
  else:
302
281
  result_dict = {"data": str(result)}
303
-
304
- debug_print(f"Result dict keys: {list(result_dict.keys()) if isinstance(result_dict, dict) else 'Not a dict'}")
305
-
282
+
283
+ logger.debug(f"Result dict keys: {list(result_dict.keys()) if isinstance(result_dict, dict) else 'Not a dict'}")
284
+
306
285
  # Return based on detailed parameter
307
286
  if detailed:
308
- debug_print("Returning detailed/raw response")
287
+ logger.debug("Returning detailed/raw response")
309
288
  return result_dict
310
289
  else:
311
- debug_print("Returning summarized response")
290
+ logger.debug("Returning summarized response")
312
291
  return self._summarize_get_snapshots_response(result_dict)
313
-
292
+
314
293
  except Exception as e:
315
- debug_print(f"Error in get_snapshots: {e}")
316
- import traceback
317
- traceback.print_exc(file=sys.stderr)
318
- return {"error": f"Failed to get snapshots: {str(e)}"}
294
+ logger.error(f"Error in get_snapshots: {e}", exc_info=True)
295
+ return {"error": f"Failed to get snapshots: {e!s}"}
319
296
 
320
297
  def _summarize_get_snapshots_response(self, response_data: Dict[str, Any]) -> Dict[str, Any]:
321
298
  """
322
299
  Create a summarized version of the get_snapshots response.
323
300
  """
324
301
  try:
325
- debug_print("Creating summarized get_snapshots response...")
302
+ logger.debug("Creating summarized get_snapshots response...")
326
303
  items = response_data.get('items', [])
327
-
304
+
328
305
  if len(items) == 0:
329
306
  return {
330
307
  "message": "No snapshots found matching your criteria.",
331
308
  "total_found": 0,
332
309
  "snapshots": []
333
310
  }
334
-
311
+
335
312
  # Create a readable summary
336
313
  summary_lines = [f"Found {len(items)} snapshot(s) matching your criteria:\n"]
337
-
314
+
338
315
  snapshots_list = []
339
316
  for i, item in enumerate(items, 1):
340
317
  snapshot_id = item.get('snapshotId', 'Unknown')
341
318
  label = item.get('label', 'No label')
342
319
  host = item.get('host', 'Unknown host')
343
320
  plugin = item.get('plugin', 'Unknown plugin')
344
-
321
+
345
322
  # Parse host information
346
323
  host_info = "Unknown"
347
324
  if 'arn:aws:ecs' in host:
@@ -350,11 +327,11 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
350
327
  cluster_info = parts[5].split('/') if len(parts) > 5 else []
351
328
  region = parts[3] if len(parts) > 3 else "unknown"
352
329
  cluster = cluster_info[1] if len(cluster_info) > 1 else "unknown"
353
- task_id = cluster_info[2] if len(cluster_info) > 2 else "unknown"
330
+ cluster_info[2] if len(cluster_info) > 2 else "unknown"
354
331
  host_info = f"AWS ECS Task in {region} (cluster: {cluster})"
355
332
  else:
356
333
  host_info = host
357
-
334
+
358
335
  # Create readable entry
359
336
  snapshot_entry = {
360
337
  "number": i,
@@ -364,27 +341,25 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
364
341
  "host_info": host_info,
365
342
  "full_host": host
366
343
  }
367
-
344
+
368
345
  snapshots_list.append(snapshot_entry)
369
-
346
+
370
347
  # Add to summary lines
371
348
  summary_lines.append(f"{i}. Snapshot ID: {snapshot_id}")
372
349
  summary_lines.append(f" Label: {label}")
373
350
  summary_lines.append(f" Plugin: {plugin}")
374
351
  summary_lines.append(f" Host: {host_info}")
375
352
  summary_lines.append("") # Empty line for spacing
376
-
353
+
377
354
  return {
378
355
  "summary": "\n".join(summary_lines),
379
356
  "total_found": len(items),
380
357
  "snapshots": snapshots_list,
381
358
  "message": f"Successfully found {len(items)} snapshot(s). See details above."
382
359
  }
383
-
360
+
384
361
  except Exception as e:
385
- debug_print(f"Error summarizing get_snapshots response: {e}")
386
- import traceback
387
- traceback.print_exc(file=sys.stderr)
362
+ logger.error(f"Error summarizing get_snapshots response: {e}", exc_info=True)
388
363
  return {
389
364
  "error": "Failed to summarize response",
390
365
  "details": str(e)
@@ -393,28 +368,29 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
393
368
 
394
369
 
395
370
  @register_as_tool
396
- async def post_snapshots(self,
397
- snapshot_ids: Union[List[str], str],
398
- to_time: Optional[int] = None,
399
- window_size: Optional[int] = None,
400
- detailed: Optional[bool] = False,
401
- ctx=None) -> Dict[str, Any]:
371
+ @with_header_auth(InfrastructureResourcesApi)
372
+ async def post_snapshots(self,
373
+ snapshot_ids: Union[List[str], str],
374
+ to_time: Optional[int] = None,
375
+ window_size: Optional[int] = None,
376
+ detailed: Optional[bool] = False,
377
+ ctx=None, api_client=None) -> Dict[str, Any]:
402
378
  """
403
379
  Get details for multiple snapshots by their IDs using SDK.
404
-
380
+
405
381
  Args:
406
382
  snapshot_ids: List of snapshot IDs to retrieve, or a comma-separated string of IDs
407
383
  to_time: End timestamp in milliseconds (optional)
408
384
  window_size: Window size in milliseconds (optional)
409
385
  detailed: If True, returns full raw data. If False (default), returns summarized data
410
386
  ctx: The MCP context (optional)
411
-
387
+
412
388
  Returns:
413
389
  Dictionary containing snapshot details (summarized by default) or error information
414
390
  """
415
391
  try:
416
- debug_print(f"post_snapshots called with snapshot_ids={snapshot_ids}, detailed={detailed}")
417
-
392
+ logger.debug(f"post_snapshots called with snapshot_ids={snapshot_ids}, detailed={detailed}")
393
+
418
394
  # Handle string input conversion
419
395
  if isinstance(snapshot_ids, str):
420
396
  if snapshot_ids.startswith('[') and snapshot_ids.endswith(']'):
@@ -422,21 +398,23 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
422
398
  snapshot_ids = ast.literal_eval(snapshot_ids)
423
399
  else:
424
400
  snapshot_ids = [id.strip() for id in snapshot_ids.split(',')]
425
-
401
+
426
402
  if not snapshot_ids:
427
403
  return {"error": "snapshot_ids parameter is required"}
428
-
404
+
429
405
  # Use working timeframe
430
406
  if not to_time:
431
407
  to_time = 1745389956000
432
408
  if not window_size:
433
409
  window_size = 3600000
434
-
435
- debug_print(f"Using to_time={to_time}, window_size={window_size}")
436
-
410
+
411
+ logger.debug(f"Using to_time={to_time}, window_size={window_size}")
412
+
437
413
  if has_get_snapshots_query:
438
- from instana_client.models.get_snapshots_query import GetSnapshotsQuery
439
-
414
+ from instana_client.models.get_snapshots_query import (
415
+ GetSnapshotsQuery, #type: ignore
416
+ )
417
+
440
418
  query_obj = GetSnapshotsQuery(
441
419
  snapshot_ids=snapshot_ids,
442
420
  time_frame={
@@ -444,30 +422,30 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
444
422
  "windowSize": window_size
445
423
  }
446
424
  )
447
-
448
- debug_print(f"Making SDK request with without_preload_content...")
449
-
425
+
426
+ logger.debug("Making SDK request with without_preload_content...")
427
+
450
428
  # Use the working SDK method that bypasses model validation
451
- response = self.infra_api.post_snapshots_without_preload_content(
429
+ response = api_client.post_snapshots_without_preload_content(
452
430
  get_snapshots_query=query_obj
453
431
  )
454
-
455
- debug_print(f"SDK response status: {response.status}")
456
-
432
+
433
+ logger.debug(f"SDK response status: {response.status}")
434
+
457
435
  if response.status == 200:
458
436
  # Parse the JSON response manually
459
437
  import json
460
438
  response_text = response.data.decode('utf-8')
461
439
  result_dict = json.loads(response_text)
462
-
463
- debug_print(f"Successfully parsed response with {len(result_dict.get('items', []))} items")
464
-
440
+
441
+ logger.debug(f"Successfully parsed response with {len(result_dict.get('items', []))} items")
442
+
465
443
  # Return based on detailed parameter
466
444
  if detailed:
467
- debug_print("Returning detailed/raw response")
445
+ logger.debug("Returning detailed/raw response")
468
446
  return result_dict
469
447
  else:
470
- debug_print("Returning summarized response")
448
+ logger.debug("Returning summarized response")
471
449
  return self._summarize_snapshots_response(result_dict)
472
450
  else:
473
451
  return {
@@ -476,29 +454,27 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
476
454
  }
477
455
  else:
478
456
  return {"error": "GetSnapshotsQuery model not available"}
479
-
457
+
480
458
  except Exception as e:
481
- debug_print(f"Error in post_snapshots: {e}")
482
- import traceback
483
- traceback.print_exc(file=sys.stderr)
484
- return {"error": f"Failed to post snapshots: {str(e)}"}
459
+ logger.error(f"Error in post_snapshots: {e}", exc_info=True)
460
+ return {"error": f"Failed to post snapshots: {e!s}"}
485
461
 
486
462
  def _summarize_snapshots_response(self, response_data: Dict[str, Any]) -> Dict[str, Any]:
487
463
  """
488
464
  Create a summarized version of the snapshots response.
489
465
  """
490
466
  try:
491
- debug_print("Creating summarized response...")
467
+ logger.debug("Creating summarized response...")
492
468
  items = response_data.get('items', [])
493
-
469
+
494
470
  summary = {
495
471
  "total_snapshots": len(items),
496
472
  "snapshots": []
497
473
  }
498
-
474
+
499
475
  for item in items:
500
- debug_print(f"Processing snapshot: {item.get('snapshotId')} - {item.get('plugin')}")
501
-
476
+ logger.debug(f"Processing snapshot: {item.get('snapshotId')} - {item.get('plugin')}")
477
+
502
478
  snapshot_summary = {
503
479
  "snapshotId": item.get('snapshotId'),
504
480
  "plugin": item.get('plugin'),
@@ -510,12 +486,12 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
510
486
  },
511
487
  "tags": item.get('tags', [])
512
488
  }
513
-
489
+
514
490
  # Extract key information from data based on plugin type
515
491
  data = item.get('data', {})
516
-
492
+
517
493
  if item.get('plugin') == 'jvmRuntimePlatform':
518
- debug_print("Processing JVM snapshot...")
494
+ logger.debug("Processing JVM snapshot...")
519
495
  snapshot_summary["key_info"] = {
520
496
  "process_name": data.get('name'),
521
497
  "pid": data.get('pid'),
@@ -529,7 +505,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
529
505
  "jvm_collectors": data.get('jvm.collectors', [])
530
506
  }
531
507
  elif item.get('plugin') == 'nodeJsRuntimePlatform':
532
- debug_print("Processing Node.js snapshot...")
508
+ logger.debug("Processing Node.js snapshot...")
533
509
  versions = data.get('versions', {})
534
510
  snapshot_summary["key_info"] = {
535
511
  "app_name": data.get('name'),
@@ -548,21 +524,19 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
548
524
  }
549
525
  else:
550
526
  # Generic summary for other plugin types
551
- debug_print(f"Processing generic snapshot for plugin: {item.get('plugin')}")
527
+ logger.debug(f"Processing generic snapshot for plugin: {item.get('plugin')}")
552
528
  snapshot_summary["key_info"] = {
553
529
  "data_keys": list(data.keys())[:10], # First 10 keys
554
530
  "total_data_fields": len(data.keys())
555
531
  }
556
-
532
+
557
533
  summary["snapshots"].append(snapshot_summary)
558
-
559
- debug_print(f"Created summary with {len(summary['snapshots'])} snapshots")
534
+
535
+ logger.debug(f"Created summary with {len(summary['snapshots'])} snapshots")
560
536
  return summary
561
-
537
+
562
538
  except Exception as e:
563
- debug_print(f"Error summarizing response: {e}")
564
- import traceback
565
- traceback.print_exc(file=sys.stderr)
539
+ logger.error(f"Error summarizing response: {e}", exc_info=True)
566
540
  return {
567
541
  "error": "Failed to summarize response",
568
542
  "details": str(e),
@@ -573,57 +547,58 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
573
547
 
574
548
 
575
549
  @register_as_tool
576
- async def software_versions(self, ctx=None) -> Dict[str, Any]:
550
+ @with_header_auth(InfrastructureResourcesApi)
551
+ async def software_versions(self, ctx=None, api_client=None) -> Dict[str, Any]:
577
552
  """
578
553
  Get information about installed software versions across the monitored infrastructure.
579
554
  Retrieve information about the software that are sensed by the agent remotely, natively, or both. This includes runtime and package manager information.
580
555
  Args:
581
556
  ctx: The MCP context (optional)
582
-
557
+
583
558
  Returns:
584
559
  Dictionary containing software version information or error information
585
560
  """
586
561
  try:
587
- print("Calling software_versions API...", file=sys.stderr)
588
-
562
+ logger.info("Calling software_versions API...")
563
+
589
564
  # Call the software_versions method from the SDK with no parameters
590
- result = self.infra_api.software_versions()
591
-
592
- print(f"API call successful. Result type: {type(result)}", file=sys.stderr)
593
-
565
+ result = api_client.software_versions()
566
+
567
+ logger.info(f"API call successful. Result type: {type(result)}")
568
+
594
569
  # Handle different response formats
595
570
  if hasattr(result, 'to_dict'):
596
- print("Converting result using to_dict() method", file=sys.stderr)
571
+ logger.info("Converting result using to_dict() method")
597
572
  result_dict = result.to_dict()
598
573
  elif isinstance(result, dict):
599
- print("Result is already a dictionary", file=sys.stderr)
574
+ logger.info("Result is already a dictionary")
600
575
  result_dict = result
601
576
  elif isinstance(result, list):
602
- print("Result is a list", file=sys.stderr)
577
+ logger.info("Result is a list")
603
578
  result_dict = {"items": result}
604
579
  else:
605
- print(f"Unexpected result format: {type(result)}", file=sys.stderr)
580
+ logger.info(f"Unexpected result format: {type(result)}")
606
581
  # Try to convert to a dictionary or string representation
607
582
  try:
608
583
  result_dict = {"data": str(result)}
609
584
  except Exception as str_error:
610
585
  return {"error": f"Unexpected result format: {type(result)}", "details": str(str_error)}
611
-
586
+
612
587
  # Print a sample of the result for debugging
613
588
  if isinstance(result_dict, dict):
614
589
  keys = list(result_dict.keys())
615
- print(f"Result keys: {keys}", file=sys.stderr)
616
-
590
+ logger.info(f"Result keys: {keys}")
591
+
617
592
  # If the result is very large, return a summary
618
593
  if 'items' in result_dict and isinstance(result_dict['items'], list):
619
594
  items_count = len(result_dict['items'])
620
- print(f"Found {items_count} items in the response", file=sys.stderr)
621
-
595
+ logger.info(f"Found {items_count} items in the response")
596
+
622
597
  # Limit the number of items to return
623
598
  if items_count > 10:
624
- result_dict['summary'] = f"Showing 10 of {items_count} items"
625
- result_dict['items'] = result_dict['items'][:10]
626
-
599
+ result_dict['summary'] = f"Showing 10 of {items_count} items" #type: ignore
600
+ result_dict['items'] = result_dict['items'][:10] #type: ignore
601
+
627
602
  # If tagTree exists, extract tag names
628
603
  if 'tagTree' in result_dict and isinstance(result_dict['tagTree'], list):
629
604
  tag_names = []
@@ -637,17 +612,13 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
637
612
  'tagName': tag['tagName'],
638
613
  'description': tag.get('description', '')
639
614
  })
640
-
615
+
641
616
  # Replace the large tagTree with the extracted tag names
642
- result_dict['tagNames'] = tag_names
617
+ result_dict['tagNames'] = tag_names #type: ignore
643
618
  del result_dict['tagTree']
644
-
619
+
645
620
  return result_dict
646
621
  except Exception as e:
647
- print(f"Error in software_versions: {e}", file=sys.stderr)
648
- import traceback
649
- traceback.print_exc(file=sys.stderr)
650
- return {"error": f"Failed to get software versions: {str(e)}"}
651
-
652
-
622
+ logger.error(f"Error in software_versions: {e}", exc_info=True)
623
+ return {"error": f"Failed to get software versions: {e!s}"}
653
624