mcp-instana 0.3.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +186 -311
  2. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +30 -22
  3. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +1 -1
  4. src/application/application_alert_config.py +393 -136
  5. src/application/application_analyze.py +597 -594
  6. src/application/application_call_group.py +528 -0
  7. src/application/application_catalog.py +0 -8
  8. src/application/application_global_alert_config.py +275 -57
  9. src/application/application_metrics.py +377 -237
  10. src/application/application_resources.py +414 -325
  11. src/application/application_settings.py +608 -1530
  12. src/application/application_topology.py +62 -62
  13. src/core/custom_dashboard_smart_router_tool.py +135 -0
  14. src/core/server.py +95 -119
  15. src/core/smart_router_tool.py +574 -0
  16. src/core/utils.py +17 -8
  17. src/custom_dashboard/custom_dashboard_tools.py +422 -0
  18. src/event/events_tools.py +57 -9
  19. src/infrastructure/elicitation_handler.py +338 -0
  20. src/infrastructure/entity_registry.py +329 -0
  21. src/infrastructure/infrastructure_analyze_new.py +600 -0
  22. src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
  23. src/infrastructure/infrastructure_catalog.py +37 -32
  24. src/infrastructure/infrastructure_metrics.py +93 -16
  25. src/infrastructure/infrastructure_resources.py +6 -24
  26. src/infrastructure/infrastructure_topology.py +29 -23
  27. src/observability.py +29 -0
  28. src/prompts/application/application_settings.py +58 -0
  29. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
  30. {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -34,10 +34,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
34
34
  """Initialize the Infrastructure Catalog MCP tools client."""
35
35
  super().__init__(read_token=read_token, base_url=base_url)
36
36
 
37
- @register_as_tool(
38
- title="Get Available Payload Keys By Plugin ID",
39
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
40
- )
37
+ # @register_as_tool(...) # Disabled for future reference
41
38
  @with_header_auth(InfrastructureCatalogApi)
42
39
  async def get_available_payload_keys_by_plugin_id(self,
43
40
  plugin_id: str,
@@ -88,6 +85,11 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
88
85
  result_dict = {"data": str(result), "plugin_id": plugin_id}
89
86
 
90
87
  logger.debug(f"Result from get_available_payload_keys_by_plugin_id: {result_dict}")
88
+
89
+ # Safety check: ensure we never return a raw list
90
+ if isinstance(result_dict, list):
91
+ result_dict = {"payload_keys": result_dict, "plugin_id": plugin_id}
92
+
91
93
  return result_dict
92
94
 
93
95
  except Exception as sdk_error:
@@ -111,7 +113,16 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
111
113
  # Try to parse as JSON first
112
114
  import json
113
115
  try:
114
- result_dict = json.loads(response_text)
116
+ parsed_result = json.loads(response_text)
117
+
118
+ # Ensure we always return a dictionary, not a raw list
119
+ if isinstance(parsed_result, list):
120
+ result_dict = {"payload_keys": parsed_result, "plugin_id": plugin_id}
121
+ elif isinstance(parsed_result, dict):
122
+ result_dict = parsed_result
123
+ else:
124
+ result_dict = {"data": parsed_result, "plugin_id": plugin_id}
125
+
115
126
  logger.debug(f"Result from fallback method (JSON): {result_dict}")
116
127
  return result_dict
117
128
  except json.JSONDecodeError:
@@ -128,10 +139,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
128
139
  return {"error": f"Failed to get payload keys: {e!s}", "plugin_id": plugin_id}
129
140
 
130
141
 
131
- @register_as_tool(
132
- title="Get Infrastructure Catalog Metrics",
133
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
134
- )
142
+ # @register_as_tool(...) # Disabled for future reference
135
143
  @with_header_auth(InfrastructureCatalogApi)
136
144
  async def get_infrastructure_catalog_metrics(self,
137
145
  plugin: str,
@@ -234,10 +242,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
234
242
  return [f"Error: Failed to get metric catalog for plugin '{plugin}': {e!s}"]
235
243
 
236
244
 
237
- @register_as_tool(
238
- title="Get Infrastructure Catalog Plugins",
239
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
240
- )
245
+ # @register_as_tool(...) # Disabled for future reference
241
246
  @with_header_auth(InfrastructureCatalogApi)
242
247
  async def get_infrastructure_catalog_plugins(self, ctx=None, api_client=None) -> Dict[str, Any]:
243
248
  """
@@ -307,10 +312,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
307
312
 
308
313
 
309
314
 
310
- @register_as_tool(
311
- title="Get Infrastructure Catalog Plugins With Custom Metrics",
312
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
313
- )
315
+ # @register_as_tool(...) # Disabled for future reference
314
316
  @with_header_auth(InfrastructureCatalogApi)
315
317
  async def get_infrastructure_catalog_plugins_with_custom_metrics(self, ctx=None, api_client=None) -> Dict[str, Any] | List[Dict[str, Any]]:
316
318
  """
@@ -350,10 +352,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
350
352
  return {"error": f"Failed to get plugins with custom metrics: {e!s}"}
351
353
 
352
354
 
353
- @register_as_tool(
354
- title="Get Tag Catalog",
355
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
356
- )
355
+ # @register_as_tool(...) # Disabled for future reference
357
356
  @with_header_auth(InfrastructureCatalogApi)
358
357
  async def get_tag_catalog(self, plugin: str, ctx=None, api_client=None) -> Dict[str, Any]:
359
358
  """
@@ -390,14 +389,26 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
390
389
  return result_dict
391
390
 
392
391
  except Exception as sdk_error:
393
- logger.error(f"SDK method failed: {sdk_error}, trying with custom headers")
392
+ logger.error(f"SDK method failed: {sdk_error}, evaluating fallback conditions")
394
393
 
395
394
  # Check if it's a 406 error
396
395
  is_406_error = False
397
396
  if hasattr(sdk_error, 'status') and sdk_error.status == 406 or "406" in str(sdk_error) and "Not Acceptable" in str(sdk_error):
398
397
  is_406_error = True
399
398
 
400
- if is_406_error:
399
+ # Check for Pydantic ValidationError (SDK model deserialization issues)
400
+ is_pydantic_error = False
401
+ try:
402
+ from pydantic import (
403
+ ValidationError as _PydanticValidationError, # type: ignore
404
+ )
405
+ is_pydantic_error = isinstance(sdk_error, _PydanticValidationError)
406
+ except Exception:
407
+ # Fallback to string inspection if pydantic not importable in runtime
408
+ err_str = str(sdk_error).lower()
409
+ is_pydantic_error = ("pydantic" in err_str and "validation" in err_str) or ("validation error" in err_str)
410
+
411
+ if is_406_error or is_pydantic_error:
401
412
  # Try using the SDK's method with custom headers
402
413
  # The SDK should have a method that allows setting custom headers
403
414
  custom_headers = {
@@ -430,7 +441,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
430
441
  logger.error(error_message)
431
442
  return {"error": error_message}
432
443
  else:
433
- # Re-raise if it's not a 406 error
444
+ # Re-raise if it's not a 406 or Pydantic validation error
434
445
  raise
435
446
 
436
447
  except Exception as e:
@@ -438,10 +449,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
438
449
  return {"error": f"Failed to get tag catalog: {e!s}"}
439
450
 
440
451
 
441
- @register_as_tool(
442
- title="Get Tag Catalog All",
443
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
444
- )
452
+ # @register_as_tool(...) # Disabled for future reference
445
453
  @with_header_auth(InfrastructureCatalogApi)
446
454
  async def get_tag_catalog_all(self, ctx=None, api_client=None) -> Dict[str, Any]:
447
455
  """
@@ -560,10 +568,7 @@ class InfrastructureCatalogMCPTools(BaseInstanaClient):
560
568
  return summary
561
569
 
562
570
 
563
- @register_as_tool(
564
- title="Get Infrastructure Catalog Search Fields",
565
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
566
- )
571
+ # @register_as_tool(...) # Disabled for future reference
567
572
  @with_header_auth(InfrastructureCatalogApi)
568
573
  async def get_infrastructure_catalog_search_fields(self, ctx=None, api_client=None) -> List[str] | Dict[str, Any]:
569
574
  """
@@ -41,10 +41,8 @@ class InfrastructureMetricsMCPTools(BaseInstanaClient):
41
41
  """Initialize the Infrastructure Analyze MCP tools client."""
42
42
  super().__init__(read_token=read_token, base_url=base_url)
43
43
 
44
- @register_as_tool(
45
- title="Get Infrastructure Metrics",
46
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
47
- )
44
+ # @register_as_tool(...) # Disabled for future reference
45
+ # Note: Not exposed as direct MCP tool - accessed via smart_router_tool.py
48
46
  @with_header_auth(InfrastructureMetricsApi)
49
47
  async def get_infrastructure_metrics(self,
50
48
  offline: Optional[StrictBool] = False,
@@ -78,17 +76,13 @@ class InfrastructureMetricsMCPTools(BaseInstanaClient):
78
76
 
79
77
  try:
80
78
 
81
- # If no metrics is provided, return an error
82
- if not metrics:
83
- return {"error": "Metrics is required for this operation"}
84
-
85
-
86
- # If no plugin is provided, return an error
87
- if not plugin:
88
- return {"error": "Plugin is required for this operation"}
89
-
90
- if not query:
91
- return {"error": "Query is required for this operation"}
79
+ # Two-Pass Elicitation: Check for required parameters
80
+ elicitation_request = self._check_elicitation_for_infra_metrics(
81
+ metrics, plugin, query
82
+ )
83
+ if elicitation_request:
84
+ logger.info("Elicitation needed for infrastructure metrics")
85
+ return elicitation_request
92
86
 
93
87
 
94
88
  if not time_frame:
@@ -126,6 +120,7 @@ class InfrastructureMetricsMCPTools(BaseInstanaClient):
126
120
  # Create the InfrastructureMetricsApi object
127
121
  get_combined_metrics = GetCombinedMetrics(**request_body)
128
122
 
123
+
129
124
  # Call the get_infrastructure_metrics method from the SDK
130
125
  result = api_client.get_infrastructure_metrics(
131
126
  offline=offline,
@@ -172,4 +167,86 @@ class InfrastructureMetricsMCPTools(BaseInstanaClient):
172
167
 
173
168
  except Exception as e:
174
169
  logger.error(f"Error in get_infrastructure_metrics: {e}", exc_info=True)
175
- return {"error": f"Failed to get Infra metrics: {e!s}"}
170
+ return {"error": f"Failed to get infrastructure metrics: {e!s}"}
171
+
172
+ def _check_elicitation_for_infra_metrics(
173
+ self,
174
+ metrics: Optional[List[str]],
175
+ plugin: Optional[str],
176
+ query: Optional[str]
177
+ ) -> Optional[Dict[str, Any]]:
178
+ """
179
+ Check if required parameters are missing and create elicitation request (Two-Pass).
180
+
181
+ Infrastructure metrics require plugin and query parameters.
182
+
183
+ Args:
184
+ metrics: Metrics list if provided
185
+ plugin: Plugin name if provided
186
+ query: Query filter if provided
187
+
188
+ Returns:
189
+ Elicitation request dict if parameters are missing, None otherwise
190
+ """
191
+ missing_params = []
192
+
193
+ # Check for required parameters
194
+ if not metrics:
195
+ missing_params.append({
196
+ "name": "metrics",
197
+ "description": "List of metric names to retrieve (REQUIRED)",
198
+ "examples": ["cpu.used", "memory.used", "disk.used"],
199
+ "type": "list"
200
+ })
201
+
202
+ if not plugin:
203
+ missing_params.append({
204
+ "name": "plugin",
205
+ "description": "Plugin type for infrastructure entity (REQUIRED)",
206
+ "examples": ["host", "docker", "kubernetes", "jvm"],
207
+ "type": "string"
208
+ })
209
+
210
+ if not query:
211
+ missing_params.append({
212
+ "name": "query_filter",
213
+ "description": "Query filter to select entities (REQUIRED)",
214
+ "examples": ["entity.type:host", "entity.type:docker", "entity.tag:production"],
215
+ "type": "string"
216
+ })
217
+
218
+ # If any required parameters are missing, return elicitation request
219
+ if missing_params:
220
+ return self._create_elicitation_request(missing_params)
221
+
222
+ return None
223
+
224
+ def _create_elicitation_request(self, missing_params: list) -> Dict[str, Any]:
225
+ """
226
+ Create an elicitation request following MCP pattern.
227
+
228
+ Args:
229
+ missing_params: List of missing parameter descriptions
230
+
231
+ Returns:
232
+ Elicitation request dict
233
+ """
234
+ # Build simple, user-friendly parameter descriptions
235
+ param_lines = []
236
+ for param in missing_params:
237
+ # Use the examples from the parameter definition instead of hardcoding
238
+ examples = ", ".join([str(ex) for ex in param["examples"][:3]])
239
+ param_lines.append(f"{param['name']}: {examples}")
240
+
241
+ message = (
242
+ "I need:\n\n"
243
+ + "\n".join(param_lines)
244
+ )
245
+
246
+ return {
247
+ "elicitation_needed": True,
248
+ "message": message,
249
+ "missing_parameters": [p["name"] for p in missing_params],
250
+ "parameter_details": missing_params,
251
+ "instructions": "Call query_instana_metrics again with these parameters filled in."
252
+ }
@@ -39,10 +39,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
39
39
  """Initialize the Infrastructure Resources MCP tools client."""
40
40
  super().__init__(read_token=read_token, base_url=base_url)
41
41
 
42
- @register_as_tool(
43
- title="Get Monitoring State",
44
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
45
- )
42
+ # @register_as_tool(...) # Disabled for future reference
46
43
  @with_header_auth(InfrastructureResourcesApi)
47
44
  async def get_monitoring_state(self, ctx=None, api_client=None) -> Dict[str, Any]:
48
45
  """
@@ -68,10 +65,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
68
65
  logger.error(f"Error in get_monitoring_state: {e}", exc_info=True)
69
66
  return {"error": f"Failed to get monitoring state: {e!s}"}
70
67
 
71
- @register_as_tool(
72
- title="Get Plugin Payload",
73
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
74
- )
68
+ # This tool is disabled since the underlying API is not giving a proper response.
75
69
  @with_header_auth(InfrastructureResourcesApi)
76
70
  async def get_plugin_payload(self,
77
71
  snapshot_id: str,
@@ -112,10 +106,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
112
106
  logger.error(f"Error in get_plugin_payload: {e}", exc_info=True)
113
107
  return {"error": f"Failed to get plugin payload: {e!s}"}
114
108
 
115
- @register_as_tool(
116
- title="Get Snapshot",
117
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
118
- )
109
+ # @register_as_tool(...) # Disabled for future reference
119
110
  @with_header_auth(InfrastructureResourcesApi)
120
111
  async def get_snapshot(self,
121
112
  snapshot_id: str,
@@ -223,10 +214,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
223
214
  logger.error(f"Error in get_snapshot: {e}", exc_info=True)
224
215
  return {"error": f"Failed to get snapshot: {e!s}"}
225
216
 
226
- @register_as_tool(
227
- title="Get Snapshots",
228
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
229
- )
217
+ # @register_as_tool(...) # Disabled for future reference
230
218
  @with_header_auth(InfrastructureResourcesApi)
231
219
  async def get_snapshots(self,
232
220
  query: Optional[str] = None,
@@ -381,10 +369,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
381
369
 
382
370
 
383
371
 
384
- @register_as_tool(
385
- title="Post Snapshots",
386
- annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
387
- )
372
+ # @register_as_tool(...) # Disabled for future reference
388
373
  @with_header_auth(InfrastructureResourcesApi)
389
374
  async def post_snapshots(self,
390
375
  snapshot_ids: Union[List[str], str],
@@ -562,10 +547,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
562
547
 
563
548
 
564
549
 
565
- @register_as_tool(
566
- title="Software Versions",
567
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
568
- )
550
+ # @register_as_tool(...) # Disabled for future reference
569
551
  @with_header_auth(InfrastructureResourcesApi)
570
552
  async def software_versions(self, ctx=None, api_client=None) -> Dict[str, Any]:
571
553
  """
@@ -51,10 +51,7 @@ class InfrastructureTopologyMCPTools(BaseInstanaClient):
51
51
  """Initialize the Infrastructure Topology MCP tools client."""
52
52
  super().__init__(read_token=read_token, base_url=base_url)
53
53
 
54
- @register_as_tool(
55
- title="Get Related Hosts",
56
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
57
- )
54
+ # @register_as_tool(...) # Disabled for future reference
58
55
  @with_header_auth(InfrastructureTopologyApi)
59
56
  async def get_related_hosts(self,
60
57
  snapshot_id: str,
@@ -115,10 +112,7 @@ class InfrastructureTopologyMCPTools(BaseInstanaClient):
115
112
  logger.error(f"Error in get_related_hosts: {e}", exc_info=True)
116
113
  return {"error": f"Failed to get related hosts: {e!s}"}
117
114
 
118
- @register_as_tool(
119
- title="Get Topology",
120
- annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
121
- )
115
+ # @register_as_tool(...) # Disabled for future reference
122
116
  @with_header_auth(InfrastructureTopologyApi)
123
117
  async def get_topology(self,
124
118
  include_data: Optional[bool] = False,
@@ -133,6 +127,9 @@ class InfrastructureTopologyMCPTools(BaseInstanaClient):
133
127
  The topology includes nodes (representing entities like hosts, processes, containers) and edges (representing
134
128
  connections between entities). This is useful for understanding the overall structure of your environment.
135
129
 
130
+ This implementation uses the `get_topology_without_preload_content` method from the SDK to bypass validation
131
+ issues that can occur with complex Kubernetes infrastructure data.
132
+
136
133
  For example, use this tool when:
137
134
  - You need a complete map of your infrastructure
138
135
  - You want to understand how components are connected
@@ -152,22 +149,31 @@ class InfrastructureTopologyMCPTools(BaseInstanaClient):
152
149
 
153
150
  # Use the API client from the decorator
154
151
  try:
155
- result = api_client.get_topology(include_data=include_data)
156
- logger.debug("SDK call successful, processing result")
157
- except Exception as sdk_error:
158
- logger.error(f"SDK validation error: {sdk_error}")
152
+ # Use get_topology_without_preload_content to bypass validation
153
+ response = api_client.get_topology_without_preload_content(include_data=include_data)
154
+ logger.debug("SDK call successful using get_topology_without_preload_content")
159
155
 
160
- # If it's a validation error, try to extract useful information from the error
161
- if "validation error" in str(sdk_error).lower():
162
- return {
163
- "error": "SDK validation error occurred",
164
- "details": str(sdk_error),
165
- "suggestion": "The API response format may not match the expected SDK model structure. This often happens with complex Kubernetes or cloud infrastructure data.",
166
- "workaround": "Consider using other topology tools like get_related_hosts with specific snapshot IDs, or check if the include_data parameter affects the response format."
167
- }
168
- else:
169
- # Re-raise if it's not a validation error
170
- raise sdk_error
156
+ # Parse the JSON response manually following the pattern from application_topology.py
157
+ import json
158
+ try:
159
+ # The result from get_topology_without_preload_content is a response object
160
+ # We need to read the response data and parse it as JSON
161
+ response_text = response.data.decode('utf-8')
162
+ result = json.loads(response_text)
163
+ logger.debug("Successfully parsed topology data as JSON")
164
+ except (json.JSONDecodeError, AttributeError) as json_err:
165
+ error_message = f"Failed to parse JSON response: {json_err}"
166
+ logger.error(error_message)
167
+ return {"error": error_message}
168
+
169
+ except Exception as sdk_error:
170
+ logger.error(f"SDK error: {sdk_error}")
171
+ return {
172
+ "error": "Failed to get topology data",
173
+ "details": str(sdk_error),
174
+ "suggestion": "The API may be unavailable or the request format is incorrect.",
175
+ "workaround": "Try again later or check if the include_data parameter affects the response."
176
+ }
171
177
 
172
178
  # Convert the result to a dictionary
173
179
  result_dict = None
src/observability.py ADDED
@@ -0,0 +1,29 @@
1
+ import os
2
+ import sys
3
+
4
+
5
+ def workflow(name=None):
6
+ def decorator(func):
7
+ return func
8
+ return decorator
9
+
10
+ def task(name=None):
11
+ def decorator(func):
12
+ return func
13
+ return decorator
14
+
15
+ TRACELOOP_ENABLED = os.getenv("ENABLE_MCP_OBSERVABILITY", "false").lower() in ("true", "1", "yes", "on")
16
+
17
+ if TRACELOOP_ENABLED:
18
+ try:
19
+ from traceloop.sdk import Traceloop
20
+ from traceloop.sdk.decorators import task as traceloop_task
21
+ from traceloop.sdk.decorators import workflow as traceloop_workflow
22
+ Traceloop.init(app_name="Instana-MCP-Server")
23
+ print("Traceloop enabled and initialized for MCP Client", file=sys.stderr)
24
+ # Override the no-op decorators with real ones
25
+ workflow = traceloop_workflow
26
+ task = traceloop_task
27
+ except ImportError:
28
+ print("Traceloop requested but not installed. Install with: pip install traceloop-sdk", file=sys.stderr)
29
+ TRACELOOP_ENABLED = False
@@ -18,6 +18,63 @@ class ApplicationSettingsPrompts:
18
18
  """Get an Application Perspective configuration by ID"""
19
19
  return f"Retrieve application configuration with ID: {id}"
20
20
 
21
+ @auto_register_prompt
22
+ @staticmethod
23
+ def create_application_config(
24
+ label: str,
25
+ scope: Optional[str] = None,
26
+ boundary_scope: Optional[str] = None,
27
+ access_rules: Optional[str] = None,
28
+ tag_filter_expression: Optional[dict] = None
29
+ ) -> str:
30
+ """
31
+ Create a new Application Perspective configuration with user-provided settings.
32
+
33
+ REQUIRED:
34
+ - label: Application perspective name (string)
35
+
36
+ OPTIONAL (will prompt user if not provided):
37
+ - scope: Monitoring scope
38
+ Options: "INCLUDE_ALL_DOWNSTREAM" (default), "INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING", "INCLUDE_NO_DOWNSTREAM"
39
+ - boundary_scope: Boundary scope
40
+ Options: "ALL" (default), "INBOUND", "DEFAULT"
41
+ - access_rules: Access control rules
42
+ Options: "READ_WRITE_GLOBAL" (default), "READ_ONLY_GLOBAL", "CUSTOM"
43
+ - tag_filter_expression: Tag filter to match services (optional)
44
+
45
+ ELICITATION QUESTIONS:
46
+ 1. What scope should be used for monitoring? (INCLUDE_ALL_DOWNSTREAM/INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING/INCLUDE_NO_DOWNSTREAM)
47
+ 2. What boundary scope should be applied? (ALL/INBOUND/DEFAULT)
48
+ 3. What access rules should be configured? (READ_WRITE_GLOBAL/READ_ONLY_GLOBAL/CUSTOM)
49
+ 4. Do you want to add a tag filter expression to match specific services? (yes/no)
50
+
51
+ Example with all options:
52
+ {
53
+ "label": "My Application",
54
+ "scope": "INCLUDE_ALL_DOWNSTREAM",
55
+ "boundaryScope": "ALL",
56
+ "accessRules": [{"accessType": "READ_WRITE", "relationType": "GLOBAL"}],
57
+ "tagFilterExpression": {
58
+ "type": "TAG_FILTER",
59
+ "name": "service.name",
60
+ "operator": "CONTAINS",
61
+ "entity": "DESTINATION",
62
+ "value": "my-service"
63
+ }
64
+ }
65
+ """
66
+ config_details = [f"label: {label}"]
67
+ if scope:
68
+ config_details.append(f"scope: {scope}")
69
+ if boundary_scope:
70
+ config_details.append(f"boundaryScope: {boundary_scope}")
71
+ if access_rules:
72
+ config_details.append(f"accessRules: {access_rules}")
73
+ if tag_filter_expression:
74
+ config_details.append(f"tagFilterExpression: {tag_filter_expression}")
75
+
76
+ return f"Create application perspective configuration with {', '.join(config_details)}"
77
+
21
78
  @auto_register_prompt
22
79
  @staticmethod
23
80
  def get_all_endpoint_configs() -> str:
@@ -67,6 +124,7 @@ class ApplicationSettingsPrompts:
67
124
  return [
68
125
  ('get_all_applications_configs', cls.get_all_applications_configs),
69
126
  ('get_application_config', cls.get_application_config),
127
+ ('create_application_config', cls.create_application_config),
70
128
  ('get_all_endpoint_configs', cls.get_all_endpoint_configs),
71
129
  ('get_endpoint_config', cls.get_endpoint_config),
72
130
  ('get_all_manual_service_configs', cls.get_all_manual_service_configs),