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.
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +186 -311
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +30 -22
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +1 -1
- src/application/application_alert_config.py +393 -136
- src/application/application_analyze.py +597 -594
- src/application/application_call_group.py +528 -0
- src/application/application_catalog.py +0 -8
- src/application/application_global_alert_config.py +275 -57
- src/application/application_metrics.py +377 -237
- src/application/application_resources.py +414 -325
- src/application/application_settings.py +608 -1530
- src/application/application_topology.py +62 -62
- src/core/custom_dashboard_smart_router_tool.py +135 -0
- src/core/server.py +95 -119
- src/core/smart_router_tool.py +574 -0
- src/core/utils.py +17 -8
- src/custom_dashboard/custom_dashboard_tools.py +422 -0
- src/event/events_tools.py +57 -9
- src/infrastructure/elicitation_handler.py +338 -0
- src/infrastructure/entity_registry.py +329 -0
- src/infrastructure/infrastructure_analyze_new.py +600 -0
- src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
- src/infrastructure/infrastructure_catalog.py +37 -32
- src/infrastructure/infrastructure_metrics.py +93 -16
- src/infrastructure/infrastructure_resources.py +6 -24
- src/infrastructure/infrastructure_topology.py +29 -23
- src/observability.py +29 -0
- src/prompts/application/application_settings.py +58 -0
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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},
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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),
|
|
File without changes
|
|
File without changes
|