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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. mcp_instana-0.2.0.dist-info/METADATA +1229 -0
  2. mcp_instana-0.2.0.dist-info/RECORD +59 -0
  3. {mcp_instana-0.1.0.dist-info → mcp_instana-0.2.0.dist-info}/WHEEL +1 -1
  4. mcp_instana-0.2.0.dist-info/entry_points.txt +4 -0
  5. mcp_instana-0.1.0.dist-info/LICENSE → mcp_instana-0.2.0.dist-info/licenses/LICENSE.md +3 -3
  6. src/application/__init__.py +1 -0
  7. src/{client/application_alert_config_mcp_tools.py → application/application_alert_config.py} +251 -273
  8. src/application/application_analyze.py +628 -0
  9. src/application/application_catalog.py +155 -0
  10. src/application/application_global_alert_config.py +653 -0
  11. src/{client/application_metrics_mcp_tools.py → application/application_metrics.py} +113 -131
  12. src/{client/application_resources_mcp_tools.py → application/application_resources.py} +131 -151
  13. src/application/application_settings.py +1731 -0
  14. src/application/application_topology.py +111 -0
  15. src/automation/action_catalog.py +416 -0
  16. src/automation/action_history.py +338 -0
  17. src/core/__init__.py +1 -0
  18. src/core/server.py +586 -0
  19. src/core/utils.py +213 -0
  20. src/event/__init__.py +1 -0
  21. src/event/events_tools.py +850 -0
  22. src/infrastructure/__init__.py +1 -0
  23. src/{client/infrastructure_analyze_mcp_tools.py → infrastructure/infrastructure_analyze.py} +207 -206
  24. src/{client/infrastructure_catalog_mcp_tools.py → infrastructure/infrastructure_catalog.py} +197 -265
  25. src/infrastructure/infrastructure_metrics.py +171 -0
  26. src/{client/infrastructure_resources_mcp_tools.py → infrastructure/infrastructure_resources.py} +198 -227
  27. src/{client/infrastructure_topology_mcp_tools.py → infrastructure/infrastructure_topology.py} +110 -109
  28. src/log/__init__.py +1 -0
  29. src/log/log_alert_configuration.py +331 -0
  30. src/prompts/__init__.py +16 -0
  31. src/prompts/application/__init__.py +1 -0
  32. src/prompts/application/application_alerts.py +54 -0
  33. src/prompts/application/application_catalog.py +26 -0
  34. src/prompts/application/application_metrics.py +57 -0
  35. src/prompts/application/application_resources.py +26 -0
  36. src/prompts/application/application_settings.py +75 -0
  37. src/prompts/application/application_topology.py +30 -0
  38. src/prompts/events/__init__.py +1 -0
  39. src/prompts/events/events_tools.py +161 -0
  40. src/prompts/infrastructure/infrastructure_analyze.py +72 -0
  41. src/prompts/infrastructure/infrastructure_catalog.py +53 -0
  42. src/prompts/infrastructure/infrastructure_metrics.py +45 -0
  43. src/prompts/infrastructure/infrastructure_resources.py +74 -0
  44. src/prompts/infrastructure/infrastructure_topology.py +38 -0
  45. src/prompts/settings/__init__.py +0 -0
  46. src/prompts/settings/custom_dashboard.py +157 -0
  47. src/prompts/website/__init__.py +1 -0
  48. src/prompts/website/website_analyze.py +35 -0
  49. src/prompts/website/website_catalog.py +40 -0
  50. src/prompts/website/website_configuration.py +105 -0
  51. src/prompts/website/website_metrics.py +34 -0
  52. src/settings/__init__.py +1 -0
  53. src/settings/custom_dashboard_tools.py +417 -0
  54. src/website/__init__.py +0 -0
  55. src/website/website_analyze.py +433 -0
  56. src/website/website_catalog.py +171 -0
  57. src/website/website_configuration.py +770 -0
  58. src/website/website_metrics.py +241 -0
  59. mcp_instana-0.1.0.dist-info/METADATA +0 -649
  60. mcp_instana-0.1.0.dist-info/RECORD +0 -19
  61. mcp_instana-0.1.0.dist-info/entry_points.txt +0 -3
  62. src/client/What is the sum of queue depth for all q +0 -55
  63. src/client/events_mcp_tools.py +0 -531
  64. src/client/instana_client_base.py +0 -93
  65. src/client/log_alert_configuration_mcp_tools.py +0 -316
  66. src/client/show the top 5 services with the highest +0 -28
  67. src/mcp_server.py +0 -343
