mcp-instana 0.3.1__py3-none-any.whl → 0.7.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 (30) hide show
  1. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +186 -311
  2. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +30 -22
  3. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +1 -1
  4. src/application/application_alert_config.py +393 -136
  5. src/application/application_analyze.py +597 -594
  6. src/application/application_call_group.py +528 -0
  7. src/application/application_catalog.py +0 -8
  8. src/application/application_global_alert_config.py +275 -57
  9. src/application/application_metrics.py +377 -237
  10. src/application/application_resources.py +414 -325
  11. src/application/application_settings.py +608 -1530
  12. src/application/application_topology.py +62 -62
  13. src/core/custom_dashboard_smart_router_tool.py +135 -0
  14. src/core/server.py +95 -119
  15. src/core/smart_router_tool.py +574 -0
  16. src/core/utils.py +17 -8
  17. src/custom_dashboard/custom_dashboard_tools.py +422 -0
  18. src/event/events_tools.py +57 -9
  19. src/infrastructure/elicitation_handler.py +338 -0
  20. src/infrastructure/entity_registry.py +329 -0
  21. src/infrastructure/infrastructure_analyze_new.py +600 -0
  22. src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
  23. src/infrastructure/infrastructure_catalog.py +37 -32
  24. src/infrastructure/infrastructure_metrics.py +93 -16
  25. src/infrastructure/infrastructure_resources.py +6 -24
  26. src/infrastructure/infrastructure_topology.py +29 -23
  27. src/observability.py +29 -0
  28. src/prompts/application/application_settings.py +58 -0
  29. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
  30. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,422 @@
