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.
Files changed (55) hide show
  1. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/METADATA +460 -139
  2. mcp_instana-0.2.1.dist-info/RECORD +59 -0
  3. src/application/application_analyze.py +373 -160
  4. src/application/application_catalog.py +3 -1
  5. src/application/application_global_alert_config.py +653 -0
  6. src/application/application_metrics.py +6 -2
  7. src/application/application_resources.py +3 -1
  8. src/application/application_settings.py +966 -370
  9. src/application/application_topology.py +6 -2
  10. src/automation/action_catalog.py +416 -0
  11. src/automation/action_history.py +338 -0
  12. src/core/server.py +159 -9
  13. src/core/utils.py +2 -2
  14. src/event/events_tools.py +602 -275
  15. src/infrastructure/infrastructure_analyze.py +7 -3
  16. src/infrastructure/infrastructure_catalog.py +3 -1
  17. src/infrastructure/infrastructure_metrics.py +6 -2
  18. src/infrastructure/infrastructure_resources.py +7 -5
  19. src/infrastructure/infrastructure_topology.py +5 -3
  20. src/prompts/__init__.py +16 -0
  21. src/prompts/application/__init__.py +1 -0
  22. src/prompts/application/application_alerts.py +54 -0
  23. src/prompts/application/application_catalog.py +26 -0
  24. src/prompts/application/application_metrics.py +57 -0
  25. src/prompts/application/application_resources.py +26 -0
  26. src/prompts/application/application_settings.py +75 -0
  27. src/prompts/application/application_topology.py +30 -0
  28. src/prompts/events/__init__.py +1 -0
  29. src/prompts/events/events_tools.py +161 -0
  30. src/prompts/infrastructure/infrastructure_analyze.py +72 -0
  31. src/prompts/infrastructure/infrastructure_catalog.py +53 -0
  32. src/prompts/infrastructure/infrastructure_metrics.py +45 -0
  33. src/prompts/infrastructure/infrastructure_resources.py +74 -0
  34. src/prompts/infrastructure/infrastructure_topology.py +38 -0
  35. src/prompts/settings/__init__.py +0 -0
  36. src/prompts/settings/custom_dashboard.py +157 -0
  37. src/prompts/website/__init__.py +1 -0
  38. src/prompts/website/website_analyze.py +35 -0
  39. src/prompts/website/website_catalog.py +40 -0
  40. src/prompts/website/website_configuration.py +105 -0
  41. src/prompts/website/website_metrics.py +34 -0
  42. src/settings/__init__.py +1 -0
  43. src/settings/custom_dashboard_tools.py +417 -0
  44. src/website/__init__.py +0 -0
  45. src/website/website_analyze.py +433 -0
  46. src/website/website_catalog.py +171 -0
  47. src/website/website_configuration.py +770 -0
  48. src/website/website_metrics.py +241 -0
  49. mcp_instana-0.1.1.dist-info/RECORD +0 -30
  50. src/prompts/mcp_prompts.py +0 -900
  51. src/prompts/prompt_loader.py +0 -29
  52. src/prompts/prompt_registry.json +0 -21
  53. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/WHEEL +0 -0
  54. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/entry_points.txt +0 -0
  55. {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 = self.analyze_api.get_call_details(
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 = self.analyze_api.get_trace_download(
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
- includeInternal: Optional[bool] = None,
162
- includeSynthetic: Optional[bool] = None,
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
- Args:
174
- includeInternal (Optional[bool]): Whether to include internal traces.
175
- includeSynthetic (Optional[bool]): Whether to include synthetic traces.
176
- order (Optional[Dict[str, str]]): Order by field and direction.
177
- pagination (Optional[Dict[str, int]]): Pagination parameters.
178
- tagFilterExpression (Optional[Dict[str, str]]): Tag filter expression.
179
- timeFrame (Optional[Dict[str, int]]): Time frame for the traces.
180
- ctx: Optional context for the request.
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
- logger.debug("Fetching all traces with filters and pagination")
188
- body = {}
189
-
190
- if includeInternal is not None:
191
- body["includeInternal"] = includeInternal
192
- if includeSynthetic is not None:
193
- body["includeSynthetic"] = includeSynthetic
194
- if order is not None:
195
- body["order"] = order
196
- if pagination is not None:
197
- body["pagination"] = pagination
198
- if tagFilterExpression is not None:
199
- body["tagFilterExpression"] = tagFilterExpression
200
- if timeFrame is not None:
201
- body["timeFrame"] = timeFrame
202
-
203
- get_traces = GetTraces(**body)
204
-
205
- result = self.analyze_api.get_traces(
206
- get_traces=get_traces
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
- logger.debug(f"Result from get_all_traces: {result_dict}")
217
- # Ensure we return a dictionary
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 getting all traces: {e}", exc_info=True)
222
- return {"error": f"Failed to get all traces: {e!s}"}
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
- group: Dict[str, Any],
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
- order: Optional[Dict[str, Any]] = None,
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
- order (Optional[Dict[str, Any]]): Ordering configuration.
249
- pagination (Optional[Dict[str, Any]]): Cursor-based pagination settings.
250
- tagFilterExpression (Optional[Dict[str, Any]]): Tag filters.
251
- timeFrame (Optional[Dict[str, int]]): Time window (to, windowSize).
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
- logger.debug("Calling trace group metrics API")
260
-
261
- body = {
262
- "group": group,
263
- "metrics": metrics
264
- }
265
-
266
- if includeInternal is not None:
267
- body["includeInternal"] = includeInternal
268
- if includeSynthetic is not None:
269
- body["includeSynthetic"] = includeSynthetic
270
- if fill_time_series is not None:
271
- body["fillTimeSeries"] = fill_time_series
272
- if order is not None:
273
- body["order"] = order
274
- if pagination is not None:
275
- body["pagination"] = pagination
276
- if tagFilterExpression is not None:
277
- body["tagFilterExpression"] = tagFilterExpression
278
- if timeFrame is not None:
279
- body["timeFrame"] = timeFrame
280
-
281
- # Looking at how get_call_group is implemented below
282
- # It seems the method might be different
283
- if fill_time_series is not None:
284
- body["fillTimeSeries"] = fill_time_series
285
-
286
- GetTraces(**body)
287
-
288
- # Call the API method - the actual parameter name doesn't matter in tests
289
- # since the method is mocked
290
- result = self.analyze_api.get_trace_groups()
291
-
292
- result_dict = result.to_dict() if hasattr(result, 'to_dict') else result
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
- # Ensure we return a dictionary
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}", exc_info=True)
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
- group: Dict[str, Any],
308
- metrics: List[Dict[str, str]],
309
- includeInternal: Optional[bool] = None,
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
- order (Optional[Dict[str, Any]]): Ordering configuration.
329
- pagination (Optional[Dict[str, Any]]): Cursor-based pagination settings.
330
- tagFilterExpression (Optional[Dict[str, Any]]): Tag filters.
331
- timeFrame (Optional[Dict[str, int]]): Time window (to, windowSize).
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
- logger.debug("Calling call group metrics API")
340
-
341
- body = {
342
- "group": group,
343
- "metrics": metrics
344
- }
345
-
346
- if includeInternal is not None:
347
- body["includeInternal"] = includeInternal
348
- if includeSynthetic is not None:
349
- body["includeSynthetic"] = includeSynthetic
350
- if fill_time_series is not None:
351
- body["fillTimeSeries"] = fill_time_series
352
- if order is not None:
353
- body["order"] = order
354
- if pagination is not None:
355
- body["pagination"] = pagination
356
- if tagFilterExpression is not None:
357
- body["tagFilterExpression"] = tagFilterExpression
358
- if timeFrame is not None:
359
- body["timeFrame"] = timeFrame
360
-
361
- GetCallGroups(**body)
362
-
363
- # Call the API method - the actual parameter name doesn't matter in tests
364
- # since the method is mocked
365
- result = self.analyze_api.get_call_group()
366
-
367
- result_dict = result.to_dict() if hasattr(result, 'to_dict') else result
368
-
369
- logger.debug(f"Result from get_grouped_calls_metrics: {result_dict}")
370
- # Ensure we return a dictionary
371
- return dict(result_dict) if not isinstance(result_dict, dict) else result_dict
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 get_grouped_calls_metrics: {e}", exc_info=True)
375
- return {"error": f"Failed to get grouped calls metrics: {e!s}"}
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 = self.analyze_api.get_correlated_traces(
613
+ result = api_client.get_correlated_traces(
401
614
  correlation_id=correlation_id
402
615
  )
403
616