mcp-instana 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/METADATA +459 -138
  2. mcp_instana-0.2.0.dist-info/RECORD +59 -0
  3. src/application/application_analyze.py +373 -160
  4. src/application/application_catalog.py +3 -1
  5. src/application/application_global_alert_config.py +653 -0
  6. src/application/application_metrics.py +6 -2
  7. src/application/application_resources.py +3 -1
  8. src/application/application_settings.py +966 -370
  9. src/application/application_topology.py +6 -2
  10. src/automation/action_catalog.py +416 -0
  11. src/automation/action_history.py +338 -0
  12. src/core/server.py +159 -9
  13. src/core/utils.py +2 -2
  14. src/event/events_tools.py +602 -275
  15. src/infrastructure/infrastructure_analyze.py +7 -3
  16. src/infrastructure/infrastructure_catalog.py +3 -1
  17. src/infrastructure/infrastructure_metrics.py +6 -2
  18. src/infrastructure/infrastructure_resources.py +7 -5
  19. src/infrastructure/infrastructure_topology.py +5 -3
  20. src/prompts/__init__.py +16 -0
  21. src/prompts/application/__init__.py +1 -0
  22. src/prompts/application/application_alerts.py +54 -0
  23. src/prompts/application/application_catalog.py +26 -0
  24. src/prompts/application/application_metrics.py +57 -0
  25. src/prompts/application/application_resources.py +26 -0
  26. src/prompts/application/application_settings.py +75 -0
  27. src/prompts/application/application_topology.py +30 -0
  28. src/prompts/events/__init__.py +1 -0
  29. src/prompts/events/events_tools.py +161 -0
  30. src/prompts/infrastructure/infrastructure_analyze.py +72 -0
  31. src/prompts/infrastructure/infrastructure_catalog.py +53 -0
  32. src/prompts/infrastructure/infrastructure_metrics.py +45 -0
  33. src/prompts/infrastructure/infrastructure_resources.py +74 -0
  34. src/prompts/infrastructure/infrastructure_topology.py +38 -0
  35. src/prompts/settings/__init__.py +0 -0
  36. src/prompts/settings/custom_dashboard.py +157 -0
  37. src/prompts/website/__init__.py +1 -0
  38. src/prompts/website/website_analyze.py +35 -0
  39. src/prompts/website/website_catalog.py +40 -0
  40. src/prompts/website/website_configuration.py +105 -0
  41. src/prompts/website/website_metrics.py +34 -0
  42. src/settings/__init__.py +1 -0
  43. src/settings/custom_dashboard_tools.py +417 -0
  44. src/website/__init__.py +0 -0
  45. src/website/website_analyze.py +433 -0
  46. src/website/website_catalog.py +171 -0
  47. src/website/website_configuration.py +770 -0
  48. src/website/website_metrics.py +241 -0
  49. mcp_instana-0.1.1.dist-info/RECORD +0 -30
  50. src/prompts/mcp_prompts.py +0 -900
  51. src/prompts/prompt_loader.py +0 -29
  52. src/prompts/prompt_registry.json +0 -21
  53. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/WHEEL +0 -0
  54. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/entry_points.txt +0 -0
  55. {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -6,25 +6,32 @@ This module provides application settings-specific MCP tools for Instana monitor
6
6
  The API endpoints of this group provides a way to create, read, update, delete (CRUD) for various configuration settings.
7
7
  """
8
8
 
9
+ import logging
9
10
  import re
10
11
  import sys
11
12
  import traceback
12
13
  from datetime import datetime
13
- from typing import Any, Dict, List, Optional
14
+ from typing import Any, Dict, List, Optional, Union
15
+
16
+ logger = logging.getLogger(__name__)
14
17
 
15
18
  from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
16
19
 
17
20
  # Import the necessary classes from the SDK
18
21
  try:
19
- from instana_client.api.application_settings_api import ApplicationSettingsApi
20
- from instana_client.api_client import ApiClient
21
- from instana_client.configuration import Configuration
22
- from instana_client.models.application_config import ApplicationConfig
23
- from instana_client.models.endpoint_config import EndpointConfig
24
- from instana_client.models.manual_service_config import ManualServiceConfig
25
- from instana_client.models.new_application_config import NewApplicationConfig
26
- from instana_client.models.new_manual_service_config import NewManualServiceConfig
27
- from instana_client.models.service_config import ServiceConfig
22
+ from instana_client.api import (
23
+ ApplicationSettingsApi, #type: ignore
24
+ )
25
+ from instana_client.api_client import ApiClient #type: ignore
26
+ from instana_client.configuration import Configuration #type: ignore
27
+ from instana_client.models import (
28
+ ApplicationConfig, #type: ignore
29
+ EndpointConfig, #type: ignore
30
+ ManualServiceConfig, #type: ignore
31
+ NewApplicationConfig, #type: ignore
32
+ NewManualServiceConfig, #type: ignore
33
+ ServiceConfig, #type: ignore
34
+ )
28
35
  except ImportError as e:
29
36
  print(f"Error importing Instana SDK: {e}", file=sys.stderr)
30
37
  traceback.print_exc(file=sys.stderr)
@@ -98,59 +105,147 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
98
105
  @register_as_tool
99
106
  @with_header_auth(ApplicationSettingsApi)
100
107
  async def add_application_config(self,
101
- access_rules: List[Dict[str, str]],
102
- boundary_scope: str,
103
- label: str,
104
- scope: str,
105
- tag_filter_expression: Optional[List[Dict[str, str]]] = None,
108
+ payload: Union[Dict[str, Any], str],
106
109
  ctx=None,
107
110
  api_client=None) -> Dict[str, Any]:
108
111
  """
109
112
  Add a new Application Perspective configuration.
110
113
  This tool allows you to create a new Application Perspective with specified settings.
111
- Args:
112
- accessRules: List of access rules for the application perspective
113
- boundaryScope: Boundary scope for the application perspective
114
- label: Label for the application perspective
115
- scope: Scope of the application perspective
116
- tagFilterExpression: Tag filter expression for the application perspective (Optional)
117
- ctx: The MCP context (optional)
114
+ Sample Payload: {
115
+ "accessRules": [
116
+ {
117
+ "accessType": "READ_WRITE",
118
+ "relationType": "GLOBAL",
119
+ "relatedId": null
120
+ }
121
+ ],
122
+ "boundaryScope": "INBOUND",
123
+ "label": "Discount Build 6987",
124
+ "scope": "INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING",
125
+ "tagFilterExpression": {
126
+ "type": "EXPRESSION",
127
+ "logicalOperator": "AND",
128
+ "elements": [
129
+ {
130
+ "type": "TAG_FILTER",
131
+ "name": "kubernetes.label",
132
+ "stringValue": "stage=canary",
133
+ "numberValue": null,
134
+ "booleanValue": null,
135
+ "key": "stage",
136
+ "value": "canary",
137
+ "operator": "EQUALS",
138
+ "entity": "DESTINATION"
139
+ },
140
+ {
141
+ "type": "TAG_FILTER",
142
+ "name": "kubernetes.label",
143
+ "stringValue": "build=6987",
144
+ "numberValue": null,
145
+ "booleanValue": null,
146
+ "key": "build",
147
+ "value": "6987",
148
+ "operator": "EQUALS",
149
+ "entity": "DESTINATION"
150
+ },
151
+ {
152
+ "type": "TAG_FILTER",
153
+ "name": "kubernetes.label",
154
+ "stringValue": "app=discount",
155
+ "numberValue": null,
156
+ "booleanValue": null,
157
+ "key": "app",
158
+ "value": "discount",
159
+ "operator": "EQUALS",
160
+ "entity": "DESTINATION"
161
+ }
162
+ ]
163
+ }
164
+ }
118
165
  Returns:
119
166
  Dictionary containing the created application perspective configuration or error information
120
167
  """
121
168
  try:
122
- debug_print("Adding new application perspective configuration")
123
- if not (access_rules or boundary_scope or label or scope):
124
- return {"error": "Required enitities are missing or invalid"}
125
169
 
126
- # Create a NewApplicationConfig instance with the provided parameters
127
- request_body = {
128
- "access_rules": access_rules,
129
- "boundary_scope": boundary_scope,
130
- "label": label,
131
- "scope": scope,
132
- "tag_filter_expression": tag_filter_expression
133
- }
134
- new_application_config = NewApplicationConfig(**request_body)
135
- debug_print(f"New Application Config: {new_application_config.to_dict()}")
170
+ if not payload:
171
+ return {"error": "payload is required"}
172
+
173
+ # Parse the payload if it's a string
174
+ if isinstance(payload, str):
175
+ logger.debug("Payload is a string, attempting to parse")
176
+ try:
177
+ import json
178
+ try:
179
+ parsed_payload = json.loads(payload)
180
+ logger.debug("Successfully parsed payload as JSON")
181
+ request_body = parsed_payload
182
+ except json.JSONDecodeError as e:
183
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
184
+
185
+ # Try replacing single quotes with double quotes
186
+ fixed_payload = payload.replace("'", "\"")
187
+ try:
188
+ parsed_payload = json.loads(fixed_payload)
189
+ logger.debug("Successfully parsed fixed JSON")
190
+ request_body = parsed_payload
191
+ except json.JSONDecodeError:
192
+ # Try as Python literal
193
+ import ast
194
+ try:
195
+ parsed_payload = ast.literal_eval(payload)
196
+ logger.debug("Successfully parsed payload as Python literal")
197
+ request_body = parsed_payload
198
+ except (SyntaxError, ValueError) as e2:
199
+ logger.debug(f"Failed to parse payload string: {e2}")
200
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
201
+ except Exception as e:
202
+ logger.debug(f"Error parsing payload string: {e}")
203
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
204
+ else:
205
+ # If payload is already a dictionary, use it directly
206
+ logger.debug("Using provided payload dictionary")
207
+ request_body = payload
208
+
209
+ # Import the NewApplicationConfig class
210
+ try:
211
+ from instana_client.models.new_application_config import (
212
+ NewApplicationConfig,
213
+ )
214
+ logger.debug("Successfully imported NewApplicationConfig")
215
+ except ImportError as e:
216
+ logger.debug(f"Error importing NewApplicationConfig: {e}")
217
+ return {"error": f"Failed to import NewApplicationConfig: {e!s}"}
218
+
219
+ # Create an NewApplicationConfig object from the request body
220
+ try:
221
+ logger.debug(f"Creating NewApplicationConfig with params: {request_body}")
222
+ config_object = NewApplicationConfig(**request_body)
223
+ logger.debug("Successfully created config object")
224
+ except Exception as e:
225
+ logger.debug(f"Error creating NewApplicationConfig: {e}")
226
+ return {"error": f"Failed to create config object: {e!s}"}
136
227
 
137
228
  # Call the add_application_config method from the SDK
229
+ logger.debug("Calling add_application_config with config object")
138
230
  result = api_client.add_application_config(
139
- new_application_config=new_application_config
231
+ new_application_config=config_object
140
232
  )
141
233
 
142
234
  # Convert the result to a dictionary
143
235
  if hasattr(result, 'to_dict'):
144
236
  result_dict = result.to_dict()
145
237
  else:
146
- result_dict = result
238
+ # If it's already a dict or another format, use it as is
239
+ result_dict = result or {
240
+ "success": True,
241
+ "message": "Create new application config"
242
+ }
147
243
 
148
- debug_print(f"Result from add_application_config: {result_dict}")
244
+ logger.debug(f"Result from add_application_config: {result_dict}")
149
245
  return result_dict
150
246
  except Exception as e:
151
- debug_print(f"Error in add_application_config: {e}")
152
- traceback.print_exc(file=sys.stderr)
153
- return {"error": f"Failed to add application configuration: {e!s}"}
247
+ logger.error(f"Error in add_application_config: {e}")
248
+ return {"error": f"Failed to add new application config: {e!s}"}
154
249
 
155
250
  @register_as_tool
156
251
  @with_header_auth(ApplicationSettingsApi)
@@ -176,7 +271,7 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
176
271
 
177
272
  debug_print(f"Deleting application perspective with ID: {id}")
178
273
  # Call the delete_application_config method from the SDK
179
- self.settings_api.delete_application_config(id=id)
274
+ api_client.delete_application_config(id=id)
180
275
 
181
276
  result_dict = {
182
277
  "success": True,
@@ -230,12 +325,7 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
230
325
  async def update_application_config(
231
326
  self,
232
327
  id: str,
233
- access_rules: List[Dict[str, str]],
234
- boundary_scope: str,
235
- label: str,
236
- scope: str,
237
- tag_filter_expression: Optional[List[Dict[str, str]]] = None,
238
- match_specification: Optional[List[Dict[str, Any]]] = None,
328
+ payload: Union[Dict[str, Any], str],
239
329
  ctx=None,
240
330
  api_client=None
241
331
  ) -> Dict[str, Any]:
@@ -245,52 +335,143 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
245
335
 
246
336
  Args:
247
337
  id: The ID of the application perspective to retrieve
248
- access_rules: List of access rules for the application perspective
249
- boundary_scope: Boundary scope for the application perspective
250
- label: Label for the application perspective
251
- scope: Scope of the application perspective
252
- tag_filter_expression: Tag filter expression for the application perspective (Optional)
338
+ Sample payload: {
339
+ "accessRules": [
340
+ {
341
+ "accessType": "READ",
342
+ "relationType": "ROLE"
343
+ }
344
+ ],
345
+ "boundaryScope": "INBOUND",
346
+ "id": "CxJ55sRbQwqBIfw5DzpRmQ",
347
+ "label": "Discount Build 1",
348
+ "scope": "INCLUDE_NO_DOWNSTREAM",
349
+ "tagFilterExpression": {
350
+ "type": "EXPRESSION",
351
+ "logicalOperator": "AND",
352
+ "elements": [
353
+ {
354
+ "type": "TAG_FILTER",
355
+ "name": "kubernetes.label",
356
+ "stringValue": "stage=canary",
357
+ "numberValue": null,
358
+ "booleanValue": null,
359
+ "key": "stage",
360
+ "value": "canary",
361
+ "operator": "EQUALS",
362
+ "entity": "DESTINATION"
363
+ },
364
+ {
365
+ "type": "TAG_FILTER",
366
+ "name": "kubernetes.label",
367
+ "stringValue": "build=6987",
368
+ "numberValue": null,
369
+ "booleanValue": null,
370
+ "key": "build",
371
+ "value": "6987",
372
+ "operator": "EQUALS",
373
+ "entity": "DESTINATION"
374
+ },
375
+ {
376
+ "type": "TAG_FILTER",
377
+ "name": "kubernetes.label",
378
+ "stringValue": "app=discount",
379
+ "numberValue": null,
380
+ "booleanValue": null,
381
+ "key": "app",
382
+ "value": "discount",
383
+ "operator": "EQUALS",
384
+ "entity": "DESTINATION"
385
+ }
386
+ ]
387
+ }
388
+ }
253
389
  ctx: The MCP context (optional)
254
390
  Returns:
255
391
  Dictionary containing the created application perspective configuration or error information
256
392
  """
257
-
258
393
  try:
259
- debug_print("Update existing application perspective configuration")
260
- if not (access_rules or boundary_scope or label or scope or id):
261
- return {"error": "Required enitities are missing or invalid"}
262
394
 
263
- request_body = {
264
- "access_rules": access_rules,
265
- "boundary_scope": boundary_scope,
266
- "id": id,
267
- "label": label,
268
- "match_specification": match_specification,
269
- "scope": scope,
270
- "tag_filter_expression": tag_filter_expression
271
- }
272
- # Create a ApplicationConfig instance with the provided parameters
273
- application_config = ApplicationConfig(**request_body)
274
- debug_print(f"Application Config: {application_config.to_dict()}")
395
+ if not payload or not id:
396
+ return {"error": "missing arguments"}
397
+
398
+ # Parse the payload if it's a string
399
+ if isinstance(payload, str):
400
+ logger.debug("Payload is a string, attempting to parse")
401
+ try:
402
+ import json
403
+ try:
404
+ parsed_payload = json.loads(payload)
405
+ logger.debug("Successfully parsed payload as JSON")
406
+ request_body = parsed_payload
407
+ except json.JSONDecodeError as e:
408
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
409
+
410
+ # Try replacing single quotes with double quotes
411
+ fixed_payload = payload.replace("'", "\"")
412
+ try:
413
+ parsed_payload = json.loads(fixed_payload)
414
+ logger.debug("Successfully parsed fixed JSON")
415
+ request_body = parsed_payload
416
+ except json.JSONDecodeError:
417
+ # Try as Python literal
418
+ import ast
419
+ try:
420
+ parsed_payload = ast.literal_eval(payload)
421
+ logger.debug("Successfully parsed payload as Python literal")
422
+ request_body = parsed_payload
423
+ except (SyntaxError, ValueError) as e2:
424
+ logger.debug(f"Failed to parse payload string: {e2}")
425
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
426
+ except Exception as e:
427
+ logger.debug(f"Error parsing payload string: {e}")
428
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
429
+ else:
430
+ # If payload is already a dictionary, use it directly
431
+ logger.debug("Using provided payload dictionary")
432
+ request_body = payload
433
+
434
+ # Import the ApplicationConfig class
435
+ try:
436
+ from instana_client.models.application_config import (
437
+ ApplicationConfig,
438
+ )
439
+ logger.debug("Successfully imported ApplicationConfig")
440
+ except ImportError as e:
441
+ logger.debug(f"Error importing ApplicationConfig: {e}")
442
+ return {"error": f"Failed to import ApplicationConfig: {e!s}"}
443
+
444
+ # Create an ApplicationConfig object from the request body
445
+ try:
446
+ logger.debug(f"Creating ApplicationConfig with params: {request_body}")
447
+ config_object = ApplicationConfig(**request_body)
448
+ logger.debug("Successfully updated application config object")
449
+ except Exception as e:
450
+ logger.debug(f"Error updating ApplicationConfig: {e}")
451
+ return {"error": f"Failed to update config object: {e!s}"}
275
452
 
276
453
  # Call the put_application_config method from the SDK
454
+ logger.debug("Calling put_application_config with config object")
277
455
  result = api_client.put_application_config(
278
456
  id=id,
279
- application_config=application_config
457
+ application_config=config_object
280
458
  )
281
459
 
282
460
  # Convert the result to a dictionary
283
461
  if hasattr(result, 'to_dict'):
284
462
  result_dict = result.to_dict()
285
463
  else:
286
- result_dict = result
464
+ # If it's already a dict or another format, use it as is
465
+ result_dict = result or {
466
+ "success": True,
467
+ "message": "Update existing application config"
468
+ }
287
469
 
288
- debug_print(f"Result from put_application_config: {result_dict}")
470
+ logger.debug(f"Result from put_application_config: {result_dict}")
289
471
  return result_dict
290
472
  except Exception as e:
291
- debug_print(f"Error in put_application_config: {e}")
292
- traceback.print_exc(file=sys.stderr)
293
- return {"error": f"Failed to update application configuration: {e!s}"}
473
+ logger.error(f"Error in put_application_config: {e}")
474
+ return {"error": f"Failed to update existing application config: {e!s}"}
294
475
 
295
476
  @register_as_tool
296
477
  @with_header_auth(ApplicationSettingsApi)
@@ -328,60 +509,103 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
328
509
  @with_header_auth(ApplicationSettingsApi)
329
510
  async def create_endpoint_config(
330
511
  self,
331
- endpoint_case: str,
332
- service_id: str,
333
- endpoint_name_by_collected_path_template_rule_enabled: Optional[bool]= None,
334
- endpoint_name_by_first_path_segment_rule_enabled: Optional[bool] = None,
335
- rules: Optional[List[Dict[str, Any]]] = None,
512
+ payload: Union[Dict[str, Any], str],
336
513
  ctx=None,
337
514
  api_client=None
338
515
  ) -> Dict[str, Any]:
339
516
  """
340
517
  Create or update endpoint configuration for a service.
341
518
 
342
- Args:
343
- serviceId (str): Instana Service ID to configure.
344
- endpointCase (str): Case format for endpoints. One of: 'ORIGINAL', 'LOWER', 'UPPER'.
345
- endpointNameByCollectedPathTemplateRuleEnabled (Optional[bool]): Enable path template rule. (Optional)
346
- endpointNameByFirstPathSegmentRuleEnabled (Optional[bool]): Enable first path segment rule. (Optional)
347
- rules (Optional[List[Dict[str, Any]]]): Optional list of custom HTTP endpoint rules. (Optional)
348
- ctx: The MCP context (optional)
519
+ Sample Payload: {
520
+ "serviceId": "d0cedae516f2182ede16f57f67476dd4c7dab9cd",
521
+ "endpointCase": "LOWER",
522
+ "endpointNameByFirstPathSegmentRuleEnabled": false,
523
+ "endpointNameByCollectedPathTemplateRuleEnabled": false,
524
+ "rules": null
525
+ }
349
526
 
350
527
  Returns:
351
528
  Dict[str, Any]: Response from the create/update endpoint configuration API.
352
529
  """
353
530
  try:
354
- debug_print("Creating endpoint configs")
355
- if not endpoint_case or not service_id:
356
- return {"error": "Required enitities are missing or invalid"}
357
-
358
- request_body = {
359
- "endpoint_case": endpoint_case,
360
- "endpoint_name_by_collected_path_template_rule_enabled": endpoint_name_by_collected_path_template_rule_enabled,
361
- "endpoint_name_by_first_path_segment_rule_enabled": endpoint_name_by_first_path_segment_rule_enabled,
362
- "rules": rules,
363
- "serviceId": service_id
364
- }
365
- endpoint_config = EndpointConfig(**request_body)
366
-
531
+ if not payload:
532
+ return {"error": "missing arguments"}
533
+
534
+ # Parse the payload if it's a string
535
+ if isinstance(payload, str):
536
+ logger.debug("Payload is a string, attempting to parse")
537
+ try:
538
+ import json
539
+ try:
540
+ parsed_payload = json.loads(payload)
541
+ logger.debug("Successfully parsed payload as JSON")
542
+ request_body = parsed_payload
543
+ except json.JSONDecodeError as e:
544
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
545
+
546
+ # Try replacing single quotes with double quotes
547
+ fixed_payload = payload.replace("'", "\"")
548
+ try:
549
+ parsed_payload = json.loads(fixed_payload)
550
+ logger.debug("Successfully parsed fixed JSON")
551
+ request_body = parsed_payload
552
+ except json.JSONDecodeError:
553
+ # Try as Python literal
554
+ import ast
555
+ try:
556
+ parsed_payload = ast.literal_eval(payload)
557
+ logger.debug("Successfully parsed payload as Python literal")
558
+ request_body = parsed_payload
559
+ except (SyntaxError, ValueError) as e2:
560
+ logger.debug(f"Failed to parse payload string: {e2}")
561
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
562
+ except Exception as e:
563
+ logger.debug(f"Error parsing payload string: {e}")
564
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
565
+ else:
566
+ # If payload is already a dictionary, use it directly
567
+ logger.debug("Using provided payload dictionary")
568
+ request_body = payload
569
+
570
+ # Import the EndpointConfig class
571
+ try:
572
+ from instana_client.models.endpoint_config import (
573
+ EndpointConfig,
574
+ )
575
+ logger.debug("Successfully imported EndpointConfig")
576
+ except ImportError as e:
577
+ logger.debug(f"Error importing EndpointConfig: {e}")
578
+ return {"error": f"Failed to import EndpointConfig: {e!s}"}
579
+
580
+ # Create an EndpointConfig object from the request body
581
+ try:
582
+ logger.debug(f"Creating EndpointConfig with params: {request_body}")
583
+ config_object = EndpointConfig(**request_body)
584
+ logger.debug("Successfully created endpoint config object")
585
+ except Exception as e:
586
+ logger.debug(f"Error creating EndpointConfig: {e}")
587
+ return {"error": f"Failed to create config object: {e!s}"}
588
+
589
+ # Call the create_endpoint_config method from the SDK
590
+ logger.debug("Calling create_endpoint_config with config object")
367
591
  result = api_client.create_endpoint_config(
368
- endpoint_config=endpoint_config
592
+ endpoint_config=config_object
369
593
  )
370
-
371
594
  # Convert the result to a dictionary
372
595
  if hasattr(result, 'to_dict'):
373
596
  result_dict = result.to_dict()
374
597
  else:
375
598
  # If it's already a dict or another format, use it as is
376
- result_dict = result
599
+ result_dict = result or {
600
+ "success": True,
601
+ "message": "Create new endpoint config"
602
+ }
377
603
 
378
- debug_print(f"Result from get_endpoint_configs: {result_dict}")
604
+ logger.debug(f"Result from create_endpoint_config: {result_dict}")
379
605
  return result_dict
380
-
381
606
  except Exception as e:
382
- debug_print(f"Error in get_endpoint_configs: {e}")
383
- traceback.print_exc(file=sys.stderr)
384
- return {"error": f"Failed to get endpoint configs: {e!s}"}
607
+ logger.error(f"Error in create_endpoint_config: {e}")
608
+ return {"error": f"Failed to create new endpoint config: {e!s}"}
385
609
 
386
610
  @register_as_tool
387
611
  @with_header_auth(ApplicationSettingsApi)
@@ -466,11 +690,7 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
466
690
  async def update_endpoint_config(
467
691
  self,
468
692
  id: str,
469
- endpoint_case: str,
470
- service_id: str,
471
- endpoint_name_by_collected_path_template_rule_enabled: Optional[bool]= None,
472
- endpoint_name_by_first_path_segment_rule_enabled: Optional[bool] = None,
473
- rules: Optional[List[Dict[str, Any]]] = None,
693
+ payload: Union[Dict[str, Any], str],
474
694
  ctx=None,
475
695
  api_client=None
476
696
  ) -> Dict[str, Any]:
@@ -479,33 +699,99 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
479
699
 
480
700
  Args:
481
701
  id: An Instana generated unique identifier for a Service.
482
- serviceId: Instana Service ID to configure.
483
- endpointCase: Case format for endpoints. One of: 'ORIGINAL', 'LOWER', 'UPPER'.
484
- endpointNameByCollectedPathTemplateRuleEnabled: Enable path template rule. (Optional)
485
- endpointNameByFirstPathSegmentRuleEnabled: Enable first path segment rule. (Optional)
486
- rules: Optional list of custom HTTP endpoint rules. (Optional)
702
+ {
703
+ "serviceId": "20ba31821b079e7d845a08096124880db3eeeb40",
704
+ "endpointNameByCollectedPathTemplateRuleEnabled": true,
705
+ "endpointNameByFirstPathSegmentRuleEnabled": true,
706
+ "rules": [
707
+ {
708
+ "enabled": true,
709
+ "pathSegments": [
710
+ {
711
+ "name": "api",
712
+ "type": "FIXED"
713
+ },
714
+ {
715
+ "name": "version",
716
+ "type": "PARAMETER"
717
+ }
718
+ ],
719
+ "testCases": [
720
+ "/api/v2/users"
721
+ ]
722
+ }
723
+ ],
724
+ "endpointCase": "UPPER"
725
+ }
487
726
  ctx: The MCP context (optional)
488
727
 
489
728
  Returns:
490
729
  Dict[str, Any]: Response from the create/update endpoint configuration API.
491
730
  """
492
731
  try:
493
- debug_print("Updating endpoint configs")
494
- if not endpoint_case or not service_id:
495
- return {"error": "Required enitities are missing or invalid"}
496
-
497
- request_body = {
498
- "endpoint_case": endpoint_case,
499
- "endpoint_name_by_collected_path_template_rule_enabled": endpoint_name_by_collected_path_template_rule_enabled,
500
- "endpoint_name_by_first_path_segment_rule_enabled": endpoint_name_by_first_path_segment_rule_enabled,
501
- "rules": rules,
502
- "serviceId": service_id
503
- }
504
- endpoint_config = EndpointConfig(**request_body)
505
-
732
+ if not payload or not id:
733
+ return {"error": "missing arguments"}
734
+
735
+ # Parse the payload if it's a string
736
+ if isinstance(payload, str):
737
+ logger.debug("Payload is a string, attempting to parse")
738
+ try:
739
+ import json
740
+ try:
741
+ parsed_payload = json.loads(payload)
742
+ logger.debug("Successfully parsed payload as JSON")
743
+ request_body = parsed_payload
744
+ except json.JSONDecodeError as e:
745
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
746
+
747
+ # Try replacing single quotes with double quotes
748
+ fixed_payload = payload.replace("'", "\"")
749
+ try:
750
+ parsed_payload = json.loads(fixed_payload)
751
+ logger.debug("Successfully parsed fixed JSON")
752
+ request_body = parsed_payload
753
+ except json.JSONDecodeError:
754
+ # Try as Python literal
755
+ import ast
756
+ try:
757
+ parsed_payload = ast.literal_eval(payload)
758
+ logger.debug("Successfully parsed payload as Python literal")
759
+ request_body = parsed_payload
760
+ except (SyntaxError, ValueError) as e2:
761
+ logger.debug(f"Failed to parse payload string: {e2}")
762
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
763
+ except Exception as e:
764
+ logger.debug(f"Error parsing payload string: {e}")
765
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
766
+ else:
767
+ # If payload is already a dictionary, use it directly
768
+ logger.debug("Using provided payload dictionary")
769
+ request_body = payload
770
+
771
+ # Import the EndpointConfig class
772
+ try:
773
+ from instana_client.models.endpoint_config import (
774
+ EndpointConfig,
775
+ )
776
+ logger.debug("Successfully imported EndpointConfig")
777
+ except ImportError as e:
778
+ logger.debug(f"Error importing EndpointConfig: {e}")
779
+ return {"error": f"Failed to import EndpointConfig: {e!s}"}
780
+
781
+ # Create an EndpointConfig object from the request body
782
+ try:
783
+ logger.debug(f"Creating EndpointConfig with params: {request_body}")
784
+ config_object = EndpointConfig(**request_body)
785
+ logger.debug("Successfully updated endpoint config object")
786
+ except Exception as e:
787
+ logger.debug(f"Error updating EndpointConfig: {e}")
788
+ return {"error": f"Failed to update config object: {e!s}"}
789
+
790
+ # Call the update_endpoint_config method from the SDK
791
+ logger.debug("Calling update_endpoint_config with config object")
506
792
  result = api_client.update_endpoint_config(
507
793
  id=id,
508
- endpoint_config=endpoint_config
794
+ endpoint_config=config_object
509
795
  )
510
796
 
511
797
  # Convert the result to a dictionary
@@ -513,15 +799,17 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
513
799
  result_dict = result.to_dict()
514
800
  else:
515
801
  # If it's already a dict or another format, use it as is
516
- result_dict = result
802
+ result_dict = result or {
803
+ "success": True,
804
+ "message": "update existing endpoint config"
805
+ }
517
806
 
518
- debug_print(f"Result from get_endpoint_configs: {result_dict}")
807
+ logger.debug(f"Result from update_endpoint_config: {result_dict}")
519
808
  return result_dict
520
-
521
809
  except Exception as e:
522
- debug_print(f"Error in get_endpoint_configs: {e}")
523
- traceback.print_exc(file=sys.stderr)
524
- return {"error": f"Failed to get endpoint configs: {e!s}"}
810
+ logger.error(f"Error in update_endpoint_config: {e}")
811
+ return {"error": f"Failed to update existing application config: {e!s}"}
812
+
525
813
 
526
814
  @register_as_tool
527
815
  @with_header_auth(ApplicationSettingsApi)
@@ -559,11 +847,7 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
559
847
  @with_header_auth(ApplicationSettingsApi)
560
848
  async def add_manual_service_config(
561
849
  self,
562
- tagFilterExpression: Dict[str, Any],
563
- unmonitoredServiceName: Optional[str] = None,
564
- existingServiceId: Optional[str] = None,
565
- description: Optional[str] = None,
566
- enabled: Optional[bool] = True,
850
+ payload: Union[Dict[str, Any], str],
567
851
  ctx=None,
568
852
  api_client=None
569
853
  ) -> Dict[str, Any]:
@@ -572,59 +856,109 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
572
856
 
573
857
  Requires `CanConfigureServiceMapping` permission on the API token.
574
858
 
575
- Args:
576
- tagFilterExpression : Boolean expression of tag filters to match relevant calls.
577
- unmonitoredServiceName : Custom name for an unmonitored service to map. (Optional)
578
- existingServiceId : Service ID to link the matched calls to. (Optional)
579
- description : Description of the mapping configuration. (Optional)
580
- enabled : Enable or disable the configuration. Defaults to True. (Optional)
859
+ Sample payload:
860
+ {
861
+ "description": "Map source service example",
862
+ "enabled": true,
863
+ "existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
864
+ "tagFilterExpression": {
865
+ "type": "TAG_FILTER",
866
+ "name": "service.name",
867
+ "stringValue": "front",
868
+ "numberValue": null,
869
+ "booleanValue": null,
870
+ "key": null,
871
+ "value": "front",
872
+ "operator": "EQUALS",
873
+ "entity": "SOURCE"
874
+ },
875
+ "unmonitoredServiceName": null
876
+ }
581
877
  ctx: Optional execution context.
582
878
 
583
879
  Returns:
584
880
  Dict[str, Any]: API response indicating success or failure.
585
881
  """
586
882
  try:
587
- debug_print("Creating manual service configuration")
588
-
589
- if not (unmonitoredServiceName and existingServiceId):
590
- return {
591
- "error": "You must provide either 'unmonitoredServiceName' or 'existingServiceId'."
592
- }
593
-
594
- if not tagFilterExpression:
595
- return {"error": "Required enitities are missing or invalid"}
596
-
597
-
598
- body = {
599
- "tagFilterExpression": tagFilterExpression,
600
- "enabled": enabled
601
- }
602
-
603
- if unmonitoredServiceName:
604
- body["unmonitoredServiceName"] = unmonitoredServiceName
605
- if existingServiceId:
606
- body["existingServiceId"] = existingServiceId
607
- if description:
608
- body["description"] = description
609
-
610
- new_manual_service_config = NewManualServiceConfig(**body)
611
-
883
+ if not payload:
884
+ return {"error": "missing arguments"}
885
+
886
+ # Parse the payload if it's a string
887
+ if isinstance(payload, str):
888
+ logger.debug("Payload is a string, attempting to parse")
889
+ try:
890
+ import json
891
+ try:
892
+ parsed_payload = json.loads(payload)
893
+ logger.debug("Successfully parsed payload as JSON")
894
+ request_body = parsed_payload
895
+ except json.JSONDecodeError as e:
896
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
897
+
898
+ # Try replacing single quotes with double quotes
899
+ fixed_payload = payload.replace("'", "\"")
900
+ try:
901
+ parsed_payload = json.loads(fixed_payload)
902
+ logger.debug("Successfully parsed fixed JSON")
903
+ request_body = parsed_payload
904
+ except json.JSONDecodeError:
905
+ # Try as Python literal
906
+ import ast
907
+ try:
908
+ parsed_payload = ast.literal_eval(payload)
909
+ logger.debug("Successfully parsed payload as Python literal")
910
+ request_body = parsed_payload
911
+ except (SyntaxError, ValueError) as e2:
912
+ logger.debug(f"Failed to parse payload string: {e2}")
913
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
914
+ except Exception as e:
915
+ logger.debug(f"Error parsing payload string: {e}")
916
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
917
+ else:
918
+ # If payload is already a dictionary, use it directly
919
+ logger.debug("Using provided payload dictionary")
920
+ request_body = payload
921
+
922
+ # Import the NewManualServiceConfig class
923
+ try:
924
+ from instana_client.models.new_manual_service_config import (
925
+ NewManualServiceConfig,
926
+ )
927
+ logger.debug("Successfully imported NewManualServiceConfig")
928
+ except ImportError as e:
929
+ logger.debug(f"Error importing NewManualServiceConfig: {e}")
930
+ return {"error": f"Failed to import NewManualServiceConfig: {e!s}"}
931
+
932
+ # Create an NewManualServiceConfig object from the request body
933
+ try:
934
+ logger.debug(f"Creating NewManualServiceConfig with params: {request_body}")
935
+ config_object = NewManualServiceConfig(**request_body)
936
+ logger.debug("Successfully created manual service config object")
937
+ except Exception as e:
938
+ logger.debug(f"Error creating NewManualServiceConfig: {e}")
939
+ return {"error": f"Failed to create config object: {e!s}"}
940
+
941
+ # Call the add_manual_service_config method from the SDK
942
+ logger.debug("Calling add_manual_service_config with config object")
612
943
  result = api_client.add_manual_service_config(
613
- new_manual_service_config=new_manual_service_config
944
+ new_manual_service_config=config_object
614
945
  )
615
946
 
616
- if hasattr(result, "to_dict"):
947
+ # Convert the result to a dictionary
948
+ if hasattr(result, 'to_dict'):
617
949
  result_dict = result.to_dict()
618
950
  else:
619
- result_dict = result
951
+ # If it's already a dict or another format, use it as is
952
+ result_dict = result or {
953
+ "success": True,
954
+ "message": "Create new manual service config"
955
+ }
620
956
 
621
- debug_print(f"Manual service configuration result: {result_dict}")
957
+ logger.debug(f"Result from add_manual_service_config: {result_dict}")
622
958
  return result_dict
623
-
624
959
  except Exception as e:
625
- debug_print(f"Error creating manual service configuration: {e}")
626
- traceback.print_exc(file=sys.stderr)
627
- return {"error": f"Failed to create manual service configuration: {e!s}"}
960
+ logger.error(f"Error in add_manual_service_config: {e}")
961
+ return {"error": f"Failed to create new manual service config: {e!s}"}
628
962
 
629
963
  @register_as_tool
630
964
  @with_header_auth(ApplicationSettingsApi)
@@ -669,11 +1003,7 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
669
1003
  async def update_manual_service_config(
670
1004
  self,
671
1005
  id: str,
672
- tagFilterExpression: Dict[str, Any],
673
- unmonitoredServiceName: Optional[str] = None,
674
- existingServiceId: Optional[str] = None,
675
- description: Optional[str] = None,
676
- enabled: Optional[bool] = True,
1006
+ payload: Union[Dict[str, Any], str],
677
1007
  ctx=None,
678
1008
  api_client=None
679
1009
  ) -> Dict[str, Any]:
@@ -686,72 +1016,120 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
686
1016
  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.
687
1017
 
688
1018
  Args:
689
- id: A unique id of the manual service configuration.
690
- tagFilterExpression : Boolean expression of tag filters to match relevant calls.
691
- unmonitoredServiceName : Custom name for an unmonitored service to map. (Optional)
692
- existingServiceId : Service ID to link the matched calls to. (Optional)
693
- description: Description of the mapping configuration. (Optional)
694
- enabled: Enable or disable the configuration. Defaults to True. (Optional)
695
- ctx: Optional execution context.
1019
+ id: A unique id of the manual service configuration.
1020
+ Sample payload: {
1021
+ "description": "Map source service example",
1022
+ "enabled": true,
1023
+ "existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
1024
+ "id": "BDGeDcG4TRSzRkJ1mGOk-Q",
1025
+ "tagFilterExpression": {
1026
+ "type": "TAG_FILTER",
1027
+ "name": "service.name",
1028
+ "stringValue": "front",
1029
+ "numberValue": null,
1030
+ "booleanValue": null,
1031
+ "key": null,
1032
+ "value": "front",
1033
+ "operator": "EQUALS",
1034
+ "entity": "SOURCE"
1035
+ },
1036
+ "unmonitoredServiceName": null
1037
+ }
696
1038
 
697
1039
  Returns:
698
1040
  Dict[str, Any]: API response indicating success or failure.
699
1041
  """
700
1042
  try:
701
- debug_print("Creating manual service configuration")
702
-
703
- if not (unmonitoredServiceName and existingServiceId):
704
- return {
705
- "error": "You must provide either 'unmonitoredServiceName' or 'existingServiceId'."
706
- }
707
- if not id or not tagFilterExpression:
708
- return {"error": "Required enitities are missing or invalid"}
709
-
710
- body = {
711
- "tagFilterExpression": tagFilterExpression,
712
- "id": id
713
- }
714
-
715
- if unmonitoredServiceName:
716
- body["unmonitoredServiceName"] = unmonitoredServiceName
717
- if existingServiceId:
718
- body["existingServiceId"] = existingServiceId
719
- if description:
720
- body["description"] = description
721
-
722
- manual_service_config = ManualServiceConfig(**body)
723
-
1043
+ if not payload or not id:
1044
+ return {"error": "missing arguments"}
1045
+
1046
+ # Parse the payload if it's a string
1047
+ if isinstance(payload, str):
1048
+ logger.debug("Payload is a string, attempting to parse")
1049
+ try:
1050
+ import json
1051
+ try:
1052
+ parsed_payload = json.loads(payload)
1053
+ logger.debug("Successfully parsed payload as JSON")
1054
+ request_body = parsed_payload
1055
+ except json.JSONDecodeError as e:
1056
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
1057
+
1058
+ # Try replacing single quotes with double quotes
1059
+ fixed_payload = payload.replace("'", "\"")
1060
+ try:
1061
+ parsed_payload = json.loads(fixed_payload)
1062
+ logger.debug("Successfully parsed fixed JSON")
1063
+ request_body = parsed_payload
1064
+ except json.JSONDecodeError:
1065
+ # Try as Python literal
1066
+ import ast
1067
+ try:
1068
+ parsed_payload = ast.literal_eval(payload)
1069
+ logger.debug("Successfully parsed payload as Python literal")
1070
+ request_body = parsed_payload
1071
+ except (SyntaxError, ValueError) as e2:
1072
+ logger.debug(f"Failed to parse payload string: {e2}")
1073
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
1074
+ except Exception as e:
1075
+ logger.debug(f"Error parsing payload string: {e}")
1076
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
1077
+ else:
1078
+ # If payload is already a dictionary, use it directly
1079
+ logger.debug("Using provided payload dictionary")
1080
+ request_body = payload
1081
+
1082
+ # Import the ManualServiceConfig class
1083
+ try:
1084
+ from instana_client.models.manual_service_config import (
1085
+ ManualServiceConfig,
1086
+ )
1087
+ logger.debug("Successfully imported ManualServiceConfig")
1088
+ except ImportError as e:
1089
+ logger.debug(f"Error importing ManualServiceConfig: {e}")
1090
+ return {"error": f"Failed to import ManualServiceConfig: {e!s}"}
1091
+
1092
+ # Create an ManualServiceConfig object from the request body
1093
+ try:
1094
+ logger.debug(f"Creating ManualServiceConfig with params: {request_body}")
1095
+ config_object = ManualServiceConfig(**request_body)
1096
+ logger.debug("Successfully update manual service config object")
1097
+ except Exception as e:
1098
+ logger.debug(f"Error creating ManualServiceConfig: {e}")
1099
+ return {"error": f"Failed to update manual config object: {e!s}"}
1100
+
1101
+ # Call the update_manual_service_config method from the SDK
1102
+ logger.debug("Calling update_manual_service_config with config object")
724
1103
  result = api_client.update_manual_service_config(
725
1104
  id=id,
726
- manual_service_config=manual_service_config
1105
+ manual_service_config=config_object
727
1106
  )
728
1107
 
729
- if hasattr(result, "to_dict"):
1108
+ # Convert the result to a dictionary
1109
+ if hasattr(result, 'to_dict'):
730
1110
  result_dict = result.to_dict()
731
1111
  else:
732
- result_dict = result
1112
+ # If it's already a dict or another format, use it as is
1113
+ result_dict = result or {
1114
+ "success": True,
1115
+ "message": "update manual service config"
1116
+ }
733
1117
 
734
- debug_print(f"Manual service configuration result: {result_dict}")
1118
+ logger.debug(f"Result from update_manual_service_config: {result_dict}")
735
1119
  return result_dict
736
-
737
1120
  except Exception as e:
738
- debug_print(f"Error creating manual service configuration: {e}")
739
- traceback.print_exc(file=sys.stderr)
740
- return {"error": f"Failed to create manual service configuration: {e!s}"}
1121
+ logger.error(f"Error in update_manual_service_config: {e}")
1122
+ return {"error": f"Failed to update manual config: {e!s}"}
741
1123
 
742
1124
 
743
1125
  @register_as_tool
744
1126
  @with_header_auth(ApplicationSettingsApi)
745
1127
  async def replace_all_manual_service_config(
746
1128
  self,
747
- tagFilterExpression: Dict[str, Any],
748
- unmonitoredServiceName: Optional[str] = None,
749
- existingServiceId: Optional[str] = None,
750
- description: Optional[str] = None,
751
- enabled: Optional[bool] = True,
1129
+ payload: Union[Dict[str, Any], str],
752
1130
  ctx=None,
753
1131
  api_client=None
754
- ) -> Dict[str, Any]:
1132
+ ) -> List[Dict[str, Any]]:
755
1133
  """
756
1134
  This tool is used if one wants to update more than 1 manual service configurations.
757
1135
 
@@ -761,57 +1139,110 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
761
1139
  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.
762
1140
 
763
1141
  Args:
764
- id: A unique id of the manual service configuration.
765
- tagFilterExpression : Boolean expression of tag filters to match relevant calls.
766
- unmonitoredServiceName : Custom name for an unmonitored service to map. (Optional)
767
- existingServiceId : Service ID to link the matched calls to. (Optional)
768
- description: Description of the mapping configuration. (Optional)
769
- enabled: Enable or disable the configuration. Defaults to True. (Optional)
1142
+ Sample payload: [
1143
+ {
1144
+ "description": "Map source service",
1145
+ "enabled": true,
1146
+ "existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
1147
+ "tagFilterExpression": {
1148
+ "type": "TAG_FILTER",
1149
+ "name": "service.name",
1150
+ "stringValue": "front",
1151
+ "numberValue": null,
1152
+ "booleanValue": null,
1153
+ "key": null,
1154
+ "value": "front",
1155
+ "operator": "EQUALS",
1156
+ "entity": "SOURCE"
1157
+ },
1158
+ "unmonitoredServiceName": null
1159
+ }
1160
+ ]
770
1161
  ctx: Optional execution context.
771
1162
 
772
1163
  Returns:
773
1164
  Dict[str, Any]: API response indicating success or failure.
774
1165
  """
775
1166
  try:
776
- debug_print("Creating manual service configuration")
777
-
778
- if not (unmonitoredServiceName and existingServiceId):
779
- return {
780
- "error": "You must provide either 'unmonitoredServiceName' or 'existingServiceId'."
781
- }
782
- if not tagFilterExpression:
783
- return {"error": "Required enitities are missing or invalid"}
784
-
785
- request_body = {}
786
-
787
- if tagFilterExpression:
788
- request_body["tagFilterExpression"] = {"tagFilterExpression": tagFilterExpression}
789
- if unmonitoredServiceName:
790
- request_body["unmonitoredServiceName"] = {"unmonitoredServiceName": unmonitoredServiceName}
791
- if existingServiceId:
792
- request_body["existingServiceId"] = {"existingServiceId": existingServiceId}
793
- if description:
794
- request_body["description"] = {"description": description}
795
-
796
- new_manual_service_config = NewManualServiceConfig(**request_body)
797
-
798
- result = api_client.replace_all_manual_service_configs(
799
- new_manual_service_config=new_manual_service_config
1167
+ if not payload:
1168
+ return [{"error": "missing arguments"}]
1169
+
1170
+ # Parse the payload if it's a string
1171
+ if isinstance(payload, str):
1172
+ logger.debug("Payload is a string, attempting to parse")
1173
+ try:
1174
+ import json
1175
+ try:
1176
+ parsed_payload = json.loads(payload)
1177
+ logger.debug("Successfully parsed payload as JSON")
1178
+ request_body = parsed_payload
1179
+ except json.JSONDecodeError as e:
1180
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
1181
+
1182
+ # Try replacing single quotes with double quotes
1183
+ fixed_payload = payload.replace("'", "\"")
1184
+ try:
1185
+ parsed_payload = json.loads(fixed_payload)
1186
+ logger.debug("Successfully parsed fixed JSON")
1187
+ request_body = parsed_payload
1188
+ except json.JSONDecodeError:
1189
+ # Try as Python literal
1190
+ import ast
1191
+ try:
1192
+ parsed_payload = ast.literal_eval(payload)
1193
+ logger.debug("Successfully parsed payload as Python literal")
1194
+ request_body = parsed_payload
1195
+ except (SyntaxError, ValueError) as e2:
1196
+ logger.debug(f"Failed to parse payload string: {e2}")
1197
+ return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
1198
+ except Exception as e:
1199
+ logger.debug(f"Error parsing payload string: {e}")
1200
+ return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
1201
+ else:
1202
+ # If payload is already a dictionary, use it directly
1203
+ logger.debug("Using provided payload dictionary")
1204
+ request_body = payload
1205
+
1206
+ # Import the NewManualServiceConfig class
1207
+ try:
1208
+ from instana_client.models.new_manual_service_config import (
1209
+ NewManualServiceConfig,
1210
+ )
1211
+ logger.debug("Successfully imported ManualServiceConfig")
1212
+ except ImportError as e:
1213
+ logger.debug(f"Error importing ManualServiceConfig: {e}")
1214
+ return [{"error": f"Failed to import ManualServiceConfig: {e!s}"}]
1215
+
1216
+ # Create an ManualServiceConfig object from the request body
1217
+ try:
1218
+ logger.debug(f"Creating ManualServiceConfig with params: {request_body}")
1219
+ config_object = [NewManualServiceConfig(**request_body)]
1220
+ logger.debug("Successfully replace all manual service config object")
1221
+ except Exception as e:
1222
+ logger.debug(f"Error creating ManualServiceConfig: {e}")
1223
+ return [{"error": f"Failed to replace all manual config object: {e!s}"}]
1224
+
1225
+ # Call the replace_all_manual_service_config method from the SDK
1226
+ logger.debug("Calling replace_all_manual_service_config with config object")
1227
+ result = api_client.replace_all_manual_service_config(
1228
+ new_manual_service_config=config_object
800
1229
  )
801
1230
 
802
- if hasattr(result, "to_dict"):
1231
+ # Convert the result to a dictionary
1232
+ if hasattr(result, 'to_dict'):
803
1233
  result_dict = result.to_dict()
804
1234
  else:
805
- result_dict = result
806
-
807
- debug_print(f"Manual service configuration result: {result_dict}")
808
- return result_dict
1235
+ # If it's already a dict or another format, use it as is
1236
+ result_dict = result or {
1237
+ "success": True,
1238
+ "message": "Create replace all manual service config"
1239
+ }
809
1240
 
1241
+ logger.debug(f"Result from replace_all_manual_service_config: {result_dict}")
1242
+ return [result_dict]
810
1243
  except Exception as e:
811
- debug_print(f"Error creating manual service configuration: {e}")
812
- traceback.print_exc(file=sys.stderr)
813
- return {"error": f"Failed to create manual service configuration: {e!s}"}
814
-
1244
+ logger.error(f"Error in replace_all_manual_service_config: {e}")
1245
+ return [{"error": f"Failed to replace all manual config: {e!s}"}]
815
1246
 
816
1247
  @register_as_tool
817
1248
  @with_header_auth(ApplicationSettingsApi)
@@ -847,45 +1278,98 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
847
1278
 
848
1279
  @register_as_tool
849
1280
  @with_header_auth(ApplicationSettingsApi)
850
- async def add_service_configs(self,
851
- enabled: bool,
852
- match_specification: List[Dict[str, str]],
853
- name: str,
854
- label:str,
855
- id: str,
856
- comment: Optional[str] = None,
1281
+ async def add_service_config(self,
1282
+ payload: Union[Dict[str, Any], str],
857
1283
  ctx=None,
858
- api_client=None) -> List[Dict[str, Any]]:
1284
+ api_client=None) -> Dict[str, Any]:
859
1285
  """
860
1286
  This tool gives is used to add new Service Perspectives Configuration
861
1287
  Get a list of all Service Perspectives with their configuration settings.
862
1288
  Args:
863
-
1289
+ {
1290
+ "comment": null,
1291
+ "enabled": true,
1292
+ "label": "{gce.zone}-{jvm.args.abc}",
1293
+ "matchSpecification": [
1294
+ {
1295
+ "key": "gce.zone",
1296
+ "value": ".*"
1297
+ },
1298
+ {
1299
+ "key": "jvm.args.abc",
1300
+ "value": ".*"
1301
+ }
1302
+ ],
1303
+ "name": "ABC is good"
1304
+ }
864
1305
  ctx: The MCP context (optional)
865
1306
 
866
1307
  Returns:
867
1308
  Dictionary containing endpoints data or error information
868
1309
  """
869
1310
  try:
870
- debug_print("Adding new service config")
871
- if not (enabled and match_specification and name and label and id):
872
- return [{"error": "Required entities are missing or invalid"}]
873
-
874
- body = {
875
- "match_specification": match_specification,
876
- "enabled": enabled,
877
- "id": id,
878
- "label": label,
879
- "name": name
880
- }
881
-
882
- if comment:
883
- body["comment"] = comment
884
-
885
- service_config = ServiceConfig(**body)
886
-
1311
+ if not payload:
1312
+ return {"error": "missing arguments"}
1313
+
1314
+ # Parse the payload if it's a string
1315
+ if isinstance(payload, str):
1316
+ logger.debug("Payload is a string, attempting to parse")
1317
+ try:
1318
+ import json
1319
+ try:
1320
+ parsed_payload = json.loads(payload)
1321
+ logger.debug("Successfully parsed payload as JSON")
1322
+ request_body = parsed_payload
1323
+ except json.JSONDecodeError as e:
1324
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
1325
+
1326
+ # Try replacing single quotes with double quotes
1327
+ fixed_payload = payload.replace("'", "\"")
1328
+ try:
1329
+ parsed_payload = json.loads(fixed_payload)
1330
+ logger.debug("Successfully parsed fixed JSON")
1331
+ request_body = parsed_payload
1332
+ except json.JSONDecodeError:
1333
+ # Try as Python literal
1334
+ import ast
1335
+ try:
1336
+ parsed_payload = ast.literal_eval(payload)
1337
+ logger.debug("Successfully parsed payload as Python literal")
1338
+ request_body = parsed_payload
1339
+ except (SyntaxError, ValueError) as e2:
1340
+ logger.debug(f"Failed to parse payload string: {e2}")
1341
+ return {"error": f"Invalid payload format: {e2}", "payload": payload}
1342
+ except Exception as e:
1343
+ logger.debug(f"Error parsing payload string: {e}")
1344
+ return {"error": f"Failed to parse payload: {e}", "payload": payload}
1345
+ else:
1346
+ # If payload is already a dictionary, use it directly
1347
+ logger.debug("Using provided payload dictionary")
1348
+ request_body = payload
1349
+
1350
+ # Import the ServiceConfig class
1351
+ try:
1352
+ from instana_client.models.service_config import (
1353
+ ServiceConfig,
1354
+ )
1355
+ logger.debug("Successfully imported ServiceConfig")
1356
+ except ImportError as e:
1357
+ logger.debug(f"Error importing ServiceConfig: {e}")
1358
+ return {"error": f"Failed to import ServiceConfig: {e!s}"}
1359
+
1360
+ # Create an ServiceConfig object from the request body
1361
+ try:
1362
+ logger.debug(f"Creating ServiceConfig with params: {request_body}")
1363
+ config_object = ServiceConfig(**request_body)
1364
+ logger.debug("Successfully add service config object")
1365
+ except Exception as e:
1366
+ logger.debug(f"Error creating ServiceConfig: {e}")
1367
+ return {"error": f"Failed to add service config object: {e!s}"}
1368
+
1369
+ # Call the ServiceConfig method from the SDK
1370
+ logger.debug("Calling add_service_config with config object")
887
1371
  result = api_client.add_service_config(
888
- service_config=service_config
1372
+ service_config=config_object
889
1373
  )
890
1374
 
891
1375
  # Convert the result to a dictionary
@@ -893,72 +1377,127 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
893
1377
  result_dict = result.to_dict()
894
1378
  else:
895
1379
  # If it's already a dict or another format, use it as is
896
- result_dict = result
1380
+ result_dict = result or {
1381
+ "success": True,
1382
+ "message": "Create service config"
1383
+ }
897
1384
 
898
- debug_print(f"Result from add_service_config: {result_dict}")
1385
+ logger.debug(f"Result from add_service_config: {result_dict}")
899
1386
  return result_dict
900
-
901
1387
  except Exception as e:
902
- debug_print(f"Error in add_service_config: {e}")
903
- traceback.print_exc(file=sys.stderr)
904
- return [{"error": f"Failed to get application data metrics: {e}"}]
1388
+ logger.error(f"Error in add_service_config: {e}")
1389
+ return {"error": f"Failed to add service config: {e!s}"}
905
1390
 
906
1391
  @register_as_tool
907
1392
  @with_header_auth(ApplicationSettingsApi)
908
1393
  async def replace_all_service_configs(self,
909
- enabled: bool,
910
- match_specification: List[Dict[str, str]],
911
- name: str,
912
- label:str,
913
- id: str,
914
- comment: Optional[str] = None,
1394
+ payload: Union[Dict[str, Any], str],
915
1395
  ctx=None,
916
1396
  api_client=None) -> List[Dict[str, Any]]:
917
1397
  """
918
-
919
1398
  Args:
920
-
1399
+ [
1400
+ {
1401
+ "comment": null,
1402
+ "enabled": true,
1403
+ "id": "8C-jGYx8Rsue854tzkh8KQ",
1404
+ "label": "{docker.container.name}",
1405
+ "matchSpecification": [
1406
+ {
1407
+ "key": "docker.container.name",
1408
+ "value": ".*"
1409
+ }
1410
+ ],
1411
+ "name": "Rule"
1412
+ }
1413
+ ]
921
1414
  ctx: The MCP context (optional)
922
1415
 
923
1416
  Returns:
924
1417
  Dictionary containing endpoints data or error information
925
1418
  """
926
1419
  try:
927
- debug_print("Fetching all service configs")
928
- if not (enabled or match_specification or name or label or id):
929
- return [{"error": "Required entities are missing or invalid"}]
930
-
931
- body = {
932
- "match_specification": match_specification,
933
- "enabled": enabled,
934
- "id": id,
935
- "label": label,
936
- "name": name
937
- }
938
-
939
- if comment:
940
- body["comment"] = comment
941
-
942
- service_config_list = [ServiceConfig(**body)]
943
-
1420
+ if not payload:
1421
+ return [{"error": "missing arguments"}]
1422
+
1423
+ # Parse the payload if it's a string
1424
+ if isinstance(payload, str):
1425
+ logger.debug("Payload is a string, attempting to parse")
1426
+ try:
1427
+ import json
1428
+ try:
1429
+ parsed_payload = json.loads(payload)
1430
+ logger.debug("Successfully parsed payload as JSON")
1431
+ request_body = parsed_payload
1432
+ except json.JSONDecodeError as e:
1433
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
1434
+
1435
+ # Try replacing single quotes with double quotes
1436
+ fixed_payload = payload.replace("'", "\"")
1437
+ try:
1438
+ parsed_payload = json.loads(fixed_payload)
1439
+ logger.debug("Successfully parsed fixed JSON")
1440
+ request_body = parsed_payload
1441
+ except json.JSONDecodeError:
1442
+ # Try as Python literal
1443
+ import ast
1444
+ try:
1445
+ parsed_payload = ast.literal_eval(payload)
1446
+ logger.debug("Successfully parsed payload as Python literal")
1447
+ request_body = parsed_payload
1448
+ except (SyntaxError, ValueError) as e2:
1449
+ logger.debug(f"Failed to parse payload string: {e2}")
1450
+ return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
1451
+ except Exception as e:
1452
+ logger.debug(f"Error parsing payload string: {e}")
1453
+ return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
1454
+ else:
1455
+ # If payload is already a dictionary, use it directly
1456
+ logger.debug("Using provided payload dictionary")
1457
+ request_body = payload
1458
+
1459
+ # Import the ServiceConfig class
1460
+ try:
1461
+ from instana_client.models.service_config import (
1462
+ ServiceConfig,
1463
+ )
1464
+ logger.debug("Successfully imported ServiceConfig")
1465
+ except ImportError as e:
1466
+ logger.debug(f"Error importing ServiceConfig: {e}")
1467
+ return [{"error": f"Failed to import ServiceConfig: {e!s}"}]
1468
+
1469
+ # Create an ServiceConfig object from the request body
1470
+ try:
1471
+ logger.debug(f"Creating ServiceConfig with params: {request_body}")
1472
+ config_object = [ServiceConfig(**request_body)]
1473
+ logger.debug("Successfully replace all manual service config object")
1474
+ except Exception as e:
1475
+ logger.debug(f"Error creating ServiceConfig: {e}")
1476
+ return [{"error": f"Failed to replace all manual config object: {e!s}"}]
1477
+
1478
+ # Call the replace_all method from the SDK
1479
+ logger.debug("Calling replace_all with config object")
944
1480
  result = api_client.replace_all(
945
- service_config=service_config_list
1481
+ service_config=config_object
946
1482
  )
947
1483
 
948
- # Convert the result to a dictionary
1484
+ # Convert the result to a list of dictionaries
949
1485
  if hasattr(result, 'to_dict'):
950
1486
  result_dict = result.to_dict()
951
1487
  else:
952
1488
  # If it's already a dict or another format, use it as is
953
- result_dict = result
954
-
955
- debug_print(f"Result from get_service_configs: {result_dict}")
956
- return result_dict
1489
+ result_dict = result or {
1490
+ "success": True,
1491
+ "message": "replace all service config"
1492
+ }
957
1493
 
1494
+ logger.debug(f"Result from replace_all: {result_dict}")
1495
+ return [result_dict]
958
1496
  except Exception as e:
959
- debug_print(f"Error in get_service_configs: {e}")
960
- traceback.print_exc(file=sys.stderr)
961
- return [{"error": f"Failed to get application data metrics: {e}"}]
1497
+ logger.error(f"Error in replace_all: {e}")
1498
+ return [{"error": f"Failed to replace all service config: {e!s}"}]
1499
+
1500
+
962
1501
  @register_as_tool
963
1502
  @with_header_auth(ApplicationSettingsApi)
964
1503
  async def order_service_config(self,
@@ -1078,45 +1617,100 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
1078
1617
 
1079
1618
  @register_as_tool
1080
1619
  @with_header_auth(ApplicationSettingsApi)
1081
- async def update_service_configs(self,
1082
- enabled: bool,
1083
- match_specification: List[Dict[str, str]],
1084
- name: str,
1085
- label:str,
1620
+ async def update_service_config(self,
1086
1621
  id: str,
1087
- comment: Optional[str] = None,
1622
+ payload: Union[Dict[str, Any], str],
1088
1623
  ctx=None,
1089
- api_client=None) -> List[Dict[str, Any]]:
1624
+ api_client=None) -> Dict[str, Any]:
1090
1625
  """
1091
1626
  This tool gives is used if one wants to update a particular custom service rule.
1092
1627
  Args:
1093
-
1628
+ {
1629
+ "comment": null,
1630
+ "enabled": true,
1631
+ "id": "9uma4MhnTTSyBzwu_FKBJA",
1632
+ "label": "{gce.zone}-{jvm.args.abc}",
1633
+ "matchSpecification": [
1634
+ {
1635
+ "key": "gce.zone",
1636
+ "value": ".*"
1637
+ },
1638
+ {
1639
+ "key": "jvm.args.abc",
1640
+ "value": ".*"
1641
+ }
1642
+ ],
1643
+ "name": "DEF is good"
1644
+ }
1094
1645
  ctx: The MCP context (optional)
1095
1646
 
1096
1647
  Returns:
1097
1648
  Dictionary containing endpoints data or error information
1098
1649
  """
1099
1650
  try:
1100
- debug_print("Adding new service config")
1101
- if not (id and name and label and isinstance(match_specification, list)):
1102
- return [{"error": "Required entities are missing or invalid"}]
1103
-
1104
- body = {
1105
- "match_specification": match_specification,
1106
- "enabled": enabled,
1107
- "id": id,
1108
- "label": label,
1109
- "name": name
1110
- }
1111
-
1112
- if comment:
1113
- body["comment"] = comment
1114
-
1115
- service_config = ServiceConfig(**body)
1116
-
1651
+ if not payload or not id:
1652
+ return [{"error": "missing arguments"}]
1653
+
1654
+ # Parse the payload if it's a string
1655
+ if isinstance(payload, str):
1656
+ logger.debug("Payload is a string, attempting to parse")
1657
+ try:
1658
+ import json
1659
+ try:
1660
+ parsed_payload = json.loads(payload)
1661
+ logger.debug("Successfully parsed payload as JSON")
1662
+ request_body = parsed_payload
1663
+ except json.JSONDecodeError as e:
1664
+ logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
1665
+
1666
+ # Try replacing single quotes with double quotes
1667
+ fixed_payload = payload.replace("'", "\"")
1668
+ try:
1669
+ parsed_payload = json.loads(fixed_payload)
1670
+ logger.debug("Successfully parsed fixed JSON")
1671
+ request_body = parsed_payload
1672
+ except json.JSONDecodeError:
1673
+ # Try as Python literal
1674
+ import ast
1675
+ try:
1676
+ parsed_payload = ast.literal_eval(payload)
1677
+ logger.debug("Successfully parsed payload as Python literal")
1678
+ request_body = parsed_payload
1679
+ except (SyntaxError, ValueError) as e2:
1680
+ logger.debug(f"Failed to parse payload string: {e2}")
1681
+ return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
1682
+ except Exception as e:
1683
+ logger.debug(f"Error parsing payload string: {e}")
1684
+ return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
1685
+ else:
1686
+ # If payload is already a dictionary, use it directly
1687
+ logger.debug("Using provided payload dictionary")
1688
+ request_body = payload
1689
+
1690
+ # Import the ServiceConfig class
1691
+ try:
1692
+ from instana_client.models.service_config import (
1693
+ ServiceConfig,
1694
+ )
1695
+ logger.debug("Successfully imported ServiceConfig")
1696
+ except ImportError as e:
1697
+ logger.debug(f"Error importing ServiceConfig: {e}")
1698
+ return [{"error": f"Failed to import ServiceConfig: {e!s}"}]
1699
+
1700
+ # Create an ServiceConfig object from the request body
1701
+ try:
1702
+ logger.debug(f"Creating ServiceConfig with params: {request_body}")
1703
+ config_object = [ServiceConfig(**request_body)]
1704
+ logger.debug("Successfully update service config object")
1705
+ except Exception as e:
1706
+ logger.debug(f"Error creating ServiceConfig: {e}")
1707
+ return [{"error": f"Failed to replace all manual config object: {e!s}"}]
1708
+
1709
+ # Call the put_service_config method from the SDK
1710
+ logger.debug("Calling put_service_config with config object")
1117
1711
  result = api_client.put_service_config(
1118
1712
  id=id,
1119
- service_config=service_config
1713
+ service_config=config_object
1120
1714
  )
1121
1715
 
1122
1716
  # Convert the result to a dictionary
@@ -1124,12 +1718,14 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
1124
1718
  result_dict = result.to_dict()
1125
1719
  else:
1126
1720
  # If it's already a dict or another format, use it as is
1127
- result_dict = result
1128
-
1129
- debug_print(f"Result from add_service_config: {result_dict}")
1130
- return result_dict
1721
+ result_dict = result or {
1722
+ "success": True,
1723
+ "message": "put service config"
1724
+ }
1131
1725
 
1726
+ logger.debug(f"Result from put_service_config: {result_dict}")
1727
+ return [result_dict]
1132
1728
  except Exception as e:
1133
- debug_print(f"Error in add_service_config: {e}")
1134
- traceback.print_exc(file=sys.stderr)
1135
- return [{"error": f"Failed to add new service config: {e!s}"}]
1729
+ logger.error(f"Error in put_service_config: {e}")
1730
+ return [{"error": f"Failed to update service config: {e!s}"}]
1731
+