1
+ """
2
+ Custom Dashboard MCP Tools Module
3
+
4
+ This module provides custom dashboard-specific MCP tools for Instana monitoring.
5
+ Uses the api/custom-dashboard endpoints.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from mcp.types import ToolAnnotations
13
+
14
+ from src.core.utils import (
15
+ BaseInstanaClient,
16
+ register_as_tool,
17
+ with_header_auth,
18
+ )
19
+
20
+ try:
21
+ from instana_client.api.custom_dashboards_api import CustomDashboardsApi
22
+ from instana_client.models.custom_dashboard import CustomDashboard
23
+
24
+ except ImportError as e:
25
+ import logging
26
+ logger = logging.getLogger(__name__)
27
+ logger.error(f"Error importing Instana SDK: {e}", exc_info=True)
28
+ raise
29
+
30
+ # Configure logger for this module
31
+ logger = logging.getLogger(__name__)
32
+
33
+ class CustomDashboardMCPTools(BaseInstanaClient):
34
+ """Tools for custom dashboards in Instana MCP."""
35
+
36
+ def __init__(self, read_token: str, base_url: str):
37
+ """Initialize the Custom Dashboard MCP tools client."""
38
+ super().__init__(read_token=read_token, base_url=base_url)
39
+
40
+ # CRUD Operations Dispatcher - called by custom_dashboard_smart_router_tool.py
41
+ async def execute_dashboard_operation(
42
+ self,
43
+ operation: str,
44
+ dashboard_id: Optional[str] = None,
45
+ custom_dashboard: Optional[Dict[str, Any]] = None,
46
+ ctx=None
47
+ ) -> Dict[str, Any]:
48
+ """
49
+ Execute Custom Dashboard CRUD operations.
50
+ Called by the custom dashboard smart router tool.
51
+
52
+ Args:
53
+ operation: Operation to perform (get_all, get, create, update, delete, get_shareable_users, get_shareable_api_tokens)
54
+ dashboard_id: Dashboard ID
55
+ custom_dashboard: Dashboard configuration payload
56
+ ctx: MCP context
57
+
58
+ Returns:
59
+ Operation result dictionary
60
+ """
61
+ try:
62
+ if operation == "get_all":
63
+ return await self.get_custom_dashboards(ctx=ctx)
64
+ elif operation == "get":
65
+ if not dashboard_id:
66
+ return {"error": "dashboard_id is required for 'get' operation"}
67
+ return await self.get_custom_dashboard(dashboard_id=dashboard_id, ctx=ctx)
68
+ elif operation == "create":
69
+ if not custom_dashboard:
70
+ return {"error": "custom_dashboard is required for 'create' operation"}
71
+ return await self.add_custom_dashboard(custom_dashboard=custom_dashboard, ctx=ctx)
72
+ elif operation == "update":
73
+ if not dashboard_id:
74
+ return {"error": "dashboard_id is required for 'update' operation"}
75
+ if not custom_dashboard:
76
+ return {"error": "custom_dashboard is required for 'update' operation"}
77
+ return await self.update_custom_dashboard(dashboard_id=dashboard_id, custom_dashboard=custom_dashboard, ctx=ctx)
78
+ elif operation == "delete":
79
+ if not dashboard_id:
80
+ return {"error": "dashboard_id is required for 'delete' operation"}
81
+ return await self.delete_custom_dashboard(dashboard_id=dashboard_id, ctx=ctx)
82
+ elif operation == "get_shareable_users":
83
+ if not dashboard_id:
84
+ return {"error": "dashboard_id is required for 'get_shareable_users' operation"}
85
+ return await self.get_shareable_users(dashboard_id=dashboard_id, ctx=ctx)
86
+ elif operation == "get_shareable_api_tokens":
87
+ if not dashboard_id:
88
+ return {"error": "dashboard_id is required for 'get_shareable_api_tokens' operation"}
89
+ return await self.get_shareable_api_tokens(dashboard_id=dashboard_id, ctx=ctx)
90
+ else:
91
+ return {"error": f"Operation '{operation}' not supported"}
92
+
93
+ except Exception as e:
94
+ logger.error(f"Error executing {operation}: {e}", exc_info=True)
95
+ return {"error": f"Error executing {operation}: {e!s}"}
96
+
97
+ # Individual operation functions
98
+
99
+ @with_header_auth(CustomDashboardsApi)
100
+ async def get_custom_dashboards(self,
101
+ ctx=None, api_client=None) -> Dict[str, Any]:
102
+ """
103
+ Get all custom dashboards from Instana server.
104
+ Uses api/custom-dashboard endpoint.
105
+ """
106
+ try:
107
+ logger.debug("Getting custom dashboards from Instana SDK")
108
+
109
+ # Call the get_custom_dashboards method from the SDK
110
+ result = api_client.get_custom_dashboards()
111
+
112
+ # Convert the result to a dictionary
113
+ result_dict: Dict[str, Any] = {}
114
+
115
+ if hasattr(result, 'to_dict'):
116
+ result_dict = result.to_dict()
117
+ elif isinstance(result, dict):
118
+ result_dict = result
119
+ elif isinstance(result, list):
120
+ # If it's a list, wrap it in a dictionary
121
+ result_dict = {"items": result}
122
+ else:
123
+ # For any other type, convert to string and wrap
124
+ result_dict = {"result": str(result)}
125
+
126
+ # Limit the response size
127
+ if "items" in result_dict and isinstance(result_dict["items"], list):
128
+ # Limit items to top 10
129
+ items_list = result_dict["items"]
130
+ original_count = len(items_list)
131
+ if original_count > 10:
132
+ result_dict["items"] = items_list[:10]
133
+ logger.debug(f"Limited response items from {original_count} to 10")
134
+
135
+ try:
136
+ logger.debug(f"Result from get_custom_dashboards: {json.dumps(result_dict, indent=2)}")
137
+ except TypeError:
138
+ logger.debug(f"Result from get_custom_dashboards: {result_dict} (not JSON serializable)")
139
+
140
+ return result_dict
141
+
142
+ except Exception as e:
143
+ logger.error(f"Error in get_custom_dashboards: {e}", exc_info=True)
144
+ return {"error": f"Failed to get custom dashboards: {e!s}"}
145
+
146
+ @with_header_auth(CustomDashboardsApi)
147
+ async def get_custom_dashboard(self,
148
+ dashboard_id: str,
149
+ ctx=None, api_client=None) -> Dict[str, Any]:
150
+ """
151
+ Get a specific custom dashboard by ID from Instana server.
152
+ Uses api/custom-dashboard/{id} endpoint.
153
+ """
154
+ try:
155
+ if not dashboard_id:
156
+ return {"error": "Dashboard ID is required for this operation"}
157
+
158
+ logger.debug(f"Getting custom dashboard {dashboard_id} from Instana SDK")
159
+
160
+ # Call the get_custom_dashboard method from the SDK
161
+ result = api_client.get_custom_dashboard(dashboard_id=dashboard_id)
162
+
163
+ # Convert the result to a dictionary
164
+ result_dict: Dict[str, Any] = {}
165
+
166
+ if hasattr(result, 'to_dict'):
167
+ result_dict = result.to_dict()
168
+ elif isinstance(result, dict):
169
+ result_dict = result
170
+ else:
171
+ # For any other type, convert to string and wrap
172
+ result_dict = {"result": str(result)}
173
+
174
+ try:
175
+ logger.debug(f"Result from get_custom_dashboard: {json.dumps(result_dict, indent=2)}")
176
+ except TypeError:
177
+ logger.debug(f"Result from get_custom_dashboard: {result_dict} (not JSON serializable)")
178
+
179
+ return result_dict
180
+
181
+ except Exception as e:
182
+ logger.error(f"Error in get_custom_dashboard: {e}", exc_info=True)
183
+ return {"error": f"Failed to get custom dashboard: {e!s}"}
184
+
185
+ @with_header_auth(CustomDashboardsApi)
186
+ async def add_custom_dashboard(self,
187
+ custom_dashboard: Dict[str, Any],
188
+ ctx=None, api_client=None) -> Dict[str, Any]:
189
+ """
190
+ Add a new custom dashboard to Instana server.
191
+ Uses api/custom-dashboard POST endpoint.
192
+ """
193
+ try:
194
+ if not custom_dashboard:
195
+ return {"error": "Custom dashboard configuration is required for this operation"}
196
+
197
+ logger.debug("Adding custom dashboard to Instana SDK")
198
+ logger.debug(json.dumps(custom_dashboard, indent=2))
199
+
200
+ # Add a temporary ID for validation (will be replaced by server)
201
+ dashboard_config = custom_dashboard.copy()
202
+ if 'id' not in dashboard_config:
203
+ dashboard_config['id'] = '' # Empty string as placeholder
204
+
205
+ # Create the CustomDashboard object
206
+ dashboard_obj = CustomDashboard(**dashboard_config)
207
+
208
+ # Call the add_custom_dashboard method from the SDK
209
+ result = api_client.add_custom_dashboard(custom_dashboard=dashboard_obj)
210
+
211
+ # Convert the result to a dictionary
212
+ result_dict: Dict[str, Any] = {}
213
+
214
+ if hasattr(result, 'to_dict'):
215
+ result_dict = result.to_dict()
216
+ elif isinstance(result, dict):
217
+ result_dict = result
218
+ else:
219
+ # For any other type, convert to string and wrap
220
+ result_dict = {"result": str(result)}
221
+
222
+ try:
223
+ logger.debug(f"Result from add_custom_dashboard: {json.dumps(result_dict, indent=2)}")
224
+ except TypeError:
225
+ logger.debug(f"Result from add_custom_dashboard: {result_dict} (not JSON serializable)")
226
+
227
+ return result_dict
228
+
229
+ except Exception as e:
230
+ logger.error(f"Error in add_custom_dashboard: {e}", exc_info=True)
231
+ return {"error": f"Failed to add custom dashboard: {e!s}"}
232
+
233
+ @with_header_auth(CustomDashboardsApi)
234
+ async def update_custom_dashboard(self,
235
+ dashboard_id: str,
236
+ custom_dashboard: Dict[str, Any],
237
+ ctx=None, api_client=None) -> Dict[str, Any]:
238
+ """
239
+ Update an existing custom dashboard in Instana server.
240
+ Uses api/custom-dashboard/{id} PUT endpoint.
241
+ """
242
+ try:
243
+ if not dashboard_id:
244
+ return {"error": "Dashboard ID is required for this operation"}
245
+
246
+ if not custom_dashboard:
247
+ return {"error": "Custom dashboard configuration is required for this operation"}
248
+
249
+ logger.debug(f"Updating custom dashboard {dashboard_id} in Instana SDK")
250
+ logger.debug(json.dumps(custom_dashboard, indent=2))
251
+
252
+ # Create the CustomDashboard object
253
+ dashboard_obj = CustomDashboard(**custom_dashboard)
254
+
255
+ # Call the update_custom_dashboard method from the SDK
256
+ result = api_client.update_custom_dashboard(
257
+ dashboard_id=dashboard_id,
258
+ custom_dashboard=dashboard_obj
259
+ )
260
+
261
+ # Convert the result to a dictionary
262
+ result_dict: Dict[str, Any] = {}
263
+
264
+ if hasattr(result, 'to_dict'):
265
+ result_dict = result.to_dict()
266
+ elif isinstance(result, dict):
267
+ result_dict = result
268
+ else:
269
+ # For any other type, convert to string and wrap
270
+ result_dict = {"result": str(result)}
271
+
272
+ try:
273
+ logger.debug(f"Result from update_custom_dashboard: {json.dumps(result_dict, indent=2)}")
274
+ except TypeError:
275
+ logger.debug(f"Result from update_custom_dashboard: {result_dict} (not JSON serializable)")
276
+
277
+ return result_dict
278
+
279
+ except Exception as e:
280
+ logger.error(f"Error in update_custom_dashboard: {e}", exc_info=True)
281
+ return {"error": f"Failed to update custom dashboard: {e!s}"}
282
+
283
+ @with_header_auth(CustomDashboardsApi)
284
+ async def delete_custom_dashboard(self,
285
+ dashboard_id: str,
286
+ ctx=None, api_client=None) -> Dict[str, Any]:
287
+ """
288
+ Delete a custom dashboard from Instana server.
289
+ Uses api/custom-dashboard/{id} DELETE endpoint.
290
+ """
291
+ try:
292
+ if not dashboard_id:
293
+ return {"error": "Dashboard ID is required for this operation"}
294
+
295
+ logger.debug(f"Deleting custom dashboard {dashboard_id} from Instana SDK")
296
+
297
+ # Call the delete_custom_dashboard method from the SDK
298
+ result = api_client.delete_custom_dashboard(dashboard_id=dashboard_id)
299
+
300
+ # Convert the result to a dictionary
301
+ result_dict: Dict[str, Any] = {}
302
+
303
+ if hasattr(result, 'to_dict'):
304
+ result_dict = result.to_dict()
305
+ elif isinstance(result, dict):
306
+ result_dict = result
307
+ else:
308
+ # For any other type, convert to string and wrap
309
+ result_dict = {"result": str(result)}
310
+
311
+ try:
312
+ logger.debug(f"Result from delete_custom_dashboard: {json.dumps(result_dict, indent=2)}")
313
+ except TypeError:
314
+ logger.debug(f"Result from delete_custom_dashboard: {result_dict} (not JSON serializable)")
315
+
316
+ return result_dict
317
+
318
+ except Exception as e:
319
+ logger.error(f"Error in delete_custom_dashboard: {e}", exc_info=True)
320
+ return {"error": f"Failed to delete custom dashboard: {e!s}"}
321
+
322
+ @with_header_auth(CustomDashboardsApi)
323
+ async def get_shareable_users(self,
324
+ dashboard_id: str,
325
+ ctx=None, api_client=None) -> Dict[str, Any]:
326
+ """
327
+ Get shareable users for a custom dashboard from Instana server.
328
+ Uses api/custom-dashboard/{id}/shareable-users endpoint.
329
+ """
330
+ try:
331
+ if not dashboard_id:
332
+ return {"error": "Dashboard ID is required for this operation"}
333
+
334
+ logger.debug(f"Getting shareable users for dashboard {dashboard_id} from Instana SDK")
335
+
336
+ # Call the get_shareable_users method from the SDK
337
+ result = api_client.get_shareable_users(dashboard_id=dashboard_id)
338
+
339
+ # Convert the result to a dictionary
340
+ result_dict: Dict[str, Any] = {}
341
+
342
+ if hasattr(result, 'to_dict'):
343
+ result_dict = result.to_dict()
344
+ elif isinstance(result, dict):
345
+ result_dict = result
346
+ elif isinstance(result, list):
347
+ # If it's a list, wrap it in a dictionary
348
+ result_dict = {"items": result}
349
+ else:
350
+ # For any other type, convert to string and wrap
351
+ result_dict = {"result": str(result)}
352
+
353
+ # Limit the response size
354
+ if "items" in result_dict and isinstance(result_dict["items"], list):
355
+ # Limit items to top 20
356
+ items_list = result_dict["items"]
357
+ original_count = len(items_list)
358
+ if original_count > 20:
359
+ result_dict["items"] = items_list[:20]
360
+ logger.debug(f"Limited response items from {original_count} to 20")
361
+
362
+ try:
363
+ logger.debug(f"Result from get_shareable_users: {json.dumps(result_dict, indent=2)}")
364
+ except TypeError:
365
+ logger.debug(f"Result from get_shareable_users: {result_dict} (not JSON serializable)")
366
+
367
+ return result_dict
368
+
369
+ except Exception as e:
370
+ logger.error(f"Error in get_shareable_users: {e}", exc_info=True)
371
+ return {"error": f"Failed to get shareable users: {e!s}"}
372
+
373
+ @with_header_auth(CustomDashboardsApi)
374
+ async def get_shareable_api_tokens(self,
375
+ dashboard_id: str,
376
+ ctx=None, api_client=None) -> Dict[str, Any]:
377
+ """
378
+ Get shareable API tokens for a custom dashboard from Instana server.
379
+ Uses api/custom-dashboard/{id}/shareable-api-tokens endpoint.
380
+ """
381
+ try:
382
+ if not dashboard_id:
383
+ return {"error": "Dashboard ID is required for this operation"}
384
+
385
+ logger.debug(f"Getting shareable API tokens for dashboard {dashboard_id} from Instana SDK")
386
+
387
+ # Call the get_shareable_api_tokens method from the SDK
388
+ result = api_client.get_shareable_api_tokens(dashboard_id=dashboard_id)
389
+
390
+ # Convert the result to a dictionary
391
+ result_dict: Dict[str, Any] = {}
392
+
393
+ if hasattr(result, 'to_dict'):
394
+ result_dict = result.to_dict()
395
+ elif isinstance(result, dict):
396
+ result_dict = result
397
+ elif isinstance(result, list):
398
+ # If it's a list, wrap it in a dictionary
399
+ result_dict = {"items": result}
400
+ else:
401
+ # For any other type, convert to string and wrap
402
+ result_dict = {"result": str(result)}
403
+
404
+ # Limit the response size
405
+ if "items" in result_dict and isinstance(result_dict["items"], list):
406
+ # Limit items to top 10
407
+ items_list = result_dict["items"]
408
+ original_count = len(items_list)
409
+ if original_count > 10:
410
+ result_dict["items"] = items_list[:10]
411
+ logger.debug(f"Limited response items from {original_count} to 10")
412
+
413
+ try:
414
+ logger.debug(f"Result from get_shareable_api_tokens: {json.dumps(result_dict, indent=2)}")
415
+ except TypeError:
416
+ logger.debug(f"Result from get_shareable_api_tokens: {result_dict} (not JSON serializable)")
417
+
418
+ return result_dict
419
+
420
+ except Exception as e:
421
+ logger.error(f"Error in get_shareable_api_tokens: {e}", exc_info=True)
422
+ return {"error": f"Failed to get shareable API tokens: {e!s}"}
src/event/events_tools.py CHANGED
@@ -578,11 +578,12 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
578
578
  """
