mcp-instana 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/METADATA +459 -138
  2. mcp_instana-0.2.0.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.0.dist-info}/WHEEL +0 -0
  54. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/entry_points.txt +0 -0
  55. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,433 @@
1
+ """
2
+ Website Analyze MCP Tools Module
3
+
4
+ This module provides website analyze-specific MCP tools for Instana monitoring.
5
+ """
6
+
7
+ import logging
8
+ from datetime import datetime
9
+ from typing import Any, Dict, List, Optional, Union
10
+
11
+
12
+ def clean_nan_values(data: Any) -> Any:
13
+ """
14
+ Recursively clean 'NaN' string values from data structures.
15
+ This is needed because the Instana API sometimes returns 'NaN' as strings
16
+ instead of proper null values, which causes Pydantic validation errors.
17
+ """
18
+ if isinstance(data, dict):
19
+ return {key: clean_nan_values(value) for key, value in data.items()}
20
+ elif isinstance(data, list):
21
+ return [clean_nan_values(item) for item in data]
22
+ elif isinstance(data, str) and data == 'NaN':
23
+ return None
24
+ else:
25
+ return data
26
+
27
+ # Import the necessary classes from the SDK
28
+ try:
29
+ from instana_client.api.website_analyze_api import WebsiteAnalyzeApi
30
+ from instana_client.models.get_website_beacon_groups import GetWebsiteBeaconGroups
31
+ except ImportError as e:
32
+ import logging
33
+ logger = logging.getLogger(__name__)
34
+ logger.error(f"Error importing Instana SDK: {e}", exc_info=True)
35
+ raise
36
+
37
+ from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
38
+
39
+ # Configure logger for this module
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ class WebsiteAnalyzeMCPTools(BaseInstanaClient):
44
+ """Tools for website analyze in Instana MCP."""
45
+
46
+ def __init__(self, read_token: str, base_url: str):
47
+ """Initialize the Website Analyze MCP tools client."""
48
+ super().__init__(read_token=read_token, base_url=base_url)
49
+
50
+ @register_as_tool
51
+ @with_header_auth(WebsiteAnalyzeApi)
52
+ async def get_website_beacon_groups(self,
53
+ payload: Optional[Union[Dict[str, Any], str]] = None,
54
+ fill_time_series: Optional[bool] = True,
55
+ ctx=None, api_client=None) -> Dict[str, Any]:
56
+ """
57
+ Get grouped website beacon metrics.
58
+
59
+ This API endpoint retrieves grouped website monitoring beacon metrics, allowing you to analyze
60
+ performance across different dimensions like page URLs, browsers, or geographic locations.
61
+
62
+ Args:
63
+ payload: Complete request payload as a dictionary or JSON string
64
+ {
65
+ "metrics": [
66
+ {
67
+ "metric": "beaconCount",
68
+ "aggregation": "SUM",
69
+ "granularity": 60
70
+ }
71
+ ],
72
+ "group": {
73
+ "groupByTag": "beacon.page.name"
74
+ },
75
+ "tagFilterExpression": {
76
+ "type": "EXPRESSION",
77
+ "logicalOperator": "AND",
78
+ "elements": [
79
+ {
80
+ "type": "TAG_FILTER",
81
+ "name": "beacon.website.name",
82
+ "operator": "EQUALS",
83
+ "entity": "NOT_APPLICABLE",
84
+ "value": "robot-shop"
85
+ },
86
+ {
87
+ "type": "TAG_FILTER",
88
+ "name": "beacon.location.path",
89
+ "operator": "EQUALS",
90
+ "entity": "NOT_APPLICABLE",
91
+ "value": "/checkout"
92
+ }
93
+ ]
94
+ },
95
+ "timeFrame": {
96
+ "to": null,
97
+ "windowSize": 3600000
98
+ },
99
+ "type": "PAGELOAD"
100
+ }
101
+ fill_time_series: Whether to fill missing data points with timestamp and value 0
102
+ ctx: The MCP context (optional)
103
+
104
+ Returns:
105
+ Dictionary containing grouped website metrics data or error information
106
+ """
107
+ try:
108
+ logger.debug("get_website_beacon_groups called")
109
+
110
+ # Parse the payload
111
+ if isinstance(payload, str):
112
+ logger.debug("Payload is a string, attempting to parse")
113
+ try:
114
+ import json
115
+ try:
116
+ parsed_payload = json.loads(payload)
117
+ logger.debug("Successfully parsed payload as JSON")
118
+ request_body = parsed_payload
119
+ except json.JSONDecodeError as e:
120
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
121
+
122
+ # Try replacing single quotes with double quotes
123
+ fixed_payload = payload.replace("'", "\"")
124
+ try:
125
+ parsed_payload = json.loads(fixed_payload)
126
+ logger.debug("Successfully parsed fixed JSON")
127
+ request_body = parsed_payload
128
+ except json.JSONDecodeError:
129
+ # Try as Python literal
130
+ import ast
131
+ try:
132
+ parsed_payload = ast.literal_eval(payload)
133
+ logger.debug("Successfully parsed payload as Python literal")
134
+ request_body = parsed_payload
135
+ except (SyntaxError, ValueError) as e2:
136
+ logger.debug(f"Failed to parse payload string: {e2}")
137
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
138
+ except Exception as e:
139
+ logger.debug(f"Error parsing payload string: {e}")
140
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
141
+ else:
142
+ # If payload is already a dictionary, use it directly
143
+ logger.debug("Using provided payload dictionary")
144
+ request_body = payload
145
+
146
+ # Handle nested payload structure - if the payload contains a 'payload' key, extract it
147
+ if isinstance(request_body, dict) and "payload" in request_body and len(request_body) == 1:
148
+ logger.debug("Found nested payload structure, extracting inner payload")
149
+ request_body = request_body["payload"]
150
+
151
+ logger.debug(f"Final request_body structure: {request_body}")
152
+ logger.debug(f"Request body keys: {list(request_body.keys()) if isinstance(request_body, dict) else 'Not a dict'}")
153
+
154
+ try:
155
+ from instana_client.models.get_website_beacon_groups import (
156
+ GetWebsiteBeaconGroups,
157
+ )
158
+ logger.debug("Successfully imported GetWebsiteBeaconGroups")
159
+ except ImportError as e:
160
+ logger.debug(f"Error importing GetWebsiteBeaconGroups: {e}")
161
+ return {"error": f"Failed to import GetWebsiteBeaconGroups: {e!s}"}
162
+
163
+ # Create an GetWebsiteBeaconGroups object from the request body
164
+ try:
165
+ query_params = {}
166
+
167
+ # Extract required fields from request_body
168
+ if request_body and "type" in request_body:
169
+ query_params["type"] = request_body["type"]
170
+
171
+ # Handle required 'group' field
172
+ if request_body and "group" in request_body:
173
+ group_data = request_body["group"]
174
+ # Map the group field names to match Pydantic model expectations
175
+ mapped_group = {}
176
+ if "groupByTag" in group_data:
177
+ mapped_group["groupbyTag"] = group_data["groupByTag"]
178
+ if "groupByTagEntity" in group_data:
179
+ mapped_group["groupbyTagEntity"] = group_data["groupByTagEntity"]
180
+ # Also handle the correct case if already present
181
+ if "groupbyTag" in group_data:
182
+ mapped_group["groupbyTag"] = group_data["groupbyTag"]
183
+ if "groupbyTagEntity" in group_data:
184
+ mapped_group["groupbyTagEntity"] = group_data["groupbyTagEntity"]
185
+
186
+ # Ensure both required fields are present - provide default for groupbyTagEntity if missing
187
+ if "groupbyTag" not in mapped_group:
188
+ return {"error": "Required field 'groupByTag' is missing from group payload"}
189
+ if "groupbyTagEntity" not in mapped_group:
190
+ # Provide default value for groupbyTagEntity when not specified
191
+ mapped_group["groupbyTagEntity"] = "NOT_APPLICABLE"
192
+
193
+ query_params["group"] = mapped_group
194
+ else:
195
+ return {"error": "Required field 'group' is missing from payload"}
196
+
197
+ # Handle required 'metrics' field
198
+ if request_body and "metrics" in request_body:
199
+ query_params["metrics"] = request_body["metrics"]
200
+ else:
201
+ return {"error": "Required field 'metrics' is missing from payload"}
202
+
203
+ # Handle optional fields
204
+ if request_body and "timeFrame" in request_body:
205
+ query_params["timeFrame"] = request_body["timeFrame"]
206
+ if request_body and "tagFilterExpression" in request_body:
207
+ query_params["tagFilterExpression"] = request_body["tagFilterExpression"]
208
+ if request_body and "tagFilters" in request_body:
209
+ query_params["tagFilters"] = request_body["tagFilters"]
210
+ if request_body and "order" in request_body:
211
+ query_params["order"] = request_body["order"]
212
+ if request_body and "pagination" in request_body:
213
+ query_params["pagination"] = request_body["pagination"]
214
+
215
+ logger.debug(f"Creating GetWebsiteBeaconGroups with params: {query_params}")
216
+ config_object = GetWebsiteBeaconGroups(**query_params)
217
+ logger.debug("Successfully created GetWebsiteBeaconGroups object")
218
+ except Exception as e:
219
+ logger.debug(f"Error creating GetWebsiteBeaconGroups: {e}")
220
+ return {"error": f"Failed to get website beacon groups: {e!s}"}
221
+
222
+ # Call the get_beacon_groups method from the SDK using without_preload_content to avoid NaN validation errors
223
+ logger.debug("Calling get_beacon_groups with config object")
224
+ try:
225
+ # Use without_preload_content to bypass Pydantic validation and handle NaN values manually
226
+ response = api_client.get_beacon_groups_without_preload_content(
227
+ get_website_beacon_groups=config_object,
228
+ fill_time_series=fill_time_series
229
+ )
230
+
231
+ # Check if the response was successful
232
+ if response.status != 200:
233
+ error_message = f"Failed to get website beacon groups: HTTP {response.status}"
234
+ logger.debug(error_message)
235
+ return {"error": error_message}
236
+
237
+ # Read the response content
238
+ response_text = response.data.decode('utf-8')
239
+
240
+ # Parse the response as JSON
241
+ import json
242
+ result_dict = json.loads(response_text)
243
+
244
+ logger.debug("Successfully parsed raw response")
245
+
246
+ logger.debug(f"Result from get_website_beacon_groups: {result_dict}")
247
+ return result_dict
248
+ except Exception as api_error:
249
+ # Handle validation errors from the SDK, particularly for customMetric 'NaN' values
250
+ error_msg = str(api_error)
251
+ if "customMetric" in error_msg and "NaN" in error_msg:
252
+ logger.warning(f"API returned 'NaN' values for customMetric field: {error_msg}")
253
+ return {
254
+ "error": "API returned invalid data (NaN values for customMetric field). This is a known issue with the Instana API response format.",
255
+ "details": error_msg,
256
+ "suggestion": "Try using a different time range or filtering criteria to avoid beacons with NaN customMetric values."
257
+ }
258
+ else:
259
+ # Re-raise other errors
260
+ raise api_error
261
+ except Exception as e:
262
+ logger.error(f"Error in get_website_beacon_groups: {e}")
263
+ return {"error": f"Failed to get website beacon groups: {e!s}"}
264
+
265
+
266
+ @register_as_tool
267
+ @with_header_auth(WebsiteAnalyzeApi)
268
+ async def get_website_beacons(self,
269
+ payload: Optional[Union[Dict[str, Any], str]] = None,
270
+ ctx=None, api_client=None) -> Dict[str, Any]:
271
+ """
272
+ Get all website beacon metrics.
273
+
274
+ This API endpoint retrieves all website monitoring beacon metrics with matching type.
275
+
276
+ Args:
277
+ payload: Complete request payload as a dictionary or JSON string
278
+ {
279
+ "tagFilterExpression": {
280
+ "type": "TAG_FILTER",
281
+ "name": "beacon.website.name",
282
+ "operator": "EQUALS",
283
+ "entity": "NOT_APPLICABLE",
284
+ "value": "Ecommerce_Bob_Squad"
285
+ },
286
+ "timeFrame": {
287
+ "to": 1757333439394,
288
+ "windowSize": 3600000
289
+ },
290
+ "type": "ERROR"
291
+ }
292
+ ctx: The MCP context (optional)
293
+
294
+ Returns:
295
+ Dictionary containing all website beacon data with matching type or error information
296
+ """
297
+ try:
298
+ logger.debug("get_website_beacons called")
299
+
300
+ # Parse the payload
301
+ if isinstance(payload, str):
302
+ logger.debug("Payload is a string, attempting to parse")
303
+ try:
304
+ import json
305
+ try:
306
+ parsed_payload = json.loads(payload)
307
+ logger.debug("Successfully parsed payload as JSON")
308
+ request_body = parsed_payload
309
+ except json.JSONDecodeError as e:
310
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
311
+
312
+ # Try replacing single quotes with double quotes
313
+ fixed_payload = payload.replace("'", "\"")
314
+ try:
315
+ parsed_payload = json.loads(fixed_payload)
316
+ logger.debug("Successfully parsed fixed JSON")
317
+ request_body = parsed_payload
318
+ except json.JSONDecodeError:
319
+ # Try as Python literal
320
+ import ast
321
+ try:
322
+ parsed_payload = ast.literal_eval(payload)
323
+ logger.debug("Successfully parsed payload as Python literal")
324
+ request_body = parsed_payload
325
+ except (SyntaxError, ValueError) as e2:
326
+ logger.debug(f"Failed to parse payload string: {e2}")
327
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
328
+ except Exception as e:
329
+ logger.debug(f"Error parsing payload string: {e}")
330
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
331
+ else:
332
+ # If payload is already a dictionary, use it directly
333
+ logger.debug("Using provided payload dictionary")
334
+ request_body = payload
335
+
336
+ # Handle nested payload structure - if the payload contains a 'payload' key, extract it
337
+ if isinstance(request_body, dict) and "payload" in request_body and len(request_body) == 1:
338
+ logger.debug("Found nested payload structure, extracting inner payload")
339
+ request_body = request_body["payload"]
340
+
341
+ logger.debug(f"Final request_body structure: {request_body}")
342
+ logger.debug(f"Request body keys: {list(request_body.keys()) if isinstance(request_body, dict) else 'Not a dict'}")
343
+
344
+ try:
345
+ from instana_client.models.get_website_beacons import (
346
+ GetWebsiteBeacons,
347
+ )
348
+ logger.debug("Successfully imported GetWebsiteBeacons")
349
+ except ImportError as e:
350
+ logger.debug(f"Error importing GetWebsiteBeacons: {e}")
351
+ return {"error": f"Failed to import GetWebsiteBeacons: {e!s}"}
352
+
353
+ # Create an GetWebsiteBeacons object from the request body
354
+ try:
355
+ query_params = {}
356
+
357
+ # Handle required 'type' field
358
+ if request_body and "type" in request_body:
359
+ query_params["type"] = request_body["type"]
360
+ else:
361
+ return {"error": "Required field 'type' is missing from payload"}
362
+
363
+ # Handle optional fields
364
+ if request_body and "timeFrame" in request_body:
365
+ time_frame = {}
366
+ if "to" in request_body["timeFrame"]:
367
+ time_frame["to"] = request_body["timeFrame"]["to"]
368
+ if "windowSize" in request_body["timeFrame"]:
369
+ time_frame["windowSize"] = request_body["timeFrame"]["windowSize"]
370
+ query_params["timeFrame"] = time_frame
371
+
372
+ if request_body and "tagFilters" in request_body:
373
+ query_params["tagFilters"] = request_body["tagFilters"]
374
+
375
+ if request_body and "pagination" in request_body:
376
+ query_params["pagination"] = request_body["pagination"]
377
+
378
+ logger.debug(f"Creating GetWebsiteBeacons with params: {query_params}")
379
+ config_object = GetWebsiteBeacons(**query_params)
380
+ logger.debug("Successfully created GetWebsiteBeacons object")
381
+ except Exception as e:
382
+ logger.debug(f"Error creating GetWebsiteBeacons: {e}")
383
+ return {"error": f"Failed to get website beacons: {e!s}"}
384
+
385
+ # Call the get_beacons method from the SDK using without_preload_content to avoid NaN validation errors
386
+ logger.debug("Calling get_beacons with config object")
387
+ try:
388
+ # Use without_preload_content to bypass Pydantic validation and handle NaN values manually
389
+ result = api_client.get_beacons_without_preload_content(
390
+ get_website_beacons=config_object
391
+ )
392
+
393
+ # Read the response content
394
+ response_text = result.data.decode('utf-8')
395
+
396
+ # Parse the response as JSON
397
+ import json
398
+ result_dict = json.loads(response_text)
399
+
400
+ logger.debug("Successfully parsed raw response")
401
+
402
+ # Clean any NaN values from the response
403
+ result_dict = clean_nan_values(result_dict)
404
+
405
+ # Handle nested JSON response format
406
+ if isinstance(result_dict, dict) and "data" in result_dict and isinstance(result_dict["data"], list):
407
+ # Check if data contains JSON strings that need to be parsed
408
+ if len(result_dict["data"]) > 0 and isinstance(result_dict["data"][0], str):
409
+ try:
410
+ # Parse the JSON string from the data array
411
+ parsed_data = json.loads(result_dict["data"][0])
412
+ result_dict = parsed_data
413
+ logger.debug("Successfully parsed nested JSON response")
414
+ except (json.JSONDecodeError, IndexError) as e:
415
+ logger.debug(f"Failed to parse nested JSON: {e}")
416
+ # Fall back to original structure
417
+ pass
418
+
419
+ # Ensure we always return a dictionary, not a list
420
+ if isinstance(result_dict, list):
421
+ result_dict = {"beacons": result_dict, "count": len(result_dict)}
422
+ elif not isinstance(result_dict, dict):
423
+ result_dict = {"data": result_dict}
424
+ logger.debug(f"Result from get_website_beacons: {result_dict}")
425
+ return result_dict
426
+ except Exception as api_error:
427
+ # Handle validation errors from the SDK, particularly for customMetric 'NaN' values
428
+ error_msg = str(api_error)
429
+ logger.debug(f"API error details: {error_msg}")
430
+ return {"error": error_msg}
431
+ except Exception as e:
432
+ logger.error(f"Error in get_website_beacons: {e}")
433
+ return {"error": f"Failed to get website beacons: {e!s}"}
@@ -0,0 +1,171 @@
1
+ """
2
+ Website Catalog MCP Tools Module
3
+
4
+ This module provides website catalog-specific MCP tools for Instana monitoring.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ # Import the necessary classes from the SDK
11
+ try:
12
+ from instana_client.api.website_catalog_api import WebsiteCatalogApi
13
+ except ImportError as e:
14
+ import logging
15
+ logger = logging.getLogger(__name__)
16
+ logger.error(f"Error importing Instana SDK: {e}", exc_info=True)
17
+ raise
18
+
19
+ from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
20
+
21
+ # Configure logger for this module
22
+ logger = logging.getLogger(__name__)
23
+
24
+ class WebsiteCatalogMCPTools(BaseInstanaClient):
25
+ """Tools for website catalog in Instana MCP."""
26
+
27
+ def __init__(self, read_token: str, base_url: str):
28
+ """Initialize the Website Catalog MCP tools client."""
29
+ super().__init__(read_token=read_token, base_url=base_url)
30
+
31
+ @register_as_tool
32
+ @with_header_auth(WebsiteCatalogApi)
33
+ async def get_website_catalog_metrics(self, ctx=None, api_client=None) -> Dict[str, Any]:
34
+ """
35
+ Get website monitoring metrics catalog.
36
+
37
+ This API endpoint retrieves all available metric definitions for website monitoring.
38
+ Use this to discover what metrics are available for website monitoring.
39
+
40
+ Args:
41
+ ctx: The MCP context (optional)
42
+
43
+ Returns:
44
+ Dictionary containing available website metrics or error information
45
+ """
46
+ try:
47
+ logger.debug("get_website_catalog_metrics called")
48
+
49
+ # Call the get_website_catalog_metrics method from the SDK
50
+ result = api_client.get_website_catalog_metrics()
51
+
52
+ # Convert the result to a list of dictionaries
53
+ if isinstance(result, list):
54
+ # If it's a list, convert each item to dict if possible
55
+ result_list = []
56
+ for item in result:
57
+ if hasattr(item, 'to_dict'):
58
+ result_list.append(item.to_dict())
59
+ else:
60
+ result_list.append(item)
61
+ elif hasattr(result, 'to_dict'):
62
+ result_list = [result.to_dict()]
63
+ else:
64
+ # If it's already a list or another format, use it as is
65
+ result_list = result
66
+
67
+ # Ensure we always return a dictionary, not a list
68
+ if isinstance(result_list, list):
69
+ result_dict = {"metrics": result_list, "count": len(result_list)}
70
+ else:
71
+ result_dict = {"data": result_list}
72
+
73
+ logger.debug(f"Result from get_website_catalog_metrics: {result_dict}")
74
+ return result_dict
75
+ except Exception as e:
76
+ logger.error(f"Error in get_website_catalog_metrics: {e}", exc_info=True)
77
+ return {"error": f"Failed to get website catalog metrics: {e!s}"}
78
+
79
+ @register_as_tool
80
+ @with_header_auth(WebsiteCatalogApi)
81
+ async def get_website_catalog_tags(self, ctx=None, api_client=None) -> Dict[str, Any]:
82
+ """
83
+ Get website monitoring tags catalog.
84
+
85
+ This API endpoint retrieves all available tags for website monitoring.
86
+ Use this to discover what tags are available for filtering website beacons.
87
+
88
+ Args:
89
+ ctx: The MCP context (optional)
90
+
91
+ Returns:
92
+ Dictionary containing available website tags or error information
93
+ """
94
+ try:
95
+ logger.debug("get_website_catalog_tags called")
96
+
97
+ # Call the get_website_catalog_tags method from the SDK
98
+ result = api_client.get_website_catalog_tags()
99
+
100
+ # Convert the result to a list of dictionaries
101
+ if isinstance(result, list):
102
+ # If it's a list, convert each item to dict if possible
103
+ result_list = []
104
+ for item in result:
105
+ if hasattr(item, 'to_dict'):
106
+ result_list.append(item.to_dict())
107
+ else:
108
+ result_list.append(item)
109
+ elif hasattr(result, 'to_dict'):
110
+ result_list = [result.to_dict()]
111
+ else:
112
+ # If it's already a list or another format, use it as is
113
+ result_list = result
114
+
115
+ # Ensure we always return a dictionary, not a list
116
+ if isinstance(result_list, list):
117
+ result_dict = {"tags": result_list, "count": len(result_list)}
118
+ else:
119
+ result_dict = {"data": result_list}
120
+
121
+ logger.debug(f"Result from get_website_catalog_tags: {result_dict}")
122
+ return result_dict
123
+ except Exception as e:
124
+ logger.error(f"Error in get_website_catalog_tags: {e}", exc_info=True)
125
+ return {"error": f"Failed to get website catalog tags: {e!s}"}
126
+
127
+ @register_as_tool
128
+ @with_header_auth(WebsiteCatalogApi)
129
+ async def get_website_tag_catalog(self,
130
+ beacon_type: str,
131
+ use_case: str,
132
+ ctx=None, api_client=None) -> Dict[str, Any]:
133
+ """
134
+ Get website monitoring tag catalog.
135
+
136
+ This API endpoint retrieves all available tags for website monitoring.
137
+ Use this to discover what tags are available for filtering website beacons.
138
+
139
+ Args:
140
+ beacon_type: The beacon type (e.g., 'PAGELOAD')
141
+ use_case: The use case (e.g., 'GROUPING')
142
+ ctx: The MCP context (optional)
143
+
144
+ Returns:
145
+ Dictionary containing available website tags or error information
146
+ """
147
+ try:
148
+ logger.debug("get_website_tag_catalog called")
149
+ if not beacon_type:
150
+ return {"error": "beacon_type parameter is required"}
151
+ if not use_case:
152
+ return {"error": "use_case parameter is required"}
153
+
154
+ # Call the get_website_tag_catalog method from the SDK
155
+ result = api_client.get_website_tag_catalog(
156
+ beacon_type=beacon_type,
157
+ use_case=use_case
158
+ )
159
+
160
+ # Convert the result to a dictionary
161
+ if hasattr(result, 'to_dict'):
162
+ result_dict = result.to_dict()
163
+ else:
164
+ # If it's already a dict or another format, use it as is
165
+ result_dict = result
166
+
167
+ logger.debug(f"Result from get_website_tag_catalog: {result_dict}")
168
+ return result_dict
169
+ except Exception as e:
170
+ logger.error(f"Error in get_website_tag_catalog: {e}", exc_info=True)
171
+ return {"error": f"Failed to get website tag catalog: {e!s}"}