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,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 (
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,21 @@ 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
414
  from instana_client.models.get_snapshots_query import GetSnapshotsQuery
439
-
415
+
440
416
  query_obj = GetSnapshotsQuery(
441
417
  snapshot_ids=snapshot_ids,
442
418
  time_frame={
@@ -444,30 +420,30 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
444
420
  "windowSize": window_size
445
421
  }
446
422
  )
447
-
448
- debug_print(f"Making SDK request with without_preload_content...")
449
-
423
+
424
+ logger.debug("Making SDK request with without_preload_content...")
425
+
450
426
  # Use the working SDK method that bypasses model validation
451
- response = self.infra_api.post_snapshots_without_preload_content(
427
+ response = api_client.post_snapshots_without_preload_content(
452
428
  get_snapshots_query=query_obj
453
429
  )
454
-
455
- debug_print(f"SDK response status: {response.status}")
456
-
430
+
431
+ logger.debug(f"SDK response status: {response.status}")
432
+
457
433
  if response.status == 200:
458
434
  # Parse the JSON response manually
459
435
  import json
460
436
  response_text = response.data.decode('utf-8')
461
437
  result_dict = json.loads(response_text)
462
-
463
- debug_print(f"Successfully parsed response with {len(result_dict.get('items', []))} items")
464
-
438
+
439
+ logger.debug(f"Successfully parsed response with {len(result_dict.get('items', []))} items")
440
+
465
441
  # Return based on detailed parameter
466
442
  if detailed:
467
- debug_print("Returning detailed/raw response")
443
+ logger.debug("Returning detailed/raw response")
468
444
  return result_dict
469
445
  else:
470
- debug_print("Returning summarized response")
446
+ logger.debug("Returning summarized response")
471
447
  return self._summarize_snapshots_response(result_dict)
472
448
  else:
473
449
  return {
@@ -476,29 +452,27 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
476
452
  }
477
453
  else:
478
454
  return {"error": "GetSnapshotsQuery model not available"}
479
-
455
+
480
456
  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)}"}
457
+ logger.error(f"Error in post_snapshots: {e}", exc_info=True)
458
+ return {"error": f"Failed to post snapshots: {e!s}"}
485
459
 
486
460
  def _summarize_snapshots_response(self, response_data: Dict[str, Any]) -> Dict[str, Any]:
487
461
  """
488
462
  Create a summarized version of the snapshots response.
489
463
  """
490
464
  try:
491
- debug_print("Creating summarized response...")
465
+ logger.debug("Creating summarized response...")
492
466
  items = response_data.get('items', [])
493
-
467
+
494
468
  summary = {
495
469
  "total_snapshots": len(items),
496
470
  "snapshots": []
497
471
  }
498
-
472
+
499
473
  for item in items:
500
- debug_print(f"Processing snapshot: {item.get('snapshotId')} - {item.get('plugin')}")
501
-
474
+ logger.debug(f"Processing snapshot: {item.get('snapshotId')} - {item.get('plugin')}")
475
+
502
476
  snapshot_summary = {
503
477
  "snapshotId": item.get('snapshotId'),
504
478
  "plugin": item.get('plugin'),
@@ -510,12 +484,12 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
510
484
  },
511
485
  "tags": item.get('tags', [])
512
486
  }
513
-
487
+
514
488
  # Extract key information from data based on plugin type
515
489
  data = item.get('data', {})
516
-
490
+
517
491
  if item.get('plugin') == 'jvmRuntimePlatform':
518
- debug_print("Processing JVM snapshot...")
492
+ logger.debug("Processing JVM snapshot...")
519
493
  snapshot_summary["key_info"] = {
520
494
  "process_name": data.get('name'),
521
495
  "pid": data.get('pid'),
@@ -529,7 +503,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
529
503
  "jvm_collectors": data.get('jvm.collectors', [])
530
504
  }
531
505
  elif item.get('plugin') == 'nodeJsRuntimePlatform':
532
- debug_print("Processing Node.js snapshot...")
506
+ logger.debug("Processing Node.js snapshot...")
533
507
  versions = data.get('versions', {})
534
508
  snapshot_summary["key_info"] = {
535
509
  "app_name": data.get('name'),
@@ -548,21 +522,19 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
548
522
  }
549
523
  else:
550
524
  # Generic summary for other plugin types
551
- debug_print(f"Processing generic snapshot for plugin: {item.get('plugin')}")
525
+ logger.debug(f"Processing generic snapshot for plugin: {item.get('plugin')}")
552
526
  snapshot_summary["key_info"] = {
553
527
  "data_keys": list(data.keys())[:10], # First 10 keys
554
528
  "total_data_fields": len(data.keys())
555
529
  }
