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.
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +186 -311
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +30 -22
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +1 -1
- src/application/application_alert_config.py +393 -136
- src/application/application_analyze.py +597 -594
- src/application/application_call_group.py +528 -0
- src/application/application_catalog.py +0 -8
- src/application/application_global_alert_config.py +275 -57
- src/application/application_metrics.py +377 -237
- src/application/application_resources.py +414 -325
- src/application/application_settings.py +608 -1530
- src/application/application_topology.py +62 -62
- src/core/custom_dashboard_smart_router_tool.py +135 -0
- src/core/server.py +95 -119
- src/core/smart_router_tool.py +574 -0
- src/core/utils.py +17 -8
- src/custom_dashboard/custom_dashboard_tools.py +422 -0
- src/event/events_tools.py +57 -9
- src/infrastructure/elicitation_handler.py +338 -0
- src/infrastructure/entity_registry.py +329 -0
- src/infrastructure/infrastructure_analyze_new.py +600 -0
- src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
- src/infrastructure/infrastructure_catalog.py +37 -32
- src/infrastructure/infrastructure_metrics.py +93 -16
- src/infrastructure/infrastructure_resources.py +6 -24
- src/infrastructure/infrastructure_topology.py +29 -23
- src/observability.py +29 -0
- src/prompts/application/application_settings.py +58 -0
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
- {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"
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
-
|
|
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
|
|
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(
|