mcp-instana 0.6.2__py3-none-any.whl → 0.7.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.6.2.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +179 -120
- {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +28 -21
- src/application/application_alert_config.py +397 -146
- src/application/application_analyze.py +597 -597
- src/application/application_call_group.py +528 -0
- src/application/application_catalog.py +0 -8
- src/application/application_global_alert_config.py +255 -38
- src/application/application_metrics.py +377 -237
- src/application/application_resources.py +414 -365
- src/application/application_settings.py +605 -1651
- src/application/application_topology.py +62 -62
- src/core/custom_dashboard_smart_router_tool.py +135 -0
- src/core/server.py +92 -119
- src/core/smart_router_tool.py +574 -0
- src/core/utils.py +17 -8
- src/custom_dashboard/custom_dashboard_tools.py +422 -0
- src/infrastructure/elicitation_handler.py +338 -0
- src/infrastructure/entity_registry.py +329 -0
- src/infrastructure/infrastructure_analyze_new.py +600 -0
- src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
- src/infrastructure/infrastructure_catalog.py +7 -28
- src/infrastructure/infrastructure_metrics.py +93 -17
- src/infrastructure/infrastructure_resources.py +5 -20
- src/infrastructure/infrastructure_topology.py +2 -8
- src/prompts/application/application_settings.py +58 -0
- {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +0 -0
- {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
- {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Dashboard MCP Tools Module
|
|
3
|
+
|
|
4
|
+
This module provides custom dashboard-specific MCP tools for Instana monitoring.
|
|
5
|
+
Uses the api/custom-dashboard endpoints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from mcp.types import ToolAnnotations
|
|
13
|
+
|
|
14
|
+
from src.core.utils import (
|
|
15
|
+
BaseInstanaClient,
|
|
16
|
+
register_as_tool,
|
|
17
|
+
with_header_auth,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from instana_client.api.custom_dashboards_api import CustomDashboardsApi
|
|
22
|
+
from instana_client.models.custom_dashboard import CustomDashboard
|
|
23
|
+
|
|
24
|
+
except ImportError as e:
|
|
25
|
+
import logging
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
logger.error(f"Error importing Instana SDK: {e}", exc_info=True)
|
|
28
|
+
raise
|
|
29
|
+
|
|
30
|
+
# Configure logger for this module
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
class CustomDashboardMCPTools(BaseInstanaClient):
|
|
34
|
+
"""Tools for custom dashboards in Instana MCP."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, read_token: str, base_url: str):
|
|
37
|
+
"""Initialize the Custom Dashboard MCP tools client."""
|
|
38
|
+
super().__init__(read_token=read_token, base_url=base_url)
|
|
39
|
+
|
|
40
|
+
# CRUD Operations Dispatcher - called by custom_dashboard_smart_router_tool.py
|
|
41
|
+
async def execute_dashboard_operation(
|
|
42
|
+
self,
|
|
43
|
+
operation: str,
|
|
44
|
+
dashboard_id: Optional[str] = None,
|
|
45
|
+
custom_dashboard: Optional[Dict[str, Any]] = None,
|
|
46
|
+
ctx=None
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Execute Custom Dashboard CRUD operations.
|
|
50
|
+
Called by the custom dashboard smart router tool.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
operation: Operation to perform (get_all, get, create, update, delete, get_shareable_users, get_shareable_api_tokens)
|
|
54
|
+
dashboard_id: Dashboard ID
|
|
55
|
+
custom_dashboard: Dashboard configuration payload
|
|
56
|
+
ctx: MCP context
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Operation result dictionary
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
if operation == "get_all":
|
|
63
|
+
return await self.get_custom_dashboards(ctx=ctx)
|
|
64
|
+
elif operation == "get":
|
|
65
|
+
if not dashboard_id:
|
|
66
|
+
return {"error": "dashboard_id is required for 'get' operation"}
|
|
67
|
+
return await self.get_custom_dashboard(dashboard_id=dashboard_id, ctx=ctx)
|
|
68
|
+
elif operation == "create":
|
|
69
|
+
if not custom_dashboard:
|
|
70
|
+
return {"error": "custom_dashboard is required for 'create' operation"}
|
|
71
|
+
return await self.add_custom_dashboard(custom_dashboard=custom_dashboard, ctx=ctx)
|
|
72
|
+
elif operation == "update":
|
|
73
|
+
if not dashboard_id:
|
|
74
|
+
return {"error": "dashboard_id is required for 'update' operation"}
|
|
75
|
+
if not custom_dashboard:
|
|
76
|
+
return {"error": "custom_dashboard is required for 'update' operation"}
|
|
77
|
+
return await self.update_custom_dashboard(dashboard_id=dashboard_id, custom_dashboard=custom_dashboard, ctx=ctx)
|
|
78
|
+
elif operation == "delete":
|
|
79
|
+
if not dashboard_id:
|
|
80
|
+
return {"error": "dashboard_id is required for 'delete' operation"}
|
|
81
|
+
return await self.delete_custom_dashboard(dashboard_id=dashboard_id, ctx=ctx)
|
|
82
|
+
elif operation == "get_shareable_users":
|
|
83
|
+
if not dashboard_id:
|
|
84
|
+
return {"error": "dashboard_id is required for 'get_shareable_users' operation"}
|
|
85
|
+
return await self.get_shareable_users(dashboard_id=dashboard_id, ctx=ctx)
|
|
86
|
+
elif operation == "get_shareable_api_tokens":
|
|
87
|
+
if not dashboard_id:
|
|
88
|
+
return {"error": "dashboard_id is required for 'get_shareable_api_tokens' operation"}
|
|
89
|
+
return await self.get_shareable_api_tokens(dashboard_id=dashboard_id, ctx=ctx)
|
|
90
|
+
else:
|
|
91
|
+
return {"error": f"Operation '{operation}' not supported"}
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(f"Error executing {operation}: {e}", exc_info=True)
|
|
95
|
+
return {"error": f"Error executing {operation}: {e!s}"}
|
|
96
|
+
|
|
97
|
+
# Individual operation functions
|
|
98
|
+
|
|
99
|
+
@with_header_auth(CustomDashboardsApi)
|
|
100
|
+
async def get_custom_dashboards(self,
|
|
101
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
Get all custom dashboards from Instana server.
|
|
104
|
+
Uses api/custom-dashboard endpoint.
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
logger.debug("Getting custom dashboards from Instana SDK")
|
|
108
|
+
|
|
109
|
+
# Call the get_custom_dashboards method from the SDK
|
|
110
|
+
result = api_client.get_custom_dashboards()
|
|
111
|
+
|
|
112
|
+
# Convert the result to a dictionary
|
|
113
|
+
result_dict: Dict[str, Any] = {}
|
|
114
|
+
|
|
115
|
+
if hasattr(result, 'to_dict'):
|
|
116
|
+
result_dict = result.to_dict()
|
|
117
|
+
elif isinstance(result, dict):
|
|
118
|
+
result_dict = result
|
|
119
|
+
elif isinstance(result, list):
|
|
120
|
+
# If it's a list, wrap it in a dictionary
|
|
121
|
+
result_dict = {"items": result}
|
|
122
|
+
else:
|
|
123
|
+
# For any other type, convert to string and wrap
|
|
124
|
+
result_dict = {"result": str(result)}
|
|
125
|
+
|
|
126
|
+
# Limit the response size
|
|
127
|
+
if "items" in result_dict and isinstance(result_dict["items"], list):
|
|
128
|
+
# Limit items to top 10
|
|
129
|
+
items_list = result_dict["items"]
|
|
130
|
+
original_count = len(items_list)
|
|
131
|
+
if original_count > 10:
|
|
132
|
+
result_dict["items"] = items_list[:10]
|
|
133
|
+
logger.debug(f"Limited response items from {original_count} to 10")
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
logger.debug(f"Result from get_custom_dashboards: {json.dumps(result_dict, indent=2)}")
|
|
137
|
+
except TypeError:
|
|
138
|
+
logger.debug(f"Result from get_custom_dashboards: {result_dict} (not JSON serializable)")
|
|
139
|
+
|
|
140
|
+
return result_dict
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"Error in get_custom_dashboards: {e}", exc_info=True)
|
|
144
|
+
return {"error": f"Failed to get custom dashboards: {e!s}"}
|
|
145
|
+
|
|
146
|
+
@with_header_auth(CustomDashboardsApi)
|
|
147
|
+
async def get_custom_dashboard(self,
|
|
148
|
+
dashboard_id: str,
|
|
149
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
150
|
+
"""
|
|
151
|
+
Get a specific custom dashboard by ID from Instana server.
|
|
152
|
+
Uses api/custom-dashboard/{id} endpoint.
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
if not dashboard_id:
|
|
156
|
+
return {"error": "Dashboard ID is required for this operation"}
|
|
157
|
+
|
|
158
|
+
logger.debug(f"Getting custom dashboard {dashboard_id} from Instana SDK")
|
|
159
|
+
|
|
160
|
+
# Call the get_custom_dashboard method from the SDK
|
|
161
|
+
result = api_client.get_custom_dashboard(dashboard_id=dashboard_id)
|
|
162
|
+
|
|
163
|
+
# Convert the result to a dictionary
|
|
164
|
+
result_dict: Dict[str, Any] = {}
|
|
165
|
+
|
|
166
|
+
if hasattr(result, 'to_dict'):
|
|
167
|
+
result_dict = result.to_dict()
|
|
168
|
+
elif isinstance(result, dict):
|
|
169
|
+
result_dict = result
|
|
170
|
+
else:
|
|
171
|
+
# For any other type, convert to string and wrap
|
|
172
|
+
result_dict = {"result": str(result)}
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
logger.debug(f"Result from get_custom_dashboard: {json.dumps(result_dict, indent=2)}")
|
|
176
|
+
except TypeError:
|
|
177
|
+
logger.debug(f"Result from get_custom_dashboard: {result_dict} (not JSON serializable)")
|
|
178
|
+
|
|
179
|
+
return result_dict
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(f"Error in get_custom_dashboard: {e}", exc_info=True)
|
|
183
|
+
return {"error": f"Failed to get custom dashboard: {e!s}"}
|
|
184
|
+
|
|
185
|
+
@with_header_auth(CustomDashboardsApi)
|
|
186
|
+
async def add_custom_dashboard(self,
|
|
187
|
+
custom_dashboard: Dict[str, Any],
|
|
188
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
189
|
+
"""
|
|
190
|
+
Add a new custom dashboard to Instana server.
|
|
191
|
+
Uses api/custom-dashboard POST endpoint.
|
|
192
|
+
"""
|
|
193
|
+
try:
|
|
194
|
+
if not custom_dashboard:
|
|
195
|
+
return {"error": "Custom dashboard configuration is required for this operation"}
|
|
196
|
+
|
|
197
|
+
logger.debug("Adding custom dashboard to Instana SDK")
|
|
198
|
+
logger.debug(json.dumps(custom_dashboard, indent=2))
|
|
199
|
+
|
|
200
|
+
# Add a temporary ID for validation (will be replaced by server)
|
|
201
|
+
dashboard_config = custom_dashboard.copy()
|
|
202
|
+
if 'id' not in dashboard_config:
|
|
203
|
+
dashboard_config['id'] = '' # Empty string as placeholder
|
|
204
|
+
|
|
205
|
+
# Create the CustomDashboard object
|
|
206
|
+
dashboard_obj = CustomDashboard(**dashboard_config)
|
|
207
|
+
|
|
208
|
+
# Call the add_custom_dashboard method from the SDK
|
|
209
|
+
result = api_client.add_custom_dashboard(custom_dashboard=dashboard_obj)
|
|
210
|
+
|
|
211
|
+
# Convert the result to a dictionary
|
|
212
|
+
result_dict: Dict[str, Any] = {}
|
|
213
|
+
|
|
214
|
+
if hasattr(result, 'to_dict'):
|
|
215
|
+
result_dict = result.to_dict()
|
|
216
|
+
elif isinstance(result, dict):
|
|
217
|
+
result_dict = result
|
|
218
|
+
else:
|
|
219
|
+
# For any other type, convert to string and wrap
|
|
220
|
+
result_dict = {"result": str(result)}
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
logger.debug(f"Result from add_custom_dashboard: {json.dumps(result_dict, indent=2)}")
|
|
224
|
+
except TypeError:
|
|
225
|
+
logger.debug(f"Result from add_custom_dashboard: {result_dict} (not JSON serializable)")
|
|
226
|
+
|
|
227
|
+
return result_dict
|
|
228
|
+
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logger.error(f"Error in add_custom_dashboard: {e}", exc_info=True)
|
|
231
|
+
return {"error": f"Failed to add custom dashboard: {e!s}"}
|
|
232
|
+
|
|
233
|
+
@with_header_auth(CustomDashboardsApi)
|
|
234
|
+
async def update_custom_dashboard(self,
|
|
235
|
+
dashboard_id: str,
|
|
236
|
+
custom_dashboard: Dict[str, Any],
|
|
237
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
238
|
+
"""
|
|
239
|
+
Update an existing custom dashboard in Instana server.
|
|
240
|
+
Uses api/custom-dashboard/{id} PUT endpoint.
|
|
241
|
+
"""
|
|
242
|
+
try:
|
|
243
|
+
if not dashboard_id:
|
|
244
|
+
return {"error": "Dashboard ID is required for this operation"}
|
|
245
|
+
|
|
246
|
+
if not custom_dashboard:
|
|
247
|
+
return {"error": "Custom dashboard configuration is required for this operation"}
|
|
248
|
+
|
|
249
|
+
logger.debug(f"Updating custom dashboard {dashboard_id} in Instana SDK")
|
|
250
|
+
logger.debug(json.dumps(custom_dashboard, indent=2))
|
|
251
|
+
|
|
252
|
+
# Create the CustomDashboard object
|
|
253
|
+
dashboard_obj = CustomDashboard(**custom_dashboard)
|
|
254
|
+
|
|
255
|
+
# Call the update_custom_dashboard method from the SDK
|
|
256
|
+
result = api_client.update_custom_dashboard(
|
|
257
|
+
dashboard_id=dashboard_id,
|
|
258
|
+
custom_dashboard=dashboard_obj
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Convert the result to a dictionary
|
|
262
|
+
result_dict: Dict[str, Any] = {}
|
|
263
|
+
|
|
264
|
+
if hasattr(result, 'to_dict'):
|
|
265
|
+
result_dict = result.to_dict()
|
|
266
|
+
elif isinstance(result, dict):
|
|
267
|
+
result_dict = result
|
|
268
|
+
else:
|
|
269
|
+
# For any other type, convert to string and wrap
|
|
270
|
+
result_dict = {"result": str(result)}
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
logger.debug(f"Result from update_custom_dashboard: {json.dumps(result_dict, indent=2)}")
|
|
274
|
+
except TypeError:
|
|
275
|
+
logger.debug(f"Result from update_custom_dashboard: {result_dict} (not JSON serializable)")
|
|
276
|
+
|
|
277
|
+
return result_dict
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
logger.error(f"Error in update_custom_dashboard: {e}", exc_info=True)
|
|
281
|
+
return {"error": f"Failed to update custom dashboard: {e!s}"}
|
|
282
|
+
|
|
283
|
+
@with_header_auth(CustomDashboardsApi)
|
|
284
|
+
async def delete_custom_dashboard(self,
|
|
285
|
+
dashboard_id: str,
|
|
286
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
287
|
+
"""
|
|
288
|
+
Delete a custom dashboard from Instana server.
|
|
289
|
+
Uses api/custom-dashboard/{id} DELETE endpoint.
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
if not dashboard_id:
|
|
293
|
+
return {"error": "Dashboard ID is required for this operation"}
|
|
294
|
+
|
|
295
|
+
logger.debug(f"Deleting custom dashboard {dashboard_id} from Instana SDK")
|
|
296
|
+
|
|
297
|
+
# Call the delete_custom_dashboard method from the SDK
|
|
298
|
+
result = api_client.delete_custom_dashboard(dashboard_id=dashboard_id)
|
|
299
|
+
|
|
300
|
+
# Convert the result to a dictionary
|
|
301
|
+
result_dict: Dict[str, Any] = {}
|
|
302
|
+
|
|
303
|
+
if hasattr(result, 'to_dict'):
|
|
304
|
+
result_dict = result.to_dict()
|
|
305
|
+
elif isinstance(result, dict):
|
|
306
|
+
result_dict = result
|
|
307
|
+
else:
|
|
308
|
+
# For any other type, convert to string and wrap
|
|
309
|
+
result_dict = {"result": str(result)}
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
logger.debug(f"Result from delete_custom_dashboard: {json.dumps(result_dict, indent=2)}")
|
|
313
|
+
except TypeError:
|
|
314
|
+
logger.debug(f"Result from delete_custom_dashboard: {result_dict} (not JSON serializable)")
|
|
315
|
+
|
|
316
|
+
return result_dict
|
|
317
|
+
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.error(f"Error in delete_custom_dashboard: {e}", exc_info=True)
|
|
320
|
+
return {"error": f"Failed to delete custom dashboard: {e!s}"}
|
|
321
|
+
|
|
322
|
+
@with_header_auth(CustomDashboardsApi)
|
|
323
|
+
async def get_shareable_users(self,
|
|
324
|
+
dashboard_id: str,
|
|
325
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
326
|
+
"""
|
|
327
|
+
Get shareable users for a custom dashboard from Instana server.
|
|
328
|
+
Uses api/custom-dashboard/{id}/shareable-users endpoint.
|
|
329
|
+
"""
|
|
330
|
+
try:
|
|
331
|
+
if not dashboard_id:
|
|
332
|
+
return {"error": "Dashboard ID is required for this operation"}
|
|
333
|
+
|
|
334
|
+
logger.debug(f"Getting shareable users for dashboard {dashboard_id} from Instana SDK")
|
|
335
|
+
|
|
336
|
+
# Call the get_shareable_users method from the SDK
|
|
337
|
+
result = api_client.get_shareable_users(dashboard_id=dashboard_id)
|
|
338
|
+
|
|
339
|
+
# Convert the result to a dictionary
|
|
340
|
+
result_dict: Dict[str, Any] = {}
|
|
341
|
+
|
|
342
|
+
if hasattr(result, 'to_dict'):
|
|
343
|
+
result_dict = result.to_dict()
|
|
344
|
+
elif isinstance(result, dict):
|
|
345
|
+
result_dict = result
|
|
346
|
+
elif isinstance(result, list):
|
|
347
|
+
# If it's a list, wrap it in a dictionary
|
|
348
|
+
result_dict = {"items": result}
|
|
349
|
+
else:
|
|
350
|
+
# For any other type, convert to string and wrap
|
|
351
|
+
result_dict = {"result": str(result)}
|
|
352
|
+
|
|
353
|
+
# Limit the response size
|
|
354
|
+
if "items" in result_dict and isinstance(result_dict["items"], list):
|
|
355
|
+
# Limit items to top 20
|
|
356
|
+
items_list = result_dict["items"]
|
|
357
|
+
original_count = len(items_list)
|
|
358
|
+
if original_count > 20:
|
|
359
|
+
result_dict["items"] = items_list[:20]
|
|
360
|
+
logger.debug(f"Limited response items from {original_count} to 20")
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
logger.debug(f"Result from get_shareable_users: {json.dumps(result_dict, indent=2)}")
|
|
364
|
+
except TypeError:
|
|
365
|
+
logger.debug(f"Result from get_shareable_users: {result_dict} (not JSON serializable)")
|
|
366
|
+
|
|
367
|
+
return result_dict
|
|
368
|
+
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"Error in get_shareable_users: {e}", exc_info=True)
|
|
371
|
+
return {"error": f"Failed to get shareable users: {e!s}"}
|
|
372
|
+
|
|
373
|
+
@with_header_auth(CustomDashboardsApi)
|
|
374
|
+
async def get_shareable_api_tokens(self,
|
|
375
|
+
dashboard_id: str,
|
|
376
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
377
|
+
"""
|
|
378
|
+
Get shareable API tokens for a custom dashboard from Instana server.
|
|
379
|
+
Uses api/custom-dashboard/{id}/shareable-api-tokens endpoint.
|
|
380
|
+
"""
|
|
381
|
+
try:
|
|
382
|
+
if not dashboard_id:
|
|
383
|
+
return {"error": "Dashboard ID is required for this operation"}
|
|
384
|
+
|
|
385
|
+
logger.debug(f"Getting shareable API tokens for dashboard {dashboard_id} from Instana SDK")
|
|
386
|
+
|
|
387
|
+
# Call the get_shareable_api_tokens method from the SDK
|
|
388
|
+
result = api_client.get_shareable_api_tokens(dashboard_id=dashboard_id)
|
|
389
|
+
|
|
390
|
+
# Convert the result to a dictionary
|
|
391
|
+
result_dict: Dict[str, Any] = {}
|
|
392
|
+
|
|
393
|
+
if hasattr(result, 'to_dict'):
|
|
394
|
+
result_dict = result.to_dict()
|
|
395
|
+
elif isinstance(result, dict):
|
|
396
|
+
result_dict = result
|
|
397
|
+
elif isinstance(result, list):
|
|
398
|
+
# If it's a list, wrap it in a dictionary
|
|
399
|
+
result_dict = {"items": result}
|
|
400
|
+
else:
|
|
401
|
+
# For any other type, convert to string and wrap
|
|
402
|
+
result_dict = {"result": str(result)}
|
|
403
|
+
|
|
404
|
+
# Limit the response size
|
|
405
|
+
if "items" in result_dict and isinstance(result_dict["items"], list):
|
|
406
|
+
# Limit items to top 10
|
|
407
|
+
items_list = result_dict["items"]
|
|
408
|
+
original_count = len(items_list)
|
|
409
|
+
if original_count > 10:
|
|
410
|
+
result_dict["items"] = items_list[:10]
|
|
411
|
+
logger.debug(f"Limited response items from {original_count} to 10")
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
logger.debug(f"Result from get_shareable_api_tokens: {json.dumps(result_dict, indent=2)}")
|
|
415
|
+
except TypeError:
|
|
416
|
+
logger.debug(f"Result from get_shareable_api_tokens: {result_dict} (not JSON serializable)")
|
|
417
|
+
|
|
418
|
+
return result_dict
|
|
419
|
+
|
|
420
|
+
except Exception as e:
|
|
421
|
+
logger.error(f"Error in get_shareable_api_tokens: {e}", exc_info=True)
|
|
422
|
+
return {"error": f"Failed to get shareable API tokens: {e!s}"}
|