579
579
 
580
580
  try:
581
- logger.debug(f"get_issue_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
581
+ logger.debug(f"get_issues called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
582
582
  from_time, to_time = self._process_time_range(time_range, from_time, to_time)
583
583
  if not from_time:
584
584
  from_time = to_time - (60 * 60 * 1000)
585
585
  try:
586
+ # Use the optimized without_preload_content approach for faster response
586
587
  response_data = api_client.get_events_without_preload_content(
587
588
  var_from=from_time,
588
589
  to=to_time,
@@ -591,20 +592,35 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
591
592
  exclude_triggered_before=exclude_triggered_before,
592
593
  event_type_filters=["issue"]
593
594
  )
595
+
596
+ # Check response status immediately
594
597
  if response_data.status != 200:
595
598
  return {"error": f"Failed to get issue events: HTTP {response_data.status}"}
599
+
600
+ # Process the response data directly without additional parsing
596
601
  response_text = response_data.data.decode('utf-8')
597
602
  result = json.loads(response_text)
603
+
604
+ # Create a standardized result format
598
605
  if isinstance(result, list):
599
- result_dict = {"events": result, "events_count": len(result)}
606
+ # Include a summary of the events for quicker analysis
607
+ events_count = len(result)
608
+ result_dict = {
609
+ "events": result[:max_events], # Limit to max_events for performance
610
+ "events_count": events_count,
611
+ "total_events": events_count,
612
+ "time_range": f"From {datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')} to {datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')}"
613
+ }
600
614
  else:
601
615
  result_dict = result
616
+
617
+ logger.debug(f"Successfully retrieved {result_dict.get('events_count', 0)} issue events")
602
618
  return result_dict
603
619
  except Exception as api_error:
604
620
  logger.error(f"API call failed: {api_error}", exc_info=True)
605
621
  return {"error": f"Failed to get issue events: {api_error}"}
606
622
  except Exception as e:
607
- logger.error(f"Error in get_issue_events: {e}", exc_info=True)
623
+ logger.error(f"Error in get_issues: {e}", exc_info=True)
608
624
  return {"error": f"Failed to get issue events: {e!s}"}
609
625
 
610
626
  @register_as_tool(
@@ -649,11 +665,12 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
649
665
  """
650
666
 
651
667
  try:
652
- logger.debug(f"get_incident_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
668
+ logger.debug(f"get_incidents called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
653
669
  from_time, to_time = self._process_time_range(time_range, from_time, to_time)
654
670
  if not from_time:
655
671
  from_time = to_time - (60 * 60 * 1000)
656
672
  try:
673
+ # Use the optimized without_preload_content approach for faster response
657
674
  response_data = api_client.get_events_without_preload_content(
658
675
  var_from=from_time,
659
676
  to=to_time,
@@ -662,20 +679,35 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
662
679
  exclude_triggered_before=exclude_triggered_before,
663
680
  event_type_filters=["incident"]
664
681
  )
682
+
683
+ # Check response status immediately
665
684
  if response_data.status != 200:
666
685
  return {"error": f"Failed to get incident events: HTTP {response_data.status}"}
686
+
687
+ # Process the response data directly without additional parsing
667
688
  response_text = response_data.data.decode('utf-8')
668
689
  result = json.loads(response_text)
690
+
691
+ # Create a standardized result format
669
692
  if isinstance(result, list):
670
- result_dict = {"events": result, "events_count": len(result)}
693
+ # Include a summary of the events for quicker analysis
694
+ events_count = len(result)
695
+ result_dict = {
696
+ "events": result[:max_events], # Limit to max_events for performance
697
+ "events_count": events_count,
698
+ "total_events": events_count,
699
+ "time_range": f"From {datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')} to {datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')}"
700
+ }
671
701
  else:
672
702
  result_dict = result
703
+
704
+ logger.debug(f"Successfully retrieved {result_dict.get('events_count', 0)} incident events")
673
705
  return result_dict
674
706
  except Exception as api_error:
675
707
  logger.error(f"API call failed: {api_error}", exc_info=True)
676
708
  return {"error": f"Failed to get incident events: {api_error}"}
677
709
  except Exception as e:
678
- logger.error(f"Error in get_incident_events: {e}", exc_info=True)
710
+ logger.error(f"Error in get_incidents: {e}", exc_info=True)
679
711
  return {"error": f"Failed to get incident events: {e!s}"}
680
712
 
681
713
  @register_as_tool(
@@ -720,11 +752,12 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
720
752
  """
721
753
 
722
754
  try:
723
- logger.debug(f"get_change_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
755
+ logger.debug(f"get_changes called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
724
756
  from_time, to_time = self._process_time_range(time_range, from_time, to_time)
725
757
  if not from_time:
726
758
  from_time = to_time - (60 * 60 * 1000)
727
759
  try:
760
+ # Use the optimized without_preload_content approach for faster response
728
761
  response_data = api_client.get_events_without_preload_content(
729
762
  var_from=from_time,
730
763
  to=to_time,
@@ -733,20 +766,35 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
733
766
  exclude_triggered_before=exclude_triggered_before,
734
767
  event_type_filters=["change"]
735
768
  )
769
+
770
+ # Check response status immediately
736
771
  if response_data.status != 200:
737
772
  return {"error": f"Failed to get change events: HTTP {response_data.status}"}
773
+
774
+ # Process the response data directly without additional parsing
738
775
  response_text = response_data.data.decode('utf-8')
739
776
  result = json.loads(response_text)
777
+
778
+ # Create a standardized result format
740
779
  if isinstance(result, list):
741
- result_dict = {"events": result, "events_count": len(result)}
780
+ # Include a summary of the events for quicker analysis
781
+ events_count = len(result)
782
+ result_dict = {
783
+ "events": result[:max_events], # Limit to max_events for performance
784
+ "events_count": events_count,
785
+ "total_events": events_count,
786
+ "time_range": f"From {datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')} to {datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')}"
787
+ }
742
788
  else:
743
789
  result_dict = result
790
+
791
+ logger.debug(f"Successfully retrieved {result_dict.get('events_count', 0)} change events")
744
792
  return result_dict
745
793
  except Exception as api_error:
746
794
  logger.error(f"API call failed: {api_error}", exc_info=True)
747
795
  return {"error": f"Failed to get change events: {api_error}"}
748
796
  except Exception as e:
749
- logger.error(f"Error in get_change_events: {e}", exc_info=True)
797
+ logger.error(f"Error in get_changes: {e}", exc_info=True)
750
798
  return {"error": f"Failed to get change events: {e!s}"}
751
799
 
752
800
  @register_as_tool(