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
|
@@ -73,14 +73,101 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
73
73
|
traceback.print_exc(file=sys.stderr)
|
|
74
74
|
raise
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
# CRUD Operations Dispatcher - called by smart_router_tool.py
|
|
77
|
+
async def execute_settings_operation(
|
|
78
|
+
self,
|
|
79
|
+
operation: str,
|
|
80
|
+
resource_subtype: str,
|
|
81
|
+
id: Optional[str] = None,
|
|
82
|
+
payload: Optional[Union[Dict[str, Any], str]] = None,
|
|
83
|
+
request_body: Optional[List[str]] = None,
|
|
84
|
+
ctx=None
|
|
85
|
+
) -> Dict[str, Any]:
|
|
86
|
+
"""
|
|
87
|
+
Execute Application Settings CRUD operations.
|
|
88
|
+
Called by the smart router tool.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
operation: Operation to perform (get_all, get, create, update, delete, order, replace_all)
|
|
92
|
+
resource_subtype: Type of settings resource (application, endpoint, service, manual_service)
|
|
93
|
+
id: Resource ID (for get, update, delete operations)
|
|
94
|
+
payload: Configuration payload (for create, update operations)
|
|
95
|
+
request_body: List of IDs (for order, replace_all operations)
|
|
96
|
+
ctx: MCP context
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Operation result dictionary
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
# Route based on resource_subtype and operation
|
|
103
|
+
if resource_subtype == "application":
|
|
104
|
+
if operation == "get_all":
|
|
105
|
+
return await self._get_all_applications_configs(ctx)
|
|
106
|
+
elif operation == "get":
|
|
107
|
+
return await self._get_application_config(id, ctx)
|
|
108
|
+
elif operation == "create":
|
|
109
|
+
return await self._add_application_config(payload, ctx)
|
|
110
|
+
elif operation == "update":
|
|
111
|
+
return await self._update_application_config(id, payload, ctx)
|
|
112
|
+
elif operation == "delete":
|
|
113
|
+
return await self._delete_application_config(id, ctx)
|
|
114
|
+
|
|
115
|
+
elif resource_subtype == "endpoint":
|
|
116
|
+
if operation == "get_all":
|
|
117
|
+
return await self._get_all_endpoint_configs(ctx)
|
|
118
|
+
elif operation == "get":
|
|
119
|
+
return await self._get_endpoint_config(id, ctx)
|
|
120
|
+
elif operation == "create":
|
|
121
|
+
return await self._create_endpoint_config(payload, ctx)
|
|
122
|
+
elif operation == "update":
|
|
123
|
+
return await self._update_endpoint_config(id, payload, ctx)
|
|
124
|
+
elif operation == "delete":
|
|
125
|
+
return await self._delete_endpoint_config(id, ctx)
|
|
126
|
+
|
|
127
|
+
elif resource_subtype == "service":
|
|
128
|
+
if operation == "get_all":
|
|
129
|
+
return await self._get_all_service_configs(ctx)
|
|
130
|
+
elif operation == "get":
|
|
131
|
+
return await self._get_service_config(id, ctx)
|
|
132
|
+
elif operation == "create":
|
|
133
|
+
return await self._add_service_config(payload, ctx)
|
|
134
|
+
elif operation == "update":
|
|
135
|
+
return await self._update_service_config(id, payload, ctx)
|
|
136
|
+
elif operation == "delete":
|
|
137
|
+
return await self._delete_service_config(id, ctx)
|
|
138
|
+
elif operation == "order":
|
|
139
|
+
return await self._order_service_config(request_body, ctx)
|
|
140
|
+
elif operation == "replace_all":
|
|
141
|
+
return await self._replace_all_service_configs(payload, ctx)
|
|
142
|
+
|
|
143
|
+
elif resource_subtype == "manual_service":
|
|
144
|
+
if operation == "get_all":
|
|
145
|
+
return await self._get_all_manual_service_configs(ctx)
|
|
146
|
+
elif operation == "create":
|
|
147
|
+
return await self._add_manual_service_config(payload, ctx)
|
|
148
|
+
elif operation == "update":
|
|
149
|
+
return await self._update_manual_service_config(id, payload, ctx)
|
|
150
|
+
elif operation == "delete":
|
|
151
|
+
return await self._delete_manual_service_config(id, ctx)
|
|
152
|
+
elif operation == "replace_all":
|
|
153
|
+
return await self._replace_all_manual_service_config(payload, ctx)
|
|
154
|
+
|
|
155
|
+
return {"error": f"Operation '{operation}' not supported for resource_subtype '{resource_subtype}'"}
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Error executing {operation} on {resource_subtype}: {e}", exc_info=True)
|
|
159
|
+
return {"error": f"Error executing {operation} on {resource_subtype}: {e!s}"}
|
|
160
|
+
|
|
161
|
+
# Individual operation functions
|
|
162
|
+
|
|
163
|
+
# @register_as_tool(
|
|
164
|
+
# title="Get All Applications Configs",
|
|
165
|
+
# annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
166
|
+
# )
|
|
80
167
|
@with_header_auth(ApplicationSettingsApi)
|
|
81
|
-
async def
|
|
82
|
-
|
|
83
|
-
|
|
168
|
+
async def _get_all_applications_configs(self,
|
|
169
|
+
ctx=None,
|
|
170
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
84
171
|
"""
|
|
85
172
|
All Application Perspectives Configuration
|
|
86
173
|
Get a list of all Application Perspectives with their configuration settings.
|
|
@@ -117,1815 +204,682 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
117
204
|
traceback.print_exc(file=sys.stderr)
|
|
118
205
|
return [{"error": f"Failed to get all applications: {e!s}"}]
|
|
119
206
|
|
|
120
|
-
|
|
121
|
-
title="Add Application Config",
|
|
122
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
123
|
-
)
|
|
124
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
125
|
-
async def add_application_config(self,
|
|
126
|
-
payload: Union[Dict[str, Any], str],
|
|
127
|
-
ctx=None,
|
|
128
|
-
api_client=None) -> Dict[str, Any]:
|
|
207
|
+
def _validate_and_prepare_application_payload(self, payload: Union[Dict[str, Any], str]) -> Dict[str, Any]:
|
|
129
208
|
"""
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
Sample Payload: {
|
|
133
|
-
"accessRules": [
|
|
134
|
-
{
|
|
135
|
-
"accessType": "READ_WRITE",
|
|
136
|
-
"relationType": "GLOBAL",
|
|
137
|
-
"relatedId": null
|
|
138
|
-
}
|
|
139
|
-
],
|
|
140
|
-
"boundaryScope": "INBOUND",
|
|
141
|
-
"label": "Discount Build 6987",
|
|
142
|
-
"scope": "INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING",
|
|
143
|
-
"tagFilterExpression": {
|
|
144
|
-
"type": "EXPRESSION",
|
|
145
|
-
"logicalOperator": "AND",
|
|
146
|
-
"elements": [
|
|
147
|
-
{
|
|
148
|
-
"type": "TAG_FILTER",
|
|
149
|
-
"name": "kubernetes.label",
|
|
150
|
-
"stringValue": "stage=canary",
|
|
151
|
-
"numberValue": null,
|
|
152
|
-
"booleanValue": null,
|
|
153
|
-
"key": "stage",
|
|
154
|
-
"value": "canary",
|
|
155
|
-
"operator": "EQUALS",
|
|
156
|
-
"entity": "DESTINATION"
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
"type": "TAG_FILTER",
|
|
160
|
-
"name": "kubernetes.label",
|
|
161
|
-
"stringValue": "build=6987",
|
|
162
|
-
"numberValue": null,
|
|
163
|
-
"booleanValue": null,
|
|
164
|
-
"key": "build",
|
|
165
|
-
"value": "6987",
|
|
166
|
-
"operator": "EQUALS",
|
|
167
|
-
"entity": "DESTINATION"
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
"type": "TAG_FILTER",
|
|
171
|
-
"name": "kubernetes.label",
|
|
172
|
-
"stringValue": "app=discount",
|
|
173
|
-
"numberValue": null,
|
|
174
|
-
"booleanValue": null,
|
|
175
|
-
"key": "app",
|
|
176
|
-
"value": "discount",
|
|
177
|
-
"operator": "EQUALS",
|
|
178
|
-
"entity": "DESTINATION"
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
}
|
|
209
|
+
Validate and prepare application configuration payload with proper defaults.
|
|
210
|
+
|
|
183
211
|
Returns:
|
|
184
|
-
|
|
212
|
+
Dict with either 'payload' (validated) or 'error' and 'missing_fields'
|
|
185
213
|
"""
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return {"error": "payload is required"}
|
|
190
|
-
|
|
191
|
-
# Parse the payload if it's a string
|
|
192
|
-
if isinstance(payload, str):
|
|
193
|
-
logger.debug("Payload is a string, attempting to parse")
|
|
194
|
-
try:
|
|
195
|
-
import json
|
|
196
|
-
try:
|
|
197
|
-
parsed_payload = json.loads(payload)
|
|
198
|
-
logger.debug("Successfully parsed payload as JSON")
|
|
199
|
-
request_body = parsed_payload
|
|
200
|
-
except json.JSONDecodeError as e:
|
|
201
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
202
|
-
|
|
203
|
-
# Try replacing single quotes with double quotes
|
|
204
|
-
fixed_payload = payload.replace("'", "\"")
|
|
205
|
-
try:
|
|
206
|
-
parsed_payload = json.loads(fixed_payload)
|
|
207
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
208
|
-
request_body = parsed_payload
|
|
209
|
-
except json.JSONDecodeError:
|
|
210
|
-
# Try as Python literal
|
|
211
|
-
import ast
|
|
212
|
-
try:
|
|
213
|
-
parsed_payload = ast.literal_eval(payload)
|
|
214
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
215
|
-
request_body = parsed_payload
|
|
216
|
-
except (SyntaxError, ValueError) as e2:
|
|
217
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
218
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
219
|
-
except Exception as e:
|
|
220
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
221
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
222
|
-
else:
|
|
223
|
-
# If payload is already a dictionary, use it directly
|
|
224
|
-
logger.debug("Using provided payload dictionary")
|
|
225
|
-
request_body = payload
|
|
226
|
-
|
|
227
|
-
# Import the NewApplicationConfig class
|
|
214
|
+
# Parse the payload if it's a string
|
|
215
|
+
if isinstance(payload, str):
|
|
216
|
+
import json
|
|
228
217
|
try:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
logger.debug("Successfully imported NewApplicationConfig")
|
|
233
|
-
except ImportError as e:
|
|
234
|
-
logger.debug(f"Error importing NewApplicationConfig: {e}")
|
|
235
|
-
return {"error": f"Failed to import NewApplicationConfig: {e!s}"}
|
|
236
|
-
|
|
237
|
-
# Convert nested tagFilterExpression to model objects
|
|
238
|
-
if 'tagFilterExpression' in request_body and isinstance(request_body['tagFilterExpression'], dict):
|
|
218
|
+
request_body = json.loads(payload)
|
|
219
|
+
except json.JSONDecodeError:
|
|
220
|
+
import ast
|
|
239
221
|
try:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
222
|
+
request_body = ast.literal_eval(payload)
|
|
223
|
+
except (SyntaxError, ValueError) as e:
|
|
224
|
+
return {"error": f"Invalid payload format: {e}"}
|
|
225
|
+
else:
|
|
226
|
+
request_body = payload.copy() if payload else {}
|
|
227
|
+
|
|
228
|
+
# Define required fields
|
|
229
|
+
required_fields = ['label']
|
|
230
|
+
missing_fields = []
|
|
231
|
+
|
|
232
|
+
# Check for required fields
|
|
233
|
+
for field in required_fields:
|
|
234
|
+
if field not in request_body or not request_body[field]:
|
|
235
|
+
missing_fields.append(field)
|
|
236
|
+
|
|
237
|
+
if missing_fields:
|
|
238
|
+
return {
|
|
239
|
+
"error": "Missing required fields for application configuration",
|
|
240
|
+
"missing_fields": missing_fields,
|
|
241
|
+
"required_fields": {
|
|
242
|
+
"label": "Application perspective name (string, required)",
|
|
243
|
+
},
|
|
244
|
+
"optional_fields": {
|
|
245
|
+
"tagFilterExpression": "Tag filter to match services (dict, optional - defaults to empty EXPRESSION)",
|
|
246
|
+
"scope": "Monitoring scope (string, optional - defaults to 'INCLUDE_ALL_DOWNSTREAM')",
|
|
247
|
+
"boundaryScope": "Boundary scope (string, optional - defaults to 'ALL')",
|
|
248
|
+
"accessRules": "Access control rules (list, optional - defaults to READ_WRITE GLOBAL access)"
|
|
249
|
+
},
|
|
250
|
+
"scope_options": ["INCLUDE_ALL_DOWNSTREAM", "INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING", "INCLUDE_NO_DOWNSTREAM"],
|
|
251
|
+
"boundary_scope_options": ["ALL", "INBOUND", "DEFAULT"],
|
|
252
|
+
"access_rules_options": ["READ_WRITE_GLOBAL", "READ_ONLY_GLOBAL", "CUSTOM"],
|
|
253
|
+
"elicitation_prompt": "Please provide the following configuration options:\n1. Scope (INCLUDE_ALL_DOWNSTREAM/INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING/INCLUDE_NO_DOWNSTREAM)\n2. Boundary Scope (ALL/INBOUND/DEFAULT)\n3. Access Rules (READ_WRITE_GLOBAL/READ_ONLY_GLOBAL/CUSTOM)\n4. Tag Filter Expression (optional)",
|
|
254
|
+
"example_minimal": {
|
|
255
|
+
"label": "My Application"
|
|
256
|
+
},
|
|
257
|
+
"example_with_options": {
|
|
258
|
+
"label": "My Application",
|
|
259
|
+
"scope": "INCLUDE_ALL_DOWNSTREAM",
|
|
260
|
+
"boundaryScope": "ALL",
|
|
261
|
+
"accessRules": [{"accessType": "READ_WRITE", "relationType": "GLOBAL"}],
|
|
262
|
+
"tagFilterExpression": {
|
|
263
|
+
"type": "TAG_FILTER",
|
|
264
|
+
"name": "service.name",
|
|
265
|
+
"operator": "CONTAINS",
|
|
266
|
+
"entity": "DESTINATION",
|
|
267
|
+
"value": "my-service"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
265
271
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
272
|
+
# Apply defaults for optional fields only if not provided
|
|
273
|
+
if 'scope' not in request_body:
|
|
274
|
+
request_body['scope'] = 'INCLUDE_ALL_DOWNSTREAM'
|
|
275
|
+
debug_print("Applied default scope: INCLUDE_ALL_DOWNSTREAM")
|
|
270
276
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
config_object = NewApplicationConfig(**request_body)
|
|
275
|
-
logger.debug("Successfully created config object")
|
|
276
|
-
except Exception as e:
|
|
277
|
-
logger.debug(f"Error creating NewApplicationConfig: {e}")
|
|
278
|
-
return {"error": f"Failed to create config object: {e!s}"}
|
|
279
|
-
|
|
280
|
-
# Call the add_application_config method from the SDK
|
|
281
|
-
logger.debug("Calling add_application_config with config object")
|
|
282
|
-
result = api_client.add_application_config(
|
|
283
|
-
new_application_config=config_object
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
# Convert the result to a dictionary
|
|
287
|
-
if hasattr(result, 'to_dict'):
|
|
288
|
-
result_dict = result.to_dict()
|
|
289
|
-
else:
|
|
290
|
-
# If it's already a dict or another format, use it as is
|
|
291
|
-
result_dict = result or {
|
|
292
|
-
"success": True,
|
|
293
|
-
"message": "Create new application config"
|
|
294
|
-
}
|
|
277
|
+
if 'boundaryScope' not in request_body:
|
|
278
|
+
request_body['boundaryScope'] = 'ALL'
|
|
279
|
+
debug_print("Applied default boundaryScope: ALL")
|
|
295
280
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
281
|
+
if 'accessRules' not in request_body:
|
|
282
|
+
request_body['accessRules'] = [
|
|
283
|
+
{
|
|
284
|
+
"accessType": "READ_WRITE",
|
|
285
|
+
"relationType": "GLOBAL"
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
debug_print("Applied default accessRules: READ_WRITE GLOBAL")
|
|
289
|
+
|
|
290
|
+
# If no tagFilterExpression provided, use empty EXPRESSION
|
|
291
|
+
if 'tagFilterExpression' not in request_body:
|
|
292
|
+
request_body['tagFilterExpression'] = {
|
|
293
|
+
"type": "EXPRESSION",
|
|
294
|
+
"logicalOperator": "AND",
|
|
295
|
+
"elements": []
|
|
296
|
+
}
|
|
297
|
+
debug_print("Applied default tagFilterExpression: empty EXPRESSION")
|
|
298
|
+
|
|
299
|
+
# Convert nested tagFilterExpression to model objects if present
|
|
300
|
+
if 'tagFilterExpression' in request_body and isinstance(request_body['tagFilterExpression'], dict):
|
|
301
|
+
tag_expr = request_body['tagFilterExpression']
|
|
302
|
+
|
|
303
|
+
# Handle EXPRESSION type with nested elements
|
|
304
|
+
if tag_expr.get('type') == 'EXPRESSION' and 'elements' in tag_expr:
|
|
305
|
+
converted_elements = []
|
|
306
|
+
for element in tag_expr['elements']:
|
|
307
|
+
if isinstance(element, dict):
|
|
308
|
+
element_copy = element.copy()
|
|
309
|
+
element_copy.pop('value', None)
|
|
310
|
+
element_copy.pop('key', None)
|
|
311
|
+
converted_elements.append(TagFilter(**element_copy))
|
|
312
|
+
else:
|
|
313
|
+
converted_elements.append(element)
|
|
314
|
+
tag_expr['elements'] = converted_elements
|
|
315
|
+
request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
|
|
316
|
+
|
|
317
|
+
# Handle TAG_FILTER type (simple filter)
|
|
318
|
+
elif tag_expr.get('type') == 'TAG_FILTER':
|
|
319
|
+
# For TAG_FILTER, ensure both 'value' and 'stringValue' are present
|
|
320
|
+
# Don't convert to TagFilter model - keep as dict to preserve both fields
|
|
321
|
+
tag_filter_copy = tag_expr.copy()
|
|
322
|
+
tag_filter_copy.pop('key', None)
|
|
323
|
+
if 'stringValue' not in tag_filter_copy and 'value' in tag_expr:
|
|
324
|
+
tag_filter_copy['stringValue'] = tag_expr['value']
|
|
325
|
+
if 'value' not in tag_filter_copy and 'stringValue' in tag_expr:
|
|
326
|
+
tag_filter_copy['value'] = tag_expr['stringValue']
|
|
327
|
+
# Keep as dictionary - don't convert to TagFilter model
|
|
328
|
+
request_body['tagFilterExpression'] = tag_filter_copy
|
|
329
|
+
|
|
330
|
+
return {"payload": request_body}
|
|
301
331
|
|
|
302
|
-
@register_as_tool(
|
|
303
|
-
title="Delete Application Config",
|
|
304
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
305
|
-
)
|
|
306
332
|
@with_header_auth(ApplicationSettingsApi)
|
|
307
|
-
async def
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
333
|
+
async def _add_application_config(self,
|
|
334
|
+
payload: Union[Dict[str, Any], str],
|
|
335
|
+
ctx=None,
|
|
336
|
+
api_client=None) -> Dict[str, Any]:
|
|
311
337
|
"""
|
|
312
|
-
|
|
313
|
-
This tool allows you to delete an existing Application Perspective by its ID.
|
|
338
|
+
Add a new Application Perspective configuration.
|
|
314
339
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
ctx: The MCP context (optional)
|
|
340
|
+
Required fields:
|
|
341
|
+
- label: Application perspective name
|
|
318
342
|
|
|
319
|
-
|
|
320
|
-
|
|
343
|
+
Optional fields (with defaults):
|
|
344
|
+
- tagFilterExpression: Tag filter (defaults to empty EXPRESSION)
|
|
345
|
+
- scope: Monitoring scope (defaults to 'INCLUDE_ALL_DOWNSTREAM')
|
|
346
|
+
- boundaryScope: Boundary scope (defaults to 'ALL')
|
|
347
|
+
- accessRules: Access rules (defaults to READ_WRITE GLOBAL)
|
|
321
348
|
"""
|
|
322
349
|
try:
|
|
323
|
-
if not
|
|
324
|
-
return {
|
|
350
|
+
if not payload:
|
|
351
|
+
return {
|
|
352
|
+
"error": "payload is required",
|
|
353
|
+
"required_fields": {
|
|
354
|
+
"label": "Application perspective name (string, required)"
|
|
355
|
+
},
|
|
356
|
+
"example": {
|
|
357
|
+
"label": "My Application"
|
|
358
|
+
}
|
|
359
|
+
}
|
|
325
360
|
|
|
361
|
+
# Validate and prepare payload with defaults
|
|
362
|
+
validation_result = self._validate_and_prepare_application_payload(payload)
|
|
326
363
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
api_client.delete_application_config(id=id)
|
|
364
|
+
if "error" in validation_result:
|
|
365
|
+
return validation_result
|
|
330
366
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
367
|
+
request_body = validation_result["payload"]
|
|
368
|
+
|
|
369
|
+
# Debug: Log the request body before creating the config object
|
|
370
|
+
debug_print(f"DEBUG: request_body before NewApplicationConfig: {request_body}")
|
|
335
371
|
|
|
336
|
-
|
|
337
|
-
|
|
372
|
+
config_object = NewApplicationConfig(**request_body)
|
|
373
|
+
|
|
374
|
+
# Debug: Log what the config object looks like after creation
|
|
375
|
+
if hasattr(config_object, 'to_dict'):
|
|
376
|
+
debug_print(f"DEBUG: config_object.to_dict(): {config_object.to_dict()}")
|
|
377
|
+
|
|
378
|
+
result = api_client.add_application_config(new_application_config=config_object)
|
|
379
|
+
|
|
380
|
+
if hasattr(result, 'to_dict'):
|
|
381
|
+
result_dict = result.to_dict()
|
|
382
|
+
# Add helpful information about what was created
|
|
383
|
+
return {
|
|
384
|
+
**result_dict,
|
|
385
|
+
"message": f"Application perspective '{request_body.get('label')}' created successfully",
|
|
386
|
+
"applied_defaults": {
|
|
387
|
+
"scope": request_body.get('scope'),
|
|
388
|
+
"boundaryScope": request_body.get('boundaryScope'),
|
|
389
|
+
"accessRules": "READ_WRITE GLOBAL" if not payload or 'accessRules' not in (payload if isinstance(payload, dict) else {}) else "Custom"
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return result or {"success": True, "message": "Application config created"}
|
|
338
393
|
except Exception as e:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return {"error": f"Failed to delete application configuration: {e!s}"}
|
|
394
|
+
logger.error(f"Error in _add_application_config: {e}", exc_info=True)
|
|
395
|
+
return {"error": f"Failed to add application config: {e!s}"}
|
|
342
396
|
|
|
343
|
-
@register_as_tool(
|
|
344
|
-
title="Get Application Config",
|
|
345
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
346
|
-
)
|
|
347
397
|
@with_header_auth(ApplicationSettingsApi)
|
|
348
|
-
async def
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
398
|
+
async def _get_application_config(self,
|
|
399
|
+
id: str,
|
|
400
|
+
ctx=None,
|
|
401
|
+
api_client=None) -> Dict[str, Any]:
|
|
352
402
|
"""
|
|
353
403
|
Get an Application Perspective configuration by ID.
|
|
354
|
-
|
|
404
|
+
|
|
405
|
+
Note: To get by application name instead of ID, use the smart router tool
|
|
406
|
+
with application_name parameter. The router will automatically resolve
|
|
407
|
+
the name to ID and call this method.
|
|
355
408
|
|
|
356
409
|
Args:
|
|
357
|
-
id:
|
|
358
|
-
ctx:
|
|
410
|
+
id: Application perspective configuration ID
|
|
411
|
+
ctx: MCP context
|
|
412
|
+
api_client: API client instance
|
|
359
413
|
|
|
360
414
|
Returns:
|
|
361
|
-
|
|
415
|
+
Application configuration dictionary
|
|
362
416
|
"""
|
|
363
417
|
try:
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
result = api_client.get_application_config(id=id)
|
|
418
|
+
if not id:
|
|
419
|
+
return {"error": "id is required"}
|
|
367
420
|
|
|
368
|
-
|
|
421
|
+
result = api_client.get_application_config(id=id)
|
|
369
422
|
if hasattr(result, 'to_dict'):
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
result_dict = result
|
|
373
|
-
|
|
374
|
-
debug_print(f"Result from get_application_config: {result_dict}")
|
|
375
|
-
return result_dict
|
|
423
|
+
return result.to_dict()
|
|
424
|
+
return result
|
|
376
425
|
except Exception as e:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return {"error": f"Failed to get application configuration: {e!s}"}
|
|
426
|
+
logger.error(f"Error in _get_application_config: {e}", exc_info=True)
|
|
427
|
+
return {"error": f"Failed to get application config: {e!s}"}
|
|
380
428
|
|
|
381
|
-
@register_as_tool(
|
|
382
|
-
title="Update Application Config",
|
|
383
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
384
|
-
)
|
|
385
429
|
@with_header_auth(ApplicationSettingsApi)
|
|
386
|
-
async def
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
) -> Dict[str, Any]:
|
|
393
|
-
"""
|
|
394
|
-
Update an existing Application Perspective configuration.
|
|
395
|
-
This tool allows you to update an existing Application Perspective with specified application Id.
|
|
396
|
-
|
|
397
|
-
Args:
|
|
398
|
-
id: The ID of the application perspective to retrieve
|
|
399
|
-
Sample payload: {
|
|
400
|
-
"accessRules": [
|
|
401
|
-
{
|
|
402
|
-
"accessType": "READ",
|
|
403
|
-
"relationType": "ROLE"
|
|
404
|
-
}
|
|
405
|
-
],
|
|
406
|
-
"boundaryScope": "INBOUND",
|
|
407
|
-
"id": "CxJ55sRbQwqBIfw5DzpRmQ",
|
|
408
|
-
"label": "Discount Build 1",
|
|
409
|
-
"scope": "INCLUDE_NO_DOWNSTREAM",
|
|
410
|
-
"tagFilterExpression": {
|
|
411
|
-
"type": "EXPRESSION",
|
|
412
|
-
"logicalOperator": "AND",
|
|
413
|
-
"elements": [
|
|
414
|
-
{
|
|
415
|
-
"type": "TAG_FILTER",
|
|
416
|
-
"name": "kubernetes.label",
|
|
417
|
-
"stringValue": "stage=canary",
|
|
418
|
-
"numberValue": null,
|
|
419
|
-
"booleanValue": null,
|
|
420
|
-
"key": "stage",
|
|
421
|
-
"value": "canary",
|
|
422
|
-
"operator": "EQUALS",
|
|
423
|
-
"entity": "DESTINATION"
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
"type": "TAG_FILTER",
|
|
427
|
-
"name": "kubernetes.label",
|
|
428
|
-
"stringValue": "build=6987",
|
|
429
|
-
"numberValue": null,
|
|
430
|
-
"booleanValue": null,
|
|
431
|
-
"key": "build",
|
|
432
|
-
"value": "6987",
|
|
433
|
-
"operator": "EQUALS",
|
|
434
|
-
"entity": "DESTINATION"
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
"type": "TAG_FILTER",
|
|
438
|
-
"name": "kubernetes.label",
|
|
439
|
-
"stringValue": "app=discount",
|
|
440
|
-
"numberValue": null,
|
|
441
|
-
"booleanValue": null,
|
|
442
|
-
"key": "app",
|
|
443
|
-
"value": "discount",
|
|
444
|
-
"operator": "EQUALS",
|
|
445
|
-
"entity": "DESTINATION"
|
|
446
|
-
}
|
|
447
|
-
]
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
ctx: The MCP context (optional)
|
|
451
|
-
Returns:
|
|
452
|
-
Dictionary containing the created application perspective configuration or error information
|
|
453
|
-
"""
|
|
430
|
+
async def _update_application_config(self,
|
|
431
|
+
id: str,
|
|
432
|
+
payload: Union[Dict[str, Any], str],
|
|
433
|
+
ctx=None,
|
|
434
|
+
api_client=None) -> Dict[str, Any]:
|
|
435
|
+
"""Update an existing Application Perspective configuration."""
|
|
454
436
|
try:
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return {"error": "missing arguments"}
|
|
437
|
+
if not id or not payload:
|
|
438
|
+
return {"error": "id and payload are required"}
|
|
458
439
|
|
|
459
440
|
# Parse the payload if it's a string
|
|
460
441
|
if isinstance(payload, str):
|
|
461
|
-
|
|
442
|
+
import json
|
|
462
443
|
try:
|
|
463
|
-
|
|
444
|
+
request_body = json.loads(payload)
|
|
445
|
+
except json.JSONDecodeError:
|
|
446
|
+
import ast
|
|
464
447
|
try:
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
except json.JSONDecodeError as e:
|
|
469
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
470
|
-
|
|
471
|
-
# Try replacing single quotes with double quotes
|
|
472
|
-
fixed_payload = payload.replace("'", "\"")
|
|
473
|
-
try:
|
|
474
|
-
parsed_payload = json.loads(fixed_payload)
|
|
475
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
476
|
-
request_body = parsed_payload
|
|
477
|
-
except json.JSONDecodeError:
|
|
478
|
-
# Try as Python literal
|
|
479
|
-
import ast
|
|
480
|
-
try:
|
|
481
|
-
parsed_payload = ast.literal_eval(payload)
|
|
482
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
483
|
-
request_body = parsed_payload
|
|
484
|
-
except (SyntaxError, ValueError) as e2:
|
|
485
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
486
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
487
|
-
except Exception as e:
|
|
488
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
489
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
448
|
+
request_body = ast.literal_eval(payload)
|
|
449
|
+
except (SyntaxError, ValueError) as e:
|
|
450
|
+
return {"error": f"Invalid payload format: {e}"}
|
|
490
451
|
else:
|
|
491
|
-
# If payload is already a dictionary, use it directly
|
|
492
|
-
logger.debug("Using provided payload dictionary")
|
|
493
452
|
request_body = payload
|
|
494
453
|
|
|
495
|
-
#
|
|
496
|
-
try:
|
|
497
|
-
from instana_client.models.application_config import (
|
|
498
|
-
ApplicationConfig,
|
|
499
|
-
)
|
|
500
|
-
logger.debug("Successfully imported ApplicationConfig")
|
|
501
|
-
except ImportError as e:
|
|
502
|
-
logger.debug(f"Error importing ApplicationConfig: {e}")
|
|
503
|
-
return {"error": f"Failed to import ApplicationConfig: {e!s}"}
|
|
504
|
-
|
|
505
|
-
# Convert nested tagFilterExpression to model objects
|
|
454
|
+
# Convert nested tagFilterExpression to model objects if present
|
|
506
455
|
if 'tagFilterExpression' in request_body and isinstance(request_body['tagFilterExpression'], dict):
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
#
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
456
|
+
tag_expr = request_body['tagFilterExpression']
|
|
457
|
+
|
|
458
|
+
# Handle EXPRESSION type with nested elements
|
|
459
|
+
if tag_expr.get('type') == 'EXPRESSION' and 'elements' in tag_expr:
|
|
460
|
+
converted_elements = []
|
|
461
|
+
for element in tag_expr['elements']:
|
|
462
|
+
if isinstance(element, dict):
|
|
463
|
+
element_copy = element.copy()
|
|
464
|
+
element_copy.pop('value', None)
|
|
465
|
+
element_copy.pop('key', None)
|
|
466
|
+
converted_elements.append(TagFilter(**element_copy))
|
|
467
|
+
else:
|
|
468
|
+
converted_elements.append(element)
|
|
469
|
+
tag_expr['elements'] = converted_elements
|
|
470
|
+
request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
|
|
471
|
+
|
|
472
|
+
# Handle TAG_FILTER type (simple filter)
|
|
473
|
+
elif tag_expr.get('type') == 'TAG_FILTER':
|
|
474
|
+
# For TAG_FILTER, ensure both 'value' and 'stringValue' are present
|
|
475
|
+
# Don't convert to TagFilter model - keep as dict to preserve both fields
|
|
476
|
+
tag_filter_copy = tag_expr.copy()
|
|
477
|
+
tag_filter_copy.pop('key', None)
|
|
478
|
+
if 'stringValue' not in tag_filter_copy and 'value' in tag_expr:
|
|
479
|
+
tag_filter_copy['stringValue'] = tag_expr['value']
|
|
480
|
+
if 'value' not in tag_filter_copy and 'stringValue' in tag_expr:
|
|
481
|
+
tag_filter_copy['value'] = tag_expr['stringValue']
|
|
482
|
+
# Keep as dictionary - don't convert to TagFilter model
|
|
483
|
+
request_body['tagFilterExpression'] = tag_filter_copy
|
|
484
|
+
|
|
485
|
+
config_object = ApplicationConfig(**request_body)
|
|
486
|
+
result = api_client.put_application_config(id=id, application_config=config_object)
|
|
533
487
|
|
|
534
|
-
logger.debug("Successfully converted tagFilterExpression to model objects")
|
|
535
|
-
except Exception as e:
|
|
536
|
-
logger.debug(f"Error converting tagFilterExpression: {e}")
|
|
537
|
-
return {"error": f"Failed to convert tagFilterExpression: {e!s}"}
|
|
538
|
-
|
|
539
|
-
# Create an ApplicationConfig object from the request body
|
|
540
|
-
try:
|
|
541
|
-
logger.debug(f"Creating ApplicationConfig with params: {request_body}")
|
|
542
|
-
config_object = ApplicationConfig(**request_body)
|
|
543
|
-
logger.debug("Successfully updated application config object")
|
|
544
|
-
except Exception as e:
|
|
545
|
-
logger.debug(f"Error updating ApplicationConfig: {e}")
|
|
546
|
-
return {"error": f"Failed to update config object: {e!s}"}
|
|
547
|
-
|
|
548
|
-
# Call the put_application_config method from the SDK
|
|
549
|
-
logger.debug("Calling put_application_config with config object")
|
|
550
|
-
result = api_client.put_application_config(
|
|
551
|
-
id=id,
|
|
552
|
-
application_config=config_object
|
|
553
|
-
)
|
|
554
|
-
|
|
555
|
-
# Convert the result to a dictionary
|
|
556
488
|
if hasattr(result, 'to_dict'):
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
# If it's already a dict or another format, use it as is
|
|
560
|
-
result_dict = result or {
|
|
561
|
-
"success": True,
|
|
562
|
-
"message": "Update existing application config"
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
logger.debug(f"Result from put_application_config: {result_dict}")
|
|
566
|
-
return result_dict
|
|
489
|
+
return result.to_dict()
|
|
490
|
+
return result or {"success": True, "message": f"Application config '{id}' updated"}
|
|
567
491
|
except Exception as e:
|
|
568
|
-
logger.error(f"Error in
|
|
569
|
-
return {"error": f"Failed to update
|
|
492
|
+
logger.error(f"Error in _update_application_config: {e}", exc_info=True)
|
|
493
|
+
return {"error": f"Failed to update application config: {e!s}"}
|
|
570
494
|
|
|
571
|
-
@register_as_tool(
|
|
572
|
-
title="Get All Endpoint Configs",
|
|
573
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
574
|
-
)
|
|
575
495
|
@with_header_auth(ApplicationSettingsApi)
|
|
576
|
-
async def
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
496
|
+
async def _delete_application_config(self,
|
|
497
|
+
id: str,
|
|
498
|
+
ctx=None,
|
|
499
|
+
api_client=None) -> Dict[str, Any]:
|
|
500
|
+
"""Delete an Application Perspective configuration."""
|
|
501
|
+
try:
|
|
502
|
+
if not id:
|
|
503
|
+
return {"error": "id is required"}
|
|
584
504
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
505
|
+
api_client.delete_application_config(id=id)
|
|
506
|
+
return {"success": True, "message": f"Application config '{id}' deleted successfully"}
|
|
507
|
+
except Exception as e:
|
|
508
|
+
logger.error(f"Error in _delete_application_config: {e}", exc_info=True)
|
|
509
|
+
return {"error": f"Failed to delete application config: {e!s}"}
|
|
510
|
+
|
|
511
|
+
# Endpoint Config Operations
|
|
512
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
513
|
+
async def _get_all_endpoint_configs(self,
|
|
514
|
+
ctx=None,
|
|
515
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
516
|
+
"""Get all Endpoint Perspectives Configuration."""
|
|
588
517
|
try:
|
|
589
|
-
debug_print("Fetching all endpoint configs")
|
|
590
518
|
result = api_client.get_endpoint_configs_without_preload_content()
|
|
591
|
-
# Convert the result to a dictionary
|
|
592
519
|
import json
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if isinstance(json_data, list):
|
|
597
|
-
result_dict=json_data
|
|
598
|
-
else:
|
|
599
|
-
# If it's a single object, wrap it in a list
|
|
600
|
-
result_dict=[json_data] if json_data else []
|
|
601
|
-
debug_print("Successfully retrieved endpoint configs data")
|
|
602
|
-
return result_dict
|
|
603
|
-
except (json.JSONDecodeError, AttributeError) as json_err:
|
|
604
|
-
error_message = f"Failed to parse JSON response: {json_err}"
|
|
605
|
-
logger.error(error_message)
|
|
606
|
-
return [{"error": error_message}]
|
|
607
|
-
|
|
520
|
+
response_text = result.data.decode('utf-8')
|
|
521
|
+
json_data = json.loads(response_text)
|
|
522
|
+
return json_data if isinstance(json_data, list) else [json_data] if json_data else []
|
|
608
523
|
except Exception as e:
|
|
609
|
-
|
|
610
|
-
traceback.print_exc(file=sys.stderr)
|
|
524
|
+
logger.error(f"Error in _get_all_endpoint_configs: {e}", exc_info=True)
|
|
611
525
|
return [{"error": f"Failed to get endpoint configs: {e!s}"}]
|
|
612
526
|
|
|
613
|
-
@register_as_tool(
|
|
614
|
-
title="Create Endpoint Config",
|
|
615
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
616
|
-
)
|
|
617
527
|
@with_header_auth(ApplicationSettingsApi)
|
|
618
|
-
async def
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
528
|
+
async def _get_endpoint_config(self,
|
|
529
|
+
id: str,
|
|
530
|
+
ctx=None,
|
|
531
|
+
api_client=None) -> Dict[str, Any]:
|
|
532
|
+
"""Get an Endpoint configuration by ID."""
|
|
533
|
+
try:
|
|
534
|
+
if not id:
|
|
535
|
+
return {"error": "id is required"}
|
|
536
|
+
result = api_client.get_endpoint_config(id=id)
|
|
537
|
+
if hasattr(result, 'to_dict'):
|
|
538
|
+
return result.to_dict()
|
|
539
|
+
return result
|
|
540
|
+
except Exception as e:
|
|
541
|
+
logger.error(f"Error in _get_endpoint_config: {e}", exc_info=True)
|
|
542
|
+
return {"error": f"Failed to get endpoint config: {e!s}"}
|
|
634
543
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
544
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
545
|
+
async def _create_endpoint_config(self,
|
|
546
|
+
payload: Union[Dict[str, Any], str],
|
|
547
|
+
ctx=None,
|
|
548
|
+
api_client=None) -> Dict[str, Any]:
|
|
549
|
+
"""Create or update endpoint configuration for a service."""
|
|
638
550
|
try:
|
|
639
551
|
if not payload:
|
|
640
|
-
return {"error": "
|
|
552
|
+
return {"error": "payload is required"}
|
|
641
553
|
|
|
642
|
-
# Parse the payload if it's a string
|
|
643
554
|
if isinstance(payload, str):
|
|
644
|
-
|
|
555
|
+
import json
|
|
645
556
|
try:
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
request_body = parsed_payload
|
|
651
|
-
except json.JSONDecodeError as e:
|
|
652
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
653
|
-
|
|
654
|
-
# Try replacing single quotes with double quotes
|
|
655
|
-
fixed_payload = payload.replace("'", "\"")
|
|
656
|
-
try:
|
|
657
|
-
parsed_payload = json.loads(fixed_payload)
|
|
658
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
659
|
-
request_body = parsed_payload
|
|
660
|
-
except json.JSONDecodeError:
|
|
661
|
-
# Try as Python literal
|
|
662
|
-
import ast
|
|
663
|
-
try:
|
|
664
|
-
parsed_payload = ast.literal_eval(payload)
|
|
665
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
666
|
-
request_body = parsed_payload
|
|
667
|
-
except (SyntaxError, ValueError) as e2:
|
|
668
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
669
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
670
|
-
except Exception as e:
|
|
671
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
672
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
557
|
+
request_body = json.loads(payload)
|
|
558
|
+
except json.JSONDecodeError:
|
|
559
|
+
import ast
|
|
560
|
+
request_body = ast.literal_eval(payload)
|
|
673
561
|
else:
|
|
674
|
-
# If payload is already a dictionary, use it directly
|
|
675
|
-
logger.debug("Using provided payload dictionary")
|
|
676
562
|
request_body = payload
|
|
677
563
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
from instana_client.models.endpoint_config import (
|
|
681
|
-
EndpointConfig,
|
|
682
|
-
)
|
|
683
|
-
logger.debug("Successfully imported EndpointConfig")
|
|
684
|
-
except ImportError as e:
|
|
685
|
-
logger.debug(f"Error importing EndpointConfig: {e}")
|
|
686
|
-
return {"error": f"Failed to import EndpointConfig: {e!s}"}
|
|
687
|
-
|
|
688
|
-
# Create an EndpointConfig object from the request body
|
|
689
|
-
try:
|
|
690
|
-
logger.debug(f"Creating EndpointConfig with params: {request_body}")
|
|
691
|
-
config_object = EndpointConfig(**request_body)
|
|
692
|
-
logger.debug("Successfully created endpoint config object")
|
|
693
|
-
except Exception as e:
|
|
694
|
-
logger.debug(f"Error creating EndpointConfig: {e}")
|
|
695
|
-
return {"error": f"Failed to create config object: {e!s}"}
|
|
696
|
-
|
|
697
|
-
# Call the create_endpoint_config method from the SDK
|
|
698
|
-
logger.debug("Calling create_endpoint_config with config object")
|
|
699
|
-
result = api_client.create_endpoint_config(
|
|
700
|
-
endpoint_config=config_object
|
|
701
|
-
)
|
|
702
|
-
# Convert the result to a dictionary
|
|
564
|
+
config_object = EndpointConfig(**request_body)
|
|
565
|
+
result = api_client.create_endpoint_config(endpoint_config=config_object)
|
|
703
566
|
if hasattr(result, 'to_dict'):
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
# If it's already a dict or another format, use it as is
|
|
707
|
-
result_dict = result or {
|
|
708
|
-
"success": True,
|
|
709
|
-
"message": "Create new endpoint config"
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
logger.debug(f"Result from create_endpoint_config: {result_dict}")
|
|
713
|
-
return result_dict
|
|
567
|
+
return result.to_dict()
|
|
568
|
+
return result or {"success": True, "message": "Endpoint config created"}
|
|
714
569
|
except Exception as e:
|
|
715
|
-
logger.error(f"Error in
|
|
716
|
-
return {"error": f"Failed to create
|
|
570
|
+
logger.error(f"Error in _create_endpoint_config: {e}", exc_info=True)
|
|
571
|
+
return {"error": f"Failed to create endpoint config: {e!s}"}
|
|
717
572
|
|
|
718
|
-
@register_as_tool(
|
|
719
|
-
title="Delete Endpoint Config",
|
|
720
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
721
|
-
)
|
|
722
573
|
@with_header_auth(ApplicationSettingsApi)
|
|
723
|
-
async def
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
574
|
+
async def _update_endpoint_config(self,
|
|
575
|
+
id: str,
|
|
576
|
+
payload: Union[Dict[str, Any], str],
|
|
577
|
+
ctx=None,
|
|
578
|
+
api_client=None) -> Dict[str, Any]:
|
|
579
|
+
"""Update an endpoint configuration."""
|
|
580
|
+
try:
|
|
581
|
+
if not id or not payload:
|
|
582
|
+
return {"error": "id and payload are required"}
|
|
731
583
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
584
|
+
if isinstance(payload, str):
|
|
585
|
+
import json
|
|
586
|
+
try:
|
|
587
|
+
request_body = json.loads(payload)
|
|
588
|
+
except json.JSONDecodeError:
|
|
589
|
+
import ast
|
|
590
|
+
request_body = ast.literal_eval(payload)
|
|
591
|
+
else:
|
|
592
|
+
request_body = payload
|
|
735
593
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
594
|
+
config_object = EndpointConfig(**request_body)
|
|
595
|
+
result = api_client.update_endpoint_config(id=id, endpoint_config=config_object)
|
|
596
|
+
if hasattr(result, 'to_dict'):
|
|
597
|
+
return result.to_dict()
|
|
598
|
+
return result or {"success": True, "message": f"Endpoint config '{id}' updated"}
|
|
599
|
+
except Exception as e:
|
|
600
|
+
logger.error(f"Error in _update_endpoint_config: {e}", exc_info=True)
|
|
601
|
+
return {"error": f"Failed to update endpoint config: {e!s}"}
|
|
602
|
+
|
|
603
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
604
|
+
async def _delete_endpoint_config(self,
|
|
605
|
+
id: str,
|
|
606
|
+
ctx=None,
|
|
607
|
+
api_client=None) -> Dict[str, Any]:
|
|
608
|
+
"""Delete an endpoint configuration."""
|
|
739
609
|
try:
|
|
740
|
-
debug_print("Delete endpoint configs")
|
|
741
610
|
if not id:
|
|
742
|
-
return {"error": "
|
|
743
|
-
|
|
611
|
+
return {"error": "id is required"}
|
|
744
612
|
api_client.delete_endpoint_config(id=id)
|
|
745
|
-
|
|
746
|
-
result_dict = {
|
|
747
|
-
"success": True,
|
|
748
|
-
"message": f"Endpoint Confiuguration '{id}' has been successfully deleted"
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
debug_print(f"Successfully deleted endpoint perspective with ID: {id}")
|
|
752
|
-
return result_dict
|
|
753
|
-
|
|
613
|
+
return {"success": True, "message": f"Endpoint config '{id}' deleted successfully"}
|
|
754
614
|
except Exception as e:
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
return {"error": f"Failed to delete endpoint configs: {e!s}"}
|
|
615
|
+
logger.error(f"Error in _delete_endpoint_config: {e}", exc_info=True)
|
|
616
|
+
return {"error": f"Failed to delete endpoint config: {e!s}"}
|
|
758
617
|
|
|
759
|
-
|
|
760
|
-
title="Get Endpoint Config",
|
|
761
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
762
|
-
)
|
|
618
|
+
# Service Config Operations
|
|
763
619
|
@with_header_auth(ApplicationSettingsApi)
|
|
764
|
-
async def
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
Dict[str, Any]: Response from the create/update endpoint configuration API.
|
|
620
|
+
async def _get_all_service_configs(self,
|
|
621
|
+
ctx=None,
|
|
622
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
623
|
+
"""Get all Service configurations."""
|
|
624
|
+
try:
|
|
625
|
+
result = api_client.get_service_configs_without_preload_content()
|
|
626
|
+
import json
|
|
627
|
+
response_text = result.data.decode('utf-8')
|
|
628
|
+
json_data = json.loads(response_text)
|
|
629
|
+
return json_data if isinstance(json_data, list) else [json_data] if json_data else []
|
|
630
|
+
except Exception as e:
|
|
631
|
+
logger.error(f"Error in _get_all_service_configs: {e}", exc_info=True)
|
|
632
|
+
return [{"error": f"Failed to get service configs: {e!s}"}]
|
|
778
633
|
|
|
779
|
-
|
|
634
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
635
|
+
async def _get_service_config(self,
|
|
636
|
+
id: str,
|
|
637
|
+
ctx=None,
|
|
638
|
+
api_client=None) -> Dict[str, Any]:
|
|
639
|
+
"""Get a Service configuration by ID."""
|
|
780
640
|
try:
|
|
781
|
-
debug_print("get endpoint config")
|
|
782
641
|
if not id:
|
|
783
|
-
return {"error": "
|
|
784
|
-
|
|
785
|
-
result = api_client.get_endpoint_config(
|
|
786
|
-
id=id
|
|
787
|
-
)
|
|
788
|
-
# Convert the result to a dictionary
|
|
642
|
+
return {"error": "id is required"}
|
|
643
|
+
result = api_client.get_service_config(id=id)
|
|
789
644
|
if hasattr(result, 'to_dict'):
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
# If it's already a dict or another format, use it as is
|
|
793
|
-
result_dict = result
|
|
794
|
-
|
|
795
|
-
debug_print(f"Result from get_endpoint_configs: {result_dict}")
|
|
796
|
-
return result_dict
|
|
645
|
+
return result.to_dict()
|
|
646
|
+
return result
|
|
797
647
|
except Exception as e:
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return {"error": f"Failed to get endpoint configs: {e!s}"}
|
|
648
|
+
logger.error(f"Error in _get_service_config: {e}", exc_info=True)
|
|
649
|
+
return {"error": f"Failed to get service config: {e!s}"}
|
|
801
650
|
|
|
802
|
-
@register_as_tool(
|
|
803
|
-
title="Update Endpoint Config",
|
|
804
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
805
|
-
)
|
|
806
651
|
@with_header_auth(ApplicationSettingsApi)
|
|
807
|
-
async def
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
api_client=None
|
|
813
|
-
) -> Dict[str, Any]:
|
|
814
|
-
"""
|
|
815
|
-
update endpoint configuration for a service.
|
|
816
|
-
|
|
817
|
-
Args:
|
|
818
|
-
id: An Instana generated unique identifier for a Service.
|
|
819
|
-
{
|
|
820
|
-
"serviceId": "20ba31821b079e7d845a08096124880db3eeeb40",
|
|
821
|
-
"endpointNameByCollectedPathTemplateRuleEnabled": true,
|
|
822
|
-
"endpointNameByFirstPathSegmentRuleEnabled": true,
|
|
823
|
-
"rules": [
|
|
824
|
-
{
|
|
825
|
-
"enabled": true,
|
|
826
|
-
"pathSegments": [
|
|
827
|
-
{
|
|
828
|
-
"name": "api",
|
|
829
|
-
"type": "FIXED"
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
"name": "version",
|
|
833
|
-
"type": "PARAMETER"
|
|
834
|
-
}
|
|
835
|
-
],
|
|
836
|
-
"testCases": [
|
|
837
|
-
"/api/v2/users"
|
|
838
|
-
]
|
|
839
|
-
}
|
|
840
|
-
],
|
|
841
|
-
"endpointCase": "UPPER"
|
|
842
|
-
}
|
|
843
|
-
ctx: The MCP context (optional)
|
|
844
|
-
|
|
845
|
-
Returns:
|
|
846
|
-
Dict[str, Any]: Response from the create/update endpoint configuration API.
|
|
847
|
-
"""
|
|
652
|
+
async def _add_service_config(self,
|
|
653
|
+
payload: Union[Dict[str, Any], str],
|
|
654
|
+
ctx=None,
|
|
655
|
+
api_client=None) -> Dict[str, Any]:
|
|
656
|
+
"""Add a new Service configuration."""
|
|
848
657
|
try:
|
|
849
|
-
if not payload
|
|
850
|
-
return {"error": "
|
|
658
|
+
if not payload:
|
|
659
|
+
return {"error": "payload is required"}
|
|
851
660
|
|
|
852
|
-
# Parse the payload if it's a string
|
|
853
661
|
if isinstance(payload, str):
|
|
854
|
-
|
|
662
|
+
import json
|
|
855
663
|
try:
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
request_body = parsed_payload
|
|
861
|
-
except json.JSONDecodeError as e:
|
|
862
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
863
|
-
|
|
864
|
-
# Try replacing single quotes with double quotes
|
|
865
|
-
fixed_payload = payload.replace("'", "\"")
|
|
866
|
-
try:
|
|
867
|
-
parsed_payload = json.loads(fixed_payload)
|
|
868
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
869
|
-
request_body = parsed_payload
|
|
870
|
-
except json.JSONDecodeError:
|
|
871
|
-
# Try as Python literal
|
|
872
|
-
import ast
|
|
873
|
-
try:
|
|
874
|
-
parsed_payload = ast.literal_eval(payload)
|
|
875
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
876
|
-
request_body = parsed_payload
|
|
877
|
-
except (SyntaxError, ValueError) as e2:
|
|
878
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
879
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
880
|
-
except Exception as e:
|
|
881
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
882
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
664
|
+
request_body = json.loads(payload)
|
|
665
|
+
except json.JSONDecodeError:
|
|
666
|
+
import ast
|
|
667
|
+
request_body = ast.literal_eval(payload)
|
|
883
668
|
else:
|
|
884
|
-
# If payload is already a dictionary, use it directly
|
|
885
|
-
logger.debug("Using provided payload dictionary")
|
|
886
669
|
request_body = payload
|
|
887
670
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
from instana_client.models.endpoint_config import (
|
|
891
|
-
EndpointConfig,
|
|
892
|
-
)
|
|
893
|
-
logger.debug("Successfully imported EndpointConfig")
|
|
894
|
-
except ImportError as e:
|
|
895
|
-
logger.debug(f"Error importing EndpointConfig: {e}")
|
|
896
|
-
return {"error": f"Failed to import EndpointConfig: {e!s}"}
|
|
897
|
-
|
|
898
|
-
# Create an EndpointConfig object from the request body
|
|
899
|
-
try:
|
|
900
|
-
logger.debug(f"Creating EndpointConfig with params: {request_body}")
|
|
901
|
-
config_object = EndpointConfig(**request_body)
|
|
902
|
-
logger.debug("Successfully updated endpoint config object")
|
|
903
|
-
except Exception as e:
|
|
904
|
-
logger.debug(f"Error updating EndpointConfig: {e}")
|
|
905
|
-
return {"error": f"Failed to update config object: {e!s}"}
|
|
906
|
-
|
|
907
|
-
# Call the update_endpoint_config method from the SDK
|
|
908
|
-
logger.debug("Calling update_endpoint_config with config object")
|
|
909
|
-
result = api_client.update_endpoint_config(
|
|
910
|
-
id=id,
|
|
911
|
-
endpoint_config=config_object
|
|
912
|
-
)
|
|
913
|
-
|
|
914
|
-
# Convert the result to a dictionary
|
|
671
|
+
config_object = ServiceConfig(**request_body)
|
|
672
|
+
result = api_client.add_service_config(service_config=config_object)
|
|
915
673
|
if hasattr(result, 'to_dict'):
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
# If it's already a dict or another format, use it as is
|
|
919
|
-
result_dict = result or {
|
|
920
|
-
"success": True,
|
|
921
|
-
"message": "update existing endpoint config"
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
logger.debug(f"Result from update_endpoint_config: {result_dict}")
|
|
925
|
-
return result_dict
|
|
926
|
-
except Exception as e:
|
|
927
|
-
logger.error(f"Error in update_endpoint_config: {e}")
|
|
928
|
-
return {"error": f"Failed to update existing application config: {e!s}"}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
@register_as_tool(
|
|
932
|
-
title="Get All Manual Service Configs",
|
|
933
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
934
|
-
)
|
|
935
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
936
|
-
async def get_all_manual_service_configs(self,
|
|
937
|
-
ctx=None,
|
|
938
|
-
api_client=None) -> List[Dict[str, Any]]:
|
|
939
|
-
"""
|
|
940
|
-
All Manual Service Perspectives Configuration
|
|
941
|
-
Get a list of all Manual Service Perspectives with their configuration settings.
|
|
942
|
-
Args:
|
|
943
|
-
ctx: The MCP context (optional)
|
|
944
|
-
|
|
945
|
-
Returns:
|
|
946
|
-
Dictionary containing endpoints data or error information
|
|
947
|
-
"""
|
|
948
|
-
try:
|
|
949
|
-
debug_print("Fetching all manual configs")
|
|
950
|
-
result = api_client.get_all_manual_service_configs_without_preload_content()
|
|
951
|
-
# Convert the result to a dictionary
|
|
952
|
-
import json
|
|
953
|
-
try:
|
|
954
|
-
response_text=result.data.decode('utf-8')
|
|
955
|
-
json_data=json.loads(response_text)
|
|
956
|
-
logger.debug("Successfully retrieved manual service configs data")
|
|
957
|
-
if isinstance(json_data, list):
|
|
958
|
-
result_dict=json_data
|
|
959
|
-
else:
|
|
960
|
-
# If it's a single object, wrap it in a list
|
|
961
|
-
result_dict=[json_data] if json_data else []
|
|
962
|
-
debug_print("Successfully retrieved manual service configs data")
|
|
963
|
-
return result_dict
|
|
964
|
-
except (json.JSONDecodeError, AttributeError) as json_err:
|
|
965
|
-
error_message = f"Failed to parse JSON response: {json_err}"
|
|
966
|
-
logger.error(error_message)
|
|
967
|
-
return [{"error": error_message}]
|
|
968
|
-
|
|
674
|
+
return result.to_dict()
|
|
675
|
+
return result or {"success": True, "message": "Service config created"}
|
|
969
676
|
except Exception as e:
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
return [{"error": f"Failed to get manual service configs: {e!s}"}]
|
|
677
|
+
logger.error(f"Error in _add_service_config: {e}", exc_info=True)
|
|
678
|
+
return {"error": f"Failed to add service config: {e!s}"}
|
|
973
679
|
|
|
974
|
-
@register_as_tool(
|
|
975
|
-
title="Add Manual Service Config",
|
|
976
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
977
|
-
)
|
|
978
680
|
@with_header_auth(ApplicationSettingsApi)
|
|
979
|
-
async def
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
"""
|
|
986
|
-
Create a manual service mapping configuration.
|
|
987
|
-
|
|
988
|
-
Requires `CanConfigureServiceMapping` permission on the API token.
|
|
989
|
-
|
|
990
|
-
Sample payload:
|
|
991
|
-
{
|
|
992
|
-
"description": "Map source service example",
|
|
993
|
-
"enabled": true,
|
|
994
|
-
"existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
|
|
995
|
-
"tagFilterExpression": {
|
|
996
|
-
"type": "TAG_FILTER",
|
|
997
|
-
"name": "service.name",
|
|
998
|
-
"stringValue": "front",
|
|
999
|
-
"numberValue": null,
|
|
1000
|
-
"booleanValue": null,
|
|
1001
|
-
"key": null,
|
|
1002
|
-
"value": "front",
|
|
1003
|
-
"operator": "EQUALS",
|
|
1004
|
-
"entity": "SOURCE"
|
|
1005
|
-
},
|
|
1006
|
-
"unmonitoredServiceName": null
|
|
1007
|
-
}
|
|
1008
|
-
ctx: Optional execution context.
|
|
1009
|
-
|
|
1010
|
-
Returns:
|
|
1011
|
-
Dict[str, Any]: API response indicating success or failure.
|
|
1012
|
-
"""
|
|
681
|
+
async def _update_service_config(self,
|
|
682
|
+
id: str,
|
|
683
|
+
payload: Union[Dict[str, Any], str],
|
|
684
|
+
ctx=None,
|
|
685
|
+
api_client=None) -> Dict[str, Any]:
|
|
686
|
+
"""Update a Service configuration."""
|
|
1013
687
|
try:
|
|
1014
|
-
if not payload:
|
|
1015
|
-
return {"error": "
|
|
688
|
+
if not id or not payload:
|
|
689
|
+
return {"error": "id and payload are required"}
|
|
1016
690
|
|
|
1017
|
-
# Parse the payload if it's a string
|
|
1018
691
|
if isinstance(payload, str):
|
|
1019
|
-
|
|
692
|
+
import json
|
|
1020
693
|
try:
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
request_body = parsed_payload
|
|
1026
|
-
except json.JSONDecodeError as e:
|
|
1027
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1028
|
-
|
|
1029
|
-
# Try replacing single quotes with double quotes
|
|
1030
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1031
|
-
try:
|
|
1032
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1033
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1034
|
-
request_body = parsed_payload
|
|
1035
|
-
except json.JSONDecodeError:
|
|
1036
|
-
# Try as Python literal
|
|
1037
|
-
import ast
|
|
1038
|
-
try:
|
|
1039
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1040
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1041
|
-
request_body = parsed_payload
|
|
1042
|
-
except (SyntaxError, ValueError) as e2:
|
|
1043
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1044
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
1045
|
-
except Exception as e:
|
|
1046
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1047
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
694
|
+
request_body = json.loads(payload)
|
|
695
|
+
except json.JSONDecodeError:
|
|
696
|
+
import ast
|
|
697
|
+
request_body = ast.literal_eval(payload)
|
|
1048
698
|
else:
|
|
1049
|
-
# If payload is already a dictionary, use it directly
|
|
1050
|
-
logger.debug("Using provided payload dictionary")
|
|
1051
699
|
request_body = payload
|
|
1052
700
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
from instana_client.models.new_manual_service_config import (
|
|
1056
|
-
NewManualServiceConfig,
|
|
1057
|
-
)
|
|
1058
|
-
logger.debug("Successfully imported NewManualServiceConfig")
|
|
1059
|
-
except ImportError as e:
|
|
1060
|
-
logger.debug(f"Error importing NewManualServiceConfig: {e}")
|
|
1061
|
-
return {"error": f"Failed to import NewManualServiceConfig: {e!s}"}
|
|
1062
|
-
|
|
1063
|
-
# Convert tagFilterExpression dict to TagFilter object with all required fields
|
|
1064
|
-
if 'tagFilterExpression' in request_body and isinstance(request_body['tagFilterExpression'], dict):
|
|
1065
|
-
try:
|
|
1066
|
-
tag_filter_dict = request_body['tagFilterExpression']
|
|
1067
|
-
logger.debug(f"Converting tagFilterExpression to TagFilter object: {tag_filter_dict}")
|
|
1068
|
-
|
|
1069
|
-
# Use TagFilter.from_dict() which properly handles all fields including aliases
|
|
1070
|
-
tag_filter = TagFilter.from_dict(tag_filter_dict)
|
|
1071
|
-
request_body['tagFilterExpression'] = tag_filter
|
|
1072
|
-
logger.debug("Successfully converted tagFilterExpression to TagFilter object")
|
|
1073
|
-
except Exception as e:
|
|
1074
|
-
logger.debug(f"Error converting tagFilterExpression: {e}")
|
|
1075
|
-
return {"error": f"Failed to convert tagFilterExpression: {e!s}"}
|
|
1076
|
-
|
|
1077
|
-
# Create NewManualServiceConfig object
|
|
1078
|
-
logger.debug("Creating NewManualServiceConfig object")
|
|
1079
|
-
try:
|
|
1080
|
-
config_object = NewManualServiceConfig(**request_body)
|
|
1081
|
-
logger.debug("Successfully created NewManualServiceConfig object")
|
|
1082
|
-
except Exception as e:
|
|
1083
|
-
logger.debug(f"Error creating NewManualServiceConfig: {e}")
|
|
1084
|
-
return {"error": f"Failed to create config object: {e!s}"}
|
|
1085
|
-
|
|
1086
|
-
# Call the add_manual_service_config method from the SDK with the config object
|
|
1087
|
-
logger.debug("Calling add_manual_service_config with config object")
|
|
1088
|
-
try:
|
|
1089
|
-
result = api_client.add_manual_service_config(
|
|
1090
|
-
new_manual_service_config=config_object
|
|
1091
|
-
)
|
|
1092
|
-
except Exception as e:
|
|
1093
|
-
logger.debug(f"Error calling add_manual_service_config: {e}")
|
|
1094
|
-
return {"error": f"Failed to create config object: {e!s}"}
|
|
1095
|
-
|
|
1096
|
-
# Convert the result to a dictionary
|
|
701
|
+
config_object = ServiceConfig(**request_body)
|
|
702
|
+
result = api_client.update_service_config(id=id, service_config=config_object)
|
|
1097
703
|
if hasattr(result, 'to_dict'):
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
# If it's already a dict or another format, use it as is
|
|
1101
|
-
result_dict = result or {
|
|
1102
|
-
"success": True,
|
|
1103
|
-
"message": "Create new manual service config"
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
logger.debug(f"Result from add_manual_service_config: {result_dict}")
|
|
1107
|
-
return result_dict
|
|
704
|
+
return result.to_dict()
|
|
705
|
+
return result or {"success": True, "message": f"Service config '{id}' updated"}
|
|
1108
706
|
except Exception as e:
|
|
1109
|
-
logger.error(f"Error in
|
|
1110
|
-
return {"error": f"Failed to
|
|
707
|
+
logger.error(f"Error in _update_service_config: {e}", exc_info=True)
|
|
708
|
+
return {"error": f"Failed to update service config: {e!s}"}
|
|
1111
709
|
|
|
1112
|
-
@register_as_tool(
|
|
1113
|
-
title="Delete Manual Service Config",
|
|
1114
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1115
|
-
)
|
|
1116
710
|
@with_header_auth(ApplicationSettingsApi)
|
|
1117
|
-
async def
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
) -> Dict[str, Any]:
|
|
1123
|
-
"""
|
|
1124
|
-
Delete a manual service configuration.
|
|
1125
|
-
|
|
1126
|
-
Args:
|
|
1127
|
-
id: A unique id of the manual service configuration.
|
|
1128
|
-
ctx: The MCP context (optional)
|
|
1129
|
-
|
|
1130
|
-
Returns:
|
|
1131
|
-
Dict[str, Any]: Response from the delete manual service configuration API.
|
|
1132
|
-
"""
|
|
711
|
+
async def _delete_service_config(self,
|
|
712
|
+
id: str,
|
|
713
|
+
ctx=None,
|
|
714
|
+
api_client=None) -> Dict[str, Any]:
|
|
715
|
+
"""Delete a Service configuration."""
|
|
1133
716
|
try:
|
|
1134
|
-
debug_print("Delete manual service configs")
|
|
1135
717
|
if not id:
|
|
1136
|
-
return {"error": "
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
result_dict = {
|
|
1141
|
-
"success": True,
|
|
1142
|
-
"message": f"Manual Service Confiuguration '{id}' has been successfully deleted"
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
debug_print(f"Successfully deleted manual service config perspective with ID: {id}")
|
|
1146
|
-
return result_dict
|
|
1147
|
-
|
|
718
|
+
return {"error": "id is required"}
|
|
719
|
+
api_client.delete_service_config(id=id)
|
|
720
|
+
return {"success": True, "message": f"Service config '{id}' deleted successfully"}
|
|
1148
721
|
except Exception as e:
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
return {"error": f"Failed to delete manual service configs: {e!s}"}
|
|
722
|
+
logger.error(f"Error in _delete_service_config: {e}", exc_info=True)
|
|
723
|
+
return {"error": f"Failed to delete service config: {e!s}"}
|
|
1152
724
|
|
|
1153
|
-
@register_as_tool(
|
|
1154
|
-
title="Update Manual Service Config",
|
|
1155
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1156
|
-
)
|
|
1157
725
|
@with_header_auth(ApplicationSettingsApi)
|
|
1158
|
-
async def
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
api_client=None
|
|
1164
|
-
) -> Dict[str, Any]:
|
|
1165
|
-
"""
|
|
1166
|
-
The manual service configuration APIs enables mapping calls to services using tag filter expressions based on call tags.
|
|
1167
|
-
|
|
1168
|
-
There are two use cases on the usage of these APIs:
|
|
1169
|
-
|
|
1170
|
-
Map to an Unmonitored Service with a Custom Name. For example, Map HTTP calls to different Google domains (www.ibm.com, www.ibm.fr) into a single service named IBM using the call.http.host tag.
|
|
1171
|
-
Link Calls to an Existing Monitored Service. For example, Link database calls (jdbc:mysql://10.128.0.1:3306) to an existing service like MySQL@3306 on demo-host by referencing its service ID.
|
|
1172
|
-
|
|
1173
|
-
Args:
|
|
1174
|
-
id: A unique id of the manual service configuration.
|
|
1175
|
-
Sample payload: {
|
|
1176
|
-
"description": "Map source service example",
|
|
1177
|
-
"enabled": true,
|
|
1178
|
-
"existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
|
|
1179
|
-
"id": "BDGeDcG4TRSzRkJ1mGOk-Q",
|
|
1180
|
-
"tagFilterExpression": {
|
|
1181
|
-
"type": "TAG_FILTER",
|
|
1182
|
-
"name": "service.name",
|
|
1183
|
-
"stringValue": "front",
|
|
1184
|
-
"numberValue": null,
|
|
1185
|
-
"booleanValue": null,
|
|
1186
|
-
"key": null,
|
|
1187
|
-
"value": "front",
|
|
1188
|
-
"operator": "EQUALS",
|
|
1189
|
-
"entity": "SOURCE"
|
|
1190
|
-
},
|
|
1191
|
-
"unmonitoredServiceName": null
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
Returns:
|
|
1195
|
-
Dict[str, Any]: API response indicating success or failure.
|
|
1196
|
-
"""
|
|
726
|
+
async def _order_service_config(self,
|
|
727
|
+
request_body: List[str],
|
|
728
|
+
ctx=None,
|
|
729
|
+
api_client=None) -> Dict[str, Any]:
|
|
730
|
+
"""Order Service configurations."""
|
|
1197
731
|
try:
|
|
1198
|
-
if not
|
|
1199
|
-
return {"error": "
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
if isinstance(payload, str):
|
|
1203
|
-
logger.debug("Payload is a string, attempting to parse")
|
|
1204
|
-
try:
|
|
1205
|
-
import json
|
|
1206
|
-
try:
|
|
1207
|
-
parsed_payload = json.loads(payload)
|
|
1208
|
-
logger.debug("Successfully parsed payload as JSON")
|
|
1209
|
-
request_body = parsed_payload
|
|
1210
|
-
except json.JSONDecodeError as e:
|
|
1211
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1212
|
-
|
|
1213
|
-
# Try replacing single quotes with double quotes
|
|
1214
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1215
|
-
try:
|
|
1216
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1217
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1218
|
-
request_body = parsed_payload
|
|
1219
|
-
except json.JSONDecodeError:
|
|
1220
|
-
# Try as Python literal
|
|
1221
|
-
import ast
|
|
1222
|
-
try:
|
|
1223
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1224
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1225
|
-
request_body = parsed_payload
|
|
1226
|
-
except (SyntaxError, ValueError) as e2:
|
|
1227
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1228
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
1229
|
-
except Exception as e:
|
|
1230
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1231
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
1232
|
-
else:
|
|
1233
|
-
# If payload is already a dictionary, use it directly
|
|
1234
|
-
logger.debug("Using provided payload dictionary")
|
|
1235
|
-
request_body = payload
|
|
1236
|
-
|
|
1237
|
-
# Import the ManualServiceConfig class
|
|
1238
|
-
try:
|
|
1239
|
-
from instana_client.models.manual_service_config import (
|
|
1240
|
-
ManualServiceConfig,
|
|
1241
|
-
)
|
|
1242
|
-
logger.debug("Successfully imported ManualServiceConfig")
|
|
1243
|
-
except ImportError as e:
|
|
1244
|
-
logger.debug(f"Error importing ManualServiceConfig: {e}")
|
|
1245
|
-
return {"error": f"Failed to import ManualServiceConfig: {e!s}"}
|
|
1246
|
-
|
|
1247
|
-
# Keep tagFilterExpression as dictionary - SDK will handle serialization
|
|
1248
|
-
if 'tagFilterExpression' in request_body and isinstance(request_body['tagFilterExpression'], dict):
|
|
1249
|
-
logger.debug(f"tagFilterExpression will be passed as dict: {request_body['tagFilterExpression']}")
|
|
1250
|
-
|
|
1251
|
-
# Call the update_manual_service_config method from the SDK directly with dict
|
|
1252
|
-
logger.debug("Calling update_manual_service_config with request body")
|
|
1253
|
-
try:
|
|
1254
|
-
result = api_client.update_manual_service_config(
|
|
1255
|
-
id=id,
|
|
1256
|
-
manual_service_config=request_body
|
|
1257
|
-
)
|
|
1258
|
-
except Exception as e:
|
|
1259
|
-
logger.debug(f"Error calling update_manual_service_config: {e}")
|
|
1260
|
-
return {"error": f"Failed to update manual config object: {e!s}"}
|
|
1261
|
-
|
|
1262
|
-
# Convert the result to a dictionary
|
|
1263
|
-
if hasattr(result, 'to_dict'):
|
|
1264
|
-
result_dict = result.to_dict()
|
|
1265
|
-
else:
|
|
1266
|
-
# If it's already a dict or another format, use it as is
|
|
1267
|
-
result_dict = result or {
|
|
1268
|
-
"success": True,
|
|
1269
|
-
"message": "update manual service config"
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
logger.debug(f"Result from update_manual_service_config: {result_dict}")
|
|
1273
|
-
return result_dict
|
|
732
|
+
if not request_body:
|
|
733
|
+
return {"error": "request_body is required"}
|
|
734
|
+
result = api_client.order_service_config(request_body=request_body)
|
|
735
|
+
return {"success": True, "message": "Service configs ordered successfully", "result": result}
|
|
1274
736
|
except Exception as e:
|
|
1275
|
-
logger.error(f"Error in
|
|
1276
|
-
return {"error": f"Failed to
|
|
1277
|
-
|
|
737
|
+
logger.error(f"Error in _order_service_config: {e}", exc_info=True)
|
|
738
|
+
return {"error": f"Failed to order service configs: {e!s}"}
|
|
1278
739
|
|
|
1279
|
-
@register_as_tool(
|
|
1280
|
-
title="Replace All Manual Service Config",
|
|
1281
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1282
|
-
)
|
|
1283
740
|
@with_header_auth(ApplicationSettingsApi)
|
|
1284
|
-
async def
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
) -> List[Dict[str, Any]]:
|
|
1290
|
-
"""
|
|
1291
|
-
This tool is used if one wants to update more than 1 manual service configurations.
|
|
1292
|
-
|
|
1293
|
-
There are two use cases on the usage of these APIs:
|
|
1294
|
-
|
|
1295
|
-
Map to an Unmonitored Service with a Custom Name. For example, Map HTTP calls to different Google domains (www.ibm.com, www.ibm.fr) into a single service named IBM using the call.http.host tag.
|
|
1296
|
-
Link Calls to an Existing Monitored Service. For example, Link database calls (jdbc:mysql://10.128.0.1:3306) to an existing service like MySQL@3306 on demo-host by referencing its service ID.
|
|
1297
|
-
|
|
1298
|
-
Args:
|
|
1299
|
-
Sample payload: [
|
|
1300
|
-
{
|
|
1301
|
-
"description": "Map source service",
|
|
1302
|
-
"enabled": true,
|
|
1303
|
-
"existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
|
|
1304
|
-
"tagFilterExpression": {
|
|
1305
|
-
"type": "TAG_FILTER",
|
|
1306
|
-
"name": "service.name",
|
|
1307
|
-
"stringValue": "front",
|
|
1308
|
-
"numberValue": null,
|
|
1309
|
-
"booleanValue": null,
|
|
1310
|
-
"key": null,
|
|
1311
|
-
"value": "front",
|
|
1312
|
-
"operator": "EQUALS",
|
|
1313
|
-
"entity": "SOURCE"
|
|
1314
|
-
},
|
|
1315
|
-
"unmonitoredServiceName": null
|
|
1316
|
-
}
|
|
1317
|
-
]
|
|
1318
|
-
ctx: Optional execution context.
|
|
1319
|
-
|
|
1320
|
-
Returns:
|
|
1321
|
-
Dict[str, Any]: API response indicating success or failure.
|
|
1322
|
-
"""
|
|
741
|
+
async def _replace_all_service_configs(self,
|
|
742
|
+
payload: Union[Dict[str, Any], str],
|
|
743
|
+
ctx=None,
|
|
744
|
+
api_client=None) -> Dict[str, Any]:
|
|
745
|
+
"""Replace all Service configurations."""
|
|
1323
746
|
try:
|
|
1324
747
|
if not payload:
|
|
1325
|
-
return
|
|
748
|
+
return {"error": "payload is required"}
|
|
1326
749
|
|
|
1327
|
-
# Parse the payload if it's a string
|
|
1328
750
|
if isinstance(payload, str):
|
|
1329
|
-
|
|
751
|
+
import json
|
|
1330
752
|
try:
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
request_body = parsed_payload
|
|
1336
|
-
except json.JSONDecodeError as e:
|
|
1337
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1338
|
-
|
|
1339
|
-
# Try replacing single quotes with double quotes
|
|
1340
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1341
|
-
try:
|
|
1342
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1343
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1344
|
-
request_body = parsed_payload
|
|
1345
|
-
except json.JSONDecodeError:
|
|
1346
|
-
# Try as Python literal
|
|
1347
|
-
import ast
|
|
1348
|
-
try:
|
|
1349
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1350
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1351
|
-
request_body = parsed_payload
|
|
1352
|
-
except (SyntaxError, ValueError) as e2:
|
|
1353
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1354
|
-
return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
|
|
1355
|
-
except Exception as e:
|
|
1356
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1357
|
-
return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
|
|
753
|
+
request_body = json.loads(payload)
|
|
754
|
+
except json.JSONDecodeError:
|
|
755
|
+
import ast
|
|
756
|
+
request_body = ast.literal_eval(payload)
|
|
1358
757
|
else:
|
|
1359
|
-
# If payload is already a dictionary, use it directly
|
|
1360
|
-
logger.debug("Using provided payload dictionary")
|
|
1361
758
|
request_body = payload
|
|
1362
759
|
|
|
1363
|
-
#
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
)
|
|
1368
|
-
logger.debug("Successfully imported ManualServiceConfig")
|
|
1369
|
-
except ImportError as e:
|
|
1370
|
-
logger.debug(f"Error importing ManualServiceConfig: {e}")
|
|
1371
|
-
return [{"error": f"Failed to import ManualServiceConfig: {e!s}"}]
|
|
1372
|
-
|
|
1373
|
-
# Create an ManualServiceConfig object from the request body
|
|
1374
|
-
try:
|
|
1375
|
-
logger.debug(f"Creating ManualServiceConfig with params: {request_body}")
|
|
1376
|
-
config_object = [NewManualServiceConfig(**request_body)]
|
|
1377
|
-
logger.debug("Successfully replace all manual service config object")
|
|
1378
|
-
except Exception as e:
|
|
1379
|
-
logger.debug(f"Error creating ManualServiceConfig: {e}")
|
|
1380
|
-
return [{"error": f"Failed to replace all manual config object: {e!s}"}]
|
|
1381
|
-
|
|
1382
|
-
# Call the replace_all_manual_service_config method from the SDK
|
|
1383
|
-
logger.debug("Calling replace_all_manual_service_config with config object")
|
|
1384
|
-
result = api_client.replace_all_manual_service_config(
|
|
1385
|
-
new_manual_service_config=config_object
|
|
1386
|
-
)
|
|
1387
|
-
|
|
1388
|
-
# Convert the result to a dictionary
|
|
1389
|
-
if hasattr(result, 'to_dict'):
|
|
1390
|
-
result_dict = result.to_dict()
|
|
1391
|
-
else:
|
|
1392
|
-
# If it's already a dict or another format, use it as is
|
|
1393
|
-
result_dict = result or {
|
|
1394
|
-
"success": True,
|
|
1395
|
-
"message": "Create replace all manual service config"
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
logger.debug(f"Result from replace_all_manual_service_config: {result_dict}")
|
|
1399
|
-
return [result_dict]
|
|
760
|
+
# Assuming request_body is a list of ServiceConfig objects
|
|
761
|
+
config_objects = [ServiceConfig(**item) if isinstance(item, dict) else item for item in request_body]
|
|
762
|
+
result = api_client.replace_all_service_configs(service_config=config_objects)
|
|
763
|
+
return {"success": True, "message": "All service configs replaced successfully", "result": result}
|
|
1400
764
|
except Exception as e:
|
|
1401
|
-
logger.error(f"Error in
|
|
1402
|
-
return
|
|
765
|
+
logger.error(f"Error in _replace_all_service_configs: {e}", exc_info=True)
|
|
766
|
+
return {"error": f"Failed to replace all service configs: {e!s}"}
|
|
1403
767
|
|
|
1404
|
-
|
|
1405
|
-
title="Get All Service Configs",
|
|
1406
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
1407
|
-
)
|
|
768
|
+
# Manual Service Config Operations
|
|
1408
769
|
@with_header_auth(ApplicationSettingsApi)
|
|
1409
|
-
async def
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
"""
|
|
1413
|
-
This tool gives list of All Service Perspectives Configuration
|
|
1414
|
-
Get a list of all Service Perspectives with their configuration settings.
|
|
1415
|
-
Args:
|
|
1416
|
-
ctx: The MCP context (optional)
|
|
1417
|
-
|
|
1418
|
-
Returns:
|
|
1419
|
-
Dictionary containing endpoints data or error information
|
|
1420
|
-
"""
|
|
770
|
+
async def _get_all_manual_service_configs(self,
|
|
771
|
+
ctx=None,
|
|
772
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
773
|
+
"""Get all Manual Service configurations."""
|
|
1421
774
|
try:
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
else:
|
|
1428
|
-
# If it's already a dict or another format, use it as is
|
|
1429
|
-
result_dict = result
|
|
1430
|
-
|
|
1431
|
-
debug_print(f"Result from get_service_configs: {result_dict}")
|
|
1432
|
-
return result_dict
|
|
1433
|
-
|
|
775
|
+
result = api_client.get_manual_service_configs_without_preload_content()
|
|
776
|
+
import json
|
|
777
|
+
response_text = result.data.decode('utf-8')
|
|
778
|
+
json_data = json.loads(response_text)
|
|
779
|
+
return json_data if isinstance(json_data, list) else [json_data] if json_data else []
|
|
1434
780
|
except Exception as e:
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
return [{"error": f"Failed to get application data metrics: {e}"}]
|
|
781
|
+
logger.error(f"Error in _get_all_manual_service_configs: {e}", exc_info=True)
|
|
782
|
+
return [{"error": f"Failed to get manual service configs: {e!s}"}]
|
|
1438
783
|
|
|
1439
|
-
@register_as_tool(
|
|
1440
|
-
title="Add Service Config",
|
|
1441
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1442
|
-
)
|
|
1443
784
|
@with_header_auth(ApplicationSettingsApi)
|
|
1444
|
-
async def
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
"""
|
|
1449
|
-
This tool gives is used to add new Service Perspectives Configuration
|
|
1450
|
-
Get a list of all Service Perspectives with their configuration settings.
|
|
1451
|
-
Args:
|
|
1452
|
-
{
|
|
1453
|
-
"comment": null,
|
|
1454
|
-
"enabled": true,
|
|
1455
|
-
"label": "{gce.zone}-{jvm.args.abc}",
|
|
1456
|
-
"matchSpecification": [
|
|
1457
|
-
{
|
|
1458
|
-
"key": "gce.zone",
|
|
1459
|
-
"value": ".*"
|
|
1460
|
-
},
|
|
1461
|
-
{
|
|
1462
|
-
"key": "jvm.args.abc",
|
|
1463
|
-
"value": ".*"
|
|
1464
|
-
}
|
|
1465
|
-
],
|
|
1466
|
-
"name": "ABC is good"
|
|
1467
|
-
}
|
|
1468
|
-
ctx: The MCP context (optional)
|
|
1469
|
-
|
|
1470
|
-
Returns:
|
|
1471
|
-
Dictionary containing endpoints data or error information
|
|
1472
|
-
"""
|
|
785
|
+
async def _add_manual_service_config(self,
|
|
786
|
+
payload: Union[Dict[str, Any], str],
|
|
787
|
+
ctx=None,
|
|
788
|
+
api_client=None) -> Dict[str, Any]:
|
|
789
|
+
"""Add a new Manual Service configuration."""
|
|
1473
790
|
try:
|
|
1474
791
|
if not payload:
|
|
1475
|
-
return {"error": "
|
|
792
|
+
return {"error": "payload is required"}
|
|
1476
793
|
|
|
1477
|
-
# Parse the payload if it's a string
|
|
1478
794
|
if isinstance(payload, str):
|
|
1479
|
-
|
|
795
|
+
import json
|
|
1480
796
|
try:
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
request_body = parsed_payload
|
|
1486
|
-
except json.JSONDecodeError as e:
|
|
1487
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1488
|
-
|
|
1489
|
-
# Try replacing single quotes with double quotes
|
|
1490
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1491
|
-
try:
|
|
1492
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1493
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1494
|
-
request_body = parsed_payload
|
|
1495
|
-
except json.JSONDecodeError:
|
|
1496
|
-
# Try as Python literal
|
|
1497
|
-
import ast
|
|
1498
|
-
try:
|
|
1499
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1500
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1501
|
-
request_body = parsed_payload
|
|
1502
|
-
except (SyntaxError, ValueError) as e2:
|
|
1503
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1504
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
1505
|
-
except Exception as e:
|
|
1506
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1507
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
797
|
+
request_body = json.loads(payload)
|
|
798
|
+
except json.JSONDecodeError:
|
|
799
|
+
import ast
|
|
800
|
+
request_body = ast.literal_eval(payload)
|
|
1508
801
|
else:
|
|
1509
|
-
# If payload is already a dictionary, use it directly
|
|
1510
|
-
logger.debug("Using provided payload dictionary")
|
|
1511
802
|
request_body = payload
|
|
1512
803
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
from instana_client.models.service_config import (
|
|
1516
|
-
ServiceConfig,
|
|
1517
|
-
)
|
|
1518
|
-
logger.debug("Successfully imported ServiceConfig")
|
|
1519
|
-
except ImportError as e:
|
|
1520
|
-
logger.debug(f"Error importing ServiceConfig: {e}")
|
|
1521
|
-
return {"error": f"Failed to import ServiceConfig: {e!s}"}
|
|
1522
|
-
|
|
1523
|
-
# Create an ServiceConfig object from the request body
|
|
1524
|
-
try:
|
|
1525
|
-
logger.debug(f"Creating ServiceConfig with params: {request_body}")
|
|
1526
|
-
request_body_with_id = dict(request_body)
|
|
1527
|
-
request_body_with_id.setdefault("id", "temp-id")
|
|
1528
|
-
config_object = ServiceConfig(**request_body_with_id)
|
|
1529
|
-
logger.debug("Successfully add service config object")
|
|
1530
|
-
except Exception as e:
|
|
1531
|
-
logger.debug(f"Error creating ServiceConfig: {e}")
|
|
1532
|
-
return {"error": f"Failed to add service config object: {e!s}"}
|
|
1533
|
-
|
|
1534
|
-
# Call the ServiceConfig method from the SDK
|
|
1535
|
-
logger.debug("Calling add_service_config with config object")
|
|
1536
|
-
result = api_client.add_service_config(
|
|
1537
|
-
service_config=config_object
|
|
1538
|
-
)
|
|
1539
|
-
# Convert the result to a dictionary
|
|
804
|
+
config_object = NewManualServiceConfig(**request_body)
|
|
805
|
+
result = api_client.add_manual_service_config(new_manual_service_config=config_object)
|
|
1540
806
|
if hasattr(result, 'to_dict'):
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
# If it's already a dict or another format, use it as is
|
|
1544
|
-
result_dict = result or {
|
|
1545
|
-
"success": True,
|
|
1546
|
-
"message": "Create service config"
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
logger.debug(f"Result from add_service_config: {result_dict}")
|
|
1550
|
-
return result_dict
|
|
807
|
+
return result.to_dict()
|
|
808
|
+
return result or {"success": True, "message": "Manual service config created"}
|
|
1551
809
|
except Exception as e:
|
|
1552
|
-
logger.error(f"Error in
|
|
1553
|
-
return {"error": f"Failed to add service config: {e!s}"}
|
|
810
|
+
logger.error(f"Error in _add_manual_service_config: {e}", exc_info=True)
|
|
811
|
+
return {"error": f"Failed to add manual service config: {e!s}"}
|
|
1554
812
|
|
|
1555
|
-
@register_as_tool(
|
|
1556
|
-
title="Replace All Service Configs",
|
|
1557
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1558
|
-
)
|
|
1559
813
|
@with_header_auth(ApplicationSettingsApi)
|
|
1560
|
-
async def
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
[
|
|
1567
|
-
{
|
|
1568
|
-
"comment": null,
|
|
1569
|
-
"enabled": true,
|
|
1570
|
-
"id": "8C-jGYx8Rsue854tzkh8KQ",
|
|
1571
|
-
"label": "{docker.container.name}",
|
|
1572
|
-
"matchSpecification": [
|
|
1573
|
-
{
|
|
1574
|
-
"key": "docker.container.name",
|
|
1575
|
-
"value": ".*"
|
|
1576
|
-
}
|
|
1577
|
-
],
|
|
1578
|
-
"name": "Rule"
|
|
1579
|
-
}
|
|
1580
|
-
]
|
|
1581
|
-
ctx: The MCP context (optional)
|
|
1582
|
-
|
|
1583
|
-
Returns:
|
|
1584
|
-
Dictionary containing endpoints data or error information
|
|
1585
|
-
"""
|
|
814
|
+
async def _update_manual_service_config(self,
|
|
815
|
+
id: str,
|
|
816
|
+
payload: Union[Dict[str, Any], str],
|
|
817
|
+
ctx=None,
|
|
818
|
+
api_client=None) -> Dict[str, Any]:
|
|
819
|
+
"""Update a Manual Service configuration."""
|
|
1586
820
|
try:
|
|
1587
|
-
if not payload:
|
|
1588
|
-
return
|
|
821
|
+
if not id or not payload:
|
|
822
|
+
return {"error": "id and payload are required"}
|
|
1589
823
|
|
|
1590
|
-
# Parse the payload if it's a string
|
|
1591
824
|
if isinstance(payload, str):
|
|
1592
|
-
|
|
825
|
+
import json
|
|
1593
826
|
try:
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
request_body = parsed_payload
|
|
1599
|
-
except json.JSONDecodeError as e:
|
|
1600
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1601
|
-
# Try replacing single quotes with double quotes
|
|
1602
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1603
|
-
try:
|
|
1604
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1605
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1606
|
-
request_body = parsed_payload
|
|
1607
|
-
except json.JSONDecodeError:
|
|
1608
|
-
# Try as Python literal
|
|
1609
|
-
import ast
|
|
1610
|
-
try:
|
|
1611
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1612
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1613
|
-
request_body = parsed_payload
|
|
1614
|
-
except (SyntaxError, ValueError) as e2:
|
|
1615
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1616
|
-
return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
|
|
1617
|
-
except Exception as e:
|
|
1618
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1619
|
-
return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
|
|
827
|
+
request_body = json.loads(payload)
|
|
828
|
+
except json.JSONDecodeError:
|
|
829
|
+
import ast
|
|
830
|
+
request_body = ast.literal_eval(payload)
|
|
1620
831
|
else:
|
|
1621
|
-
# If payload is already a dictionary, use it directly
|
|
1622
|
-
logger.debug("Using provided payload dictionary")
|
|
1623
832
|
request_body = payload
|
|
1624
833
|
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
logger.debug("Successfully imported ServiceConfig")
|
|
1631
|
-
except ImportError as e:
|
|
1632
|
-
logger.debug(f"Error importing ServiceConfig: {e}")
|
|
1633
|
-
return [{"error": f"Failed to import ServiceConfig: {e!s}"}]
|
|
1634
|
-
|
|
1635
|
-
# Create an ServiceConfig object from the request body
|
|
1636
|
-
try:
|
|
1637
|
-
logger.debug(f"Creating ServiceConfig with params: {request_body}")
|
|
1638
|
-
configs = request_body.get("serviceConfigs")
|
|
1639
|
-
if not isinstance(configs, list):
|
|
1640
|
-
return [{"error": "serviceConfigs must be a list"}]
|
|
1641
|
-
config_object = [ServiceConfig(**item) for item in configs]
|
|
1642
|
-
logger.debug("Successfully replace all manual service config object")
|
|
1643
|
-
except Exception as e:
|
|
1644
|
-
logger.debug(f"Error creating ServiceConfig: {e}")
|
|
1645
|
-
return [{"error": f"Failed to replace all manual config object: {e!s}"}]
|
|
1646
|
-
|
|
1647
|
-
# Call the replace_all method from the SDK
|
|
1648
|
-
logger.debug("Calling replace_all with config object")
|
|
1649
|
-
result = api_client.replace_all_without_preload_content(
|
|
1650
|
-
service_config=config_object
|
|
1651
|
-
)
|
|
1652
|
-
|
|
1653
|
-
# Convert the result to a list of dictionaries
|
|
1654
|
-
import json
|
|
1655
|
-
try:
|
|
1656
|
-
response_text = result.data.decode('utf-8')
|
|
1657
|
-
result_dict = json.loads(response_text)
|
|
1658
|
-
logger.debug("Successfully replaced all service configs.")
|
|
1659
|
-
return result_dict
|
|
1660
|
-
except (json.JSONDecodeError, AttributeError) as json_err:
|
|
1661
|
-
error_message = f"Failed to parse JSON response: {json_err}"
|
|
1662
|
-
logger.error(error_message)
|
|
1663
|
-
return [{"error": error_message}]
|
|
1664
|
-
|
|
1665
|
-
except Exception as e:
|
|
1666
|
-
logger.error(f"Error in replace_all: {e}")
|
|
1667
|
-
return [{"error": f"Failed to replace all service config: {e!s}"}]
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
@register_as_tool(
|
|
1671
|
-
title="Order Service Config",
|
|
1672
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1673
|
-
)
|
|
1674
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
1675
|
-
async def order_service_config(self,
|
|
1676
|
-
request_body: List[str],
|
|
1677
|
-
ctx=None,
|
|
1678
|
-
api_client=None) -> Dict[str, Any]:
|
|
1679
|
-
"""
|
|
1680
|
-
order Service Configurations (Custom Service Rules)
|
|
1681
|
-
|
|
1682
|
-
This tool changes the order of service configurations based on the provided list of IDs.
|
|
1683
|
-
All service configuration IDs must be included in the request.
|
|
1684
|
-
|
|
1685
|
-
Args:
|
|
1686
|
-
request_body: List of service configuration IDs in the desired order.
|
|
1687
|
-
ctx: The MCP context (optional)
|
|
1688
|
-
|
|
1689
|
-
Returns:
|
|
1690
|
-
A dictionary with the API response or error message.
|
|
1691
|
-
"""
|
|
1692
|
-
try:
|
|
1693
|
-
debug_print("ordering service configurations")
|
|
1694
|
-
|
|
1695
|
-
if not request_body:
|
|
1696
|
-
return {"error": "The list of service configuration IDs cannot be empty."}
|
|
1697
|
-
|
|
1698
|
-
response = api_client.order_service_config_with_http_info(
|
|
1699
|
-
request_body=request_body,
|
|
1700
|
-
_content_type='application/json',
|
|
1701
|
-
)
|
|
1702
|
-
|
|
1703
|
-
status = getattr(response, 'status', None)
|
|
1704
|
-
data = getattr(response, 'data', None)
|
|
1705
|
-
|
|
1706
|
-
if hasattr(data, 'to_dict'):
|
|
1707
|
-
payload = data.to_dict()
|
|
1708
|
-
elif data is None:
|
|
1709
|
-
payload = None
|
|
1710
|
-
else:
|
|
1711
|
-
payload = data
|
|
1712
|
-
|
|
1713
|
-
if status == 204 or payload is None:
|
|
1714
|
-
return {
|
|
1715
|
-
"success": True,
|
|
1716
|
-
"status": status,
|
|
1717
|
-
"message": "Service configs reordered successfully"
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
return {
|
|
1721
|
-
"success": True,
|
|
1722
|
-
"status": status,
|
|
1723
|
-
"data": payload
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
except Exception as e:
|
|
1727
|
-
debug_print(f"Error in order_service_config: {e}")
|
|
1728
|
-
traceback.print_exc(file=sys.stderr)
|
|
1729
|
-
return {"error": f"Failed to order service configs: {e!s}"}
|
|
1730
|
-
|
|
1731
|
-
@register_as_tool(
|
|
1732
|
-
title="Delete Service Config",
|
|
1733
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1734
|
-
)
|
|
1735
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
1736
|
-
async def delete_service_config(self,
|
|
1737
|
-
id: str,
|
|
1738
|
-
ctx=None,
|
|
1739
|
-
api_client=None) -> Dict[str, Any]:
|
|
1740
|
-
"""
|
|
1741
|
-
Delete a Service Perspective configuration.
|
|
1742
|
-
This tool allows you to delete an existing Service Config by its ID.
|
|
1743
|
-
|
|
1744
|
-
Args:
|
|
1745
|
-
id: The ID of the application perspective to delete
|
|
1746
|
-
ctx: The MCP context (optional)
|
|
1747
|
-
|
|
1748
|
-
Returns:
|
|
1749
|
-
Dictionary containing the result of the deletion or error information
|
|
1750
|
-
"""
|
|
1751
|
-
try:
|
|
1752
|
-
if not id:
|
|
1753
|
-
return {"error": "Service perspective ID is required for deletion"}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
debug_print(f"Deleting application perspective with ID: {id}")
|
|
1757
|
-
# Call the delete_service_config method from the SDK
|
|
1758
|
-
api_client.delete_service_config(id=id)
|
|
1759
|
-
|
|
1760
|
-
result_dict = {
|
|
1761
|
-
"success": True,
|
|
1762
|
-
"message": f"Service Confiuguration '{id}' has been successfully deleted"
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
debug_print(f"Successfully deleted service perspective with ID: {id}")
|
|
1766
|
-
return result_dict
|
|
834
|
+
config_object = ManualServiceConfig(**request_body)
|
|
835
|
+
result = api_client.update_manual_service_config(id=id, manual_service_config=config_object)
|
|
836
|
+
if hasattr(result, 'to_dict'):
|
|
837
|
+
return result.to_dict()
|
|
838
|
+
return result or {"success": True, "message": f"Manual service config '{id}' updated"}
|
|
1767
839
|
except Exception as e:
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
return {"error": f"Failed to delete service configuration: {e!s}"}
|
|
840
|
+
logger.error(f"Error in _update_manual_service_config: {e}", exc_info=True)
|
|
841
|
+
return {"error": f"Failed to update manual service config: {e!s}"}
|
|
1771
842
|
|
|
1772
|
-
@register_as_tool(
|
|
1773
|
-
title="Get Service Config",
|
|
1774
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
1775
|
-
)
|
|
1776
843
|
@with_header_auth(ApplicationSettingsApi)
|
|
1777
|
-
async def
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
) -> Dict[str, Any]:
|
|
1783
|
-
"""
|
|
1784
|
-
This MCP tool is used if one wants to retrieve the particular custom service configuration.
|
|
1785
|
-
Args:
|
|
1786
|
-
id: An Instana generated unique identifier for a Service.
|
|
1787
|
-
ctx: The MCP context (optional)
|
|
1788
|
-
|
|
1789
|
-
Returns:
|
|
1790
|
-
Dict[str, Any]: Response from the create/update endpoint configuration API.
|
|
1791
|
-
|
|
1792
|
-
"""
|
|
844
|
+
async def _delete_manual_service_config(self,
|
|
845
|
+
id: str,
|
|
846
|
+
ctx=None,
|
|
847
|
+
api_client=None) -> Dict[str, Any]:
|
|
848
|
+
"""Delete a Manual Service configuration."""
|
|
1793
849
|
try:
|
|
1794
|
-
debug_print("get service config")
|
|
1795
850
|
if not id:
|
|
1796
|
-
return {"error": "
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
id=id
|
|
1800
|
-
)
|
|
1801
|
-
# Convert the result to a dictionary
|
|
1802
|
-
if hasattr(result, 'to_dict'):
|
|
1803
|
-
result_dict = result.to_dict()
|
|
1804
|
-
else:
|
|
1805
|
-
# If it's already a dict or another format, use it as is
|
|
1806
|
-
result_dict = result
|
|
1807
|
-
|
|
1808
|
-
debug_print(f"Result from get_service_config: {result_dict}")
|
|
1809
|
-
return result_dict
|
|
851
|
+
return {"error": "id is required"}
|
|
852
|
+
api_client.delete_manual_service_config(id=id)
|
|
853
|
+
return {"success": True, "message": f"Manual service config '{id}' deleted successfully"}
|
|
1810
854
|
except Exception as e:
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
return {"error": f"Failed to get service config: {e!s}"}
|
|
855
|
+
logger.error(f"Error in _delete_manual_service_config: {e}", exc_info=True)
|
|
856
|
+
return {"error": f"Failed to delete manual service config: {e!s}"}
|
|
1814
857
|
|
|
1815
|
-
@register_as_tool(
|
|
1816
|
-
title="Update Service Config",
|
|
1817
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1818
|
-
)
|
|
1819
858
|
@with_header_auth(ApplicationSettingsApi)
|
|
1820
|
-
async def
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
"""
|
|
1826
|
-
This tool gives is used if one wants to update a particular custom service rule.
|
|
1827
|
-
Args:
|
|
1828
|
-
{
|
|
1829
|
-
"comment": null,
|
|
1830
|
-
"enabled": true,
|
|
1831
|
-
"id": "9uma4MhnTTSyBzwu_FKBJA",
|
|
1832
|
-
"label": "{gce.zone}-{jvm.args.abc}",
|
|
1833
|
-
"matchSpecification": [
|
|
1834
|
-
{
|
|
1835
|
-
"key": "gce.zone",
|
|
1836
|
-
"value": ".*"
|
|
1837
|
-
},
|
|
1838
|
-
{
|
|
1839
|
-
"key": "jvm.args.abc",
|
|
1840
|
-
"value": ".*"
|
|
1841
|
-
}
|
|
1842
|
-
],
|
|
1843
|
-
"name": "DEF is good"
|
|
1844
|
-
}
|
|
1845
|
-
ctx: The MCP context (optional)
|
|
1846
|
-
|
|
1847
|
-
Returns:
|
|
1848
|
-
Dictionary containing endpoints data or error information
|
|
1849
|
-
"""
|
|
859
|
+
async def _replace_all_manual_service_config(self,
|
|
860
|
+
payload: Union[Dict[str, Any], str],
|
|
861
|
+
ctx=None,
|
|
862
|
+
api_client=None) -> Dict[str, Any]:
|
|
863
|
+
"""Replace all Manual Service configurations."""
|
|
1850
864
|
try:
|
|
1851
|
-
if not payload
|
|
1852
|
-
return {"error": "
|
|
865
|
+
if not payload:
|
|
866
|
+
return {"error": "payload is required"}
|
|
1853
867
|
|
|
1854
|
-
# Parse the payload if it's a string
|
|
1855
868
|
if isinstance(payload, str):
|
|
1856
|
-
|
|
869
|
+
import json
|
|
1857
870
|
try:
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
request_body = parsed_payload
|
|
1863
|
-
except json.JSONDecodeError as e:
|
|
1864
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1865
|
-
|
|
1866
|
-
# Try replacing single quotes with double quotes
|
|
1867
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1868
|
-
try:
|
|
1869
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1870
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1871
|
-
request_body = parsed_payload
|
|
1872
|
-
except json.JSONDecodeError:
|
|
1873
|
-
# Try as Python literal
|
|
1874
|
-
import ast
|
|
1875
|
-
try:
|
|
1876
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1877
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1878
|
-
request_body = parsed_payload
|
|
1879
|
-
except (SyntaxError, ValueError) as e2:
|
|
1880
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1881
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
1882
|
-
except Exception as e:
|
|
1883
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1884
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
871
|
+
request_body = json.loads(payload)
|
|
872
|
+
except json.JSONDecodeError:
|
|
873
|
+
import ast
|
|
874
|
+
request_body = ast.literal_eval(payload)
|
|
1885
875
|
else:
|
|
1886
|
-
# If payload is already a dictionary, use it directly
|
|
1887
|
-
logger.debug("Using provided payload dictionary")
|
|
1888
876
|
request_body = payload
|
|
1889
877
|
|
|
1890
|
-
#
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
)
|
|
1895
|
-
logger.debug("Successfully imported ServiceConfig")
|
|
1896
|
-
except ImportError as e:
|
|
1897
|
-
logger.debug(f"Error importing ServiceConfig: {e}")
|
|
1898
|
-
return {"error": f"Failed to import ServiceConfig: {e!s}"}
|
|
1899
|
-
|
|
1900
|
-
# Create an ServiceConfig object from the request body
|
|
1901
|
-
try:
|
|
1902
|
-
logger.debug(f"Creating ServiceConfig with params: {request_body}")
|
|
1903
|
-
config_object = ServiceConfig(**request_body)
|
|
1904
|
-
logger.debug("Successfully update service config object")
|
|
1905
|
-
except Exception as e:
|
|
1906
|
-
logger.debug(f"Error creating ServiceConfig: {e}")
|
|
1907
|
-
return {"error": f"Failed to update service config object: {e!s}"}
|
|
1908
|
-
|
|
1909
|
-
# Call the put_service_config method from the SDK
|
|
1910
|
-
logger.debug("Calling put_service_config with config object")
|
|
1911
|
-
result = api_client.put_service_config(
|
|
1912
|
-
id=id,
|
|
1913
|
-
service_config=config_object
|
|
1914
|
-
)
|
|
1915
|
-
|
|
1916
|
-
# Convert the result to a dictionary
|
|
1917
|
-
if hasattr(result, 'to_dict'):
|
|
1918
|
-
result_dict = result.to_dict()
|
|
1919
|
-
else:
|
|
1920
|
-
# If it's already a dict or another format, use it as is
|
|
1921
|
-
result_dict = result or {
|
|
1922
|
-
"success": True,
|
|
1923
|
-
"message": "put service config"
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
logger.debug(f"Result from put_service_config: {result_dict}")
|
|
1927
|
-
return result_dict
|
|
878
|
+
# Assuming request_body is a list of ManualServiceConfig objects
|
|
879
|
+
config_objects = [ManualServiceConfig(**item) if isinstance(item, dict) else item for item in request_body]
|
|
880
|
+
result = api_client.replace_all_manual_service_configs(manual_service_config=config_objects)
|
|
881
|
+
return {"success": True, "message": "All manual service configs replaced successfully", "result": result}
|
|
1928
882
|
except Exception as e:
|
|
1929
|
-
logger.error(f"Error in
|
|
1930
|
-
return {"error": f"Failed to
|
|
883
|
+
logger.error(f"Error in _replace_all_manual_service_config: {e}", exc_info=True)
|
|
884
|
+
return {"error": f"Failed to replace all manual service configs: {e!s}"}
|
|
1931
885
|
|