556
-
530
+
557
531
  summary["snapshots"].append(snapshot_summary)
558
-
559
- debug_print(f"Created summary with {len(summary['snapshots'])} snapshots")
532
+
533
+ logger.debug(f"Created summary with {len(summary['snapshots'])} snapshots")
560
534
  return summary
561
-
535
+
562
536
  except Exception as e:
563
- debug_print(f"Error summarizing response: {e}")
564
- import traceback
565
- traceback.print_exc(file=sys.stderr)
537
+ logger.error(f"Error summarizing response: {e}", exc_info=True)
566
538
  return {
567
539
  "error": "Failed to summarize response",
568
540
  "details": str(e),
@@ -573,57 +545,58 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
573
545
 
574
546
 
575
547
  @register_as_tool
576
- async def software_versions(self, ctx=None) -> Dict[str, Any]:
548
+ @with_header_auth(InfrastructureResourcesApi)
549
+ async def software_versions(self, ctx=None, api_client=None) -> Dict[str, Any]:
577
550
  """
578
551
  Get information about installed software versions across the monitored infrastructure.
579
552
  Retrieve information about the software that are sensed by the agent remotely, natively, or both. This includes runtime and package manager information.
580
553
  Args:
581
554
  ctx: The MCP context (optional)
582
-
555
+
583
556
  Returns:
584
557
  Dictionary containing software version information or error information
585
558
  """
586
559
  try:
587
- print("Calling software_versions API...", file=sys.stderr)
588
-
560
+ logger.info("Calling software_versions API...")
561
+
589
562
  # 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
-
563
+ result = api_client.software_versions()
564
+
565
+ logger.info(f"API call successful. Result type: {type(result)}")
566
+
594
567
  # Handle different response formats
595
568
  if hasattr(result, 'to_dict'):
596
- print("Converting result using to_dict() method", file=sys.stderr)
569
+ logger.info("Converting result using to_dict() method")
597
570
  result_dict = result.to_dict()
598
571
  elif isinstance(result, dict):
599
- print("Result is already a dictionary", file=sys.stderr)
572
+ logger.info("Result is already a dictionary")
600
573
  result_dict = result
601
574
  elif isinstance(result, list):
602
- print("Result is a list", file=sys.stderr)
575
+ logger.info("Result is a list")
603
576
  result_dict = {"items": result}
604
577
  else:
605
- print(f"Unexpected result format: {type(result)}", file=sys.stderr)
578
+ logger.info(f"Unexpected result format: {type(result)}")
606
579
  # Try to convert to a dictionary or string representation
607
580
  try:
608
581
  result_dict = {"data": str(result)}
609
582
  except Exception as str_error:
610
583
  return {"error": f"Unexpected result format: {type(result)}", "details": str(str_error)}
611
-
584
+
612
585
  # Print a sample of the result for debugging
613
586
  if isinstance(result_dict, dict):
614
587
  keys = list(result_dict.keys())
615
- print(f"Result keys: {keys}", file=sys.stderr)
616
-
588
+ logger.info(f"Result keys: {keys}")
589
+
617
590
  # If the result is very large, return a summary
618
591
  if 'items' in result_dict and isinstance(result_dict['items'], list):
619
592
  items_count = len(result_dict['items'])
620
- print(f"Found {items_count} items in the response", file=sys.stderr)
621
-
593
+ logger.info(f"Found {items_count} items in the response")
594
+
622
595
  # Limit the number of items to return
623
596
  if items_count > 10:
624
597
  result_dict['summary'] = f"Showing 10 of {items_count} items"
625
598
  result_dict['items'] = result_dict['items'][:10]
626
-
599
+
627
600
  # If tagTree exists, extract tag names
628
601
  if 'tagTree' in result_dict and isinstance(result_dict['tagTree'], list):
629
602
  tag_names = []
@@ -637,17 +610,13 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
637
610
  'tagName': tag['tagName'],
638
611
  'description': tag.get('description', '')
639
612
  })
640
-
613
+
641
614
  # Replace the large tagTree with the extracted tag names
642
615
  result_dict['tagNames'] = tag_names
643
616
  del result_dict['tagTree']
644
-
617
+
645
618
  return result_dict
646
619
  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
-
620
+ logger.error(f"Error in software_versions: {e}", exc_info=True)
621
+ return {"error": f"Failed to get software versions: {e!s}"}
653
622