mcp-instana 0.1.1__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.
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/METADATA +459 -138
- mcp_instana-0.2.0.dist-info/RECORD +59 -0
- src/application/application_analyze.py +373 -160
- src/application/application_catalog.py +3 -1
- src/application/application_global_alert_config.py +653 -0
- src/application/application_metrics.py +6 -2
- src/application/application_resources.py +3 -1
- src/application/application_settings.py +966 -370
- src/application/application_topology.py +6 -2
- src/automation/action_catalog.py +416 -0
- src/automation/action_history.py +338 -0
- src/core/server.py +159 -9
- src/core/utils.py +2 -2
- src/event/events_tools.py +602 -275
- src/infrastructure/infrastructure_analyze.py +7 -3
- src/infrastructure/infrastructure_catalog.py +3 -1
- src/infrastructure/infrastructure_metrics.py +6 -2
- src/infrastructure/infrastructure_resources.py +7 -5
- src/infrastructure/infrastructure_topology.py +5 -3
- src/prompts/__init__.py +16 -0
- src/prompts/application/__init__.py +1 -0
- src/prompts/application/application_alerts.py +54 -0
- src/prompts/application/application_catalog.py +26 -0
- src/prompts/application/application_metrics.py +57 -0
- src/prompts/application/application_resources.py +26 -0
- src/prompts/application/application_settings.py +75 -0
- src/prompts/application/application_topology.py +30 -0
- src/prompts/events/__init__.py +1 -0
- src/prompts/events/events_tools.py +161 -0
- src/prompts/infrastructure/infrastructure_analyze.py +72 -0
- src/prompts/infrastructure/infrastructure_catalog.py +53 -0
- src/prompts/infrastructure/infrastructure_metrics.py +45 -0
- src/prompts/infrastructure/infrastructure_resources.py +74 -0
- src/prompts/infrastructure/infrastructure_topology.py +38 -0
- src/prompts/settings/__init__.py +0 -0
- src/prompts/settings/custom_dashboard.py +157 -0
- src/prompts/website/__init__.py +1 -0
- src/prompts/website/website_analyze.py +35 -0
- src/prompts/website/website_catalog.py +40 -0
- src/prompts/website/website_configuration.py +105 -0
- src/prompts/website/website_metrics.py +34 -0
- src/settings/__init__.py +1 -0
- src/settings/custom_dashboard_tools.py +417 -0
- src/website/__init__.py +0 -0
- src/website/website_analyze.py +433 -0
- src/website/website_catalog.py +171 -0
- src/website/website_configuration.py +770 -0
- src/website/website_metrics.py +241 -0
- mcp_instana-0.1.1.dist-info/RECORD +0 -30
- src/prompts/mcp_prompts.py +0 -900
- src/prompts/prompt_loader.py +0 -29
- src/prompts/prompt_registry.json +0 -21
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/WHEEL +0 -0
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/entry_points.txt +0 -0
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/licenses/LICENSE.md +0 -0
src/event/events_tools.py
CHANGED
|
@@ -1,34 +1,171 @@
|
|
|
1
|
+
|
|
1
2
|
"""
|
|
2
3
|
Agent Monitoring Events MCP Tools Module
|
|
3
4
|
|
|
4
5
|
This module provides agent monitoring events-specific MCP tools for Instana monitoring.
|
|
5
6
|
"""
|
|
6
7
|
|
|
8
|
+
import json
|
|
7
9
|
import logging
|
|
10
|
+
import re
|
|
8
11
|
from datetime import datetime
|
|
9
|
-
from typing import Any, Dict, Optional
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from instana_client.api.events_api import
|
|
12
|
+
from typing import Any, Dict, List, Optional, Union
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from instana_client.api.events_api import (
|
|
16
|
+
EventsApi,
|
|
17
|
+
)
|
|
18
|
+
try:
|
|
19
|
+
has_get_events_id_query = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
has_get_events_id_query = False
|
|
22
|
+
except ImportError:
|
|
23
|
+
import logging
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
logger.error("Failed to import event resources API", exc_info=True)
|
|
26
|
+
raise
|
|
13
27
|
|
|
14
28
|
from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
|
|
15
29
|
|
|
16
|
-
# Configure logger for this module
|
|
17
30
|
logger = logging.getLogger(__name__)
|
|
18
31
|
|
|
19
32
|
class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
20
|
-
"""Tools for agent monitoring events in Instana MCP."""
|
|
21
33
|
|
|
22
34
|
def __init__(self, read_token: str, base_url: str):
|
|
23
|
-
"""Initialize the Agent Monitoring Events MCP tools client."""
|
|
24
35
|
super().__init__(read_token=read_token, base_url=base_url)
|
|
25
36
|
|
|
37
|
+
def _process_time_range(self, time_range=None, from_time=None, to_time=None):
|
|
38
|
+
"""
|
|
39
|
+
Process time range parameters to get standardized from_time and to_time values.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
time_range: Natural language time range like "last 24 hours"
|
|
43
|
+
from_time: Start timestamp in milliseconds (optional)
|
|
44
|
+
to_time: End timestamp in milliseconds (optional)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Tuple of (from_time, to_time) in milliseconds
|
|
48
|
+
"""
|
|
49
|
+
# Current time in milliseconds
|
|
50
|
+
current_time_ms = int(datetime.now().timestamp() * 1000)
|
|
51
|
+
|
|
52
|
+
# Process natural language time range if provided
|
|
53
|
+
if time_range:
|
|
54
|
+
logger.debug(f"Processing natural language time range: '{time_range}'")
|
|
55
|
+
|
|
56
|
+
# Default to 24 hours if just "last few hours" is specified
|
|
57
|
+
if time_range.lower() in ["last few hours", "last hours", "few hours"]:
|
|
58
|
+
hours = 24
|
|
59
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
60
|
+
to_time = current_time_ms
|
|
61
|
+
# Extract hours if specified
|
|
62
|
+
elif "hour" in time_range.lower():
|
|
63
|
+
hour_match = re.search(r'(\d+)\s*hour', time_range.lower())
|
|
64
|
+
hours = int(hour_match.group(1)) if hour_match else 24
|
|
65
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
66
|
+
to_time = current_time_ms
|
|
67
|
+
# Extract days if specified
|
|
68
|
+
elif "day" in time_range.lower():
|
|
69
|
+
day_match = re.search(r'(\d+)\s*day', time_range.lower())
|
|
70
|
+
days = int(day_match.group(1)) if day_match else 1
|
|
71
|
+
from_time = current_time_ms - (days * 24 * 60 * 60 * 1000)
|
|
72
|
+
to_time = current_time_ms
|
|
73
|
+
# Handle "last week"
|
|
74
|
+
elif "week" in time_range.lower():
|
|
75
|
+
week_match = re.search(r'(\d+)\s*week', time_range.lower())
|
|
76
|
+
weeks = int(week_match.group(1)) if week_match else 1
|
|
77
|
+
from_time = current_time_ms - (weeks * 7 * 24 * 60 * 60 * 1000)
|
|
78
|
+
to_time = current_time_ms
|
|
79
|
+
# Handle "last month"
|
|
80
|
+
elif "month" in time_range.lower():
|
|
81
|
+
month_match = re.search(r'(\d+)\s*month', time_range.lower())
|
|
82
|
+
months = int(month_match.group(1)) if month_match else 1
|
|
83
|
+
from_time = current_time_ms - (months * 30 * 24 * 60 * 60 * 1000)
|
|
84
|
+
to_time = current_time_ms
|
|
85
|
+
# Default to 24 hours for any other time range
|
|
86
|
+
else:
|
|
87
|
+
hours = 24
|
|
88
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
89
|
+
to_time = current_time_ms
|
|
90
|
+
|
|
91
|
+
# Set default time range if not provided
|
|
92
|
+
if not to_time:
|
|
93
|
+
to_time = current_time_ms
|
|
94
|
+
if not from_time:
|
|
95
|
+
from_time = to_time - (24 * 60 * 60 * 1000) # Default to 24 hours
|
|
96
|
+
|
|
97
|
+
return from_time, to_time
|
|
98
|
+
|
|
99
|
+
def _process_result(self, result):
|
|
100
|
+
|
|
101
|
+
# Convert the result to a dictionary
|
|
102
|
+
if hasattr(result, 'to_dict'):
|
|
103
|
+
result_dict = result.to_dict()
|
|
104
|
+
elif isinstance(result, list):
|
|
105
|
+
# Convert list items if they have to_dict method
|
|
106
|
+
items = []
|
|
107
|
+
for item in result:
|
|
108
|
+
if hasattr(item, 'to_dict'):
|
|
109
|
+
items.append(item.to_dict())
|
|
110
|
+
else:
|
|
111
|
+
items.append(item)
|
|
112
|
+
# Wrap list in a dictionary
|
|
113
|
+
result_dict = {"items": items, "count": len(items)}
|
|
114
|
+
elif isinstance(result, dict):
|
|
115
|
+
# If it's already a dict, use it as is
|
|
116
|
+
result_dict = result
|
|
117
|
+
else:
|
|
118
|
+
# For any other format, convert to string and wrap in dict
|
|
119
|
+
result_dict = {"data": str(result)}
|
|
120
|
+
|
|
121
|
+
return result_dict
|
|
122
|
+
|
|
123
|
+
def _summarize_events_result(self, events, total_count=None, max_events=None):
|
|
124
|
+
|
|
125
|
+
if not events:
|
|
126
|
+
return {"events_count": 0, "summary": "No events found"}
|
|
127
|
+
|
|
128
|
+
# Use provided total count or length of events list
|
|
129
|
+
total_events_count = total_count or len(events)
|
|
130
|
+
|
|
131
|
+
# Limit events if max_events is specified
|
|
132
|
+
if max_events and len(events) > max_events:
|
|
133
|
+
events = events[:max_events]
|
|
134
|
+
|
|
135
|
+
# Group events by type
|
|
136
|
+
event_types = {}
|
|
137
|
+
for event in events:
|
|
138
|
+
event_type = event.get("eventType", "Unknown")
|
|
139
|
+
if event_type not in event_types:
|
|
140
|
+
event_types[event_type] = 0
|
|
141
|
+
event_types[event_type] += 1
|
|
142
|
+
|
|
143
|
+
# Sort event types by count
|
|
144
|
+
sorted_types = sorted(event_types.items(), key=lambda x: x[1], reverse=True)
|
|
145
|
+
|
|
146
|
+
# Create summary
|
|
147
|
+
summary = {
|
|
148
|
+
"events_count": total_events_count,
|
|
149
|
+
"events_analyzed": len(events),
|
|
150
|
+
"event_types": dict(sorted_types),
|
|
151
|
+
"top_event_types": sorted_types[:5] if len(sorted_types) > 5 else sorted_types
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return summary
|
|
155
|
+
|
|
26
156
|
@register_as_tool
|
|
27
157
|
@with_header_auth(EventsApi)
|
|
28
158
|
async def get_event(self, event_id: str, ctx=None, api_client=None) -> Dict[str, Any]:
|
|
29
159
|
"""
|
|
30
160
|
Get a specific event by ID.
|
|
31
161
|
|
|
162
|
+
This tool retrieves detailed information about a specific event using its unique ID.
|
|
163
|
+
Use this when you need to examine a particular event's details, severity, or related entities.
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
Get details of a specific incident:
|
|
167
|
+
- event_id: "1a2b3c4d5e6f"
|
|
168
|
+
|
|
32
169
|
Args:
|
|
33
170
|
event_id: The ID of the event to retrieve
|
|
34
171
|
ctx: The MCP context (optional)
|
|
@@ -38,13 +175,68 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
38
175
|
Dictionary containing the event data or error information
|
|
39
176
|
"""
|
|
40
177
|
try:
|
|
41
|
-
|
|
42
|
-
|
|
178
|
+
logger.debug(f"get_event called with event_id={event_id}")
|
|
179
|
+
|
|
180
|
+
if not event_id:
|
|
181
|
+
return {"error": "event_id parameter is required"}
|
|
182
|
+
|
|
183
|
+
# Try standard API call first
|
|
184
|
+
try:
|
|
185
|
+
result = api_client.get_event(event_id=event_id)
|
|
186
|
+
|
|
187
|
+
# New robust conversion to dict
|
|
188
|
+
if hasattr(result, "to_dict"):
|
|
189
|
+
result_dict = result.to_dict()
|
|
190
|
+
elif isinstance(result, dict):
|
|
191
|
+
result_dict = result
|
|
192
|
+
else:
|
|
193
|
+
# Convert to dictionary using __dict__ or as a fallback, create a new dict with string representation
|
|
194
|
+
result_dict = getattr(result, "__dict__", {"data": str(result)})
|
|
195
|
+
|
|
196
|
+
logger.debug(f"Successfully retrieved event with ID {event_id}")
|
|
197
|
+
return result_dict
|
|
198
|
+
|
|
199
|
+
except Exception as api_error:
|
|
200
|
+
# Check for specific error types
|
|
201
|
+
if hasattr(api_error, 'status'):
|
|
202
|
+
if api_error.status == 404:
|
|
203
|
+
return {"error": f"Event with ID {event_id} not found", "event_id": event_id}
|
|
204
|
+
elif api_error.status in (401, 403):
|
|
205
|
+
return {"error": "Authentication failed. Please check your API token and permissions."}
|
|
206
|
+
|
|
207
|
+
# Try fallback approach
|
|
208
|
+
logger.warning(f"Standard API call failed: {api_error}, trying fallback approach")
|
|
209
|
+
|
|
210
|
+
# Use the without_preload_content version to get the raw response
|
|
211
|
+
try:
|
|
212
|
+
response_data = api_client.get_event_without_preload_content(event_id=event_id)
|
|
213
|
+
|
|
214
|
+
# Check if the response was successful
|
|
215
|
+
if response_data.status != 200:
|
|
216
|
+
error_message = f"Failed to get event: HTTP {response_data.status}"
|
|
217
|
+
logger.error(error_message)
|
|
218
|
+
return {"error": error_message, "event_id": event_id}
|
|
219
|
+
|
|
220
|
+
# Read the response content
|
|
221
|
+
response_text = response_data.data.decode('utf-8')
|
|
222
|
+
|
|
223
|
+
# Parse the JSON manually
|
|
224
|
+
try:
|
|
225
|
+
result_dict = json.loads(response_text)
|
|
226
|
+
logger.debug(f"Successfully retrieved event with ID {event_id} using fallback")
|
|
227
|
+
return result_dict
|
|
228
|
+
except json.JSONDecodeError as json_err:
|
|
229
|
+
error_message = f"Failed to parse JSON response: {json_err}"
|
|
230
|
+
logger.error(error_message)
|
|
231
|
+
return {"error": error_message, "event_id": event_id}
|
|
232
|
+
|
|
233
|
+
except Exception as fallback_error:
|
|
234
|
+
logger.error(f"Fallback approach failed: {fallback_error}")
|
|
235
|
+
raise
|
|
43
236
|
|
|
44
|
-
return result
|
|
45
237
|
except Exception as e:
|
|
46
238
|
logger.error(f"Error in get_event: {e}", exc_info=True)
|
|
47
|
-
return {"error": f"Failed to get event: {e!s}"}
|
|
239
|
+
return {"error": f"Failed to get event: {e!s}", "event_id": event_id}
|
|
48
240
|
|
|
49
241
|
@register_as_tool
|
|
50
242
|
@with_header_auth(EventsApi)
|
|
@@ -52,7 +244,7 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
52
244
|
from_time: Optional[int] = None,
|
|
53
245
|
to_time: Optional[int] = None,
|
|
54
246
|
time_range: Optional[str] = None,
|
|
55
|
-
max_events: Optional[int] = 50,
|
|
247
|
+
max_events: Optional[int] = 50,
|
|
56
248
|
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
57
249
|
"""
|
|
58
250
|
Get Kubernetes info events based on the provided parameters and return a detailed analysis.
|
|
@@ -61,6 +253,10 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
61
253
|
their details, and actionable fix suggestions. You can specify a time range using timestamps or natural language
|
|
62
254
|
like "last 24 hours" or "last 2 days".
|
|
63
255
|
|
|
256
|
+
Examples:
|
|
257
|
+
Get Kubernetes events from the last 24 hours:
|
|
258
|
+
- time_range: "last 24 hours"
|
|
259
|
+
|
|
64
260
|
Args:
|
|
65
261
|
from_time: Start timestamp in milliseconds since epoch (optional)
|
|
66
262
|
to_time: End timestamp in milliseconds since epoch (optional)
|
|
@@ -73,108 +269,45 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
73
269
|
Dictionary containing detailed Kubernetes events analysis or error information
|
|
74
270
|
"""
|
|
75
271
|
try:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
hour_match = re.search(r'(\d+)\s*hour', time_range.lower())
|
|
93
|
-
hours = int(hour_match.group(1)) if hour_match else 24
|
|
94
|
-
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
95
|
-
to_time = current_time_ms
|
|
96
|
-
# Extract days if specified
|
|
97
|
-
elif "day" in time_range.lower():
|
|
98
|
-
import re
|
|
99
|
-
day_match = re.search(r'(\d+)\s*day', time_range.lower())
|
|
100
|
-
days = int(day_match.group(1)) if day_match else 1
|
|
101
|
-
from_time = current_time_ms - (days * 24 * 60 * 60 * 1000)
|
|
102
|
-
to_time = current_time_ms
|
|
103
|
-
# Handle "last week"
|
|
104
|
-
elif "week" in time_range.lower():
|
|
105
|
-
import re
|
|
106
|
-
week_match = re.search(r'(\d+)\s*week', time_range.lower())
|
|
107
|
-
weeks = int(week_match.group(1)) if week_match else 1
|
|
108
|
-
from_time = current_time_ms - (weeks * 7 * 24 * 60 * 60 * 1000)
|
|
109
|
-
to_time = current_time_ms
|
|
110
|
-
# Handle "last month"
|
|
111
|
-
elif "month" in time_range.lower():
|
|
112
|
-
import re
|
|
113
|
-
month_match = re.search(r'(\d+)\s*month', time_range.lower())
|
|
114
|
-
months = int(month_match.group(1)) if month_match else 1
|
|
115
|
-
from_time = current_time_ms - (months * 30 * 24 * 60 * 60 * 1000)
|
|
116
|
-
to_time = current_time_ms
|
|
117
|
-
# Default to 24 hours for any other time range
|
|
118
|
-
else:
|
|
119
|
-
hours = 24
|
|
120
|
-
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
121
|
-
to_time = current_time_ms
|
|
122
|
-
|
|
123
|
-
# Set default time range if not provided
|
|
124
|
-
if not to_time:
|
|
125
|
-
to_time = int(datetime.now().timestamp() * 1000)
|
|
126
|
-
if not from_time:
|
|
127
|
-
from_time = to_time - (24 * 60 * 60 * 1000) # Default to 24 hours
|
|
128
|
-
|
|
129
|
-
# Call the kubernetes_info_events method from the SDK
|
|
130
|
-
result = api_client.kubernetes_info_events(
|
|
131
|
-
to=to_time,
|
|
132
|
-
var_from=from_time,
|
|
133
|
-
window_size=None,
|
|
134
|
-
filter_event_updates=None,
|
|
135
|
-
exclude_triggered_before=None
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
# Print the raw result for debugging
|
|
139
|
-
logger.debug(f"Raw API result type: {type(result)}")
|
|
140
|
-
logger.debug(f"Raw API result length: {len(result) if isinstance(result, list) else 'not a list'}")
|
|
141
|
-
|
|
142
|
-
# If there are no events, return early
|
|
143
|
-
if not result or (isinstance(result, list) and len(result) == 0):
|
|
144
|
-
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
145
|
-
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
272
|
+
logger.debug(f"get_kubernetes_info_events called with time_range={time_range}, from_time={from_time}, to_time={to_time}, max_events={max_events}")
|
|
273
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
274
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
275
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
276
|
+
try:
|
|
277
|
+
result = api_client.kubernetes_info_events(
|
|
278
|
+
var_from=from_time,
|
|
279
|
+
to=to_time,
|
|
280
|
+
window_size=max_events,
|
|
281
|
+
filter_event_updates=None,
|
|
282
|
+
exclude_triggered_before=None
|
|
283
|
+
)
|
|
284
|
+
logger.debug(f"Raw API result type: {type(result)}")
|
|
285
|
+
logger.debug(f"Raw API result length: {len(result) if isinstance(result, list) else 'not a list'}")
|
|
286
|
+
except Exception as api_error:
|
|
287
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
146
288
|
return {
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"events_count": 0
|
|
289
|
+
"error": f"Failed to get Kubernetes info events: {api_error}",
|
|
290
|
+
"details": str(api_error)
|
|
150
291
|
}
|
|
151
|
-
|
|
152
|
-
# Process the events to create a summary
|
|
153
|
-
events = result if isinstance(result, list) else [result]
|
|
154
|
-
|
|
155
|
-
# Get the total number of events before limiting
|
|
292
|
+
events = result if isinstance(result, list) else ([result] if result else [])
|
|
156
293
|
total_events_count = len(events)
|
|
157
|
-
|
|
158
|
-
# Limit the number of events to process
|
|
159
294
|
events = events[:max_events]
|
|
160
|
-
logger.debug(f"Limited to processing {len(events)} events out of {total_events_count} total events")
|
|
161
|
-
|
|
162
|
-
# Convert InfraEventResult objects to dictionaries if needed
|
|
163
295
|
event_dicts = []
|
|
164
296
|
for event in events:
|
|
165
297
|
if hasattr(event, 'to_dict'):
|
|
166
298
|
event_dicts.append(event.to_dict())
|
|
167
299
|
else:
|
|
168
300
|
event_dicts.append(event)
|
|
169
|
-
|
|
170
|
-
|
|
301
|
+
if not event_dicts:
|
|
302
|
+
return {
|
|
303
|
+
"events": [],
|
|
304
|
+
"events_count": 0,
|
|
305
|
+
"time_range": f"{from_date} to {to_date}",
|
|
306
|
+
"analysis": f"No Kubernetes events found between {from_date} and {to_date}."
|
|
307
|
+
}
|
|
171
308
|
problem_groups = {}
|
|
172
|
-
|
|
173
|
-
# Process each event
|
|
174
309
|
for event in event_dicts:
|
|
175
310
|
problem = event.get("problem", "Unknown")
|
|
176
|
-
|
|
177
|
-
# Initialize problem group if not exists
|
|
178
311
|
if problem not in problem_groups:
|
|
179
312
|
problem_groups[problem] = {
|
|
180
313
|
"count": 0,
|
|
@@ -184,27 +317,18 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
184
317
|
"fix_suggestions": set(),
|
|
185
318
|
"sample_events": []
|
|
186
319
|
}
|
|
187
|
-
|
|
188
|
-
# Update problem group
|
|
189
320
|
problem_groups[problem]["count"] += 1
|
|
190
|
-
|
|
191
|
-
# Extract namespace from entityLabel
|
|
192
321
|
entity_label = event.get("entityLabel", "")
|
|
193
322
|
if "/" in entity_label:
|
|
194
323
|
namespace, entity = entity_label.split("/", 1)
|
|
195
324
|
problem_groups[problem]["affected_namespaces"].add(namespace)
|
|
196
325
|
problem_groups[problem]["affected_entities"].add(entity)
|
|
197
|
-
|
|
198
|
-
# Add detail and fix suggestion
|
|
199
326
|
detail = event.get("detail", "")
|
|
200
327
|
if detail:
|
|
201
328
|
problem_groups[problem]["details"].add(detail)
|
|
202
|
-
|
|
203
329
|
fix_suggestion = event.get("fixSuggestion", "")
|
|
204
330
|
if fix_suggestion:
|
|
205
331
|
problem_groups[problem]["fix_suggestions"].add(fix_suggestion)
|
|
206
|
-
|
|
207
|
-
# Add sample event (up to 3 per problem)
|
|
208
332
|
if len(problem_groups[problem]["sample_events"]) < 3:
|
|
209
333
|
simple_event = {
|
|
210
334
|
"eventId": event.get("eventId", ""),
|
|
@@ -213,20 +337,9 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
213
337
|
"detail": detail
|
|
214
338
|
}
|
|
215
339
|
problem_groups[problem]["sample_events"].append(simple_event)
|
|
216
|
-
|
|
217
|
-
# Sort problems by count (most frequent first)
|
|
218
340
|
sorted_problems = sorted(problem_groups.items(), key=lambda x: x[1]["count"], reverse=True)
|
|
219
|
-
|
|
220
|
-
# Format the time range in a human-readable format
|
|
221
|
-
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
222
|
-
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
223
|
-
|
|
224
|
-
# Create a detailed analysis of each problem
|
|
225
341
|
problem_analyses = []
|
|
226
|
-
|
|
227
|
-
# Process each problem
|
|
228
342
|
for problem_name, problem_data in sorted_problems:
|
|
229
|
-
# Create a detailed problem analysis
|
|
230
343
|
problem_analysis = {
|
|
231
344
|
"problem": problem_name,
|
|
232
345
|
"count": problem_data["count"],
|
|
@@ -235,55 +348,39 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
235
348
|
"fix_suggestions": list(problem_data["fix_suggestions"]),
|
|
236
349
|
"sample_events": problem_data["sample_events"]
|
|
237
350
|
}
|
|
238
|
-
|
|
239
351
|
problem_analyses.append(problem_analysis)
|
|
240
|
-
|
|
241
|
-
# Create a comprehensive analysis
|
|
242
352
|
analysis_result = {
|
|
243
353
|
"summary": f"Analysis based on {len(events)} of {total_events_count} Kubernetes events between {from_date} and {to_date}.",
|
|
244
354
|
"time_range": f"{from_date} to {to_date}",
|
|
245
355
|
"events_count": total_events_count,
|
|
246
356
|
"events_analyzed": len(events),
|
|
247
|
-
"problem_analyses": problem_analyses[:10]
|
|
357
|
+
"problem_analyses": problem_analyses[:10]
|
|
248
358
|
}
|
|
249
|
-
|
|
250
|
-
# Create a more user-friendly text summary for direct display
|
|
251
359
|
markdown_summary = "# Kubernetes Events Analysis\n\n"
|
|
252
360
|
markdown_summary += f"Analysis based on {len(events)} of {total_events_count} Kubernetes events between {from_date} and {to_date}.\n\n"
|
|
253
|
-
|
|
254
361
|
markdown_summary += "## Top Problems\n\n"
|
|
255
|
-
|
|
256
|
-
# Add each problem to the markdown summary
|
|
257
|
-
for problem_analysis in problem_analyses[:5]: # Limit to top 5 for readability
|
|
362
|
+
for problem_analysis in problem_analyses[:5]:
|
|
258
363
|
problem_name = problem_analysis["problem"]
|
|
259
364
|
count = problem_analysis["count"]
|
|
260
|
-
|
|
261
365
|
markdown_summary += f"### {problem_name} ({count} events)\n\n"
|
|
262
|
-
|
|
263
|
-
# Add affected namespaces if available
|
|
264
366
|
if problem_analysis.get("affected_namespaces"):
|
|
265
367
|
namespaces = ", ".join(problem_analysis["affected_namespaces"][:5])
|
|
266
368
|
if len(problem_analysis["affected_namespaces"]) > 5:
|
|
267
369
|
namespaces += f" and {len(problem_analysis['affected_namespaces']) - 5} more"
|
|
268
370
|
markdown_summary += f"**Affected Namespaces:** {namespaces}\n\n"
|
|
269
|
-
|
|
270
|
-
# Add fix suggestions
|
|
271
371
|
if problem_analysis.get("fix_suggestions"):
|
|
272
372
|
markdown_summary += "**Fix Suggestions:**\n\n"
|
|
273
|
-
for suggestion in list(problem_analysis["fix_suggestions"])[:3]:
|
|
373
|
+
for suggestion in list(problem_analysis["fix_suggestions"])[:3]:
|
|
274
374
|
markdown_summary += f"- {suggestion}\n"
|
|
275
|
-
|
|
276
375
|
markdown_summary += "\n"
|
|
277
|
-
|
|
278
|
-
# Add the markdown summary to the result
|
|
279
376
|
analysis_result["markdown_summary"] = markdown_summary
|
|
280
|
-
|
|
377
|
+
analysis_result["events"] = event_dicts
|
|
281
378
|
return analysis_result
|
|
282
|
-
|
|
283
379
|
except Exception as e:
|
|
284
380
|
logger.error(f"Error in get_kubernetes_info_events: {e}", exc_info=True)
|
|
285
381
|
return {
|
|
286
|
-
"error": f"Failed to get Kubernetes info events: {e!s}"
|
|
382
|
+
"error": f"Failed to get Kubernetes info events: {e!s}",
|
|
383
|
+
"details": str(e)
|
|
287
384
|
}
|
|
288
385
|
|
|
289
386
|
@register_as_tool
|
|
@@ -293,8 +390,8 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
293
390
|
from_time: Optional[int] = None,
|
|
294
391
|
to_time: Optional[int] = None,
|
|
295
392
|
size: Optional[int] = 100,
|
|
296
|
-
max_events: Optional[int] = 50,
|
|
297
|
-
time_range: Optional[str] = None,
|
|
393
|
+
max_events: Optional[int] = 50,
|
|
394
|
+
time_range: Optional[str] = None,
|
|
298
395
|
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
299
396
|
"""
|
|
300
397
|
Get agent monitoring events from Instana and return a detailed analysis.
|
|
@@ -303,6 +400,10 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
303
400
|
monitoring issues, their frequency, and affected entities. You can specify a time range using timestamps
|
|
304
401
|
or natural language like "last 24 hours" or "last 2 days".
|
|
305
402
|
|
|
403
|
+
Examples:
|
|
404
|
+
Get agent monitoring events from the last 24 hours:
|
|
405
|
+
- time_range: "last 24 hours"
|
|
406
|
+
|
|
306
407
|
Args:
|
|
307
408
|
query: Query string to filter events (optional)
|
|
308
409
|
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
@@ -317,114 +418,48 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
317
418
|
Dictionary containing summarized agent monitoring events data or error information
|
|
318
419
|
"""
|
|
319
420
|
try:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
logger.debug(f"Processing natural language time range: '{time_range}'")
|
|
323
|
-
|
|
324
|
-
# Current time in milliseconds
|
|
325
|
-
current_time_ms = int(datetime.now().timestamp() * 1000)
|
|
326
|
-
|
|
327
|
-
# Default to 24 hours if just "last few hours" is specified
|
|
328
|
-
if time_range.lower() in ["last few hours", "last hours", "few hours"]:
|
|
329
|
-
hours = 24
|
|
330
|
-
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
331
|
-
to_time = current_time_ms
|
|
332
|
-
logger.debug(f"Interpreted as last {hours} hours")
|
|
333
|
-
# Extract hours if specified
|
|
334
|
-
elif "hour" in time_range.lower():
|
|
335
|
-
import re
|
|
336
|
-
hour_match = re.search(r'(\d+)\s*hour', time_range.lower())
|
|
337
|
-
hours = int(hour_match.group(1)) if hour_match else 24
|
|
338
|
-
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
339
|
-
to_time = current_time_ms
|
|
340
|
-
# Extract days if specified
|
|
341
|
-
elif "day" in time_range.lower():
|
|
342
|
-
import re
|
|
343
|
-
day_match = re.search(r'(\d+)\s*day', time_range.lower())
|
|
344
|
-
days = int(day_match.group(1)) if day_match else 1
|
|
345
|
-
from_time = current_time_ms - (days * 24 * 60 * 60 * 1000)
|
|
346
|
-
to_time = current_time_ms
|
|
347
|
-
# Handle "last week"
|
|
348
|
-
elif "week" in time_range.lower():
|
|
349
|
-
import re
|
|
350
|
-
week_match = re.search(r'(\d+)\s*week', time_range.lower())
|
|
351
|
-
weeks = int(week_match.group(1)) if week_match else 1
|
|
352
|
-
from_time = current_time_ms - (weeks * 7 * 24 * 60 * 60 * 1000)
|
|
353
|
-
to_time = current_time_ms
|
|
354
|
-
# Handle "last month"
|
|
355
|
-
elif "month" in time_range.lower():
|
|
356
|
-
import re
|
|
357
|
-
month_match = re.search(r'(\d+)\s*month', time_range.lower())
|
|
358
|
-
months = int(month_match.group(1)) if month_match else 1
|
|
359
|
-
from_time = current_time_ms - (months * 30 * 24 * 60 * 60 * 1000)
|
|
360
|
-
to_time = current_time_ms
|
|
361
|
-
# Default to 24 hours for any other time range
|
|
362
|
-
else:
|
|
363
|
-
hours = 24
|
|
364
|
-
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
365
|
-
to_time = current_time_ms
|
|
366
|
-
|
|
367
|
-
logger.debug(f"get_agent_monitoring_events called with query={query}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
368
|
-
|
|
369
|
-
# Set default time range if not provided
|
|
370
|
-
if not to_time:
|
|
371
|
-
to_time = int(datetime.now().timestamp() * 1000)
|
|
372
|
-
|
|
421
|
+
logger.debug(f"get_agent_monitoring_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
422
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
373
423
|
if not from_time:
|
|
374
|
-
from_time = to_time - (60 * 60 * 1000)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
# If there are no events, return early
|
|
390
|
-
if not result or (isinstance(result, list) and len(result) == 0):
|
|
391
|
-
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
392
|
-
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
424
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
425
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
426
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
427
|
+
try:
|
|
428
|
+
result = api_client.agent_monitoring_events(
|
|
429
|
+
var_from=from_time,
|
|
430
|
+
to=to_time,
|
|
431
|
+
window_size=max_events,
|
|
432
|
+
filter_event_updates=None,
|
|
433
|
+
exclude_triggered_before=None
|
|
434
|
+
)
|
|
435
|
+
logger.debug(f"Raw API result type: {type(result)}")
|
|
436
|
+
logger.debug(f"Raw API result length: {len(result) if isinstance(result, list) else 'not a list'}")
|
|
437
|
+
except Exception as api_error:
|
|
438
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
393
439
|
return {
|
|
394
|
-
"
|
|
395
|
-
"
|
|
396
|
-
"events_count": 0
|
|
440
|
+
"error": f"Failed to get agent monitoring events: {api_error}",
|
|
441
|
+
"details": str(api_error)
|
|
397
442
|
}
|
|
398
|
-
|
|
399
|
-
# Process the events to create a summary
|
|
400
|
-
events = result if isinstance(result, list) else [result]
|
|
401
|
-
|
|
402
|
-
# Get the total number of events before limiting
|
|
443
|
+
events = result if isinstance(result, list) else ([result] if result else [])
|
|
403
444
|
total_events_count = len(events)
|
|
404
|
-
|
|
405
|
-
# Limit the number of events to process
|
|
406
445
|
events = events[:max_events]
|
|
407
|
-
logger.debug(f"Limited to processing {len(events)} events out of {total_events_count} total events")
|
|
408
|
-
|
|
409
|
-
# Convert objects to dictionaries if needed
|
|
410
446
|
event_dicts = []
|
|
411
447
|
for event in events:
|
|
412
448
|
if hasattr(event, 'to_dict'):
|
|
413
449
|
event_dicts.append(event.to_dict())
|
|
414
450
|
else:
|
|
415
451
|
event_dicts.append(event)
|
|
416
|
-
|
|
417
|
-
|
|
452
|
+
if not event_dicts:
|
|
453
|
+
return {
|
|
454
|
+
"events": [],
|
|
455
|
+
"events_count": 0,
|
|
456
|
+
"time_range": f"{from_date} to {to_date}",
|
|
457
|
+
"analysis": f"No agent monitoring events found between {from_date} and {to_date}."
|
|
458
|
+
}
|
|
418
459
|
problem_groups = {}
|
|
419
|
-
|
|
420
|
-
# Process each event
|
|
421
460
|
for event in event_dicts:
|
|
422
|
-
# Extract the monitoring issue from the problem field
|
|
423
461
|
full_problem = event.get("problem", "Unknown")
|
|
424
|
-
# Strip "Monitoring issue: " prefix if present
|
|
425
462
|
problem = full_problem.replace("Monitoring issue: ", "") if "Monitoring issue: " in full_problem else full_problem
|
|
426
|
-
|
|
427
|
-
# Initialize problem group if not exists
|
|
428
463
|
if problem not in problem_groups:
|
|
429
464
|
problem_groups[problem] = {
|
|
430
465
|
"count": 0,
|
|
@@ -432,20 +467,13 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
432
467
|
"entity_types": set(),
|
|
433
468
|
"sample_events": []
|
|
434
469
|
}
|
|
435
|
-
|
|
436
|
-
# Update problem group
|
|
437
470
|
problem_groups[problem]["count"] += 1
|
|
438
|
-
|
|
439
|
-
# Add entity information
|
|
440
471
|
entity_name = event.get("entityName", "Unknown")
|
|
441
472
|
entity_label = event.get("entityLabel", "Unknown")
|
|
442
473
|
entity_type = event.get("entityType", "Unknown")
|
|
443
|
-
|
|
444
474
|
entity_info = f"{entity_name} ({entity_label})"
|
|
445
475
|
problem_groups[problem]["affected_entities"].add(entity_info)
|
|
446
476
|
problem_groups[problem]["entity_types"].add(entity_type)
|
|
447
|
-
|
|
448
|
-
# Add sample event (up to 3 per problem)
|
|
449
477
|
if len(problem_groups[problem]["sample_events"]) < 3:
|
|
450
478
|
simple_event = {
|
|
451
479
|
"eventId": event.get("eventId", ""),
|
|
@@ -455,20 +483,9 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
455
483
|
"severity": event.get("severity", 0)
|
|
456
484
|
}
|
|
457
485
|
problem_groups[problem]["sample_events"].append(simple_event)
|
|
458
|
-
|
|
459
|
-
# Sort problems by count (most frequent first)
|
|
460
486
|
sorted_problems = sorted(problem_groups.items(), key=lambda x: x[1]["count"], reverse=True)
|
|
461
|
-
|
|
462
|
-
# Format the time range in a human-readable format
|
|
463
|
-
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
464
|
-
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
465
|
-
|
|
466
|
-
# Create a detailed analysis of each problem
|
|
467
487
|
problem_analyses = []
|
|
468
|
-
|
|
469
|
-
# Process each problem
|
|
470
488
|
for problem_name, problem_data in sorted_problems:
|
|
471
|
-
# Create a detailed problem analysis
|
|
472
489
|
problem_analysis = {
|
|
473
490
|
"problem": problem_name,
|
|
474
491
|
"count": problem_data["count"],
|
|
@@ -476,48 +493,358 @@ class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
|
476
493
|
"entity_types": list(problem_data["entity_types"]),
|
|
477
494
|
"sample_events": problem_data["sample_events"]
|
|
478
495
|
}
|
|
479
|
-
|
|
480
496
|
problem_analyses.append(problem_analysis)
|
|
481
|
-
|
|
482
|
-
# Create a comprehensive analysis
|
|
483
497
|
analysis_result = {
|
|
484
498
|
"summary": f"Analysis based on {len(events)} of {total_events_count} agent monitoring events between {from_date} and {to_date}.",
|
|
485
499
|
"time_range": f"{from_date} to {to_date}",
|
|
486
500
|
"events_count": total_events_count,
|
|
487
501
|
"events_analyzed": len(events),
|
|
488
|
-
"problem_analyses": problem_analyses[:10]
|
|
502
|
+
"problem_analyses": problem_analyses[:10]
|
|
489
503
|
}
|
|
490
|
-
|
|
491
|
-
# Create a more user-friendly text summary for direct display
|
|
492
504
|
markdown_summary = "# Agent Monitoring Events Analysis\n\n"
|
|
493
505
|
markdown_summary += f"Analysis based on {len(events)} of {total_events_count} agent monitoring events between {from_date} and {to_date}.\n\n"
|
|
494
|
-
|
|
495
506
|
markdown_summary += "## Top Monitoring Issues\n\n"
|
|
496
|
-
|
|
497
|
-
# Add each problem to the markdown summary
|
|
498
|
-
for problem_analysis in problem_analyses[:5]: # Limit to top 5 for readability
|
|
507
|
+
for problem_analysis in problem_analyses[:5]:
|
|
499
508
|
problem_name = problem_analysis["problem"]
|
|
500
509
|
count = problem_analysis["count"]
|
|
501
|
-
|
|
502
510
|
markdown_summary += f"### {problem_name} ({count} events)\n\n"
|
|
503
|
-
|
|
504
|
-
# Add affected entities if available
|
|
505
511
|
if problem_analysis.get("affected_entities"):
|
|
506
512
|
entities = ", ".join(problem_analysis["affected_entities"][:5])
|
|
507
513
|
if len(problem_analysis["affected_entities"]) > 5:
|
|
508
514
|
entities += f" and {len(problem_analysis['affected_entities']) - 5} more"
|
|
509
515
|
markdown_summary += f"**Affected Entities:** {entities}\n\n"
|
|
510
|
-
|
|
511
516
|
markdown_summary += "\n"
|
|
512
|
-
|
|
513
|
-
# Add the markdown summary to the result
|
|
514
517
|
analysis_result["markdown_summary"] = markdown_summary
|
|
515
|
-
|
|
518
|
+
analysis_result["events"] = event_dicts
|
|
516
519
|
return analysis_result
|
|
517
|
-
|
|
518
520
|
except Exception as e:
|
|
519
521
|
logger.error(f"Error in get_agent_monitoring_events: {e}", exc_info=True)
|
|
520
522
|
return {
|
|
521
|
-
"error": f"Failed to get agent monitoring events: {e!s}"
|
|
523
|
+
"error": f"Failed to get agent monitoring events: {e!s}",
|
|
524
|
+
"details": str(e)
|
|
522
525
|
}
|
|
523
526
|
|
|
527
|
+
|
|
528
|
+
@register_as_tool
|
|
529
|
+
@with_header_auth(EventsApi)
|
|
530
|
+
async def get_issues(self,
|
|
531
|
+
query: Optional[str] = None,
|
|
532
|
+
from_time: Optional[int] = None,
|
|
533
|
+
to_time: Optional[int] = None,
|
|
534
|
+
filter_event_updates: Optional[bool] = None,
|
|
535
|
+
exclude_triggered_before: Optional[int] = None,
|
|
536
|
+
max_events: Optional[int] = 50,
|
|
537
|
+
size: Optional[int] = 100,
|
|
538
|
+
time_range: Optional[str] = None,
|
|
539
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
540
|
+
"""
|
|
541
|
+
Get issue events from Instana based on the provided parameters.
|
|
542
|
+
|
|
543
|
+
This tool retrieves issue events from Instana based on specified filters and time range.
|
|
544
|
+
Issues are events that represent problems that need attention but are not critical.
|
|
545
|
+
|
|
546
|
+
Examples:
|
|
547
|
+
Get all issue events from the last 24 hours:
|
|
548
|
+
- time_range: "last 24 hours"
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
query: Query string to filter events (optional)
|
|
552
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
553
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
554
|
+
filter_event_updates: Whether to filter event updates (optional)
|
|
555
|
+
exclude_triggered_before: Exclude events triggered before this timestamp (optional)
|
|
556
|
+
max_events: Maximum number of events to process (default: 50)
|
|
557
|
+
size: Maximum number of events to return from API (default: 100)
|
|
558
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
559
|
+
ctx: The MCP context (optional)
|
|
560
|
+
api_client: API client for testing (optional)
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Dictionary containing the list of issue events or error information
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
try:
|
|
567
|
+
logger.debug(f"get_issue_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
568
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
569
|
+
if not from_time:
|
|
570
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
571
|
+
try:
|
|
572
|
+
response_data = api_client.get_events_without_preload_content(
|
|
573
|
+
var_from=from_time,
|
|
574
|
+
to=to_time,
|
|
575
|
+
window_size=size,
|
|
576
|
+
filter_event_updates=filter_event_updates,
|
|
577
|
+
exclude_triggered_before=exclude_triggered_before,
|
|
578
|
+
event_type_filters=["issue"]
|
|
579
|
+
)
|
|
580
|
+
if response_data.status != 200:
|
|
581
|
+
return {"error": f"Failed to get issue events: HTTP {response_data.status}"}
|
|
582
|
+
response_text = response_data.data.decode('utf-8')
|
|
583
|
+
result = json.loads(response_text)
|
|
584
|
+
if isinstance(result, list):
|
|
585
|
+
result_dict = {"events": result, "events_count": len(result)}
|
|
586
|
+
else:
|
|
587
|
+
result_dict = result
|
|
588
|
+
return result_dict
|
|
589
|
+
except Exception as api_error:
|
|
590
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
591
|
+
return {"error": f"Failed to get issue events: {api_error}"}
|
|
592
|
+
except Exception as e:
|
|
593
|
+
logger.error(f"Error in get_issue_events: {e}", exc_info=True)
|
|
594
|
+
return {"error": f"Failed to get issue events: {e!s}"}
|
|
595
|
+
|
|
596
|
+
@register_as_tool
|
|
597
|
+
@with_header_auth(EventsApi)
|
|
598
|
+
async def get_incidents(self,
|
|
599
|
+
query: Optional[str] = None,
|
|
600
|
+
from_time: Optional[int] = None,
|
|
601
|
+
to_time: Optional[int] = None,
|
|
602
|
+
filter_event_updates: Optional[bool] = None,
|
|
603
|
+
exclude_triggered_before: Optional[int] = None,
|
|
604
|
+
max_events: Optional[int] = 50,
|
|
605
|
+
size: Optional[int] = 100,
|
|
606
|
+
time_range: Optional[str] = None,
|
|
607
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
608
|
+
"""
|
|
609
|
+
Get incident events from Instana based on the provided parameters.
|
|
610
|
+
|
|
611
|
+
This tool retrieves incident events from Instana based on specified filters and time range.
|
|
612
|
+
Incidents are critical events that require immediate attention.
|
|
613
|
+
|
|
614
|
+
Examples:
|
|
615
|
+
Get all incident events from the last 24 hours:
|
|
616
|
+
- time_range: "last 24 hours"
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
query: Query string to filter events (optional)
|
|
620
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
621
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
622
|
+
filter_event_updates: Whether to filter event updates (optional)
|
|
623
|
+
exclude_triggered_before: Exclude events triggered before this timestamp (optional)
|
|
624
|
+
max_events: Maximum number of events to process (default: 50)
|
|
625
|
+
size: Maximum number of events to return from API (default: 100)
|
|
626
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
627
|
+
ctx: The MCP context (optional)
|
|
628
|
+
api_client: API client for testing (optional)
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Dictionary containing the list of incident events or error information
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
try:
|
|
635
|
+
logger.debug(f"get_incident_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
636
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
637
|
+
if not from_time:
|
|
638
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
639
|
+
try:
|
|
640
|
+
response_data = api_client.get_events_without_preload_content(
|
|
641
|
+
var_from=from_time,
|
|
642
|
+
to=to_time,
|
|
643
|
+
window_size=size,
|
|
644
|
+
filter_event_updates=filter_event_updates,
|
|
645
|
+
exclude_triggered_before=exclude_triggered_before,
|
|
646
|
+
event_type_filters=["incident"]
|
|
647
|
+
)
|
|
648
|
+
if response_data.status != 200:
|
|
649
|
+
return {"error": f"Failed to get incident events: HTTP {response_data.status}"}
|
|
650
|
+
response_text = response_data.data.decode('utf-8')
|
|
651
|
+
result = json.loads(response_text)
|
|
652
|
+
if isinstance(result, list):
|
|
653
|
+
result_dict = {"events": result, "events_count": len(result)}
|
|
654
|
+
else:
|
|
655
|
+
result_dict = result
|
|
656
|
+
return result_dict
|
|
657
|
+
except Exception as api_error:
|
|
658
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
659
|
+
return {"error": f"Failed to get incident events: {api_error}"}
|
|
660
|
+
except Exception as e:
|
|
661
|
+
logger.error(f"Error in get_incident_events: {e}", exc_info=True)
|
|
662
|
+
return {"error": f"Failed to get incident events: {e!s}"}
|
|
663
|
+
|
|
664
|
+
@register_as_tool
|
|
665
|
+
@with_header_auth(EventsApi)
|
|
666
|
+
async def get_changes(self,
|
|
667
|
+
query: Optional[str] = None,
|
|
668
|
+
from_time: Optional[int] = None,
|
|
669
|
+
to_time: Optional[int] = None,
|
|
670
|
+
filter_event_updates: Optional[bool] = None,
|
|
671
|
+
exclude_triggered_before: Optional[int] = None,
|
|
672
|
+
max_events: Optional[int] = 50,
|
|
673
|
+
size: Optional[int] = 100,
|
|
674
|
+
time_range: Optional[str] = None,
|
|
675
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
676
|
+
"""
|
|
677
|
+
Get change events from Instana based on the provided parameters.
|
|
678
|
+
|
|
679
|
+
This tool retrieves change events from Instana based on specified filters and time range.
|
|
680
|
+
Change events represent modifications to the system, such as deployments or configuration changes.
|
|
681
|
+
|
|
682
|
+
Examples:
|
|
683
|
+
Get all change events from the last 24 hours:
|
|
684
|
+
- time_range: "last 24 hours"
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
query: Query string to filter events (optional)
|
|
688
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
689
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
690
|
+
filter_event_updates: Whether to filter event updates (optional)
|
|
691
|
+
exclude_triggered_before: Exclude events triggered before this timestamp (optional)
|
|
692
|
+
max_events: Maximum number of events to process (default: 50)
|
|
693
|
+
size: Maximum number of events to return from API (default: 100)
|
|
694
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
695
|
+
ctx: The MCP context (optional)
|
|
696
|
+
api_client: API client for testing (optional)
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
Dictionary containing the list of change events or error information
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
try:
|
|
703
|
+
logger.debug(f"get_change_events called with query={query}, time_range={time_range}, from_time={from_time}, to_time={to_time}, size={size}")
|
|
704
|
+
from_time, to_time = self._process_time_range(time_range, from_time, to_time)
|
|
705
|
+
if not from_time:
|
|
706
|
+
from_time = to_time - (60 * 60 * 1000)
|
|
707
|
+
try:
|
|
708
|
+
response_data = api_client.get_events_without_preload_content(
|
|
709
|
+
var_from=from_time,
|
|
710
|
+
to=to_time,
|
|
711
|
+
window_size=size,
|
|
712
|
+
filter_event_updates=filter_event_updates,
|
|
713
|
+
exclude_triggered_before=exclude_triggered_before,
|
|
714
|
+
event_type_filters=["change"]
|
|
715
|
+
)
|
|
716
|
+
if response_data.status != 200:
|
|
717
|
+
return {"error": f"Failed to get change events: HTTP {response_data.status}"}
|
|
718
|
+
response_text = response_data.data.decode('utf-8')
|
|
719
|
+
result = json.loads(response_text)
|
|
720
|
+
if isinstance(result, list):
|
|
721
|
+
result_dict = {"events": result, "events_count": len(result)}
|
|
722
|
+
else:
|
|
723
|
+
result_dict = result
|
|
724
|
+
return result_dict
|
|
725
|
+
except Exception as api_error:
|
|
726
|
+
logger.error(f"API call failed: {api_error}", exc_info=True)
|
|
727
|
+
return {"error": f"Failed to get change events: {api_error}"}
|
|
728
|
+
except Exception as e:
|
|
729
|
+
logger.error(f"Error in get_change_events: {e}", exc_info=True)
|
|
730
|
+
return {"error": f"Failed to get change events: {e!s}"}
|
|
731
|
+
|
|
732
|
+
@register_as_tool
|
|
733
|
+
@with_header_auth(EventsApi)
|
|
734
|
+
async def get_events_by_ids(
|
|
735
|
+
self,
|
|
736
|
+
event_ids: Union[List[str], str],
|
|
737
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
738
|
+
"""
|
|
739
|
+
Get events by their IDs.
|
|
740
|
+
This tool retrieves multiple events at once using their unique IDs.
|
|
741
|
+
It supports both batch retrieval and individual fallback requests if the batch API fails.
|
|
742
|
+
|
|
743
|
+
Examples:
|
|
744
|
+
Get events using a list of IDs:
|
|
745
|
+
- event_ids: ["1a2b3c4d5e6f", "7g8h9i0j1k2l"]
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
event_ids: List of event IDs to retrieve or a comma-separated string of IDs
|
|
749
|
+
ctx: The MCP context (optional)
|
|
750
|
+
api_client: API client for testing (optional)
|
|
751
|
+
|
|
752
|
+
Returns:
|
|
753
|
+
Dictionary containing the list of events or error information
|
|
754
|
+
"""
|
|
755
|
+
|
|
756
|
+
try:
|
|
757
|
+
logger.debug(f"get_events_by_ids called with event_ids={event_ids}")
|
|
758
|
+
|
|
759
|
+
# Handle string input conversion
|
|
760
|
+
if isinstance(event_ids, str):
|
|
761
|
+
if event_ids.startswith('[') and event_ids.endswith(']'):
|
|
762
|
+
import ast
|
|
763
|
+
try:
|
|
764
|
+
event_ids = ast.literal_eval(event_ids)
|
|
765
|
+
except (SyntaxError, ValueError) as e:
|
|
766
|
+
logger.error(f"Failed to parse event_ids as list: {e}")
|
|
767
|
+
return {"error": f"Invalid event_ids format: {e}"}
|
|
768
|
+
else:
|
|
769
|
+
event_ids = [id.strip() for id in event_ids.split(',')]
|
|
770
|
+
|
|
771
|
+
# Validate input
|
|
772
|
+
if not event_ids:
|
|
773
|
+
return {"error": "No event IDs provided"}
|
|
774
|
+
|
|
775
|
+
logger.debug(f"Processing {len(event_ids)} event IDs")
|
|
776
|
+
|
|
777
|
+
# Use the batch API to retrieve all events at once
|
|
778
|
+
try:
|
|
779
|
+
logger.debug("Retrieving events using batch API")
|
|
780
|
+
events_result = api_client.get_events_by_ids(request_body=event_ids)
|
|
781
|
+
|
|
782
|
+
all_events = []
|
|
783
|
+
for event in events_result:
|
|
784
|
+
if hasattr(event, 'to_dict'):
|
|
785
|
+
event_dict = event.to_dict()
|
|
786
|
+
else:
|
|
787
|
+
event_dict = event
|
|
788
|
+
all_events.append(event_dict)
|
|
789
|
+
|
|
790
|
+
result = {
|
|
791
|
+
"events": all_events,
|
|
792
|
+
"events_count": len(all_events),
|
|
793
|
+
"successful_retrievals": len(all_events),
|
|
794
|
+
"failed_retrievals": 0,
|
|
795
|
+
"summary": self._summarize_events_result(all_events)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
logger.debug(f"Retrieved {result['successful_retrievals']} events successfully using batch API")
|
|
799
|
+
return result
|
|
800
|
+
|
|
801
|
+
except Exception as batch_error:
|
|
802
|
+
logger.warning(f"Batch API failed: {batch_error}. Falling back to individual requests.")
|
|
803
|
+
|
|
804
|
+
# Fallback to individual requests using without_preload_content
|
|
805
|
+
all_events = []
|
|
806
|
+
for event_id in event_ids:
|
|
807
|
+
try:
|
|
808
|
+
logger.debug(f"Retrieving event ID: {event_id}")
|
|
809
|
+
response_data = api_client.get_events_by_ids_without_preload_content(request_body=[event_id])
|
|
810
|
+
|
|
811
|
+
# Check if the response was successful
|
|
812
|
+
if response_data.status != 200:
|
|
813
|
+
error_message = f"Failed to get event {event_id}: HTTP {response_data.status}"
|
|
814
|
+
logger.error(error_message)
|
|
815
|
+
all_events.append({"eventId": event_id, "error": error_message})
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
# Read and parse the response content
|
|
819
|
+
response_text = response_data.data.decode('utf-8')
|
|
820
|
+
try:
|
|
821
|
+
event_dict = json.loads(response_text)
|
|
822
|
+
if isinstance(event_dict, list) and event_dict:
|
|
823
|
+
all_events.append(event_dict[0])
|
|
824
|
+
else:
|
|
825
|
+
all_events.append({"eventId": event_id, "error": "No event data returned"})
|
|
826
|
+
except json.JSONDecodeError as json_err:
|
|
827
|
+
error_message = f"Failed to parse JSON for event {event_id}: {json_err}"
|
|
828
|
+
logger.error(error_message)
|
|
829
|
+
all_events.append({"eventId": event_id, "error": error_message})
|
|
830
|
+
|
|
831
|
+
except Exception as e:
|
|
832
|
+
logger.error(f"Error retrieving event ID {event_id}: {e}", exc_info=True)
|
|
833
|
+
all_events.append({"eventId": event_id, "error": f"Failed to retrieve: {e!s}"})
|
|
834
|
+
|
|
835
|
+
result = {
|
|
836
|
+
"events": all_events,
|
|
837
|
+
"events_count": len(all_events),
|
|
838
|
+
"successful_retrievals": sum(1 for event in all_events if "error" not in event),
|
|
839
|
+
"failed_retrievals": sum(1 for event in all_events if "error" in event),
|
|
840
|
+
"summary": self._summarize_events_result([e for e in all_events if "error" not in e])
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
logger.debug(f"Retrieved {result['successful_retrievals']} events successfully, {result['failed_retrievals']} failed using individual requests")
|
|
844
|
+
return result
|
|
845
|
+
except Exception as e:
|
|
846
|
+
logger.error(f"Error in get_events_by_ids: {e}", exc_info=True)
|
|
847
|
+
return {
|
|
848
|
+
"error": f"Failed to get events by IDs: {e!s}",
|
|
849
|
+
"details": str(e)
|
|
850
|
+
}
|