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.
Files changed (28) hide show
  1. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +179 -120
  2. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +28 -21
  3. src/application/application_alert_config.py +397 -146
  4. src/application/application_analyze.py +597 -597
  5. src/application/application_call_group.py +528 -0
  6. src/application/application_catalog.py +0 -8
  7. src/application/application_global_alert_config.py +255 -38
  8. src/application/application_metrics.py +377 -237
  9. src/application/application_resources.py +414 -365
  10. src/application/application_settings.py +605 -1651
  11. src/application/application_topology.py +62 -62
  12. src/core/custom_dashboard_smart_router_tool.py +135 -0
  13. src/core/server.py +92 -119
  14. src/core/smart_router_tool.py +574 -0
  15. src/core/utils.py +17 -8
  16. src/custom_dashboard/custom_dashboard_tools.py +422 -0
  17. src/infrastructure/elicitation_handler.py +338 -0
  18. src/infrastructure/entity_registry.py +329 -0
  19. src/infrastructure/infrastructure_analyze_new.py +600 -0
  20. src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
  21. src/infrastructure/infrastructure_catalog.py +7 -28
  22. src/infrastructure/infrastructure_metrics.py +93 -17
  23. src/infrastructure/infrastructure_resources.py +5 -20
  24. src/infrastructure/infrastructure_topology.py +2 -8
  25. src/prompts/application/application_settings.py +58 -0
  26. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +0 -0
  27. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
  28. {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
- @register_as_tool(
77
- title="Get All Applications Configs",
78
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
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 get_all_applications_configs(self,
82
- ctx=None,
83
- api_client=None) -> List[Dict[str, Any]]:
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
- @register_as_tool(
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
- Add a new Application Perspective configuration.
131
- This tool allows you to create a new Application Perspective with specified settings.
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
- Dictionary containing the created application perspective configuration or error information
212
+ Dict with either 'payload' (validated) or 'error' and 'missing_fields'
185
213
  """
186
- try:
187
-
188
- if not payload:
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
- from instana_client.models.new_application_config import (
230
- NewApplicationConfig,
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
- tag_expr = request_body['tagFilterExpression']
241
- logger.debug(f"Converting tagFilterExpression: {tag_expr}")
242
-
243
- # If it's an EXPRESSION type with elements, convert each element
244
- if tag_expr.get('type') == 'EXPRESSION' and 'elements' in tag_expr:
245
- converted_elements = []
246
- for element in tag_expr['elements']:
247
- if isinstance(element, dict):
248
- # Remove conflicting fields before creating TagFilter
249
- element_copy = element.copy()
250
- element_copy.pop('value', None)
251
- element_copy.pop('key', None)
252
- converted_elements.append(TagFilter(**element_copy))
253
- else:
254
- converted_elements.append(element)
255
- tag_expr['elements'] = converted_elements
256
- request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
257
- # If it's a TAG_FILTER type, convert directly
258
- elif tag_expr.get('type') == 'TAG_FILTER':
259
- tag_expr_copy = tag_expr.copy()
260
- tag_expr_copy.pop('value', None)
261
- tag_expr_copy.pop('key', None)
262
- request_body['tagFilterExpression'] = TagFilter(**tag_expr_copy)
263
- else:
264
- request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
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
- logger.debug("Successfully converted tagFilterExpression to model objects")
267
- except Exception as e:
268
- logger.debug(f"Error converting tagFilterExpression: {e}")
269
- return {"error": f"Failed to convert tagFilterExpression: {e!s}"}
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
- # Create an NewApplicationConfig object from the request body
272
- try:
273
- logger.debug(f"Creating NewApplicationConfig with params: {request_body}")
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
- logger.debug(f"Result from add_application_config: {result_dict}")
297
- return result_dict
298
- except Exception as e:
299
- logger.error(f"Error in add_application_config: {e}")
300
- return {"error": f"Failed to add new application config: {e!s}"}
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 delete_application_config(self,
308
- id: str,
309
- ctx=None,
310
- api_client=None) -> Dict[str, Any]:
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
- Delete an Application Perspective configuration.
313
- This tool allows you to delete an existing Application Perspective by its ID.
338
+ Add a new Application Perspective configuration.
314
339
 
315
- Args:
316
- application_id: The ID of the application perspective to delete
317
- ctx: The MCP context (optional)
340
+ Required fields:
341
+ - label: Application perspective name
318
342
 
319
- Returns:
320
- Dictionary containing the result of the deletion or error information
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 id:
324
- return {"error": "Application perspective ID is required for deletion"}
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
- debug_print(f"Deleting application perspective with ID: {id}")
328
- # Call the delete_application_config method from the SDK
329
- api_client.delete_application_config(id=id)
364
+ if "error" in validation_result:
365
+ return validation_result
330
366
 
331
- result_dict = {
332
- "success": True,
333
- "message": f"Application Confiuguration '{id}' has been successfully deleted"
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
- debug_print(f"Successfully deleted application perspective with ID: {id}")
337
- return result_dict
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
- debug_print(f"Error in delete_application_config: {e}")
340
- traceback.print_exc(file=sys.stderr)
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 get_application_config(self,
349
- id: str,
350
- ctx=None,
351
- api_client=None) -> Dict[str, Any]:
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
- This tool retrieves the configuration settings for a specific Application Perspective.
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: The ID of the application perspective to retrieve
358
- ctx: The MCP context (optional)
410
+ id: Application perspective configuration ID
411
+ ctx: MCP context
412
+ api_client: API client instance
359
413
 
360
414
  Returns:
361
- Dictionary containing the application perspective configuration or error information
415
+ Application configuration dictionary
362
416
  """
363
417
  try:
364
- debug_print(f"Fetching application perspective with ID: {id}")
365
- # Call the get_application_config method from the SDK
366
- result = api_client.get_application_config(id=id)
418
+ if not id:
419
+ return {"error": "id is required"}
367
420
 
368
- # Convert the result to a dictionary
421
+ result = api_client.get_application_config(id=id)
369
422
  if hasattr(result, 'to_dict'):
370
- result_dict = result.to_dict()
371
- else:
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
- debug_print(f"Error in get_application_config: {e}")
378
- traceback.print_exc(file=sys.stderr)
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 update_application_config(
387
- self,
388
- id: str,
389
- payload: Union[Dict[str, Any], str],
390
- ctx=None,
391
- api_client=None
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
- if not payload or not id:
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
- logger.debug("Payload is a string, attempting to parse")
442
+ import json
462
443
  try:
463
- import json
444
+ request_body = json.loads(payload)
445
+ except json.JSONDecodeError:
446
+ import ast
464
447
  try:
465
- parsed_payload = json.loads(payload)
466
- logger.debug("Successfully parsed payload as JSON")
467
- request_body = parsed_payload
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
- # Import the ApplicationConfig class
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
- try:
508
- tag_expr = request_body['tagFilterExpression']
509
- logger.debug(f"Converting tagFilterExpression: {tag_expr}")
510
-
511
- # If it's an EXPRESSION type with elements, convert each element
512
- if tag_expr.get('type') == 'EXPRESSION' and 'elements' in tag_expr:
513
- converted_elements = []
514
- for element in tag_expr['elements']:
515
- if isinstance(element, dict):
516
- # Remove conflicting fields before creating TagFilter
517
- element_copy = element.copy()
518
- element_copy.pop('value', None)
519
- element_copy.pop('key', None)
520
- converted_elements.append(TagFilter(**element_copy))
521
- else:
522
- converted_elements.append(element)
523
- tag_expr['elements'] = converted_elements
524
- request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
525
- # If it's a TAG_FILTER type, convert directly
526
- elif tag_expr.get('type') == 'TAG_FILTER':
527
- tag_expr_copy = tag_expr.copy()
528
- tag_expr_copy.pop('value', None)
529
- tag_expr_copy.pop('key', None)
530
- request_body['tagFilterExpression'] = TagFilter(**tag_expr_copy)
531
- else:
532
- request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
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
- result_dict = result.to_dict()
558
- else:
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 put_application_config: {e}")
569
- return {"error": f"Failed to update existing application config: {e!s}"}
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 get_all_endpoint_configs(self,
577
- ctx=None,
578
- api_client=None) -> List[Dict[str, Any]]:
579
- """
580
- All Endpoint Perspectives Configuration
581
- Get a list of all Endpoint Perspectives with their configuration settings.
582
- Args:
583
- ctx: The MCP context (optional)
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
- Returns:
586
- Dictionary containing endpoints data or error information
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
- try:
594
- response_text=result.data.decode('utf-8')
595
- json_data=json.loads(response_text)
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
- debug_print(f"Error in get_endpoint_configs: {e}")
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 create_endpoint_config(
619
- self,
620
- payload: Union[Dict[str, Any], str],
621
- ctx=None,
622
- api_client=None
623
- ) -> Dict[str, Any]:
624
- """
625
- Create or update endpoint configuration for a service.
626
-
627
- Sample Payload: {
628
- "serviceId": "d0cedae516f2182ede16f57f67476dd4c7dab9cd",
629
- "endpointCase": "LOWER",
630
- "endpointNameByFirstPathSegmentRuleEnabled": false,
631
- "endpointNameByCollectedPathTemplateRuleEnabled": false,
632
- "rules": null
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
- Returns:
636
- Dict[str, Any]: Response from the create/update endpoint configuration API.
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": "missing arguments"}
552
+ return {"error": "payload is required"}
641
553
 
642
- # Parse the payload if it's a string
643
554
  if isinstance(payload, str):
644
- logger.debug("Payload is a string, attempting to parse")
555
+ import json
645
556
  try:
646
- import json
647
- try:
648
- parsed_payload = json.loads(payload)
649
- logger.debug("Successfully parsed payload as JSON")
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
- # Import the EndpointConfig class
679
- try:
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
- result_dict = result.to_dict()
705
- else:
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 create_endpoint_config: {e}")
716
- return {"error": f"Failed to create new endpoint config: {e!s}"}
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 delete_endpoint_config(
724
- self,
725
- id: str,
726
- ctx=None,
727
- api_client=None
728
- ) -> Dict[str, Any]:
729
- """
730
- Delete an endpoint configuration of a service.
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
- Args:
733
- id: An Instana generated unique identifier for a Service.
734
- ctx: The MCP context (optional)
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
- Returns:
737
- Dict[str, Any]: Response from the delete endpoint configuration API.
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": "Required enitities are missing or invalid"}
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
- debug_print(f"Error in delete_endpoint_config: {e}")
756
- traceback.print_exc(file=sys.stderr)
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
- @register_as_tool(
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 get_endpoint_config(
765
- self,
766
- id: str,
767
- ctx=None,
768
- api_client=None
769
- ) -> Dict[str, Any]:
770
- """
771
- This MCP tool is used for endpoint if one wants to retrieve the endpoint configuration of a service.
772
- Args:
773
- id: An Instana generated unique identifier for a Service.
774
- ctx: The MCP context (optional)
775
-
776
- Returns:
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": "Required enitities are missing or invalid"}
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
- result_dict = result.to_dict()
791
- else:
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
- debug_print(f"Error in get_endpoint_configs: {e}")
799
- traceback.print_exc(file=sys.stderr)
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 update_endpoint_config(
808
- self,
809
- id: str,
810
- payload: Union[Dict[str, Any], str],
811
- ctx=None,
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 or not id:
850
- return {"error": "missing arguments"}
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
- logger.debug("Payload is a string, attempting to parse")
662
+ import json
855
663
  try:
856
- import json
857
- try:
858
- parsed_payload = json.loads(payload)
859
- logger.debug("Successfully parsed payload as JSON")
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
- # Import the EndpointConfig class
889
- try:
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
- result_dict = result.to_dict()
917
- else:
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
- debug_print(f"Error in get_all_manual_service_configs: {e}")
971
- traceback.print_exc(file=sys.stderr)
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 add_manual_service_config(
980
- self,
981
- payload: Union[Dict[str, Any], str],
982
- ctx=None,
983
- api_client=None
984
- ) -> Dict[str, Any]:
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": "missing arguments"}
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
- logger.debug("Payload is a string, attempting to parse")
692
+ import json
1020
693
  try:
1021
- import json
1022
- try:
1023
- parsed_payload = json.loads(payload)
1024
- logger.debug("Successfully parsed payload as JSON")
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
- # Import the NewManualServiceConfig class
1054
- try:
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
- result_dict = result.to_dict()
1099
- else:
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 add_manual_service_config: {e}")
1110
- return {"error": f"Failed to create new manual service config: {e!s}"}
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 delete_manual_service_config(
1118
- self,
1119
- id: str,
1120
- ctx=None,
1121
- api_client=None
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": "Required enitities are missing or invalid"}
1137
-
1138
- api_client.delete_manual_service_config(id=id)
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
- debug_print(f"Error in delete_manual_service_config: {e}")
1150
- traceback.print_exc(file=sys.stderr)
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 update_manual_service_config(
1159
- self,
1160
- id: str,
1161
- payload: Union[Dict[str, Any], str],
1162
- ctx=None,
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 payload or not id:
1199
- return {"error": "missing arguments"}
1200
-
1201
- # Parse the payload if it's a string
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 update_manual_service_config: {e}")
1276
- return {"error": f"Failed to update manual config: {e!s}"}
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 replace_all_manual_service_config(
1285
- self,
1286
- payload: Union[Dict[str, Any], str],
1287
- ctx=None,
1288
- api_client=None
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 [{"error": "missing arguments"}]
748
+ return {"error": "payload is required"}
1326
749
 
1327
- # Parse the payload if it's a string
1328
750
  if isinstance(payload, str):
1329
- logger.debug("Payload is a string, attempting to parse")
751
+ import json
1330
752
  try:
1331
- import json
1332
- try:
1333
- parsed_payload = json.loads(payload)
1334
- logger.debug("Successfully parsed payload as JSON")
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
- # Import the NewManualServiceConfig class
1364
- try:
1365
- from instana_client.models.new_manual_service_config import (
1366
- NewManualServiceConfig,
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 replace_all_manual_service_config: {e}")
1402
- return [{"error": f"Failed to replace all manual config: {e!s}"}]
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
- @register_as_tool(
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 get_all_service_configs(self,
1410
- ctx=None,
1411
- api_client=None) -> List[Dict[str, Any]]:
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
- debug_print("Fetching all service configs")
1423
- result = api_client.get_service_configs()
1424
- # Convert the result to a dictionary
1425
- if hasattr(result, 'to_dict'):
1426
- result_dict = result.to_dict()
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
- debug_print(f"Error in get_all_service_configs: {e}")
1436
- traceback.print_exc(file=sys.stderr)
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 add_service_config(self,
1445
- payload: Union[Dict[str, Any], str],
1446
- ctx=None,
1447
- api_client=None) -> Dict[str, Any]:
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": "missing arguments"}
792
+ return {"error": "payload is required"}
1476
793
 
1477
- # Parse the payload if it's a string
1478
794
  if isinstance(payload, str):
1479
- logger.debug("Payload is a string, attempting to parse")
795
+ import json
1480
796
  try:
1481
- import json
1482
- try:
1483
- parsed_payload = json.loads(payload)
1484
- logger.debug("Successfully parsed payload as JSON")
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
- # Import the ServiceConfig class
1514
- try:
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
- result_dict = result.to_dict()
1542
- else:
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 add_service_config: {e}")
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 replace_all_service_configs(self,
1561
- payload: Union[Dict[str, Any], str],
1562
- ctx=None,
1563
- api_client=None) -> List[Dict[str, Any]]:
1564
- """
1565
- Args:
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 [{"error": "missing arguments"}]
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
- logger.debug("Payload is a string, attempting to parse")
825
+ import json
1593
826
  try:
1594
- import json
1595
- try:
1596
- parsed_payload = json.loads(payload)
1597
- logger.debug("Successfully parsed payload as JSON")
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
- # Import the ServiceConfig class
1626
- try:
1627
- from instana_client.models.service_config import (
1628
- ServiceConfig,
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
- debug_print(f"Error in delete_service_config: {e}")
1769
- traceback.print_exc(file=sys.stderr)
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 get_service_config(
1778
- self,
1779
- id: str,
1780
- ctx=None,
1781
- api_client=None
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": "Required entities are missing or invalid"}
1797
-
1798
- result = api_client.get_service_config(
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
- debug_print(f"Error in get_service_config: {e}")
1812
- traceback.print_exc(file=sys.stderr)
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 update_service_config(self,
1821
- id: str,
1822
- payload: Union[Dict[str, Any], str],
1823
- ctx=None,
1824
- api_client=None) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
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 or not id:
1852
- return {"error": "missing arguments"}
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
- logger.debug("Payload is a string, attempting to parse")
869
+ import json
1857
870
  try:
1858
- import json
1859
- try:
1860
- parsed_payload = json.loads(payload)
1861
- logger.debug("Successfully parsed payload as JSON")
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
- # Import the ServiceConfig class
1891
- try:
1892
- from instana_client.models.service_config import (
1893
- ServiceConfig,
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 put_service_config: {e}")
1930
- return {"error": f"Failed to update service config: {e!s}"}
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