@@ -0,0 +1,1731 @@
1
+ """
2
+ Application Settings MCP Tools Module
3
+
4
+ This module provides application settings-specific MCP tools for Instana monitoring.
5
+
6
+ The API endpoints of this group provides a way to create, read, update, delete (CRUD) for various configuration settings.
7
+ """
8
+
9
+ import logging
10
+ import re
11
+ import sys
12
+ import traceback
13
+ from datetime import datetime
14
+ from typing import Any, Dict, List, Optional, Union
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
19
+
20
+ # Import the necessary classes from the SDK
21
+ try:
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
+ )
35
+ except ImportError as e:
36
+ print(f"Error importing Instana SDK: {e}", file=sys.stderr)
37
+ traceback.print_exc(file=sys.stderr)
38
+ raise
39
+
40
+
41
+ # Helper function for debug printing
42
+ def debug_print(*args, **kwargs):
43
+ """Print debug information to stderr instead of stdout"""
44
+ print(*args, file=sys.stderr, **kwargs)
45
+
46
+ class ApplicationSettingsMCPTools(BaseInstanaClient):
47
+ """Tools for application settings in Instana MCP."""
48
+
49
+ def __init__(self, read_token: str, base_url: str):
50
+ """Initialize the Application Settings MCP tools client."""
51
+ super().__init__(read_token=read_token, base_url=base_url)
52
+
53
+ try:
54
+
55
+ # Configure the API client with the correct base URL and authentication
56
+ configuration = Configuration()
57
+ configuration.host = base_url
58
+ configuration.api_key['ApiKeyAuth'] = read_token
59
+ configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
60
+
61
+ # Create an API client with this configuration
62
+ api_client = ApiClient(configuration=configuration)
63
+
64
+ # Initialize the Instana SDK's ApplicationSettingsApi with our configured client
65
+ self.settings_api = ApplicationSettingsApi(api_client=api_client)
66
+ except Exception as e:
67
+ debug_print(f"Error initializing ApplicationSettingsApi: {e}")
68
+ traceback.print_exc(file=sys.stderr)
69
+ raise
70
+
71
+ @register_as_tool
72
+ @with_header_auth(ApplicationSettingsApi)
73
+ async def get_all_applications_configs(self,
74
+ ctx=None,
75
+ api_client=None) -> List[Dict[str, Any]]:
76
+ """
77
+ All Application Perspectives Configuration
78
+ Get a list of all Application Perspectives with their configuration settings.
79
+
80
+ Args:
81
+ ctx: The MCP context (optional)
82
+
83
+ Returns:
84
+ Dictionary containing endpoints data or error information
85
+ """
86
+ try:
87
+ debug_print("Fetching all applications and their settings")
88
+ result = api_client.get_application_configs()
89
+ # Convert the result to a list of dictionaries
90
+ if isinstance(result, list):
91
+ result_dict = [item.to_dict() if hasattr(item, 'to_dict') else item for item in result]
92
+ elif hasattr(result, 'to_dict'):
93
+ result_dict = result.to_dict()
94
+ else:
95
+ result_dict = result
96
+
97
+ debug_print(f"Result from get_application_configs: {result_dict}")
98
+ return result_dict
99
+
100
+ except Exception as e:
101
+ debug_print(f"Error in get_application_configs: {e}")
102
+ traceback.print_exc(file=sys.stderr)
103
+ return [{"error": f"Failed to get all applications: {e!s}"}]
104
+
105
+ @register_as_tool
106
+ @with_header_auth(ApplicationSettingsApi)
107
+ async def add_application_config(self,
108
+ payload: Union[Dict[str, Any], str],
109
+ ctx=None,
110
+ api_client=None) -> Dict[str, Any]:
111
+ """
112
+ Add a new Application Perspective configuration.
113
+ This tool allows you to create a new Application Perspective with specified settings.
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
+ }
165
+ Returns:
166
+ Dictionary containing the created application perspective configuration or error information
167
+ """
168
+ try:
169
+
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}"}
227
+
228
+ # Call the add_application_config method from the SDK
229
+ logger.debug("Calling add_application_config with config object")
230
+ result = api_client.add_application_config(
231
+ new_application_config=config_object
232
+ )
233
+
234
+ # Convert the result to a dictionary
235
+ if hasattr(result, 'to_dict'):
236
+ result_dict = result.to_dict()
237
+ else:
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
+ }
243
+
244
+ logger.debug(f"Result from add_application_config: {result_dict}")
245
+ return result_dict
246
+ except Exception as e:
247
+ logger.error(f"Error in add_application_config: {e}")
248
+ return {"error": f"Failed to add new application config: {e!s}"}
249
+
250
+ @register_as_tool
251
+ @with_header_auth(ApplicationSettingsApi)
252
+ async def delete_application_config(self,
253
+ id: str,
254
+ ctx=None,
255
+ api_client=None) -> Dict[str, Any]:
256
+ """
257
+ Delete an Application Perspective configuration.
258
+ This tool allows you to delete an existing Application Perspective by its ID.
259
+
260
+ Args:
261
+ application_id: The ID of the application perspective to delete
262
+ ctx: The MCP context (optional)
263
+
264
+ Returns:
265
+ Dictionary containing the result of the deletion or error information
266
+ """
267
+ try:
268
+ if not id:
269
+ return {"error": "Application perspective ID is required for deletion"}
270
+
271
+
272
+ debug_print(f"Deleting application perspective with ID: {id}")
273
+ # Call the delete_application_config method from the SDK
274
+ api_client.delete_application_config(id=id)
275
+
276
+ result_dict = {
277
+ "success": True,
278
+ "message": f"Application Confiuguration '{id}' has been successfully deleted"
279
+ }
280
+
281
+ debug_print(f"Successfully deleted application perspective with ID: {id}")
282
+ return result_dict
283
+ except Exception as e:
284
+ debug_print(f"Error in delete_application_config: {e}")
285
+ traceback.print_exc(file=sys.stderr)
286
+ return {"error": f"Failed to delete application configuration: {e!s}"}
287
+
288
+ @register_as_tool
289
+ @with_header_auth(ApplicationSettingsApi)
290
+ async def get_application_config(self,
291
+ id: str,
292
+ ctx=None,
293
+ api_client=None) -> Dict[str, Any]:
294
+ """
295
+ Get an Application Perspective configuration by ID.
296
+ This tool retrieves the configuration settings for a specific Application Perspective.
297
+
298
+ Args:
299
+ id: The ID of the application perspective to retrieve
300
+ ctx: The MCP context (optional)
301
+
302
+ Returns:
303
+ Dictionary containing the application perspective configuration or error information
304
+ """
305
+ try:
306
+ debug_print(f"Fetching application perspective with ID: {id}")
307
+ # Call the get_application_config method from the SDK
308
+ result = api_client.get_application_config(id=id)
309
+
310
+ # Convert the result to a dictionary
311
+ if hasattr(result, 'to_dict'):
312
+ result_dict = result.to_dict()
313
+ else:
314
+ result_dict = result
315
+
316
+ debug_print(f"Result from get_application_config: {result_dict}")
317
+ return result_dict
318
+ except Exception as e:
319
+ debug_print(f"Error in get_application_config: {e}")
320
+ traceback.print_exc(file=sys.stderr)
321
+ return {"error": f"Failed to get application configuration: {e!s}"}
322
+
323
+ @register_as_tool
324
+ @with_header_auth(ApplicationSettingsApi)
325
+ async def update_application_config(
326
+ self,
327
+ id: str,
328
+ payload: Union[Dict[str, Any], str],
329
+ ctx=None,
330
+ api_client=None
331
+ ) -> Dict[str, Any]:
332
+ """
333
+ Update an existing Application Perspective configuration.
334
+ This tool allows you to update an existing Application Perspective with specified application Id.
335
+
336
+ Args:
337
+ id: The ID of the application perspective to retrieve
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
+ }
389
+ ctx: The MCP context (optional)
390
+ Returns:
391
+ Dictionary containing the created application perspective configuration or error information
392
+ """
393
+ try:
394
+
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}"}
452
+
453
+ # Call the put_application_config method from the SDK
454
+ logger.debug("Calling put_application_config with config object")
455
+ result = api_client.put_application_config(
456
+ id=id,
457
+ application_config=config_object
458
+ )
459
+
460
+ # Convert the result to a dictionary
461
+ if hasattr(result, 'to_dict'):
462
+ result_dict = result.to_dict()
463
+ else:
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
+ }
469
+
470
+ logger.debug(f"Result from put_application_config: {result_dict}")
471
+ return result_dict
472
+ except Exception as e:
473
+ logger.error(f"Error in put_application_config: {e}")
474
+ return {"error": f"Failed to update existing application config: {e!s}"}
475
+
476
+ @register_as_tool
477
+ @with_header_auth(ApplicationSettingsApi)
478
+ async def get_all_endpoint_configs(self,
479
+ ctx=None,
480
+ api_client=None) -> List[Dict[str, Any]]:
481
+ """
482
+ All Endpoint Perspectives Configuration
483
+ Get a list of all Endpoint Perspectives with their configuration settings.
484
+ Args:
485
+ ctx: The MCP context (optional)
486
+
487
+ Returns:
488
+ Dictionary containing endpoints data or error information
489
+ """
490
+ try:
491
+ debug_print("Fetching all endpoint configs")
492
+ result = api_client.get_endpoint_configs()
493
+ # Convert the result to a dictionary
494
+ if hasattr(result, 'to_dict'):
495
+ result_dict = result.to_dict()
496
+ else:
497
+ # If it's already a dict or another format, use it as is
498
+ result_dict = result
499
+
500
+ debug_print(f"Result from get_endpoint_configs: {result_dict}")
501
+ return result_dict
502
+
503
+ except Exception as e:
504
+ debug_print(f"Error in get_endpoint_configs: {e}")
505
+ traceback.print_exc(file=sys.stderr)
506
+ return [{"error": f"Failed to get endpoint configs: {e!s}"}]
507
+
508
+ @register_as_tool
509
+ @with_header_auth(ApplicationSettingsApi)
510
+ async def create_endpoint_config(
511
+ self,
512
+ payload: Union[Dict[str, Any], str],
513
+ ctx=None,
514
+ api_client=None
515
+ ) -> Dict[str, Any]:
516
+ """
517
+ Create or update endpoint configuration for a service.
518
+
519
+ Sample Payload: {
520
+ "serviceId": "d0cedae516f2182ede16f57f67476dd4c7dab9cd",
521
+ "endpointCase": "LOWER",
522
+ "endpointNameByFirstPathSegmentRuleEnabled": false,
523
+ "endpointNameByCollectedPathTemplateRuleEnabled": false,
524
+ "rules": null
525
+ }
526
+
527
+ Returns:
528
+ Dict[str, Any]: Response from the create/update endpoint configuration API.
529
+ """
530
+ try:
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")
591
+ result = api_client.create_endpoint_config(
592
+ endpoint_config=config_object
593
+ )
594
+ # Convert the result to a dictionary
595
+ if hasattr(result, 'to_dict'):
596
+ result_dict = result.to_dict()
597
+ else:
598
+ # If it's already a dict or another format, use it as is
599
+ result_dict = result or {
600
+ "success": True,
601
+ "message": "Create new endpoint config"
602
+ }
603
+
604
+ logger.debug(f"Result from create_endpoint_config: {result_dict}")
605
+ return result_dict
606
+ except Exception as e:
607
+ logger.error(f"Error in create_endpoint_config: {e}")
608
+ return {"error": f"Failed to create new endpoint config: {e!s}"}
609
+
610
+ @register_as_tool
611
+ @with_header_auth(ApplicationSettingsApi)
612
+ async def delete_endpoint_config(
613
+ self,
614
+ id: str,
615
+ ctx=None,
616
+ api_client=None
617
+ ) -> Dict[str, Any]:
618
+ """
619
+ Delete an endpoint configuration of a service.
620
+
621
+ Args:
622
+ id: An Instana generated unique identifier for a Service.
623
+ ctx: The MCP context (optional)
624
+
625
+ Returns:
626
+ Dict[str, Any]: Response from the delete endpoint configuration API.
627
+ """
628
+ try:
629
+ debug_print("Delete endpoint configs")
630
+ if not id:
631
+ return {"error": "Required enitities are missing or invalid"}
632
+
633
+ api_client.delete_endpoint_config(id=id)
634
+
635
+ result_dict = {
636
+ "success": True,
637
+ "message": f"Endpoint Confiuguration '{id}' has been successfully deleted"
638
+ }
639
+
640
+ debug_print(f"Successfully deleted endpoint perspective with ID: {id}")
641
+ return result_dict
642
+
643
+ except Exception as e:
644
+ debug_print(f"Error in delete_endpoint_config: {e}")
645
+ traceback.print_exc(file=sys.stderr)
646
+ return {"error": f"Failed to delete endpoint configs: {e!s}"}
647
+
648
+ @register_as_tool
649
+ @with_header_auth(ApplicationSettingsApi)
650
+ async def get_endpoint_config(
651
+ self,
652
+ id: str,
653
+ ctx=None,
654
+ api_client=None
655
+ ) -> Dict[str, Any]:
656
+ """
657
+ This MCP tool is used for endpoint if one wants to retrieve the endpoint configuration of a service.
658
+ Args:
659
+ id: An Instana generated unique identifier for a Service.
660
+ ctx: The MCP context (optional)
661
+
662
+ Returns:
663
+ Dict[str, Any]: Response from the create/update endpoint configuration API.
664
+
665
+ """
666
+ try:
667
+ debug_print("get endpoint config")
668
+ if not id:
669
+ return {"error": "Required enitities are missing or invalid"}
670
+
671
+ result = api_client.get_endpoint_config(
672
+ id=id
673
+ )
674
+ # Convert the result to a dictionary
675
+ if hasattr(result, 'to_dict'):
676
+ result_dict = result.to_dict()
677
+ else:
678
+ # If it's already a dict or another format, use it as is
679
+ result_dict = result
680
+
681
+ debug_print(f"Result from get_endpoint_configs: {result_dict}")
682
+ return result_dict
683
+ except Exception as e:
684
+ debug_print(f"Error in get_endpoint_configs: {e}")
685
+ traceback.print_exc(file=sys.stderr)
686
+ return {"error": f"Failed to get endpoint configs: {e!s}"}
687
+
688
+ @register_as_tool
689
+ @with_header_auth(ApplicationSettingsApi)
690
+ async def update_endpoint_config(
691
+ self,
692
+ id: str,
693
+ payload: Union[Dict[str, Any], str],
694
+ ctx=None,
695
+ api_client=None
696
+ ) -> Dict[str, Any]:
697
+ """
698
+ update endpoint configuration for a service.
699
+
700
+ Args:
701
+ id: An Instana generated unique identifier for a Service.
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
+ }
726
+ ctx: The MCP context (optional)
727
+
728
+ Returns:
729
+ Dict[str, Any]: Response from the create/update endpoint configuration API.
730
+ """
731
+ try:
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")
792
+ result = api_client.update_endpoint_config(
793
+ id=id,
794
+ endpoint_config=config_object
795
+ )
796
+
797
+ # Convert the result to a dictionary
798
+ if hasattr(result, 'to_dict'):
799
+ result_dict = result.to_dict()
800
+ else:
801
+ # If it's already a dict or another format, use it as is
802
+ result_dict = result or {
803
+ "success": True,
804
+ "message": "update existing endpoint config"
805
+ }
806
+
807
+ logger.debug(f"Result from update_endpoint_config: {result_dict}")
808
+ return result_dict
809
+ except Exception as e:
810
+ logger.error(f"Error in update_endpoint_config: {e}")
811
+ return {"error": f"Failed to update existing application config: {e!s}"}
812
+
813
+
814
+ @register_as_tool
815
+ @with_header_auth(ApplicationSettingsApi)
816
+ async def get_all_manual_service_configs(self,
817
+ ctx=None,
818
+ api_client=None) -> List[Dict[str, Any]]:
819
+ """
820
+ All Manual Service Perspectives Configuration
821
+ Get a list of all Manual Service Perspectives with their configuration settings.
822
+ Args:
823
+ ctx: The MCP context (optional)
824
+
825
+ Returns:
826
+ Dictionary containing endpoints data or error information
827
+ """
828
+ try:
829
+ debug_print("Fetching all manual configs")
830
+ result = api_client.get_all_manual_service_configs()
831
+ # Convert the result to a dictionary
832
+ if hasattr(result, 'to_dict'):
833
+ result_dict = result.to_dict()
834
+ else:
835
+ # If it's already a dict or another format, use it as is
836
+ result_dict = result
837
+
838
+ debug_print(f"Result from get_all_manual_service_configs: {result_dict}")
839
+ return result_dict
840
+
841
+ except Exception as e:
842
+ debug_print(f"Error in get_all_manual_service_configs: {e}")
843
+ traceback.print_exc(file=sys.stderr)
844
+ return [{"error": f"Failed to get manual service configs: {e!s}"}]
845
+
846
+ @register_as_tool
847
+ @with_header_auth(ApplicationSettingsApi)
848
+ async def add_manual_service_config(
849
+ self,
850
+ payload: Union[Dict[str, Any], str],
851
+ ctx=None,
852
+ api_client=None
853
+ ) -> Dict[str, Any]:
854
+ """
855
+ Create a manual service mapping configuration.
856
+
857
+ Requires `CanConfigureServiceMapping` permission on the API token.
858
+
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
+ }
877
+ ctx: Optional execution context.
878
+
879
+ Returns:
880
+ Dict[str, Any]: API response indicating success or failure.
881
+ """
882
+ try:
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")
943
+ result = api_client.add_manual_service_config(
944
+ new_manual_service_config=config_object
945
+ )
946
+
947
+ # Convert the result to a dictionary
948
+ if hasattr(result, 'to_dict'):
949
+ result_dict = result.to_dict()
950
+ else:
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
+ }
956
+
957
+ logger.debug(f"Result from add_manual_service_config: {result_dict}")
958
+ return result_dict
959
+ except Exception as e:
960
+ logger.error(f"Error in add_manual_service_config: {e}")
961
+ return {"error": f"Failed to create new manual service config: {e!s}"}
962
+
963
+ @register_as_tool
964
+ @with_header_auth(ApplicationSettingsApi)
965
+ async def delete_manual_service_config(
966
+ self,
967
+ id: str,
968
+ ctx=None,
969
+ api_client=None
970
+ ) -> Dict[str, Any]:
971
+ """
972
+ Delete a manual service configuration.
973
+
974
+ Args:
975
+ id: A unique id of the manual service configuration.
976
+ ctx: The MCP context (optional)
977
+
978
+ Returns:
979
+ Dict[str, Any]: Response from the delete manual service configuration API.
980
+ """
981
+ try:
982
+ debug_print("Delete manual service configs")
983
+ if not id:
984
+ return {"error": "Required enitities are missing or invalid"}
985
+
986
+ api_client.delete_manual_service_config(id=id)
987
+
988
+ result_dict = {
989
+ "success": True,
990
+ "message": f"Manual Service Confiuguration '{id}' has been successfully deleted"
991
+ }
992
+
993
+ debug_print(f"Successfully deleted manual service config perspective with ID: {id}")
994
+ return result_dict
995
+
996
+ except Exception as e:
997
+ debug_print(f"Error in delete_manual_service_config: {e}")
998
+ traceback.print_exc(file=sys.stderr)
999
+ return {"error": f"Failed to delete manual service configs: {e!s}"}
1000
+
1001
+ @register_as_tool
1002
+ @with_header_auth(ApplicationSettingsApi)
1003
+ async def update_manual_service_config(
1004
+ self,
1005
+ id: str,
1006
+ payload: Union[Dict[str, Any], str],
1007
+ ctx=None,
1008
+ api_client=None
1009
+ ) -> Dict[str, Any]:
1010
+ """
1011
+ The manual service configuration APIs enables mapping calls to services using tag filter expressions based on call tags.
1012
+
1013
+ There are two use cases on the usage of these APIs:
1014
+
1015
+ 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.
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.
1017
+
1018
+ Args:
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
+ }
1038
+
1039
+ Returns:
1040
+ Dict[str, Any]: API response indicating success or failure.
1041
+ """
1042
+ try:
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")
1103
+ result = api_client.update_manual_service_config(
1104
+ id=id,
1105
+ manual_service_config=config_object
1106
+ )
1107
+
1108
+ # Convert the result to a dictionary
1109
+ if hasattr(result, 'to_dict'):
1110
+ result_dict = result.to_dict()
1111
+ else:
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
+ }
1117
+
1118
+ logger.debug(f"Result from update_manual_service_config: {result_dict}")
1119
+ return result_dict
1120
+ except Exception as e:
1121
+ logger.error(f"Error in update_manual_service_config: {e}")
1122
+ return {"error": f"Failed to update manual config: {e!s}"}
1123
+
1124
+
1125
+ @register_as_tool
1126
+ @with_header_auth(ApplicationSettingsApi)
1127
+ async def replace_all_manual_service_config(
1128
+ self,
1129
+ payload: Union[Dict[str, Any], str],
1130
+ ctx=None,
1131
+ api_client=None
1132
+ ) -> List[Dict[str, Any]]:
1133
+ """
1134
+ This tool is used if one wants to update more than 1 manual service configurations.
1135
+
1136
+ There are two use cases on the usage of these APIs:
1137
+
1138
+ 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.
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.
1140
+
1141
+ Args:
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
+ ]
1161
+ ctx: Optional execution context.
1162
+
1163
+ Returns:
1164
+ Dict[str, Any]: API response indicating success or failure.
1165
+ """
1166
+ try:
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
1229
+ )
1230
+
1231
+ # Convert the result to a dictionary
1232
+ if hasattr(result, 'to_dict'):
1233
+ result_dict = result.to_dict()
1234
+ else:
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
+ }
1240
+
1241
+ logger.debug(f"Result from replace_all_manual_service_config: {result_dict}")
1242
+ return [result_dict]
1243
+ except Exception as e:
1244
+ logger.error(f"Error in replace_all_manual_service_config: {e}")
1245
+ return [{"error": f"Failed to replace all manual config: {e!s}"}]
1246
+
1247
+ @register_as_tool
1248
+ @with_header_auth(ApplicationSettingsApi)
1249
+ async def get_all_service_configs(self,
1250
+ ctx=None,
1251
+ api_client=None) -> List[Dict[str, Any]]:
1252
+ """
1253
+ This tool gives list of All Service Perspectives Configuration
1254
+ Get a list of all Service Perspectives with their configuration settings.
1255
+ Args:
1256
+ ctx: The MCP context (optional)
1257
+
1258
+ Returns:
1259
+ Dictionary containing endpoints data or error information
1260
+ """
1261
+ try:
1262
+ debug_print("Fetching all service configs")
1263
+ result = api_client.get_service_configs()
1264
+ # Convert the result to a dictionary
1265
+ if hasattr(result, 'to_dict'):
1266
+ result_dict = result.to_dict()
1267
+ else:
1268
+ # If it's already a dict or another format, use it as is
1269
+ result_dict = result
1270
+
1271
+ debug_print(f"Result from get_service_configs: {result_dict}")
1272
+ return result_dict
1273
+
1274
+ except Exception as e:
1275
+ debug_print(f"Error in get_all_service_configs: {e}")
1276
+ traceback.print_exc(file=sys.stderr)
1277
+ return [{"error": f"Failed to get application data metrics: {e}"}]
1278
+
1279
+ @register_as_tool
1280
+ @with_header_auth(ApplicationSettingsApi)
1281
+ async def add_service_config(self,
1282
+ payload: Union[Dict[str, Any], str],
1283
+ ctx=None,
1284
+ api_client=None) -> Dict[str, Any]:
1285
+ """
1286
+ This tool gives is used to add new Service Perspectives Configuration
1287
+ Get a list of all Service Perspectives with their configuration settings.
1288
+ Args:
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
+ }
1305
+ ctx: The MCP context (optional)
1306
+
1307
+ Returns:
1308
+ Dictionary containing endpoints data or error information
1309
+ """
1310
+ try:
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")
1371
+ result = api_client.add_service_config(
1372
+ service_config=config_object
1373
+ )
1374
+
1375
+ # Convert the result to a dictionary
1376
+ if hasattr(result, 'to_dict'):
1377
+ result_dict = result.to_dict()
1378
+ else:
1379
+ # If it's already a dict or another format, use it as is
1380
+ result_dict = result or {
1381
+ "success": True,
1382
+ "message": "Create service config"
1383
+ }
1384
+
1385
+ logger.debug(f"Result from add_service_config: {result_dict}")
1386
+ return result_dict
1387
+ except Exception as e:
1388
+ logger.error(f"Error in add_service_config: {e}")
1389
+ return {"error": f"Failed to add service config: {e!s}"}
1390
+
1391
+ @register_as_tool
1392
+ @with_header_auth(ApplicationSettingsApi)
1393
+ async def replace_all_service_configs(self,
1394
+ payload: Union[Dict[str, Any], str],
1395
+ ctx=None,
1396
+ api_client=None) -> List[Dict[str, Any]]:
1397
+ """
1398
+ Args:
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
+ ]
1414
+ ctx: The MCP context (optional)
1415
+
1416
+ Returns:
1417
+ Dictionary containing endpoints data or error information
1418
+ """
1419
+ try:
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")
1480
+ result = api_client.replace_all(
1481
+ service_config=config_object
1482
+ )
1483
+
1484
+ # Convert the result to a list of dictionaries
1485
+ if hasattr(result, 'to_dict'):
1486
+ result_dict = result.to_dict()
1487
+ else:
1488
+ # If it's already a dict or another format, use it as is
1489
+ result_dict = result or {
1490
+ "success": True,
1491
+ "message": "replace all service config"
1492
+ }
1493
+
1494
+ logger.debug(f"Result from replace_all: {result_dict}")
1495
+ return [result_dict]
1496
+ except Exception as e:
1497
+ logger.error(f"Error in replace_all: {e}")
1498
+ return [{"error": f"Failed to replace all service config: {e!s}"}]
1499
+
1500
+
1501
+ @register_as_tool
1502
+ @with_header_auth(ApplicationSettingsApi)
1503
+ async def order_service_config(self,
1504
+ request_body: List[str],
1505
+ ctx=None,
1506
+ api_client=None) -> Dict[str, Any]:
1507
+ """
1508
+ order Service Configurations (Custom Service Rules)
1509
+
1510
+ This tool changes the order of service configurations based on the provided list of IDs.
1511
+ All service configuration IDs must be included in the request.
1512
+
1513
+ Args:
1514
+ request_body: List of service configuration IDs in the desired order.
1515
+ ctx: The MCP context (optional)
1516
+
1517
+ Returns:
1518
+ A dictionary with the API response or error message.
1519
+ """
1520
+ try:
1521
+ debug_print("ordering service configurations")
1522
+
1523
+ if not request_body:
1524
+ return {"error": "The list of service configuration IDs cannot be empty."}
1525
+
1526
+ result = api_client.order_service_config(
1527
+ request_body=request_body
1528
+ )
1529
+
1530
+ # Convert result to dict if needed
1531
+ if hasattr(result, 'to_dict'):
1532
+ return result.to_dict()
1533
+ return result
1534
+
1535
+ except Exception as e:
1536
+ debug_print(f"Error in order_service_config: {e}")
1537
+ traceback.print_exc(file=sys.stderr)
1538
+ return {"error": f"Failed to order service configs: {e!s}"}
1539
+
1540
+ @register_as_tool
1541
+ @with_header_auth(ApplicationSettingsApi)
1542
+ async def delete_service_config(self,
1543
+ id: str,
1544
+ ctx=None,
1545
+ api_client=None) -> Dict[str, Any]:
1546
+ """
1547
+ Delete a Service Perspective configuration.
1548
+ This tool allows you to delete an existing Service Config by its ID.
1549
+
1550
+ Args:
1551
+ id: The ID of the application perspective to delete
1552
+ ctx: The MCP context (optional)
1553
+
1554
+ Returns:
1555
+ Dictionary containing the result of the deletion or error information
1556
+ """
1557
+ try:
1558
+ if not id:
1559
+ return {"error": "Service perspective ID is required for deletion"}
1560
+
1561
+
1562
+ debug_print(f"Deleting application perspective with ID: {id}")
1563
+ # Call the delete_service_config method from the SDK
1564
+ api_client.delete_service_config(id=id)
1565
+
1566
+ result_dict = {
1567
+ "success": True,
1568
+ "message": f"Service Confiuguration '{id}' has been successfully deleted"
1569
+ }
1570
+
1571
+ debug_print(f"Successfully deleted service perspective with ID: {id}")
1572
+ return result_dict
1573
+ except Exception as e:
1574
+ debug_print(f"Error in delete_service_config: {e}")
1575
+ traceback.print_exc(file=sys.stderr)
1576
+ return {"error": f"Failed to delete service configuration: {e!s}"}
1577
+
1578
+ @register_as_tool
1579
+ @with_header_auth(ApplicationSettingsApi)
1580
+ async def get_service_config(
1581
+ self,
1582
+ id: str,
1583
+ ctx=None,
1584
+ api_client=None
1585
+ ) -> Dict[str, Any]:
1586
+ """
1587
+ This MCP tool is used if one wants to retrieve the particular custom service configuration.
1588
+ Args:
1589
+ id: An Instana generated unique identifier for a Service.
1590
+ ctx: The MCP context (optional)
1591
+
1592
+ Returns:
1593
+ Dict[str, Any]: Response from the create/update endpoint configuration API.
1594
+
1595
+ """
1596
+ try:
1597
+ debug_print("get service config")
1598
+ if not id:
1599
+ return {"error": "Required entities are missing or invalid"}
1600
+
1601
+ result = api_client.get_service_config(
1602
+ id=id
1603
+ )
1604
+ # Convert the result to a dictionary
1605
+ if hasattr(result, 'to_dict'):
1606
+ result_dict = result.to_dict()
1607
+ else:
1608
+ # If it's already a dict or another format, use it as is
1609
+ result_dict = result
1610
+
1611
+ debug_print(f"Result from get_service_config: {result_dict}")
1612
+ return result_dict
1613
+ except Exception as e:
1614
+ debug_print(f"Error in get_service_config: {e}")
1615
+ traceback.print_exc(file=sys.stderr)
1616
+ return {"error": f"Failed to get service config: {e!s}"}
1617
+
1618
+ @register_as_tool
1619
+ @with_header_auth(ApplicationSettingsApi)
1620
+ async def update_service_config(self,
1621
+ id: str,
1622
+ payload: Union[Dict[str, Any], str],
1623
+ ctx=None,
1624
+ api_client=None) -> Dict[str, Any]:
1625
+ """
1626
+ This tool gives is used if one wants to update a particular custom service rule.
1627
+ Args:
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
+ }
1645
+ ctx: The MCP context (optional)
1646
+
1647
+ Returns:
1648
+ Dictionary containing endpoints data or error information
1649
+ """
1650
+ try:
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")
1711
+ result = api_client.put_service_config(
1712
+ id=id,
1713
+ service_config=config_object
1714
+ )
1715
+
1716
+ # Convert the result to a dictionary
1717
+ if hasattr(result, 'to_dict'):
1718
+ result_dict = result.to_dict()
1719
+ else:
1720
+ # If it's already a dict or another format, use it as is
1721
+ result_dict = result or {
1722
+ "success": True,
1723
+ "message": "put service config"
1724
+ }
1725
+
1726
+ logger.debug(f"Result from put_service_config: {result_dict}")
1727
+ return [result_dict]
1728
+ except Exception as e:
1729
+ logger.error(f"Error in put_service_config: {e}")
1730
+ return [{"error": f"Failed to update service config: {e!s}"}]
1731
+