mcp-instana 0.2.1__py3-none-any.whl → 0.3.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.2.1.dist-info → mcp_instana-0.3.0.dist-info}/METADATA +2 -1
- {mcp_instana-0.2.1.dist-info → mcp_instana-0.3.0.dist-info}/RECORD +29 -29
- src/application/application_alert_config.py +45 -12
- src/application/application_analyze.py +28 -6
- src/application/application_catalog.py +11 -2
- src/application/application_global_alert_config.py +60 -21
- src/application/application_metrics.py +20 -4
- src/application/application_resources.py +20 -4
- src/application/application_settings.py +111 -35
- src/application/application_topology.py +22 -14
- src/automation/action_catalog.py +165 -188
- src/automation/action_history.py +21 -6
- src/core/server.py +7 -1
- src/core/utils.py +42 -5
- src/event/events_tools.py +30 -7
- src/infrastructure/infrastructure_analyze.py +18 -4
- src/infrastructure/infrastructure_catalog.py +72 -16
- src/infrastructure/infrastructure_metrics.py +5 -1
- src/infrastructure/infrastructure_resources.py +30 -11
- src/infrastructure/infrastructure_topology.py +10 -2
- src/log/log_alert_configuration.py +106 -31
- src/settings/custom_dashboard_tools.py +30 -7
- src/website/website_analyze.py +10 -2
- src/website/website_catalog.py +14 -3
- src/website/website_configuration.py +54 -13
- src/website/website_metrics.py +10 -2
- {mcp_instana-0.2.1.dist-info → mcp_instana-0.3.0.dist-info}/WHEEL +0 -0
- {mcp_instana-0.2.1.dist-info → mcp_instana-0.3.0.dist-info}/entry_points.txt +0 -0
- {mcp_instana-0.2.1.dist-info → mcp_instana-0.3.0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -13,9 +13,12 @@ import traceback
|
|
|
13
13
|
from datetime import datetime
|
|
14
14
|
from typing import Any, Dict, List, Optional, Union
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
from mcp.types import ToolAnnotations
|
|
17
17
|
|
|
18
18
|
from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
|
|
19
|
+
from src.prompts import mcp
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
19
22
|
|
|
20
23
|
# Import the necessary classes from the SDK
|
|
21
24
|
try:
|
|
@@ -68,7 +71,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
68
71
|
traceback.print_exc(file=sys.stderr)
|
|
69
72
|
raise
|
|
70
73
|
|
|
71
|
-
@register_as_tool
|
|
74
|
+
@register_as_tool(
|
|
75
|
+
title="Get All Applications Configs",
|
|
76
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
77
|
+
)
|
|
72
78
|
@with_header_auth(ApplicationSettingsApi)
|
|
73
79
|
async def get_all_applications_configs(self,
|
|
74
80
|
ctx=None,
|
|
@@ -85,24 +91,34 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
85
91
|
"""
|
|
86
92
|
try:
|
|
87
93
|
debug_print("Fetching all applications and their settings")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
# Use raw JSON response to avoid Pydantic validation issues
|
|
95
|
+
result = api_client.get_application_configs_without_preload_content()
|
|
96
|
+
import json
|
|
97
|
+
try:
|
|
98
|
+
response_text = result.data.decode('utf-8')
|
|
99
|
+
json_data = json.loads(response_text)
|
|
100
|
+
# Convert to List[Dict[str, Any]] format
|
|
101
|
+
if isinstance(json_data, list):
|
|
102
|
+
result_dict = json_data
|
|
103
|
+
else:
|
|
104
|
+
# If it's a single object, wrap it in a list
|
|
105
|
+
result_dict = [json_data] if json_data else []
|
|
106
|
+
debug_print("Successfully retrieved application configs data")
|
|
107
|
+
return result_dict
|
|
108
|
+
except (json.JSONDecodeError, AttributeError) as json_err:
|
|
109
|
+
error_message = f"Failed to parse JSON response: {json_err}"
|
|
110
|
+
debug_print(error_message)
|
|
111
|
+
return [{"error": error_message}]
|
|
99
112
|
|
|
100
113
|
except Exception as e:
|
|
101
114
|
debug_print(f"Error in get_application_configs: {e}")
|
|
102
115
|
traceback.print_exc(file=sys.stderr)
|
|
103
116
|
return [{"error": f"Failed to get all applications: {e!s}"}]
|
|
104
117
|
|
|
105
|
-
@register_as_tool
|
|
118
|
+
@register_as_tool(
|
|
119
|
+
title="Add Application Config",
|
|
120
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
121
|
+
)
|
|
106
122
|
@with_header_auth(ApplicationSettingsApi)
|
|
107
123
|
async def add_application_config(self,
|
|
108
124
|
payload: Union[Dict[str, Any], str],
|
|
@@ -247,7 +263,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
247
263
|
logger.error(f"Error in add_application_config: {e}")
|
|
248
264
|
return {"error": f"Failed to add new application config: {e!s}"}
|
|
249
265
|
|
|
250
|
-
@register_as_tool
|
|
266
|
+
@register_as_tool(
|
|
267
|
+
title="Delete Application Config",
|
|
268
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
269
|
+
)
|
|
251
270
|
@with_header_auth(ApplicationSettingsApi)
|
|
252
271
|
async def delete_application_config(self,
|
|
253
272
|
id: str,
|
|
@@ -285,7 +304,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
285
304
|
traceback.print_exc(file=sys.stderr)
|
|
286
305
|
return {"error": f"Failed to delete application configuration: {e!s}"}
|
|
287
306
|
|
|
288
|
-
@register_as_tool
|
|
307
|
+
@register_as_tool(
|
|
308
|
+
title="Get Application Config",
|
|
309
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
310
|
+
)
|
|
289
311
|
@with_header_auth(ApplicationSettingsApi)
|
|
290
312
|
async def get_application_config(self,
|
|
291
313
|
id: str,
|
|
@@ -320,7 +342,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
320
342
|
traceback.print_exc(file=sys.stderr)
|
|
321
343
|
return {"error": f"Failed to get application configuration: {e!s}"}
|
|
322
344
|
|
|
323
|
-
@register_as_tool
|
|
345
|
+
@register_as_tool(
|
|
346
|
+
title="Update Application Config",
|
|
347
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
348
|
+
)
|
|
324
349
|
@with_header_auth(ApplicationSettingsApi)
|
|
325
350
|
async def update_application_config(
|
|
326
351
|
self,
|
|
@@ -473,7 +498,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
473
498
|
logger.error(f"Error in put_application_config: {e}")
|
|
474
499
|
return {"error": f"Failed to update existing application config: {e!s}"}
|
|
475
500
|
|
|
476
|
-
@register_as_tool
|
|
501
|
+
@register_as_tool(
|
|
502
|
+
title="Get All Endpoint Configs",
|
|
503
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
504
|
+
)
|
|
477
505
|
@with_header_auth(ApplicationSettingsApi)
|
|
478
506
|
async def get_all_endpoint_configs(self,
|
|
479
507
|
ctx=None,
|
|
@@ -505,7 +533,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
505
533
|
traceback.print_exc(file=sys.stderr)
|
|
506
534
|
return [{"error": f"Failed to get endpoint configs: {e!s}"}]
|
|
507
535
|
|
|
508
|
-
@register_as_tool
|
|
536
|
+
@register_as_tool(
|
|
537
|
+
title="Create Endpoint Config",
|
|
538
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
539
|
+
)
|
|
509
540
|
@with_header_auth(ApplicationSettingsApi)
|
|
510
541
|
async def create_endpoint_config(
|
|
511
542
|
self,
|
|
@@ -607,7 +638,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
607
638
|
logger.error(f"Error in create_endpoint_config: {e}")
|
|
608
639
|
return {"error": f"Failed to create new endpoint config: {e!s}"}
|
|
609
640
|
|
|
610
|
-
@register_as_tool
|
|
641
|
+
@register_as_tool(
|
|
642
|
+
title="Delete Endpoint Config",
|
|
643
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
644
|
+
)
|
|
611
645
|
@with_header_auth(ApplicationSettingsApi)
|
|
612
646
|
async def delete_endpoint_config(
|
|
613
647
|
self,
|
|
@@ -645,7 +679,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
645
679
|
traceback.print_exc(file=sys.stderr)
|
|
646
680
|
return {"error": f"Failed to delete endpoint configs: {e!s}"}
|
|
647
681
|
|
|
648
|
-
@register_as_tool
|
|
682
|
+
@register_as_tool(
|
|
683
|
+
title="Get Endpoint Config",
|
|
684
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
685
|
+
)
|
|
649
686
|
@with_header_auth(ApplicationSettingsApi)
|
|
650
687
|
async def get_endpoint_config(
|
|
651
688
|
self,
|
|
@@ -685,7 +722,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
685
722
|
traceback.print_exc(file=sys.stderr)
|
|
686
723
|
return {"error": f"Failed to get endpoint configs: {e!s}"}
|
|
687
724
|
|
|
688
|
-
@register_as_tool
|
|
725
|
+
@register_as_tool(
|
|
726
|
+
title="Update Endpoint Config",
|
|
727
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
728
|
+
)
|
|
689
729
|
@with_header_auth(ApplicationSettingsApi)
|
|
690
730
|
async def update_endpoint_config(
|
|
691
731
|
self,
|
|
@@ -811,7 +851,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
811
851
|
return {"error": f"Failed to update existing application config: {e!s}"}
|
|
812
852
|
|
|
813
853
|
|
|
814
|
-
@register_as_tool
|
|
854
|
+
@register_as_tool(
|
|
855
|
+
title="Get All Manual Service Configs",
|
|
856
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
857
|
+
)
|
|
815
858
|
@with_header_auth(ApplicationSettingsApi)
|
|
816
859
|
async def get_all_manual_service_configs(self,
|
|
817
860
|
ctx=None,
|
|
@@ -843,7 +886,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
843
886
|
traceback.print_exc(file=sys.stderr)
|
|
844
887
|
return [{"error": f"Failed to get manual service configs: {e!s}"}]
|
|
845
888
|
|
|
846
|
-
@register_as_tool
|
|
889
|
+
@register_as_tool(
|
|
890
|
+
title="Add Manual Service Config",
|
|
891
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
892
|
+
)
|
|
847
893
|
@with_header_auth(ApplicationSettingsApi)
|
|
848
894
|
async def add_manual_service_config(
|
|
849
895
|
self,
|
|
@@ -960,7 +1006,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
960
1006
|
logger.error(f"Error in add_manual_service_config: {e}")
|
|
961
1007
|
return {"error": f"Failed to create new manual service config: {e!s}"}
|
|
962
1008
|
|
|
963
|
-
@register_as_tool
|
|
1009
|
+
@register_as_tool(
|
|
1010
|
+
title="Delete Manual Service Config",
|
|
1011
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1012
|
+
)
|
|
964
1013
|
@with_header_auth(ApplicationSettingsApi)
|
|
965
1014
|
async def delete_manual_service_config(
|
|
966
1015
|
self,
|
|
@@ -998,7 +1047,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
998
1047
|
traceback.print_exc(file=sys.stderr)
|
|
999
1048
|
return {"error": f"Failed to delete manual service configs: {e!s}"}
|
|
1000
1049
|
|
|
1001
|
-
@register_as_tool
|
|
1050
|
+
@register_as_tool(
|
|
1051
|
+
title="Update Manual Service Config",
|
|
1052
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1053
|
+
)
|
|
1002
1054
|
@with_header_auth(ApplicationSettingsApi)
|
|
1003
1055
|
async def update_manual_service_config(
|
|
1004
1056
|
self,
|
|
@@ -1122,7 +1174,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1122
1174
|
return {"error": f"Failed to update manual config: {e!s}"}
|
|
1123
1175
|
|
|
1124
1176
|
|
|
1125
|
-
@register_as_tool
|
|
1177
|
+
@register_as_tool(
|
|
1178
|
+
title="Replace All Manual Service Config",
|
|
1179
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1180
|
+
)
|
|
1126
1181
|
@with_header_auth(ApplicationSettingsApi)
|
|
1127
1182
|
async def replace_all_manual_service_config(
|
|
1128
1183
|
self,
|
|
@@ -1244,7 +1299,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1244
1299
|
logger.error(f"Error in replace_all_manual_service_config: {e}")
|
|
1245
1300
|
return [{"error": f"Failed to replace all manual config: {e!s}"}]
|
|
1246
1301
|
|
|
1247
|
-
@register_as_tool
|
|
1302
|
+
@register_as_tool(
|
|
1303
|
+
title="Get All Service Configs",
|
|
1304
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
1305
|
+
)
|
|
1248
1306
|
@with_header_auth(ApplicationSettingsApi)
|
|
1249
1307
|
async def get_all_service_configs(self,
|
|
1250
1308
|
ctx=None,
|
|
@@ -1276,7 +1334,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1276
1334
|
traceback.print_exc(file=sys.stderr)
|
|
1277
1335
|
return [{"error": f"Failed to get application data metrics: {e}"}]
|
|
1278
1336
|
|
|
1279
|
-
@register_as_tool
|
|
1337
|
+
@register_as_tool(
|
|
1338
|
+
title="Add Service Config",
|
|
1339
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1340
|
+
)
|
|
1280
1341
|
@with_header_auth(ApplicationSettingsApi)
|
|
1281
1342
|
async def add_service_config(self,
|
|
1282
1343
|
payload: Union[Dict[str, Any], str],
|
|
@@ -1388,7 +1449,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1388
1449
|
logger.error(f"Error in add_service_config: {e}")
|
|
1389
1450
|
return {"error": f"Failed to add service config: {e!s}"}
|
|
1390
1451
|
|
|
1391
|
-
@register_as_tool
|
|
1452
|
+
@register_as_tool(
|
|
1453
|
+
title="Replace All Service Configs",
|
|
1454
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1455
|
+
)
|
|
1392
1456
|
@with_header_auth(ApplicationSettingsApi)
|
|
1393
1457
|
async def replace_all_service_configs(self,
|
|
1394
1458
|
payload: Union[Dict[str, Any], str],
|
|
@@ -1498,7 +1562,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1498
1562
|
return [{"error": f"Failed to replace all service config: {e!s}"}]
|
|
1499
1563
|
|
|
1500
1564
|
|
|
1501
|
-
@register_as_tool
|
|
1565
|
+
@register_as_tool(
|
|
1566
|
+
title="Order Service Config",
|
|
1567
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1568
|
+
)
|
|
1502
1569
|
@with_header_auth(ApplicationSettingsApi)
|
|
1503
1570
|
async def order_service_config(self,
|
|
1504
1571
|
request_body: List[str],
|
|
@@ -1537,7 +1604,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1537
1604
|
traceback.print_exc(file=sys.stderr)
|
|
1538
1605
|
return {"error": f"Failed to order service configs: {e!s}"}
|
|
1539
1606
|
|
|
1540
|
-
@register_as_tool
|
|
1607
|
+
@register_as_tool(
|
|
1608
|
+
title="Delete Service Config",
|
|
1609
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1610
|
+
)
|
|
1541
1611
|
@with_header_auth(ApplicationSettingsApi)
|
|
1542
1612
|
async def delete_service_config(self,
|
|
1543
1613
|
id: str,
|
|
@@ -1575,7 +1645,10 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1575
1645
|
traceback.print_exc(file=sys.stderr)
|
|
1576
1646
|
return {"error": f"Failed to delete service configuration: {e!s}"}
|
|
1577
1647
|
|
|
1578
|
-
@register_as_tool
|
|
1648
|
+
@register_as_tool(
|
|
1649
|
+
title="Get Service Config",
|
|
1650
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
1651
|
+
)
|
|
1579
1652
|
@with_header_auth(ApplicationSettingsApi)
|
|
1580
1653
|
async def get_service_config(
|
|
1581
1654
|
self,
|
|
@@ -1615,13 +1688,16 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
1615
1688
|
traceback.print_exc(file=sys.stderr)
|
|
1616
1689
|
return {"error": f"Failed to get service config: {e!s}"}
|
|
1617
1690
|
|
|
1618
|
-
@register_as_tool
|
|
1691
|
+
@register_as_tool(
|
|
1692
|
+
title="Update Service Config",
|
|
1693
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1694
|
+
)
|
|
1619
1695
|
@with_header_auth(ApplicationSettingsApi)
|
|
1620
1696
|
async def update_service_config(self,
|
|
1621
1697
|
id: str,
|
|
1622
1698
|
payload: Union[Dict[str, Any], str],
|
|
1623
1699
|
ctx=None,
|
|
1624
|
-
api_client=None) -> Dict[str, Any]:
|
|
1700
|
+
api_client=None) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
|
1625
1701
|
"""
|
|
1626
1702
|
This tool gives is used if one wants to update a particular custom service rule.
|
|
1627
1703
|
Args:
|
|
@@ -8,7 +8,10 @@ import logging
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from typing import Any, Dict, Optional
|
|
10
10
|
|
|
11
|
+
from mcp.types import ToolAnnotations
|
|
12
|
+
|
|
11
13
|
from src.core.utils import BaseInstanaClient, register_as_tool
|
|
14
|
+
from src.prompts import mcp
|
|
12
15
|
|
|
13
16
|
try:
|
|
14
17
|
from instana_client.api.application_topology_api import (
|
|
@@ -52,7 +55,10 @@ class ApplicationTopologyMCPTools(BaseInstanaClient):
|
|
|
52
55
|
logger.error(f"Error initializing ApplicationTopologyMCPTools: {e}", exc_info=True)
|
|
53
56
|
raise
|
|
54
57
|
|
|
55
|
-
@register_as_tool
|
|
58
|
+
@register_as_tool(
|
|
59
|
+
title="Get Application Topology",
|
|
60
|
+
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
61
|
+
)
|
|
56
62
|
async def get_application_topology(self,
|
|
57
63
|
window_size: Optional[int] = None,
|
|
58
64
|
to_timestamp: Optional[int] = None,
|
|
@@ -84,27 +90,29 @@ class ApplicationTopologyMCPTools(BaseInstanaClient):
|
|
|
84
90
|
if not window_size:
|
|
85
91
|
window_size = 3600000 # Default to 1 hour in milliseconds
|
|
86
92
|
|
|
87
|
-
# Call the API
|
|
93
|
+
# Call the API with raw JSON response to avoid Pydantic validation issues
|
|
88
94
|
# Note: The SDK expects parameters in camelCase, but we use snake_case in Python
|
|
89
95
|
# The SDK will handle the conversion
|
|
90
|
-
result = self.topology_api.
|
|
96
|
+
result = self.topology_api.get_services_map_without_preload_content(
|
|
91
97
|
window_size=window_size,
|
|
92
98
|
to=to_timestamp,
|
|
93
99
|
application_id=application_id,
|
|
94
100
|
application_boundary_scope=application_boundary_scope
|
|
95
101
|
)
|
|
96
102
|
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
# Parse the JSON response manually
|
|
104
|
+
import json
|
|
105
|
+
try:
|
|
106
|
+
# The result from get_services_map_without_preload_content is a response object
|
|
107
|
+
# We need to read the response data and parse it as JSON
|
|
108
|
+
response_text = result.data.decode('utf-8')
|
|
109
|
+
result_dict = json.loads(response_text)
|
|
110
|
+
logger.debug("Successfully retrieved service topology data")
|
|
111
|
+
return result_dict
|
|
112
|
+
except (json.JSONDecodeError, AttributeError) as json_err:
|
|
113
|
+
error_message = f"Failed to parse JSON response: {json_err}"
|
|
114
|
+
logger.error(error_message)
|
|
115
|
+
return {"error": error_message}
|
|
108
116
|
|
|
109
117
|
except Exception as e:
|
|
110
118
|
logger.error(f"Error in get_application_topology: {e}", exc_info=True)
|