mcp-instana 0.1.1__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/METADATA +460 -139
- mcp_instana-0.2.1.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.1.dist-info}/WHEEL +0 -0
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/entry_points.txt +0 -0
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -5,7 +5,7 @@ This module provides application analyze tool functionality for Instana monitori
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
8
|
+
from typing import Any, Dict, List, Optional, Union
|
|
9
9
|
|
|
10
10
|
# Import the necessary classes from the SDK
|
|
11
11
|
try:
|
|
@@ -21,7 +21,7 @@ except ImportError:
|
|
|
21
21
|
logger.error("Failed to import application analyze API", exc_info=True)
|
|
22
22
|
raise
|
|
23
23
|
|
|
24
|
-
from src.core.utils import BaseInstanaClient, register_as_tool
|
|
24
|
+
from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
|
|
25
25
|
|
|
26
26
|
# Configure logger for this module
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
@@ -51,11 +51,13 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
51
51
|
raise
|
|
52
52
|
|
|
53
53
|
@register_as_tool
|
|
54
|
+
@with_header_auth(ApplicationAnalyzeApi)
|
|
54
55
|
async def get_call_details(
|
|
55
56
|
self,
|
|
56
57
|
trace_id: str,
|
|
57
58
|
call_id: str,
|
|
58
|
-
ctx=None
|
|
59
|
+
ctx=None,
|
|
60
|
+
api_client=None
|
|
59
61
|
) -> Dict[str, Any]:
|
|
60
62
|
"""
|
|
61
63
|
Get details of a specific call in a trace.
|
|
@@ -75,7 +77,7 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
75
77
|
return {"error": "Both trace_id and call_id must be provided"}
|
|
76
78
|
|
|
77
79
|
logger.debug(f"Fetching call details for trace_id={trace_id}, call_id={call_id}")
|
|
78
|
-
result =
|
|
80
|
+
result = api_client.get_call_details(
|
|
79
81
|
trace_id=trace_id,
|
|
80
82
|
call_id=call_id
|
|
81
83
|
)
|
|
@@ -96,13 +98,15 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
96
98
|
return {"error": f"Failed to get call details: {e!s}"}
|
|
97
99
|
|
|
98
100
|
@register_as_tool
|
|
101
|
+
@with_header_auth(ApplicationAnalyzeApi)
|
|
99
102
|
async def get_trace_details(
|
|
100
103
|
self,
|
|
101
104
|
id: str,
|
|
102
105
|
retrievalSize: Optional[int] = None,
|
|
103
106
|
offset: Optional[int] = None,
|
|
104
107
|
ingestionTime: Optional[int] = None,
|
|
105
|
-
ctx=None
|
|
108
|
+
ctx=None,
|
|
109
|
+
api_client=None
|
|
106
110
|
) -> Dict[str, Any]:
|
|
107
111
|
"""
|
|
108
112
|
Get details of a specific trace.
|
|
@@ -132,7 +136,7 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
132
136
|
return {"error": "retrievalSize must be between 1 and 10000"}
|
|
133
137
|
|
|
134
138
|
logger.debug(f"Fetching trace details for id={id}")
|
|
135
|
-
result =
|
|
139
|
+
result = api_client.get_trace_download(
|
|
136
140
|
id=id,
|
|
137
141
|
retrieval_size=retrievalSize,
|
|
138
142
|
offset=offset,
|
|
@@ -156,83 +160,140 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
156
160
|
|
|
157
161
|
|
|
158
162
|
@register_as_tool
|
|
163
|
+
@with_header_auth(ApplicationAnalyzeApi)
|
|
159
164
|
async def get_all_traces(
|
|
160
165
|
self,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
order: Optional[Dict[str, str]] = None,
|
|
164
|
-
pagination: Optional[Dict[str, int]] = None,
|
|
165
|
-
tagFilterExpression: Optional[Dict[str, str]] = None,
|
|
166
|
-
timeFrame: Optional[Dict[str, int]] = None,
|
|
166
|
+
payload: Optional[Union[Dict[str, Any], str]]=None,
|
|
167
|
+
api_client = None,
|
|
167
168
|
ctx=None
|
|
168
169
|
) -> Dict[str, Any]:
|
|
169
170
|
"""
|
|
170
171
|
Get all traces.
|
|
171
172
|
This tool endpoint retrieves the metrics for traces.
|
|
172
173
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
Sample payload: {
|
|
175
|
+
"includeInternal": false,
|
|
176
|
+
"includeSynthetic": false,
|
|
177
|
+
"pagination": {
|
|
178
|
+
"retrievalSize": 1
|
|
179
|
+
},
|
|
180
|
+
"tagFilterExpression": {
|
|
181
|
+
"type": "EXPRESSION",
|
|
182
|
+
"logicalOperator": "AND",
|
|
183
|
+
"elements": [
|
|
184
|
+
{
|
|
185
|
+
"type": "TAG_FILTER",
|
|
186
|
+
"name": "endpoint.name",
|
|
187
|
+
"operator": "EQUALS",
|
|
188
|
+
"entity": "DESTINATION",
|
|
189
|
+
"value": "GET /"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"type": "TAG_FILTER",
|
|
193
|
+
"name": "service.name",
|
|
194
|
+
"operator": "EQUALS",
|
|
195
|
+
"entity": "DESTINATION",
|
|
196
|
+
"value": "groundskeeper"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
"order": {
|
|
201
|
+
"by": "traceLabel",
|
|
202
|
+
"direction": "DESC"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
181
205
|
|
|
182
206
|
Returns:
|
|
183
207
|
Dict[str, Any]: List of traces matching the criteria.
|
|
184
208
|
"""
|
|
185
|
-
|
|
186
209
|
try:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
# Parse the payload if it's a string
|
|
211
|
+
if isinstance(payload, str):
|
|
212
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
213
|
+
try:
|
|
214
|
+
import json
|
|
215
|
+
try:
|
|
216
|
+
parsed_payload = json.loads(payload)
|
|
217
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
218
|
+
request_body = parsed_payload
|
|
219
|
+
except json.JSONDecodeError as e:
|
|
220
|
+
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
221
|
+
|
|
222
|
+
# Try replacing single quotes with double quotes
|
|
223
|
+
fixed_payload = payload.replace("'", "\"")
|
|
224
|
+
try:
|
|
225
|
+
parsed_payload = json.loads(fixed_payload)
|
|
226
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
227
|
+
request_body = parsed_payload
|
|
228
|
+
except json.JSONDecodeError:
|
|
229
|
+
# Try as Python literal
|
|
230
|
+
import ast
|
|
231
|
+
try:
|
|
232
|
+
parsed_payload = ast.literal_eval(payload)
|
|
233
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
234
|
+
request_body = parsed_payload
|
|
235
|
+
except (SyntaxError, ValueError) as e2:
|
|
236
|
+
logger.debug(f"Failed to parse payload string: {e2}")
|
|
237
|
+
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.debug(f"Error parsing payload string: {e}")
|
|
240
|
+
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
241
|
+
else:
|
|
242
|
+
# If payload is already a dictionary, use it directly
|
|
243
|
+
logger.debug("Using provided payload dictionary")
|
|
244
|
+
request_body = payload
|
|
245
|
+
|
|
246
|
+
# Import the GetTraces class
|
|
247
|
+
try:
|
|
248
|
+
from instana_client.models.get_traces import (
|
|
249
|
+
GetTraces,
|
|
250
|
+
)
|
|
251
|
+
from instana_client.models.group import Group
|
|
252
|
+
logger.debug("Successfully imported GetTraces")
|
|
253
|
+
except ImportError as e:
|
|
254
|
+
logger.debug(f"Error importing GetTraces: {e}")
|
|
255
|
+
return {"error": f"Failed to import GetTraces: {e!s}"}
|
|
256
|
+
|
|
257
|
+
# Create an GetTraces object from the request body
|
|
258
|
+
try:
|
|
259
|
+
query_params = {}
|
|
260
|
+
if request_body and "tag_filter_expression" in request_body:
|
|
261
|
+
query_params["tag_filter_expression"] = request_body["tag_filter_expression"]
|
|
262
|
+
logger.debug(f"Creating get_traces with params: {query_params}")
|
|
263
|
+
config_object = GetTraces(**query_params)
|
|
264
|
+
logger.debug("Successfully got traces")
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.debug(f"Error creating get_traces: {e}")
|
|
267
|
+
return {"error": f"Failed to get tracest: {e!s}"}
|
|
268
|
+
|
|
269
|
+
# Call the get_traces method from the SDK
|
|
270
|
+
logger.debug("Calling get_traces with config object")
|
|
271
|
+
result = api_client.get_traces(
|
|
272
|
+
get_traces=config_object
|
|
207
273
|
)
|
|
208
|
-
|
|
209
274
|
# Convert the result to a dictionary
|
|
210
275
|
if hasattr(result, 'to_dict'):
|
|
211
276
|
result_dict = result.to_dict()
|
|
212
277
|
else:
|
|
213
278
|
# If it's already a dict or another format, use it as is
|
|
214
|
-
result_dict = result
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return dict(result_dict) if not isinstance(result_dict, dict) else result_dict
|
|
279
|
+
result_dict = result or {
|
|
280
|
+
"success": True,
|
|
281
|
+
"message": "Get traces"
|
|
282
|
+
}
|
|
219
283
|
|
|
284
|
+
logger.debug(f"Result from get_traces: {result_dict}")
|
|
285
|
+
return result_dict
|
|
220
286
|
except Exception as e:
|
|
221
|
-
logger.error(f"Error
|
|
222
|
-
return {"error": f"Failed to get
|
|
287
|
+
logger.error(f"Error in get_traces: {e}")
|
|
288
|
+
return {"error": f"Failed to get traces: {e!s}"}
|
|
223
289
|
|
|
224
290
|
@register_as_tool
|
|
291
|
+
@with_header_auth(ApplicationAnalyzeApi)
|
|
225
292
|
async def get_grouped_trace_metrics(
|
|
226
293
|
self,
|
|
227
|
-
|
|
228
|
-
metrics: List[Dict[str, str]],
|
|
229
|
-
includeInternal: Optional[bool] = None,
|
|
230
|
-
includeSynthetic: Optional[bool] = None,
|
|
294
|
+
payload: Optional[Union[Dict[str, Any], str]]=None,
|
|
231
295
|
fill_time_series: Optional[bool] = None,
|
|
232
|
-
|
|
233
|
-
pagination: Optional[Dict[str, Any]] = None,
|
|
234
|
-
tagFilterExpression: Optional[Dict[str, Any]] = None,
|
|
235
|
-
timeFrame: Optional[Dict[str, int]] = None,
|
|
296
|
+
api_client=None,
|
|
236
297
|
ctx=None
|
|
237
298
|
) -> Dict[str, Any]:
|
|
238
299
|
"""
|
|
@@ -240,79 +301,143 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
240
301
|
This tool Get grouped trace metrics (by endpoint or service name).
|
|
241
302
|
|
|
242
303
|
Args:
|
|
243
|
-
group (Dict[str, Any]): Grouping definition with groupbyTag, groupbyTagEntity, etc.
|
|
244
|
-
metrics (List[Dict[str, str]]): List of metric configs with metric and aggregation.
|
|
245
|
-
includeInternal (Optional[bool]): Whether to include internal calls.
|
|
246
|
-
includeSynthetic (Optional[bool]): Whether to include synthetic calls.
|
|
247
304
|
fillTimeSeries (Optional[bool]): Whether to fill missing data points with zeroes.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
305
|
+
Sample Payload: {
|
|
306
|
+
"group": {
|
|
307
|
+
"groupbyTag": "trace.endpoint.name",
|
|
308
|
+
"groupbyTagEntity": "NOT_APPLICABLE"
|
|
309
|
+
},
|
|
310
|
+
"metrics": [
|
|
311
|
+
{
|
|
312
|
+
"aggregation": "SUM",
|
|
313
|
+
"metric": "latency"
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
"order": {
|
|
317
|
+
"by": "latency",
|
|
318
|
+
"direction": "ASC"
|
|
319
|
+
},
|
|
320
|
+
"pagination": {
|
|
321
|
+
"retrievalSize": 20
|
|
322
|
+
},
|
|
323
|
+
"tagFilterExpression": {
|
|
324
|
+
"type": "EXPRESSION",
|
|
325
|
+
"logicalOperator": "AND",
|
|
326
|
+
"elements": [
|
|
327
|
+
{
|
|
328
|
+
"type": "TAG_FILTER",
|
|
329
|
+
"name": "call.type",
|
|
330
|
+
"operator": "EQUALS",
|
|
331
|
+
"entity": "NOT_APPLICABLE",
|
|
332
|
+
"value": "DATABASE"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"type": "TAG_FILTER",
|
|
336
|
+
"name": "service.name",
|
|
337
|
+
"operator": "EQUALS",
|
|
338
|
+
"entity": "DESTINATION",
|
|
339
|
+
"value": "ratings"
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
}
|
|
343
|
+
}
|
|
252
344
|
ctx: Optional execution context.
|
|
253
345
|
|
|
254
346
|
Returns:
|
|
255
347
|
Dict[str, Any]: Grouped trace metrics result.
|
|
256
348
|
"""
|
|
257
|
-
|
|
258
349
|
try:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
350
|
+
# Parse the payload if it's a string
|
|
351
|
+
if isinstance(payload, str):
|
|
352
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
353
|
+
try:
|
|
354
|
+
import json
|
|
355
|
+
try:
|
|
356
|
+
parsed_payload = json.loads(payload)
|
|
357
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
358
|
+
request_body = parsed_payload
|
|
359
|
+
except json.JSONDecodeError as e:
|
|
360
|
+
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
361
|
+
|
|
362
|
+
# Try replacing single quotes with double quotes
|
|
363
|
+
fixed_payload = payload.replace("'", "\"")
|
|
364
|
+
try:
|
|
365
|
+
parsed_payload = json.loads(fixed_payload)
|
|
366
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
367
|
+
request_body = parsed_payload
|
|
368
|
+
except json.JSONDecodeError:
|
|
369
|
+
# Try as Python literal
|
|
370
|
+
import ast
|
|
371
|
+
try:
|
|
372
|
+
parsed_payload = ast.literal_eval(payload)
|
|
373
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
374
|
+
request_body = parsed_payload
|
|
375
|
+
except (SyntaxError, ValueError) as e2:
|
|
376
|
+
logger.debug(f"Failed to parse payload string: {e2}")
|
|
377
|
+
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
378
|
+
except Exception as e:
|
|
379
|
+
logger.debug(f"Error parsing payload string: {e}")
|
|
380
|
+
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
381
|
+
else:
|
|
382
|
+
# If payload is already a dictionary, use it directly
|
|
383
|
+
logger.debug("Using provided payload dictionary")
|
|
384
|
+
request_body = payload
|
|
385
|
+
|
|
386
|
+
# Import the GetTraceGroups class
|
|
387
|
+
try:
|
|
388
|
+
from instana_client.models.get_trace_groups import (
|
|
389
|
+
GetTraceGroups,
|
|
390
|
+
)
|
|
391
|
+
from instana_client.models.group import Group
|
|
392
|
+
logger.debug("Successfully imported GetTraceGroups")
|
|
393
|
+
except ImportError as e:
|
|
394
|
+
logger.debug(f"Error importing GetTraceGroups: {e}")
|
|
395
|
+
return {"error": f"Failed to import GetTraceGroups: {e!s}"}
|
|
396
|
+
|
|
397
|
+
# Create an GetTraceGroups object from the request body
|
|
398
|
+
try:
|
|
399
|
+
query_params = {}
|
|
400
|
+
if request_body and "group" in request_body:
|
|
401
|
+
query_params["group"] = request_body["group"]
|
|
402
|
+
if request_body and "metrics" in request_body:
|
|
403
|
+
query_params["metrics"] = request_body["metrics"]
|
|
404
|
+
if request_body and "tag_filter_expression" in request_body:
|
|
405
|
+
query_params["tag_filter_expression"] = request_body["tag_filter_expression"]
|
|
406
|
+
logger.debug(f"Creating GetTraceGroups with params: {query_params}")
|
|
407
|
+
config_object = GetTraceGroups(**query_params)
|
|
408
|
+
logger.debug("Successfully created endpoint config object")
|
|
409
|
+
except Exception as e:
|
|
410
|
+
logger.debug(f"Error creating GetTraceGroups: {e}")
|
|
411
|
+
return {"error": f"Failed to create config object: {e!s}"}
|
|
412
|
+
|
|
413
|
+
# Call the create_endpoint_config method from the SDK
|
|
414
|
+
logger.debug("Calling create_endpoint_config with config object")
|
|
415
|
+
result = api_client.get_trace_groups(
|
|
416
|
+
get_trace_groups=config_object
|
|
417
|
+
)
|
|
418
|
+
# Convert the result to a dictionary
|
|
419
|
+
if hasattr(result, 'to_dict'):
|
|
420
|
+
result_dict = result.to_dict()
|
|
421
|
+
else:
|
|
422
|
+
# If it's already a dict or another format, use it as is
|
|
423
|
+
result_dict = result or {
|
|
424
|
+
"success": True,
|
|
425
|
+
"message": "Grouped trace metrics"
|
|
426
|
+
}
|
|
293
427
|
|
|
294
428
|
logger.debug(f"Result from get_grouped_trace_metrics: {result_dict}")
|
|
295
|
-
|
|
296
|
-
return dict(result_dict) if not isinstance(result_dict, dict) else result_dict
|
|
297
|
-
|
|
429
|
+
return result_dict
|
|
298
430
|
except Exception as e:
|
|
299
|
-
logger.error(f"Error in get_grouped_trace_metrics: {e}"
|
|
431
|
+
logger.error(f"Error in get_grouped_trace_metrics: {e}")
|
|
300
432
|
return {"error": f"Failed to get grouped trace metrics: {e!s}"}
|
|
301
433
|
|
|
302
|
-
|
|
303
|
-
|
|
304
434
|
@register_as_tool
|
|
435
|
+
@with_header_auth(ApplicationAnalyzeApi)
|
|
305
436
|
async def get_grouped_calls_metrics(
|
|
306
437
|
self,
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
includeSynthetic: Optional[bool] = None,
|
|
311
|
-
fill_time_series: Optional[bool] = None,
|
|
312
|
-
order: Optional[Dict[str, Any]] = None,
|
|
313
|
-
pagination: Optional[Dict[str, Any]] = None,
|
|
314
|
-
tagFilterExpression: Optional[Dict[str, Any]] = None,
|
|
315
|
-
timeFrame: Optional[Dict[str, int]] = None,
|
|
438
|
+
fillTimeSeries: Optional[str] = None,
|
|
439
|
+
payload: Optional[Union[Dict[str, Any], str]]=None,
|
|
440
|
+
api_client = None,
|
|
316
441
|
ctx=None
|
|
317
442
|
) -> Dict[str, Any]:
|
|
318
443
|
"""
|
|
@@ -320,64 +445,152 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
320
445
|
This endpoint retrieves the metrics for calls.
|
|
321
446
|
|
|
322
447
|
Args:
|
|
323
|
-
group (Dict[str, Any]): Grouping definition with groupbyTag, groupbyTagEntity, etc.
|
|
324
|
-
metrics (List[Dict[str, str]]): List of metric configs with metric and aggregation.
|
|
325
|
-
includeInternal (Optional[bool]): Whether to include internal calls.
|
|
326
|
-
includeSynthetic (Optional[bool]): Whether to include synthetic calls.
|
|
327
448
|
fillTimeSeries (Optional[bool]): Whether to fill missing data points with zeroes.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
449
|
+
Sample payload: {
|
|
450
|
+
"group": {
|
|
451
|
+
"groupbyTag": "service.name",
|
|
452
|
+
"groupbyTagEntity": "DESTINATION"
|
|
453
|
+
},
|
|
454
|
+
"metrics": [
|
|
455
|
+
{
|
|
456
|
+
"aggregation": "SUM",
|
|
457
|
+
"metric": "calls"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"aggregation": "P75",
|
|
461
|
+
"metric": "latency",
|
|
462
|
+
"granularity": 360
|
|
463
|
+
}
|
|
464
|
+
],
|
|
465
|
+
"includeInternal": false,
|
|
466
|
+
"includeSynthetic": false,
|
|
467
|
+
"order": {
|
|
468
|
+
"by": "calls",
|
|
469
|
+
"direction": "DESC"
|
|
470
|
+
},
|
|
471
|
+
"pagination": {
|
|
472
|
+
"retrievalSize": 20
|
|
473
|
+
},
|
|
474
|
+
"tagFilterExpression": {
|
|
475
|
+
"type": "EXPRESSION",
|
|
476
|
+
"logicalOperator": "AND",
|
|
477
|
+
"elements": [
|
|
478
|
+
{
|
|
479
|
+
"type": "TAG_FILTER",
|
|
480
|
+
"name": "call.type",
|
|
481
|
+
"operator": "EQUALS",
|
|
482
|
+
"entity": "NOT_APPLICABLE",
|
|
483
|
+
"value": "DATABASE"
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
"type": "TAG_FILTER",
|
|
487
|
+
"name": "service.name",
|
|
488
|
+
"operator": "EQUALS",
|
|
489
|
+
"entity": "DESTINATION",
|
|
490
|
+
"value": "ratings"
|
|
491
|
+
}
|
|
492
|
+
]
|
|
493
|
+
},
|
|
494
|
+
"timeFrame": {
|
|
495
|
+
"to": "1688366990000",
|
|
496
|
+
"windowSize": "600000"
|
|
497
|
+
}
|
|
498
|
+
}
|
|
332
499
|
ctx: Optional execution context.
|
|
333
500
|
|
|
334
501
|
Returns:
|
|
335
502
|
Dict[str, Any]: Grouped trace metrics result.
|
|
336
503
|
"""
|
|
337
|
-
|
|
338
504
|
try:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
505
|
+
# Parse the payload if it's a string
|
|
506
|
+
if isinstance(payload, str):
|
|
507
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
508
|
+
try:
|
|
509
|
+
import json
|
|
510
|
+
try:
|
|
511
|
+
parsed_payload = json.loads(payload)
|
|
512
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
513
|
+
request_body = parsed_payload
|
|
514
|
+
except json.JSONDecodeError as e:
|
|
515
|
+
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
516
|
+
|
|
517
|
+
# Try replacing single quotes with double quotes
|
|
518
|
+
fixed_payload = payload.replace("'", "\"")
|
|
519
|
+
try:
|
|
520
|
+
parsed_payload = json.loads(fixed_payload)
|
|
521
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
522
|
+
request_body = parsed_payload
|
|
523
|
+
except json.JSONDecodeError:
|
|
524
|
+
# Try as Python literal
|
|
525
|
+
import ast
|
|
526
|
+
try:
|
|
527
|
+
parsed_payload = ast.literal_eval(payload)
|
|
528
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
529
|
+
request_body = parsed_payload
|
|
530
|
+
except (SyntaxError, ValueError) as e2:
|
|
531
|
+
logger.debug(f"Failed to parse payload string: {e2}")
|
|
532
|
+
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
533
|
+
except Exception as e:
|
|
534
|
+
logger.debug(f"Error parsing payload string: {e}")
|
|
535
|
+
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
536
|
+
else:
|
|
537
|
+
# If payload is already a dictionary, use it directly
|
|
538
|
+
logger.debug("Using provided payload dictionary")
|
|
539
|
+
request_body = payload
|
|
540
|
+
|
|
541
|
+
# Import the GetCallGroups class
|
|
542
|
+
try:
|
|
543
|
+
from instana_client.models.get_call_groups import (
|
|
544
|
+
GetCallGroups,
|
|
545
|
+
)
|
|
546
|
+
from instana_client.models.group import Group
|
|
547
|
+
logger.debug("Successfully imported GetCallGroups")
|
|
548
|
+
except ImportError as e:
|
|
549
|
+
logger.debug(f"Error importing GetCallGroups: {e}")
|
|
550
|
+
return {"error": f"Failed to import GetCallGroups: {e!s}"}
|
|
551
|
+
|
|
552
|
+
# Create an GetCallGroups object from the request body
|
|
553
|
+
try:
|
|
554
|
+
query_params = {}
|
|
555
|
+
if request_body and "group" in request_body:
|
|
556
|
+
query_params["group"] = request_body["group"]
|
|
557
|
+
if request_body and "metrics" in request_body:
|
|
558
|
+
query_params["metrics"] = request_body["metrics"]
|
|
559
|
+
logger.debug(f"Creating GetCallGroups with params: {query_params}")
|
|
560
|
+
config_object = GetCallGroups(**query_params)
|
|
561
|
+
logger.debug("Successfully created endpoint config object")
|
|
562
|
+
except Exception as e:
|
|
563
|
+
logger.error(f"Error creating GetCallGroups: {e}")
|
|
564
|
+
return {"error": f"Failed to create config object: {e!s}"}
|
|
565
|
+
|
|
566
|
+
# Call the get_call_groups method from the SDK
|
|
567
|
+
logger.debug("Calling get_call_groups with config object")
|
|
568
|
+
result = api_client.get_call_group(
|
|
569
|
+
get_call_groups=config_object
|
|
570
|
+
)
|
|
571
|
+
# Convert the result to a dictionary
|
|
572
|
+
if hasattr(result, 'to_dict'):
|
|
573
|
+
result_dict = result.to_dict()
|
|
574
|
+
else:
|
|
575
|
+
# If it's already a dict or another format, use it as is
|
|
576
|
+
result_dict = result or {
|
|
577
|
+
"success": True,
|
|
578
|
+
"message": "Get Grouped call"
|
|
579
|
+
}
|
|
372
580
|
|
|
581
|
+
logger.debug(f"Result from get_call_group: {result_dict}")
|
|
582
|
+
return result_dict
|
|
373
583
|
except Exception as e:
|
|
374
|
-
logger.error(f"Error in
|
|
375
|
-
return {"error": f"Failed to get grouped
|
|
584
|
+
logger.error(f"Error in get_call_group: {e}")
|
|
585
|
+
return {"error": f"Failed to get grouped call: {e!s}"}
|
|
586
|
+
|
|
376
587
|
|
|
377
588
|
@register_as_tool
|
|
589
|
+
@with_header_auth(ApplicationAnalyzeApi)
|
|
378
590
|
async def get_correlated_traces(
|
|
379
591
|
self,
|
|
380
592
|
correlation_id: str,
|
|
593
|
+
api_client = None,
|
|
381
594
|
ctx=None
|
|
382
595
|
) -> Dict[str, Any]:
|
|
383
596
|
"""
|
|
@@ -397,7 +610,7 @@ class ApplicationAnalyzeMCPTools(BaseInstanaClient):
|
|
|
397
610
|
logger.warning(error_msg)
|
|
398
611
|
return {"error": error_msg}
|
|
399
612
|
|
|
400
|
-
result =
|
|
613
|
+
result = api_client.get_correlated_traces(
|
|
401
614
|
correlation_id=correlation_id
|
|
402
615
|
)
|
|
403
616
|
|