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,770 @@
1
+ """
2
+ Website Configuration MCP Tools Module
3
+
4
+ This module provides website configuration-specific MCP tools for Instana monitoring.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, List, Optional, Union
9
+
10
+ # Import the necessary classes from the SDK
11
+ try:
12
+ from instana_client.api.website_configuration_api import WebsiteConfigurationApi
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 WebsiteConfigurationMCPTools(BaseInstanaClient):
25
+ """Tools for website configuration in Instana MCP."""
26
+
27
+ def __init__(self, read_token: str, base_url: str):
28
+ """Initialize the Website Configuration MCP tools client."""
29
+ super().__init__(read_token=read_token, base_url=base_url)
30
+
31
+ @register_as_tool
32
+ @with_header_auth(WebsiteConfigurationApi)
33
+ async def get_websites(self, ctx=None, api_client=None) -> List[Dict[str, Any]]:
34
+ """
35
+ Get all websites.
36
+
37
+ This API endpoint retrieves all configured websites in your Instana environment.
38
+
39
+ Args:
40
+ ctx: The MCP context (optional)
41
+
42
+ Returns:
43
+ Dictionary containing websites data or error information
44
+ """
45
+ try:
46
+ logger.debug("get_websites called")
47
+
48
+ # Call the get_websites method from the SDK
49
+ result = api_client.get_websites()
50
+
51
+ # Convert the result to a dictionary
52
+ if hasattr(result, 'to_dict'):
53
+ result_dict = result.to_dict()
54
+ else:
55
+ # If it's already a dict or another format, use it as is
56
+ result_dict = result
57
+
58
+ logger.debug(f"Result from get_websites: {result_dict}")
59
+ return result_dict
60
+ except Exception as e:
61
+ logger.error(f"Error in get_websites: {e}", exc_info=True)
62
+ return [{"error": f"Failed to get websites: {e!s}"}]
63
+
64
+ @register_as_tool
65
+ @with_header_auth(WebsiteConfigurationApi)
66
+ async def get_website(self, website_id: str, ctx=None, api_client=None) -> Dict[str, Any]:
67
+ """
68
+ Get a specific website by ID.
69
+
70
+ This API endpoint retrieves configuration details for a specific website.
71
+
72
+ Args:
73
+ website_id: ID of the website to retrieve
74
+ ctx: The MCP context (optional)
75
+
76
+ Returns:
77
+ Dictionary containing website data or error information
78
+ """
79
+ try:
80
+ logger.debug(f"get_website called with website_id={website_id}")
81
+
82
+ # Call the get_website method from the SDK
83
+ result = api_client.get_website(website_id=website_id)
84
+
85
+ # Convert the result to a dictionary
86
+ if hasattr(result, 'to_dict'):
87
+ result_dict = result.to_dict()
88
+ else:
89
+ # If it's already a dict or another format, use it as is
90
+ result_dict = result
91
+
92
+ logger.debug(f"Result from get_website: {result_dict}")
93
+ return result_dict
94
+ except Exception as e:
95
+ logger.error(f"Error in get_website: {e}", exc_info=True)
96
+ return {"error": f"Failed to get website: {e!s}"}
97
+
98
+ @register_as_tool
99
+ @with_header_auth(WebsiteConfigurationApi)
100
+ async def create_website(self,
101
+ name: str,
102
+ payload: Optional[Dict[str, Any]] = None,
103
+ ctx=None,
104
+ api_client=None) -> Dict[str, Any]:
105
+ """
106
+ Create a new website configuration.
107
+
108
+ This API endpoint creates a new website configuration in your Instana environment.
109
+
110
+ Args:
111
+ name: Name of the website
112
+ payload: Website configuration payload as a dictionary or JSON string
113
+ ctx: The MCP context (optional)
114
+
115
+ Returns:
116
+ Dictionary containing created website data or error information
117
+ """
118
+ try:
119
+ # Parse the payload if it's a string
120
+ if isinstance(payload, str):
121
+ logger.debug("Payload is a string, attempting to parse")
122
+ try:
123
+ import json
124
+ try:
125
+ parsed_payload = json.loads(payload)
126
+ logger.debug("Successfully parsed payload as JSON")
127
+ request_body = parsed_payload
128
+ except json.JSONDecodeError as e:
129
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
130
+
131
+ # Try replacing single quotes with double quotes
132
+ fixed_payload = payload.replace("'", "\"")
133
+ try:
134
+ parsed_payload = json.loads(fixed_payload)
135
+ logger.debug("Successfully parsed fixed JSON")
136
+ request_body = parsed_payload
137
+ except json.JSONDecodeError:
138
+ # Try as Python literal
139
+ import ast
140
+ try:
141
+ parsed_payload = ast.literal_eval(payload)
142
+ logger.debug("Successfully parsed payload as Python literal")
143
+ request_body = parsed_payload
144
+ except (SyntaxError, ValueError) as e2:
145
+ logger.debug(f"Failed to parse payload string: {e2}")
146
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
147
+ except Exception as e:
148
+ logger.debug(f"Error parsing payload string: {e}")
149
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
150
+ else:
151
+ # If payload is already a dictionary, use it directly
152
+ logger.debug("Using provided payload dictionary")
153
+ request_body = payload
154
+
155
+ # Create an Website object from the request body
156
+ try:
157
+ query_params = {}
158
+ if request_body and "display_name" in request_body:
159
+ query_params["display_name"] = request_body["display_name"]
160
+ if request_body and "id" in request_body:
161
+ query_params["id"] = request_body["id"]
162
+ logger.debug(f"Creating Website with params: {query_params}")
163
+ except Exception as e:
164
+ logger.debug(f"Error creating create_website: {e}")
165
+ return {"error": f"Failed to create website: {e!s}"}
166
+
167
+ # Call the create_website method from the SDK
168
+ logger.debug("Calling create_website with config object")
169
+ result = api_client.create_website(
170
+ name=name
171
+ )
172
+ # Convert the result to a dictionary
173
+ if hasattr(result, 'to_dict'):
174
+ result_dict = result.to_dict()
175
+ else:
176
+ # If it's already a dict or another format, use it as is
177
+ result_dict = result or {
178
+ "success": True,
179
+ "message": "Create website"
180
+ }
181
+
182
+ logger.debug(f"Result from create_website: {result_dict}")
183
+ return result_dict
184
+ except Exception as e:
185
+ logger.error(f"Error in create_website: {e}")
186
+ return {"error": f"Failed to create website: {e!s}"}
187
+
188
+ @register_as_tool
189
+ @with_header_auth(WebsiteConfigurationApi)
190
+ async def delete_website(self, website_id: str, ctx=None, api_client=None) -> Dict[str, Any]:
191
+ """
192
+ Delete a website configuration.
193
+
194
+ This API endpoint deletes a website configuration from your Instana environment.
195
+
196
+ Args:
197
+ website_id: ID of the website to delete
198
+ ctx: The MCP context (optional)
199
+
200
+ Returns:
201
+ Dictionary containing deletion result or error information
202
+ """
203
+ try:
204
+ logger.debug(f"delete_website called with website_id={website_id}")
205
+
206
+ # Call the delete_website method from the SDK
207
+ api_client.delete_website(website_id=website_id)
208
+
209
+ logger.debug("Website deleted successfully")
210
+ return {"success": True, "message": f"Website {website_id} deleted successfully"}
211
+ except Exception as e:
212
+ logger.error(f"Error in delete_website: {e}", exc_info=True)
213
+ return {"error": f"Failed to delete website: {e!s}"}
214
+
215
+ @register_as_tool
216
+ @with_header_auth(WebsiteConfigurationApi)
217
+ async def rename_website(self,
218
+ website_id: str,
219
+ name: Optional[str] = None,
220
+ ctx=None,
221
+ api_client=None) -> Dict[str, Any]:
222
+ """
223
+ Rename a website configuration.
224
+
225
+ This API endpoint renames a website configuration in your Instana environment.
226
+
227
+ Args:
228
+ website_id: ID of the website to rename
229
+ name: New name for the website
230
+ ctx: The MCP context (optional)
231
+
232
+ Returns:
233
+ Dictionary containing rename result or error information
234
+ """
235
+
236
+ try:
237
+ logger.debug(f"rename_website called with website_id={website_id}")
238
+
239
+ if not website_id:
240
+ return {"error": "website_id parameter is required"}
241
+
242
+ # Call the rename_website method from the SDK
243
+ result = api_client.rename_website(website_id=website_id,name=name)
244
+
245
+ # Convert the result to a dictionary
246
+ if hasattr(result, 'to_dict'):
247
+ result_dict = result.to_dict()
248
+ else:
249
+ # If it's already a dict or another format, use it as is
250
+ result_dict = result
251
+
252
+ logger.debug(f"Result from rename_website: {result_dict}")
253
+ return result_dict
254
+ except Exception as e:
255
+ logger.error(f"Error in rename_website: {e}", exc_info=True)
256
+ return {"error": f"Failed to rename website: {e!s}"}
257
+
258
+ @register_as_tool
259
+ @with_header_auth(WebsiteConfigurationApi)
260
+ async def get_website_geo_location_configuration(self,
261
+ website_id: str,
262
+ ctx=None,
263
+ api_client=None) -> Dict[str, Any]:
264
+ """
265
+ Get geo-location configuration for a website.
266
+
267
+ This API endpoint retrieves geo-location configuration for a specific website.
268
+
269
+ Args:
270
+ website_id: ID of the website
271
+ ctx: The MCP context (optional)
272
+
273
+ Returns:
274
+ Dictionary containing geo-location configuration or error information
275
+ """
276
+ try:
277
+ logger.debug(f"get_website_geo_location_configuration called with website_id={website_id}")
278
+
279
+ # Call the get_website_geo_location_configuration method from the SDK
280
+ result = api_client.get_website_geo_location_configuration(website_id=website_id)
281
+
282
+ # Convert the result to a dictionary
283
+ if hasattr(result, 'to_dict'):
284
+ result_dict = result.to_dict()
285
+ else:
286
+ # If it's already a dict or another format, use it as is
287
+ result_dict = result
288
+
289
+ logger.debug(f"Result from get_website_geo_location_configuration: {result_dict}")
290
+ return result_dict
291
+ except Exception as e:
292
+ logger.error(f"Error in get_website_geo_location_configuration: {e}", exc_info=True)
293
+ return {"error": f"Failed to get website geo-location configuration: {e!s}"}
294
+
295
+ @register_as_tool
296
+ @with_header_auth(WebsiteConfigurationApi)
297
+ async def update_website_geo_location_configuration(self,
298
+ website_id: str,
299
+ payload: Union[Dict[str, Any], str],
300
+ ctx=None, api_client=None) -> Dict[str, Any]:
301
+ """
302
+ Update geo-location configuration for a website.
303
+
304
+ This API endpoint updates geo-location configuration for a specific website.
305
+
306
+ Args:
307
+ website_id: ID of the website
308
+ payload: Geo-location configuration payload as a dictionary or JSON string
309
+ ctx: The MCP context (optional)
310
+
311
+ Returns:
312
+ Dictionary containing updated configuration or error information
313
+ """
314
+ try:
315
+ logger.debug("update_website_geo_location_configuration called")
316
+
317
+ # Parse the payload
318
+ if isinstance(payload, str):
319
+ logger.debug("payload is a string, attempting to parse")
320
+ try:
321
+ import json
322
+ try:
323
+ parsed_payload = json.loads(payload)
324
+ logger.debug("Successfully parsed payload as JSON")
325
+ request_body = parsed_payload
326
+ except json.JSONDecodeError as e:
327
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
328
+
329
+ # Try replacing single quotes with double quotes
330
+ fixed_payload = payload.replace("'", "\"")
331
+ try:
332
+ parsed_payload = json.loads(fixed_payload)
333
+ logger.debug("Successfully parsed fixed JSON")
334
+ request_body = parsed_payload
335
+ except json.JSONDecodeError:
336
+ # Try as Python literal
337
+ import ast
338
+ try:
339
+ parsed_payload = ast.literal_eval(payload)
340
+ logger.debug("Successfully parsed payload as Python literal")
341
+ request_body = parsed_payload
342
+ except (SyntaxError, ValueError) as e2:
343
+ logger.debug(f"Failed to parse payload string: {e2}")
344
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
345
+ except Exception as e:
346
+ logger.debug(f"Error parsing payload string: {e}")
347
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
348
+ else:
349
+ # If payload is already a dictionary, use it directly
350
+ logger.debug("Using provided payload dictionary")
351
+ request_body = payload
352
+
353
+ try:
354
+ from instana_client.models.geo_location_configuration import (
355
+ GeoLocationConfiguration,
356
+ )
357
+ logger.debug("Successfully imported GeoLocationConfiguration")
358
+ except ImportError as e:
359
+ logger.debug(f"Error importing GeoLocationConfiguration: {e}")
360
+ return {"error": f"Failed to import GeoLocationConfiguration: {e!s}"}
361
+
362
+ # Create an GeoLocationConfiguration object from the request body
363
+ try:
364
+ query_params = {}
365
+
366
+ # Required field: geo_detail_removal (with alias geoDetailRemoval)
367
+ # Always provide a default value to ensure the required field is present
368
+ query_params["geo_detail_removal"] = "NO_REMOVAL" # Default value
369
+
370
+ if request_body:
371
+ if "geoDetailRemoval" in request_body:
372
+ query_params["geo_detail_removal"] = request_body["geoDetailRemoval"]
373
+ elif "geo_detail_removal" in request_body:
374
+ query_params["geo_detail_removal"] = request_body["geo_detail_removal"]
375
+
376
+ # Optional field: geo_mapping_rules
377
+ if "geoMappingRules" in request_body:
378
+ query_params["geo_mapping_rules"] = request_body["geoMappingRules"]
379
+ elif "geo_mapping_rules" in request_body:
380
+ query_params["geo_mapping_rules"] = request_body["geo_mapping_rules"]
381
+
382
+ logger.debug(f"Creating GeoLocationConfiguration with params: {query_params}")
383
+ config_object = GeoLocationConfiguration(**query_params)
384
+ logger.debug("Successfully created GeoLocationConfiguration object")
385
+ except Exception as e:
386
+ logger.debug(f"Error creating GeoLocationConfiguration: {e}")
387
+ return {"error": f"Failed to create GeoLocationConfiguration: {e!s}"}
388
+
389
+ # Call the update_website_geo_location_configuration method from the SDK
390
+ logger.debug("Calling update_website_geo_location_configuration with config object")
391
+ result = api_client.update_website_geo_location_configuration(
392
+ website_id=website_id,
393
+ geo_location_configuration=config_object,
394
+ )
395
+ # Convert the result to a dictionary
396
+ if hasattr(result, 'to_dict'):
397
+ result_dict = result.to_dict()
398
+ else:
399
+ # If it's already a dict or another format, use it as is
400
+ result_dict = result or {
401
+ "success": True,
402
+ "message": "Update website geo-location configuration"
403
+ }
404
+
405
+ logger.debug(f"Result from update_website_geo_location_configuration: {result_dict}")
406
+ return result_dict
407
+ except Exception as e:
408
+ logger.error(f"Error in update_website_geo_location_configuration: {e}")
409
+ return {"error": f"Failed to update website geo-location configuration: {e!s}"}
410
+
411
+ @register_as_tool
412
+ @with_header_auth(WebsiteConfigurationApi)
413
+ async def get_website_ip_masking_configuration(self, website_id: str, ctx=None, api_client=None) -> Dict[str, Any]:
414
+ """
415
+ Get IP masking configuration for a website.
416
+
417
+ This API endpoint retrieves IP masking configuration for a specific website.
418
+
419
+ Args:
420
+ website_id: ID of the website
421
+ ctx: The MCP context (optional)
422
+
423
+ Returns:
424
+ Dictionary containing IP masking configuration or error information
425
+ """
426
+ try:
427
+ logger.debug(f"get_website_ip_masking_configuration called with website_id={website_id}")
428
+
429
+ # Call the get_website_ip_masking_configuration method from the SDK
430
+ result = api_client.get_website_ip_masking_configuration(website_id=website_id)
431
+
432
+ # Convert the result to a dictionary
433
+ if hasattr(result, 'to_dict'):
434
+ result_dict = result.to_dict()
435
+ else:
436
+ # If it's already a dict or another format, use it as is
437
+ result_dict = result
438
+
439
+ logger.debug(f"Result from get_website_ip_masking_configuration: {result_dict}")
440
+ return result_dict
441
+ except Exception as e:
442
+ logger.error(f"Error in get_website_ip_masking_configuration: {e}", exc_info=True)
443
+ return {"error": f"Failed to get website IP masking configuration: {e!s}"}
444
+
445
+ @register_as_tool
446
+ @with_header_auth(WebsiteConfigurationApi)
447
+ async def update_website_ip_masking_configuration(self,
448
+ website_id: str,
449
+ payload: Optional[Dict[str, Any]] = None,
450
+ ctx=None, api_client=None) -> Dict[str, Any]:
451
+ """
452
+ Update IP masking configuration for a website.
453
+
454
+ This API endpoint updates IP masking configuration for a specific website.
455
+
456
+ Args:
457
+ website_id: ID of the website
458
+ payload: IP masking configuration payload as a dictionary or JSON string
459
+ {
460
+ "ipMasking": "DEFAULT"
461
+ }
462
+ ctx: The MCP context (optional)
463
+
464
+ Returns:
465
+ Dictionary containing updated configuration or error information
466
+ """
467
+ try:
468
+ logger.debug("update_website_geo_location_configuration called")
469
+
470
+ # Parse the payload
471
+ if isinstance(payload, str):
472
+ logger.debug("payload is a string, attempting to parse")
473
+ try:
474
+ import json
475
+ try:
476
+ parsed_payload = json.loads(payload)
477
+ logger.debug("Successfully parsed payload as JSON")
478
+ request_body = parsed_payload
479
+ except json.JSONDecodeError as e:
480
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
481
+
482
+ # Try replacing single quotes with double quotes
483
+ fixed_payload = payload.replace("'", "\"")
484
+ try:
485
+ parsed_payload = json.loads(fixed_payload)
486
+ logger.debug("Successfully parsed fixed JSON")
487
+ request_body = parsed_payload
488
+ except json.JSONDecodeError:
489
+ # Try as Python literal
490
+ import ast
491
+ try:
492
+ parsed_payload = ast.literal_eval(payload)
493
+ logger.debug("Successfully parsed payload as Python literal")
494
+ request_body = parsed_payload
495
+ except (SyntaxError, ValueError) as e2:
496
+ logger.debug(f"Failed to parse payload string: {e2}")
497
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
498
+ except Exception as e:
499
+ logger.debug(f"Error parsing payload string: {e}")
500
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
501
+ else:
502
+ # If payload is already a dictionary, use it directly
503
+ logger.debug("Using provided payload dictionary")
504
+ request_body = payload
505
+
506
+ try:
507
+ from instana_client.models.ip_masking_configuration import (
508
+ IpMaskingConfiguration,
509
+ )
510
+ logger.debug("Successfully imported IpMaskingConfiguration")
511
+ except ImportError as e:
512
+ logger.debug(f"Error importing IpMaskingConfiguration: {e}")
513
+ return {"error": f"Failed to import IpMaskingConfiguration: {e!s}"}
514
+
515
+ # Create an IpMaskingConfiguration object from the request body
516
+ try:
517
+ query_params = {}
518
+
519
+ # Required field: ip_masking (with alias ipMasking)
520
+ # Always provide a default value to ensure the required field is present
521
+ query_params["ip_masking"] = "DEFAULT" # Default value
522
+
523
+ if request_body:
524
+ if "ipMasking" in request_body:
525
+ query_params["ip_masking"] = request_body["ipMasking"]
526
+ elif "ip_masking" in request_body:
527
+ query_params["ip_masking"] = request_body["ip_masking"]
528
+
529
+ logger.debug(f"Creating IpMaskingConfiguration with params: {query_params}")
530
+ config_object = IpMaskingConfiguration(**query_params)
531
+ logger.debug("Successfully created IpMaskingConfiguration object")
532
+ except Exception as e:
533
+ logger.debug(f"Error creating IpMaskingConfiguration: {e}")
534
+ return {"error": f"Failed to create IpMaskingConfiguration: {e!s}"}
535
+
536
+ # Call the update_website_geo_location_configuration method from the SDK
537
+ logger.debug("Calling update_website_ip_masking_configuration with config object")
538
+ result = api_client.update_website_ip_masking_configuration(
539
+ website_id=website_id,
540
+ ip_masking_configuration=config_object,
541
+ )
542
+ # Convert the result to a dictionary
543
+ if hasattr(result, 'to_dict'):
544
+ result_dict = result.to_dict()
545
+ else:
546
+ # If it's already a dict or another format, use it as is
547
+ result_dict = result or {
548
+ "success": True,
549
+ "message": "Update website ip-masking configuration"
550
+ }
551
+
552
+ logger.debug(f"Result from update_website_ip_masking_configuration: {result_dict}")
553
+ return result_dict
554
+ except Exception as e:
555
+ logger.error(f"Error in update_website_ip_masking_configuration: {e}")
556
+ return {"error": f"Failed to update website ip-masking configuration: {e!s}"}
557
+
558
+ @register_as_tool
559
+ @with_header_auth(WebsiteConfigurationApi)
560
+ async def get_website_geo_mapping_rules(self, website_id: str, ctx=None, api_client=None) -> List[Dict[str, Any]]:
561
+ """
562
+ Get custom geo mapping rules for website
563
+
564
+ This API endpoint retrieves custom geo mapping rules for a specific website.
565
+
566
+ Args:
567
+ website_id: ID of the website
568
+ ctx: The MCP context (optional)
569
+
570
+ Returns:
571
+ List containing custom geo mapping rules or error information
572
+ """
573
+ try:
574
+ logger.debug(f"get_website_geo_mapping_rules called with website_id={website_id}")
575
+
576
+ # Call the get_website_geo_mapping_rules method from the SDK
577
+ # Use the raw response method to get the actual CSV data
578
+ try:
579
+ result = api_client.get_website_geo_mapping_rules(website_id=website_id)
580
+
581
+ # If the high-level method returns None, try the raw response
582
+ if result is None:
583
+ response = api_client.get_website_geo_mapping_rules_without_preload_content(website_id=website_id)
584
+ # Get the raw response data
585
+ if hasattr(response, 'data'):
586
+ csv_data = response.data.decode('utf-8') if isinstance(response.data, bytes) else str(response.data)
587
+ else:
588
+ csv_data = str(response)
589
+ else:
590
+ csv_data = str(result)
591
+
592
+ except Exception as api_error:
593
+ logger.warning(f"High-level API call failed: {api_error}, trying raw response")
594
+ # Fallback to raw HTTP response
595
+ response = api_client.get_website_geo_mapping_rules_without_preload_content(website_id=website_id)
596
+ if hasattr(response, 'data'):
597
+ csv_data = response.data.decode('utf-8') if isinstance(response.data, bytes) else str(response.data)
598
+ else:
599
+ csv_data = str(response)
600
+
601
+ # Handle CSV response format
602
+ result_list: List[Dict[str, Any]] = []
603
+
604
+ if csv_data and ',' in csv_data:
605
+ # Parse CSV response
606
+ import csv
607
+ import io
608
+
609
+ csv_reader = csv.DictReader(io.StringIO(csv_data))
610
+ for row in csv_reader:
611
+ result_list.append(dict(row))
612
+ elif csv_data:
613
+ # If it's not CSV but has data, return as single item
614
+ result_list.append({"data": csv_data})
615
+
616
+ logger.debug(f"Result from get_website_geo_mapping_rules: {result_list}")
617
+ return result_list
618
+ except Exception as e:
619
+ logger.error(f"Error in get_website_geo_mapping_rules: {e}", exc_info=True)
620
+ return [{"error": f"Failed to get website geo mapping rules: {e!s}"}]
621
+
622
+ @register_as_tool
623
+ @with_header_auth(WebsiteConfigurationApi)
624
+ async def set_website_geo_mapping_rules(self,
625
+ website_id: str,
626
+ body: Optional[str] = None,
627
+ ctx=None, api_client=None) -> Dict[str, Any]:
628
+ """
629
+ Set custom geo mapping rules for website
630
+
631
+ This API endpoint sets custom geo mapping rules for a specific website.
632
+
633
+ Args:
634
+ website_id: ID of the website
635
+ body: Geo mapping rules payload as a string
636
+ ctx: The MCP context (optional)
637
+
638
+ Returns:
639
+ Dictionary containing custom geo mapping rules or error information
640
+ """
641
+ try:
642
+ logger.debug(f"set_website_geo_mapping_rules called with website_id={website_id}")
643
+
644
+ if not website_id:
645
+ return {"error": "website_id parameter is required"}
646
+
647
+ # Call the set_website_geo_mapping_rules method from the SDK
648
+ # The API automatically sets content-type to text/csv
649
+ result = api_client.set_website_geo_mapping_rules(
650
+ website_id=website_id,
651
+ body=body
652
+ )
653
+
654
+ # Convert the result to a dictionary
655
+ if hasattr(result, 'to_dict'):
656
+ result_dict = result.to_dict()
657
+ else:
658
+ # If it's already a dict or another format, use it as is
659
+ result_dict = result
660
+
661
+ logger.debug(f"Result from set_website_geo_mapping_rules: {result_dict}")
662
+ return result_dict
663
+ except Exception as e:
664
+ logger.error(f"Error in set_website_geo_mapping_rules: {e}", exc_info=True)
665
+ return {"error": f"Failed to set website geo mapping rules: {e!s}"}
666
+
667
+ @register_as_tool
668
+ @with_header_auth(WebsiteConfigurationApi)
669
+ async def upload_source_map_file(self,
670
+ website_id: str,
671
+ source_map_config_id: str,
672
+ file_format: Optional[str] = None,
673
+ source_map: Optional[str] = None,
674
+ url: Optional[str] = None,
675
+ ctx=None, api_client=None) -> Dict[str, Any]:
676
+ """
677
+ Upload source map file for a website.
678
+
679
+ This API endpoint uploads a source map file for a specific website.
680
+
681
+ Args:
682
+ website_id: ID of the website
683
+ source_map_config_id: ID of the source map config
684
+ file_format: Format of the source map file
685
+ source_map: Source map file
686
+ url: URL of the source map file
687
+ ctx: The MCP context (optional)
688
+
689
+ Returns:
690
+ Dictionary containing updated configuration or error information
691
+ """
692
+ try:
693
+ if not website_id:
694
+ return {"error": "website_id parameter is required"}
695
+ if not source_map_config_id:
696
+ return {"error": "source_map_config_id parameter is required"}
697
+
698
+ logger.debug("upload_source_map_file called")
699
+
700
+ # Call the upload_source_map_file method from the SDK
701
+ logger.debug("Calling upload_source_map_file with config object")
702
+ result = api_client.upload_source_map_file(
703
+ website_id=website_id,
704
+ source_map_config_id=source_map_config_id,
705
+ file_format=file_format,
706
+ source_map=source_map,
707
+ url=url,
708
+ )
709
+ # Convert the result to a dictionary
710
+ if hasattr(result, 'to_dict'):
711
+ result_dict = result.to_dict()
712
+ else:
713
+ # If it's already a dict or another format, use it as is
714
+ result_dict = result or {
715
+ "success": True,
716
+ "message": "Upload source map file"
717
+ }
718
+
719
+ logger.debug(f"Result from upload_source_map_file: {result_dict}")
720
+ return result_dict
721
+ except Exception as e:
722
+ logger.error(f"Error in upload_source_map_file: {e}")
723
+ return {"error": f"Failed to upload source map file: {e!s}"}
724
+
725
+ @register_as_tool
726
+ @with_header_auth(WebsiteConfigurationApi)
727
+ async def clear_source_map_upload_configuration(self,
728
+ website_id: str,
729
+ source_map_config_id: str,
730
+ ctx=None, api_client=None) -> Dict[str, Any]:
731
+ """
732
+ Clear source map upload configuration for a website.
733
+
734
+ This API endpoint clears source map upload configuration for a specific website.
735
+
736
+ Args:
737
+ website_id: ID of the website
738
+ source_map_config_id: ID of the source map config
739
+ ctx: The MCP context (optional)
740
+
741
+ Returns:
742
+ Dictionary containing source map upload configuration or error information
743
+ """
744
+ try:
745
+ if not website_id:
746
+ return {"error": "website_id parameter is required"}
747
+ if not source_map_config_id:
748
+ return {"error": "source_map_config_id parameter is required"}
749
+
750
+ logger.debug(f"clear_source_map_upload_configuration called with website_id={website_id} and source_map_config_id={source_map_config_id}")
751
+
752
+ # Call the clear_source_map_upload_configuration method from the SDK
753
+ result = api_client.clear_source_map_upload_configuration(
754
+ website_id=website_id,
755
+ source_map_config_id=source_map_config_id)
756
+
757
+ # Convert the result to a dictionary
758
+ if hasattr(result, 'to_dict'):
759
+ result_dict = result.to_dict()
760
+ else:
761
+ # If it's already a dict or another format, use it as is
762
+ result_dict = result
763
+
764
+ logger.debug(f"Result from clear_source_map_upload_configuration: {result_dict}")
765
+ return result_dict
766
+ except Exception as e:
767
+ logger.error(f"Error in clear_source_map_upload_configuration: {e}", exc_info=True)
768
+ return {"error": f"Failed to clear source map upload configuration: {e!s}"}
769
+
770
+