mcp-instana 0.1.0__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 (67) hide show
  1. mcp_instana-0.2.0.dist-info/METADATA +1229 -0
  2. mcp_instana-0.2.0.dist-info/RECORD +59 -0
  3. {mcp_instana-0.1.0.dist-info → mcp_instana-0.2.0.dist-info}/WHEEL +1 -1
  4. mcp_instana-0.2.0.dist-info/entry_points.txt +4 -0
  5. mcp_instana-0.1.0.dist-info/LICENSE → mcp_instana-0.2.0.dist-info/licenses/LICENSE.md +3 -3
  6. src/application/__init__.py +1 -0
  7. src/{client/application_alert_config_mcp_tools.py → application/application_alert_config.py} +251 -273
  8. src/application/application_analyze.py +628 -0
  9. src/application/application_catalog.py +155 -0
  10. src/application/application_global_alert_config.py +653 -0
  11. src/{client/application_metrics_mcp_tools.py → application/application_metrics.py} +113 -131
  12. src/{client/application_resources_mcp_tools.py → application/application_resources.py} +131 -151
  13. src/application/application_settings.py +1731 -0
  14. src/application/application_topology.py +111 -0
  15. src/automation/action_catalog.py +416 -0
  16. src/automation/action_history.py +338 -0
  17. src/core/__init__.py +1 -0
  18. src/core/server.py +586 -0
  19. src/core/utils.py +213 -0
  20. src/event/__init__.py +1 -0
  21. src/event/events_tools.py +850 -0
  22. src/infrastructure/__init__.py +1 -0
  23. src/{client/infrastructure_analyze_mcp_tools.py → infrastructure/infrastructure_analyze.py} +207 -206
  24. src/{client/infrastructure_catalog_mcp_tools.py → infrastructure/infrastructure_catalog.py} +197 -265
  25. src/infrastructure/infrastructure_metrics.py +171 -0
  26. src/{client/infrastructure_resources_mcp_tools.py → infrastructure/infrastructure_resources.py} +198 -227
  27. src/{client/infrastructure_topology_mcp_tools.py → infrastructure/infrastructure_topology.py} +110 -109
  28. src/log/__init__.py +1 -0
  29. src/log/log_alert_configuration.py +331 -0
  30. src/prompts/__init__.py +16 -0
  31. src/prompts/application/__init__.py +1 -0
  32. src/prompts/application/application_alerts.py +54 -0
  33. src/prompts/application/application_catalog.py +26 -0
  34. src/prompts/application/application_metrics.py +57 -0
  35. src/prompts/application/application_resources.py +26 -0
  36. src/prompts/application/application_settings.py +75 -0
  37. src/prompts/application/application_topology.py +30 -0
  38. src/prompts/events/__init__.py +1 -0
  39. src/prompts/events/events_tools.py +161 -0
  40. src/prompts/infrastructure/infrastructure_analyze.py +72 -0
  41. src/prompts/infrastructure/infrastructure_catalog.py +53 -0
  42. src/prompts/infrastructure/infrastructure_metrics.py +45 -0
  43. src/prompts/infrastructure/infrastructure_resources.py +74 -0
  44. src/prompts/infrastructure/infrastructure_topology.py +38 -0
  45. src/prompts/settings/__init__.py +0 -0
  46. src/prompts/settings/custom_dashboard.py +157 -0
  47. src/prompts/website/__init__.py +1 -0
  48. src/prompts/website/website_analyze.py +35 -0
  49. src/prompts/website/website_catalog.py +40 -0
  50. src/prompts/website/website_configuration.py +105 -0
  51. src/prompts/website/website_metrics.py +34 -0
  52. src/settings/__init__.py +1 -0
  53. src/settings/custom_dashboard_tools.py +417 -0
  54. src/website/__init__.py +0 -0
  55. src/website/website_analyze.py +433 -0
  56. src/website/website_catalog.py +171 -0
  57. src/website/website_configuration.py +770 -0
  58. src/website/website_metrics.py +241 -0
  59. mcp_instana-0.1.0.dist-info/METADATA +0 -649
  60. mcp_instana-0.1.0.dist-info/RECORD +0 -19
  61. mcp_instana-0.1.0.dist-info/entry_points.txt +0 -3
  62. src/client/What is the sum of queue depth for all q +0 -55
  63. src/client/events_mcp_tools.py +0 -531
  64. src/client/instana_client_base.py +0 -93
  65. src/client/log_alert_configuration_mcp_tools.py +0 -316
  66. src/client/show the top 5 services with the highest +0 -28
  67. src/mcp_server.py +0 -343
@@ -0,0 +1,105 @@
1
+ from typing import Optional, Union
2
+
3
+ from src.prompts import auto_register_prompt
4
+
5
+
6
+ class WebsiteConfigurationPrompts:
7
+ """Class containing website configuration related prompts"""
8
+
9
+ @auto_register_prompt
10
+ @staticmethod
11
+ def get_websites() -> str:
12
+ """Retrieve all configured websites in your Instana environment"""
13
+ return """
14
+ Get all websites to see configured website monitoring setups.
15
+ """
16
+
17
+ @auto_register_prompt
18
+ @staticmethod
19
+ def get_website(website_id: str) -> str:
20
+ """Retrieve configuration details for a specific website"""
21
+ return f"""
22
+ Get website configuration:
23
+ - Website ID: {website_id}
24
+ """
25
+
26
+ @auto_register_prompt
27
+ @staticmethod
28
+ def create_website(payload: Union[dict, str]) -> str:
29
+ """Create a new website configuration in your Instana environment"""
30
+ return f"""
31
+ Create website with configuration:
32
+ - Payload: {payload}
33
+ """
34
+
35
+ @auto_register_prompt
36
+ @staticmethod
37
+ def delete_website(website_id: str) -> str:
38
+ """Delete a website configuration from your Instana environment"""
39
+ return f"""
40
+ Delete website:
41
+ - Website ID: {website_id}
42
+ """
43
+
44
+ @auto_register_prompt
45
+ @staticmethod
46
+ def rename_website(website_id: str, payload: Union[dict, str]) -> str:
47
+ """Rename a website configuration in your Instana environment"""
48
+ return f"""
49
+ Rename website:
50
+ - Website ID: {website_id}
51
+ - Rename payload: {payload}
52
+ """
53
+
54
+ @auto_register_prompt
55
+ @staticmethod
56
+ def get_website_geo_location_configuration(website_id: str) -> str:
57
+ """Retrieve geo-location configuration for a specific website"""
58
+ return f"""
59
+ Get website geo-location configuration:
60
+ - Website ID: {website_id}
61
+ """
62
+
63
+ @auto_register_prompt
64
+ @staticmethod
65
+ def update_website_geo_location_configuration(website_id: str, payload: Union[dict, str]) -> str:
66
+ """Update geo-location configuration for a specific website"""
67
+ return f"""
68
+ Update website geo-location configuration:
69
+ - Website ID: {website_id}
70
+ - Configuration payload: {payload}
71
+ """
72
+
73
+ @auto_register_prompt
74
+ @staticmethod
75
+ def get_website_ip_masking_configuration(website_id: str) -> str:
76
+ """Retrieve IP masking configuration for a specific website"""
77
+ return f"""
78
+ Get website IP masking configuration:
79
+ - Website ID: {website_id}
80
+ """
81
+
82
+ @auto_register_prompt
83
+ @staticmethod
84
+ def update_website_ip_masking_configuration(website_id: str, payload: Union[dict, str]) -> str:
85
+ """Update IP masking configuration for a specific website"""
86
+ return f"""
87
+ Update website IP masking configuration:
88
+ - Website ID: {website_id}
89
+ - Configuration payload: {payload}
90
+ """
91
+
92
+ @classmethod
93
+ def get_prompts(cls):
94
+ """Return all prompts defined in this class"""
95
+ return [
96
+ ('get_websites', cls.get_websites),
97
+ ('get_website', cls.get_website),
98
+ ('create_website', cls.create_website),
99
+ ('delete_website', cls.delete_website),
100
+ ('rename_website', cls.rename_website),
101
+ ('get_website_geo_location_configuration', cls.get_website_geo_location_configuration),
102
+ ('update_website_geo_location_configuration', cls.update_website_geo_location_configuration),
103
+ ('get_website_ip_masking_configuration', cls.get_website_ip_masking_configuration),
104
+ ('update_website_ip_masking_configuration', cls.update_website_ip_masking_configuration),
105
+ ]
@@ -0,0 +1,34 @@
1
+ from typing import Optional
2
+
3
+ from src.prompts import auto_register_prompt
4
+
5
+
6
+ class WebsiteMetricsPrompts:
7
+ """Class containing website metrics related prompts"""
8
+
9
+ @auto_register_prompt
10
+ @staticmethod
11
+ def get_website_beacon_metrics_v2(payload: Optional[dict] = None) -> str:
12
+ """Retrieve website beacon metrics using the v2 API including page load times, error rates, etc., over a given time frame"""
13
+ return f"""
14
+ Get website beacon metrics v2 with payload:
15
+ - Payload: {payload or 'None (will use default payload)'}
16
+ """
17
+
18
+ @auto_register_prompt
19
+ @staticmethod
20
+ def get_website_page_load(page_id: str, timestamp: int) -> str:
21
+ """Retrieve detailed beacon information for a specific page load event"""
22
+ return f"""
23
+ Get website page load details:
24
+ - Page ID: {page_id}
25
+ - Timestamp: {timestamp}
26
+ """
27
+
28
+ @classmethod
29
+ def get_prompts(cls):
30
+ """Return all prompts defined in this class"""
31
+ return [
32
+ ('get_website_beacon_metrics_v2', cls.get_website_beacon_metrics_v2),
33
+ ('get_website_page_load', cls.get_website_page_load),
34
+ ]
@@ -0,0 +1 @@
1
+ # Settings Module for Instana MCP
@@ -0,0 +1,417 @@
1
+ """
2
+ Custom Dashboard MCP Tools Module
3
+
4
+ This module provides custom dashboard-specific MCP tools for Instana monitoring.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from src.core.utils import (
12
+ BaseInstanaClient,
13
+ register_as_tool,
14
+ with_header_auth,
15
+ )
16
+
17
+ try:
18
+ from instana_client.api.custom_dashboards_api import CustomDashboardsApi
19
+ from instana_client.models.custom_dashboard import CustomDashboard
20
+
21
+ except ImportError as e:
22
+ import logging
23
+ logger = logging.getLogger(__name__)
24
+ logger.error(f"Error importing Instana SDK: {e}", exc_info=True)
25
+ raise
26
+
27
+ # Configure logger for this module
28
+ logger = logging.getLogger(__name__)
29
+
30
+ class CustomDashboardMCPTools(BaseInstanaClient):
31
+ """Tools for custom dashboards in Instana MCP."""
32
+
33
+ def __init__(self, read_token: str, base_url: str):
34
+ """Initialize the Custom Dashboard MCP tools client."""
35
+ super().__init__(read_token=read_token, base_url=base_url)
36
+
37
+ @register_as_tool
38
+ @with_header_auth(CustomDashboardsApi)
39
+ async def get_custom_dashboards(self,
40
+ ctx=None, api_client=None) -> Dict[str, Any]:
41
+ """
42
+ Get all custom dashboards from Instana server.
43
+ This tool retrieves a list of all custom dashboards configured in your Instana environment.
44
+ Use this tool to see what dashboards are available and their basic information.
45
+
46
+ Args:
47
+ ctx: The MCP context (optional)
48
+
49
+ Returns:
50
+ Dictionary containing custom dashboards data or error information
51
+ """
52
+ try:
53
+ logger.debug("Getting custom dashboards from Instana SDK")
54
+
55
+ # Call the get_custom_dashboards method from the SDK
56
+ result = api_client.get_custom_dashboards()
57
+
58
+ # Convert the result to a dictionary
59
+ result_dict: Dict[str, Any] = {}
60
+
61
+ if hasattr(result, 'to_dict'):
62
+ result_dict = result.to_dict()
63
+ elif isinstance(result, dict):
64
+ result_dict = result
65
+ elif isinstance(result, list):
66
+ # If it's a list, wrap it in a dictionary
67
+ result_dict = {"items": result}
68
+ else:
69
+ # For any other type, convert to string and wrap
70
+ result_dict = {"result": str(result)}
71
+
72
+ # Limit the response size
73
+ if "items" in result_dict and isinstance(result_dict["items"], list):
74
+ # Limit items to top 10
75
+ items_list = result_dict["items"]
76
+ original_count = len(items_list)
77
+ if original_count > 10:
78
+ result_dict["items"] = items_list[:10]
79
+ logger.debug(f"Limited response items from {original_count} to 10")
80
+
81
+ try:
82
+ logger.debug(f"Result from get_custom_dashboards: {json.dumps(result_dict, indent=2)}")
83
+ except TypeError:
84
+ logger.debug(f"Result from get_custom_dashboards: {result_dict} (not JSON serializable)")
85
+
86
+ return result_dict
87
+
88
+ except Exception as e:
89
+ logger.error(f"Error in get_custom_dashboards: {e}", exc_info=True)
90
+ return {"error": f"Failed to get custom dashboards: {e!s}"}
91
+
92
+ @register_as_tool
93
+ @with_header_auth(CustomDashboardsApi)
94
+ async def get_custom_dashboard(self,
95
+ dashboard_id: str,
96
+ ctx=None, api_client=None) -> Dict[str, Any]:
97
+ """
98
+ Get a specific custom dashboard by ID from Instana server.
99
+ This tool retrieves detailed information about a specific custom dashboard including
100
+ its widgets, access rules, and configuration.
101
+
102
+ Args:
103
+ dashboard_id: The ID of the custom dashboard to retrieve
104
+ ctx: The MCP context (optional)
105
+
106
+ Returns:
107
+ Dictionary containing custom dashboard data or error information
108
+ """
109
+ try:
110
+ if not dashboard_id:
111
+ return {"error": "Dashboard ID is required for this operation"}
112
+
113
+ logger.debug(f"Getting custom dashboard {dashboard_id} from Instana SDK")
114
+
115
+ # Call the get_custom_dashboard method from the SDK
116
+ result = api_client.get_custom_dashboard(dashboard_id=dashboard_id)
117
+
118
+ # Convert the result to a dictionary
119
+ result_dict: Dict[str, Any] = {}
120
+
121
+ if hasattr(result, 'to_dict'):
122
+ result_dict = result.to_dict()
123
+ elif isinstance(result, dict):
124
+ result_dict = result
125
+ else:
126
+ # For any other type, convert to string and wrap
127
+ result_dict = {"result": str(result)}
128
+
129
+ try:
130
+ logger.debug(f"Result from get_custom_dashboard: {json.dumps(result_dict, indent=2)}")
131
+ except TypeError:
132
+ logger.debug(f"Result from get_custom_dashboard: {result_dict} (not JSON serializable)")
133
+
134
+ return result_dict
135
+
136
+ except Exception as e:
137
+ logger.error(f"Error in get_custom_dashboard: {e}", exc_info=True)
138
+ return {"error": f"Failed to get custom dashboard: {e!s}"}
139
+
140
+ @register_as_tool
141
+ @with_header_auth(CustomDashboardsApi)
142
+ async def add_custom_dashboard(self,
143
+ custom_dashboard: Dict[str, Any],
144
+ ctx=None, api_client=None) -> Dict[str, Any]:
145
+ """
146
+ Add a new custom dashboard to Instana server.
147
+ This tool creates a new custom dashboard with the specified configuration,
148
+ widgets, and access rules.
149
+
150
+ Args:
151
+ custom_dashboard: Dictionary containing dashboard configuration including:
152
+ - title: Dashboard title
153
+ - widgets: List of widget configurations
154
+ - accessRules: List of access rules
155
+ ctx: The MCP context (optional)
156
+
157
+ Returns:
158
+ Dictionary containing the created custom dashboard data or error information
159
+ """
160
+ try:
161
+ if not custom_dashboard:
162
+ return {"error": "Custom dashboard configuration is required for this operation"}
163
+
164
+ logger.debug("Adding custom dashboard to Instana SDK")
165
+ logger.debug(json.dumps(custom_dashboard, indent=2))
166
+
167
+ # Create the CustomDashboard object
168
+ dashboard_obj = CustomDashboard(**custom_dashboard)
169
+
170
+ # Call the add_custom_dashboard method from the SDK
171
+ result = api_client.add_custom_dashboard(custom_dashboard=dashboard_obj)
172
+
173
+ # Convert the result to a dictionary
174
+ result_dict: Dict[str, Any] = {}
175
+
176
+ if hasattr(result, 'to_dict'):
177
+ result_dict = result.to_dict()
178
+ elif isinstance(result, dict):
179
+ result_dict = result
180
+ else:
181
+ # For any other type, convert to string and wrap
182
+ result_dict = {"result": str(result)}
183
+
184
+ try:
185
+ logger.debug(f"Result from add_custom_dashboard: {json.dumps(result_dict, indent=2)}")
186
+ except TypeError:
187
+ logger.debug(f"Result from add_custom_dashboard: {result_dict} (not JSON serializable)")
188
+
189
+ return result_dict
190
+
191
+ except Exception as e:
192
+ logger.error(f"Error in add_custom_dashboard: {e}", exc_info=True)
193
+ return {"error": f"Failed to add custom dashboard: {e!s}"}
194
+
195
+ @register_as_tool
196
+ @with_header_auth(CustomDashboardsApi)
197
+ async def update_custom_dashboard(self,
198
+ dashboard_id: str,
199
+ custom_dashboard: Dict[str, Any],
200
+ ctx=None, api_client=None) -> Dict[str, Any]:
201
+ """
202
+ Update an existing custom dashboard in Instana server.
203
+ This tool updates a custom dashboard with new configuration, widgets, or access rules.
204
+
205
+ Args:
206
+ dashboard_id: The ID of the custom dashboard to update
207
+ custom_dashboard: Dictionary containing updated dashboard configuration
208
+ ctx: The MCP context (optional)
209
+
210
+ Returns:
211
+ Dictionary containing the updated custom dashboard data or error information
212
+ """
213
+ try:
214
+ if not dashboard_id:
215
+ return {"error": "Dashboard ID is required for this operation"}
216
+
217
+ if not custom_dashboard:
218
+ return {"error": "Custom dashboard configuration is required for this operation"}
219
+
220
+ logger.debug(f"Updating custom dashboard {dashboard_id} in Instana SDK")
221
+ logger.debug(json.dumps(custom_dashboard, indent=2))
222
+
223
+ # Create the CustomDashboard object
224
+ dashboard_obj = CustomDashboard(**custom_dashboard)
225
+
226
+ # Call the update_custom_dashboard method from the SDK
227
+ result = api_client.update_custom_dashboard(
228
+ dashboard_id=dashboard_id,
229
+ custom_dashboard=dashboard_obj
230
+ )
231
+
232
+ # Convert the result to a dictionary
233
+ result_dict: Dict[str, Any] = {}
234
+
235
+ if hasattr(result, 'to_dict'):
236
+ result_dict = result.to_dict()
237
+ elif isinstance(result, dict):
238
+ result_dict = result
239
+ else:
240
+ # For any other type, convert to string and wrap
241
+ result_dict = {"result": str(result)}
242
+
243
+ try:
244
+ logger.debug(f"Result from update_custom_dashboard: {json.dumps(result_dict, indent=2)}")
245
+ except TypeError:
246
+ logger.debug(f"Result from update_custom_dashboard: {result_dict} (not JSON serializable)")
247
+
248
+ return result_dict
249
+
250
+ except Exception as e:
251
+ logger.error(f"Error in update_custom_dashboard: {e}", exc_info=True)
252
+ return {"error": f"Failed to update custom dashboard: {e!s}"}
253
+
254
+ @register_as_tool
255
+ @with_header_auth(CustomDashboardsApi)
256
+ async def delete_custom_dashboard(self,
257
+ dashboard_id: str,
258
+ ctx=None, api_client=None) -> Dict[str, Any]:
259
+ """
260
+ Delete a custom dashboard from Instana server.
261
+ This tool removes a custom dashboard from your Instana environment.
262
+
263
+ Args:
264
+ dashboard_id: The ID of the custom dashboard to delete
265
+ ctx: The MCP context (optional)
266
+
267
+ Returns:
268
+ Dictionary containing deletion status or error information
269
+ """
270
+ try:
271
+ if not dashboard_id:
272
+ return {"error": "Dashboard ID is required for this operation"}
273
+
274
+ logger.debug(f"Deleting custom dashboard {dashboard_id} from Instana SDK")
275
+
276
+ # Call the delete_custom_dashboard method from the SDK
277
+ result = api_client.delete_custom_dashboard(dashboard_id=dashboard_id)
278
+
279
+ # Convert the result to a dictionary
280
+ result_dict: Dict[str, Any] = {}
281
+
282
+ if hasattr(result, 'to_dict'):
283
+ result_dict = result.to_dict()
284
+ elif isinstance(result, dict):
285
+ result_dict = result
286
+ else:
287
+ # For any other type, convert to string and wrap
288
+ result_dict = {"result": str(result)}
289
+
290
+ try:
291
+ logger.debug(f"Result from delete_custom_dashboard: {json.dumps(result_dict, indent=2)}")
292
+ except TypeError:
293
+ logger.debug(f"Result from delete_custom_dashboard: {result_dict} (not JSON serializable)")
294
+
295
+ return result_dict
296
+
297
+ except Exception as e:
298
+ logger.error(f"Error in delete_custom_dashboard: {e}", exc_info=True)
299
+ return {"error": f"Failed to delete custom dashboard: {e!s}"}
300
+
301
+ @register_as_tool
302
+ @with_header_auth(CustomDashboardsApi)
303
+ async def get_shareable_users(self,
304
+ dashboard_id: str,
305
+ ctx=None, api_client=None) -> Dict[str, Any]:
306
+ """
307
+ Get shareable users for a custom dashboard from Instana server.
308
+ This tool retrieves the list of users who can be granted access to a specific custom dashboard.
309
+
310
+ Args:
311
+ dashboard_id: The ID of the custom dashboard
312
+ ctx: The MCP context (optional)
313
+
314
+ Returns:
315
+ Dictionary containing shareable users data or error information
316
+ """
317
+ try:
318
+ if not dashboard_id:
319
+ return {"error": "Dashboard ID is required for this operation"}
320
+
321
+ logger.debug(f"Getting shareable users for dashboard {dashboard_id} from Instana SDK")
322
+
323
+ # Call the get_shareable_users method from the SDK
324
+ result = api_client.get_shareable_users(dashboard_id=dashboard_id)
325
+
326
+ # Convert the result to a dictionary
327
+ result_dict: Dict[str, Any] = {}
328
+
329
+ if hasattr(result, 'to_dict'):
330
+ result_dict = result.to_dict()
331
+ elif isinstance(result, dict):
332
+ result_dict = result
333
+ elif isinstance(result, list):
334
+ # If it's a list, wrap it in a dictionary
335
+ result_dict = {"items": result}
336
+ else:
337
+ # For any other type, convert to string and wrap
338
+ result_dict = {"result": str(result)}
339
+
340
+ # Limit the response size
341
+ if "items" in result_dict and isinstance(result_dict["items"], list):
342
+ # Limit items to top 20
343
+ items_list = result_dict["items"]
344
+ original_count = len(items_list)
345
+ if original_count > 20:
346
+ result_dict["items"] = items_list[:20]
347
+ logger.debug(f"Limited response items from {original_count} to 20")
348
+
349
+ try:
350
+ logger.debug(f"Result from get_shareable_users: {json.dumps(result_dict, indent=2)}")
351
+ except TypeError:
352
+ logger.debug(f"Result from get_shareable_users: {result_dict} (not JSON serializable)")
353
+
354
+ return result_dict
355
+
356
+ except Exception as e:
357
+ logger.error(f"Error in get_shareable_users: {e}", exc_info=True)
358
+ return {"error": f"Failed to get shareable users: {e!s}"}
359
+
360
+ @register_as_tool
361
+ @with_header_auth(CustomDashboardsApi)
362
+ async def get_shareable_api_tokens(self,
363
+ dashboard_id: str,
364
+ ctx=None, api_client=None) -> Dict[str, Any]:
365
+ """
366
+ Get shareable API tokens for a custom dashboard from Instana server.
367
+ This tool retrieves the list of API tokens that can be used to access a specific custom dashboard.
368
+
369
+ Args:
370
+ dashboard_id: The ID of the custom dashboard
371
+ ctx: The MCP context (optional)
372
+
373
+ Returns:
374
+ Dictionary containing shareable API tokens data or error information
375
+ """
376
+ try:
377
+ if not dashboard_id:
378
+ return {"error": "Dashboard ID is required for this operation"}
379
+
380
+ logger.debug(f"Getting shareable API tokens for dashboard {dashboard_id} from Instana SDK")
381
+
382
+ # Call the get_shareable_api_tokens method from the SDK
383
+ result = api_client.get_shareable_api_tokens(dashboard_id=dashboard_id)
384
+
385
+ # Convert the result to a dictionary
386
+ result_dict: Dict[str, Any] = {}
387
+
388
+ if hasattr(result, 'to_dict'):
389
+ result_dict = result.to_dict()
390
+ elif isinstance(result, dict):
391
+ result_dict = result
392
+ elif isinstance(result, list):
393
+ # If it's a list, wrap it in a dictionary
394
+ result_dict = {"items": result}
395
+ else:
396
+ # For any other type, convert to string and wrap
397
+ result_dict = {"result": str(result)}
398
+
399
+ # Limit the response size
400
+ if "items" in result_dict and isinstance(result_dict["items"], list):
401
+ # Limit items to top 10
402
+ items_list = result_dict["items"]
403
+ original_count = len(items_list)
404
+ if original_count > 10:
405
+ result_dict["items"] = items_list[:10]
406
+ logger.debug(f"Limited response items from {original_count} to 10")
407
+
408
+ try:
409
+ logger.debug(f"Result from get_shareable_api_tokens: {json.dumps(result_dict, indent=2)}")
410
+ except TypeError:
411
+ logger.debug(f"Result from get_shareable_api_tokens: {result_dict} (not JSON serializable)")
412
+
413
+ return result_dict
414
+
415
+ except Exception as e:
416
+ logger.error(f"Error in get_shareable_api_tokens: {e}", exc_info=True)
417
+ return {"error": f"Failed to get shareable API tokens: {e!s}"}
File without changes