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
|
@@ -55,65 +55,65 @@ class ApplicationTopologyMCPTools(BaseInstanaClient):
|
|
|
55
55
|
logger.error(f"Error initializing ApplicationTopologyMCPTools: {e}", exc_info=True)
|
|
56
56
|
raise
|
|
57
57
|
|
|
58
|
-
@register_as_tool(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
async def get_application_topology(self,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
58
|
+
# @register_as_tool(
|
|
59
|
+
# title="Get Application Topology",
|
|
60
|
+
# annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
61
|
+
# )
|
|
62
|
+
# async def get_application_topology(self,
|
|
63
|
+
# window_size: Optional[int] = None,
|
|
64
|
+
# to_timestamp: Optional[int] = None,
|
|
65
|
+
# application_id: Optional[str] = None,
|
|
66
|
+
# application_boundary_scope: Optional[str] = None,
|
|
67
|
+
# ctx = None) -> Dict[str, Any]:
|
|
68
|
+
# """
|
|
69
|
+
# Get the service topology from Instana Server.
|
|
70
|
+
# This tool retrieves services and connections (call paths) between them for calls in the scope given by the parameters.
|
|
71
|
+
|
|
72
|
+
# Args:
|
|
73
|
+
# window_size: Size of time window in milliseconds
|
|
74
|
+
# to_timestamp: Timestamp since Unix Epoch in milliseconds of the end of the time window
|
|
75
|
+
# application_id: Filter by application ID
|
|
76
|
+
# application_boundary_scope: Filter by application scope, i.e., INBOUND or ALL. The default value is INBOUND.
|
|
77
|
+
# ctx: Context information
|
|
78
|
+
|
|
79
|
+
# Returns:
|
|
80
|
+
# A dictionary containing the service topology data
|
|
81
|
+
# """
|
|
82
|
+
|
|
83
|
+
# try:
|
|
84
|
+
# logger.debug("Fetching service topology data")
|
|
85
|
+
|
|
86
|
+
# # Set default values if not provided
|
|
87
|
+
# if not to_timestamp:
|
|
88
|
+
# to_timestamp = int(datetime.now().timestamp() * 1000)
|
|
89
|
+
|
|
90
|
+
# if not window_size:
|
|
91
|
+
# window_size = 3600000 # Default to 1 hour in milliseconds
|
|
92
|
+
|
|
93
|
+
# # Call the API with raw JSON response to avoid Pydantic validation issues
|
|
94
|
+
# # Note: The SDK expects parameters in camelCase, but we use snake_case in Python
|
|
95
|
+
# # The SDK will handle the conversion
|
|
96
|
+
# result = self.topology_api.get_services_map_without_preload_content(
|
|
97
|
+
# window_size=window_size,
|
|
98
|
+
# to=to_timestamp,
|
|
99
|
+
# application_id=application_id,
|
|
100
|
+
# application_boundary_scope=application_boundary_scope
|
|
101
|
+
# )
|
|
102
|
+
|
|
103
|
+
# # Parse the JSON response manually
|
|
104
|
+
# import json
|
|
105
|
+
# try:
|
|
106
|
+
# # The result from get_services_map_without_preload_content is a response object
|
|
107
|
+
# # We need to read the response data and parse it as JSON
|
|
108
|
+
# response_text = result.data.decode('utf-8')
|
|
109
|
+
# result_dict = json.loads(response_text)
|
|
110
|
+
# logger.debug("Successfully retrieved service topology data")
|
|
111
|
+
# return result_dict
|
|
112
|
+
# except (json.JSONDecodeError, AttributeError) as json_err:
|
|
113
|
+
# error_message = f"Failed to parse JSON response: {json_err}"
|
|
114
|
+
# logger.error(error_message)
|
|
115
|
+
# return {"error": error_message}
|
|
116
|
+
|
|
117
|
+
# except Exception as e:
|
|
118
|
+
# logger.error(f"Error in get_application_topology: {e}", exc_info=True)
|
|
119
|
+
# return {"error": f"Failed to get application topology: {e!s}"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Dashboard Smart Router Tool
|
|
3
|
+
|
|
4
|
+
This module provides a unified MCP tool that routes queries to the appropriate
|
|
5
|
+
custom dashboard-specific tools for Instana monitoring.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from mcp.types import ToolAnnotations
|
|
12
|
+
|
|
13
|
+
from src.core.utils import BaseInstanaClient, register_as_tool
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CustomDashboardSmartRouterMCPTool(BaseInstanaClient):
|
|
19
|
+
"""
|
|
20
|
+
Smart router that routes queries to Custom Dashboard tools.
|
|
21
|
+
The LLM agent determines the appropriate operation based on query understanding.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, read_token: str, base_url: str):
|
|
25
|
+
"""Initialize the Custom Dashboard Smart Router MCP tool."""
|
|
26
|
+
super().__init__(read_token=read_token, base_url=base_url)
|
|
27
|
+
|
|
28
|
+
# Initialize the custom dashboard tool client
|
|
29
|
+
from src.custom_dashboard.custom_dashboard_tools import CustomDashboardMCPTools
|
|
30
|
+
|
|
31
|
+
self.dashboard_client = CustomDashboardMCPTools(read_token, base_url)
|
|
32
|
+
|
|
33
|
+
logger.info("Custom Dashboard Smart Router initialized")
|
|
34
|
+
|
|
35
|
+
@register_as_tool(
|
|
36
|
+
title="Manage Instana Custom Dashboards",
|
|
37
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
38
|
+
)
|
|
39
|
+
async def manage_custom_dashboards(
|
|
40
|
+
self,
|
|
41
|
+
operation: str,
|
|
42
|
+
params: Optional[Dict[str, Any]] = None,
|
|
43
|
+
ctx=None
|
|
44
|
+
) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Unified Instana custom dashboard manager for CRUD operations.
|
|
47
|
+
|
|
48
|
+
Operations:
|
|
49
|
+
- "get_all": Get all custom dashboards
|
|
50
|
+
- "get": Get a specific dashboard by ID
|
|
51
|
+
- "create": Create a new custom dashboard
|
|
52
|
+
- "update": Update an existing custom dashboard
|
|
53
|
+
- "delete": Delete a custom dashboard
|
|
54
|
+
- "get_shareable_users": Get shareable users for a dashboard
|
|
55
|
+
- "get_shareable_api_tokens": Get shareable API tokens for a dashboard
|
|
56
|
+
|
|
57
|
+
Parameters (params dict):
|
|
58
|
+
- dashboard_id: Dashboard ID (required for get, update, delete, get_shareable_users, get_shareable_api_tokens)
|
|
59
|
+
- custom_dashboard: Dashboard configuration payload (required for create, update)
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
operation: Operation to perform
|
|
63
|
+
params: Operation-specific parameters (optional)
|
|
64
|
+
ctx: MCP context (internal)
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dictionary with results from the appropriate tool
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
# Get all dashboards
|
|
71
|
+
operation="get_all"
|
|
72
|
+
|
|
73
|
+
# Get specific dashboard
|
|
74
|
+
operation="get", params={"dashboard_id": "abc123"}
|
|
75
|
+
|
|
76
|
+
# Create dashboard
|
|
77
|
+
operation="create", params={"custom_dashboard": {"title": "My Dashboard", "widgets": []}}
|
|
78
|
+
|
|
79
|
+
# Update dashboard
|
|
80
|
+
operation="update", params={"dashboard_id": "abc123", "custom_dashboard": {"title": "Updated Dashboard"}}
|
|
81
|
+
|
|
82
|
+
# Delete dashboard
|
|
83
|
+
operation="delete", params={"dashboard_id": "abc123"}
|
|
84
|
+
|
|
85
|
+
# Get shareable users
|
|
86
|
+
operation="get_shareable_users", params={"dashboard_id": "abc123"}
|
|
87
|
+
|
|
88
|
+
# Get shareable API tokens
|
|
89
|
+
operation="get_shareable_api_tokens", params={"dashboard_id": "abc123"}
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
logger.info(f"Custom Dashboard Smart Router received: operation={operation}")
|
|
93
|
+
|
|
94
|
+
# Initialize params if not provided
|
|
95
|
+
if params is None:
|
|
96
|
+
params = {}
|
|
97
|
+
|
|
98
|
+
# Validate operation
|
|
99
|
+
valid_operations = [
|
|
100
|
+
"get_all", "get", "create", "update", "delete",
|
|
101
|
+
"get_shareable_users", "get_shareable_api_tokens"
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
if operation not in valid_operations:
|
|
105
|
+
return {
|
|
106
|
+
"error": f"Invalid operation '{operation}'",
|
|
107
|
+
"valid_operations": valid_operations
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Extract parameters
|
|
111
|
+
dashboard_id = params.get("dashboard_id")
|
|
112
|
+
custom_dashboard = params.get("custom_dashboard")
|
|
113
|
+
|
|
114
|
+
# Route to the dashboard client
|
|
115
|
+
logger.info(f"Routing to Custom Dashboard client for operation: {operation}")
|
|
116
|
+
|
|
117
|
+
result = await self.dashboard_client.execute_dashboard_operation(
|
|
118
|
+
operation=operation,
|
|
119
|
+
dashboard_id=dashboard_id,
|
|
120
|
+
custom_dashboard=custom_dashboard,
|
|
121
|
+
ctx=ctx
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"operation": operation,
|
|
126
|
+
"dashboard_id": dashboard_id if dashboard_id else None,
|
|
127
|
+
"results": result
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Error in custom dashboard smart router: {e}", exc_info=True)
|
|
132
|
+
return {
|
|
133
|
+
"error": f"Custom dashboard smart router error: {e!s}",
|
|
134
|
+
"operation": operation
|
|
135
|
+
}
|
src/core/server.py
CHANGED
|
@@ -20,6 +20,8 @@ from src.prompts import PROMPT_REGISTRY
|
|
|
20
20
|
|
|
21
21
|
load_dotenv()
|
|
22
22
|
|
|
23
|
+
from src.observability import task, workflow
|
|
24
|
+
|
|
23
25
|
# Configure logging
|
|
24
26
|
logging.basicConfig(
|
|
25
27
|
level=logging.INFO, # Default level, can be overridden
|
|
@@ -63,26 +65,30 @@ from fastmcp import FastMCP
|
|
|
63
65
|
|
|
64
66
|
@dataclass
|
|
65
67
|
class MCPState:
|
|
66
|
-
"""State for the MCP server."""
|
|
68
|
+
"""State for the MCP server with all tool categories."""
|
|
69
|
+
# Router tools
|
|
70
|
+
smart_router_client: Any = None
|
|
71
|
+
custom_dashboard_smart_router_client: Any = None
|
|
72
|
+
|
|
73
|
+
# Infrastructure - Only the new two-pass elicitation tool
|
|
74
|
+
infra_analyze_new_client: Any = None
|
|
75
|
+
|
|
76
|
+
# Events tools
|
|
67
77
|
events_client: Any = None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
infra_analyze_client: Any = None
|
|
75
|
-
infra_metrics_client: Any = None
|
|
76
|
-
app_catalog_client: Any = None
|
|
77
|
-
app_topology_client: Any = None
|
|
78
|
-
app_analyze_client: Any = None
|
|
79
|
-
app_settings_client: Any = None
|
|
80
|
-
app_global_alert_client: Any = None
|
|
78
|
+
|
|
79
|
+
# Automation tools
|
|
80
|
+
action_catalog_client: Any = None
|
|
81
|
+
action_history_client: Any = None
|
|
82
|
+
|
|
83
|
+
# Website tools
|
|
81
84
|
website_metrics_client: Any = None
|
|
82
85
|
website_catalog_client: Any = None
|
|
83
86
|
website_analyze_client: Any = None
|
|
84
87
|
website_configuration_client: Any = None
|
|
85
88
|
|
|
89
|
+
# Settings tools
|
|
90
|
+
custom_dashboard_client: Any = None
|
|
91
|
+
|
|
86
92
|
# Global variables to store credentials for lifespan
|
|
87
93
|
_global_token = None
|
|
88
94
|
_global_base_url = None
|
|
@@ -234,33 +240,15 @@ async def execute_tool(tool_name: str, arguments: dict, clients_state) -> str:
|
|
|
234
240
|
def get_client_categories():
|
|
235
241
|
"""Get client categories with lazy imports to avoid circular dependencies"""
|
|
236
242
|
try:
|
|
237
|
-
from src.application.application_alert_config import ApplicationAlertMCPTools
|
|
238
|
-
from src.application.application_analyze import ApplicationAnalyzeMCPTools
|
|
239
|
-
from src.application.application_catalog import ApplicationCatalogMCPTools
|
|
240
|
-
from src.application.application_global_alert_config import (
|
|
241
|
-
ApplicationGlobalAlertMCPTools,
|
|
242
|
-
)
|
|
243
|
-
from src.application.application_metrics import ApplicationMetricsMCPTools
|
|
244
|
-
from src.application.application_resources import ApplicationResourcesMCPTools
|
|
245
|
-
from src.application.application_settings import ApplicationSettingsMCPTools
|
|
246
|
-
from src.application.application_topology import ApplicationTopologyMCPTools
|
|
247
243
|
from src.automation.action_catalog import ActionCatalogMCPTools
|
|
248
244
|
from src.automation.action_history import ActionHistoryMCPTools
|
|
249
|
-
from src.
|
|
250
|
-
|
|
251
|
-
InfrastructureAnalyzeMCPTools,
|
|
252
|
-
)
|
|
253
|
-
from src.infrastructure.infrastructure_catalog import (
|
|
254
|
-
InfrastructureCatalogMCPTools,
|
|
255
|
-
)
|
|
256
|
-
from src.infrastructure.infrastructure_metrics import (
|
|
257
|
-
InfrastructureMetricsMCPTools,
|
|
258
|
-
)
|
|
259
|
-
from src.infrastructure.infrastructure_resources import (
|
|
260
|
-
InfrastructureResourcesMCPTools,
|
|
245
|
+
from src.core.custom_dashboard_smart_router_tool import (
|
|
246
|
+
CustomDashboardSmartRouterMCPTool,
|
|
261
247
|
)
|
|
262
|
-
from src.
|
|
263
|
-
|
|
248
|
+
from src.core.smart_router_tool import SmartRouterMCPTool
|
|
249
|
+
from src.event.events_tools import AgentMonitoringEventsMCPTools
|
|
250
|
+
from src.infrastructure.infrastructure_analyze_new import (
|
|
251
|
+
InfrastructureAnalyzeOption2,
|
|
264
252
|
)
|
|
265
253
|
from src.settings.custom_dashboard_tools import CustomDashboardMCPTools
|
|
266
254
|
from src.website.website_analyze import WebsiteAnalyzeMCPTools
|
|
@@ -272,22 +260,12 @@ def get_client_categories():
|
|
|
272
260
|
return {}
|
|
273
261
|
|
|
274
262
|
return {
|
|
275
|
-
"
|
|
276
|
-
('
|
|
277
|
-
('
|
|
278
|
-
('infra_topo_client', InfrastructureTopologyMCPTools),
|
|
279
|
-
('infra_analyze_client', InfrastructureAnalyzeMCPTools),
|
|
280
|
-
('infra_metrics_client', InfrastructureMetricsMCPTools),
|
|
263
|
+
"router": [
|
|
264
|
+
('smart_router_client', SmartRouterMCPTool),
|
|
265
|
+
('custom_dashboard_smart_router_client', CustomDashboardSmartRouterMCPTool),
|
|
281
266
|
],
|
|
282
|
-
"
|
|
283
|
-
('
|
|
284
|
-
('app_metrics_client', ApplicationMetricsMCPTools),
|
|
285
|
-
('app_alert_client', ApplicationAlertMCPTools),
|
|
286
|
-
('app_catalog_client', ApplicationCatalogMCPTools),
|
|
287
|
-
('app_topology_client', ApplicationTopologyMCPTools),
|
|
288
|
-
('app_analyze_client', ApplicationAnalyzeMCPTools),
|
|
289
|
-
('app_settings_client', ApplicationSettingsMCPTools),
|
|
290
|
-
('app_global_alert_client', ApplicationGlobalAlertMCPTools),
|
|
267
|
+
"infra": [
|
|
268
|
+
('infra_analyze_new_client', InfrastructureAnalyzeOption2),
|
|
291
269
|
],
|
|
292
270
|
"events": [
|
|
293
271
|
('events_client', AgentMonitoringEventsMCPTools),
|
|
@@ -310,79 +288,65 @@ def get_client_categories():
|
|
|
310
288
|
def get_prompt_categories():
|
|
311
289
|
"""Get prompt categories organized by functionality"""
|
|
312
290
|
# Import the class-based prompts
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
# Use the get_prompts method to get all prompts from the classes
|
|
343
|
-
infra_analyze_prompts = InfrastructureAnalyzePrompts.get_prompts()
|
|
344
|
-
infra_metrics_prompts = InfrastructureMetricsPrompts.get_prompts()
|
|
345
|
-
infra_catalog_prompts = InfrastructureCatalogPrompts.get_prompts()
|
|
346
|
-
infra_topology_prompts = InfrastructureTopologyPrompts.get_prompts()
|
|
347
|
-
infra_resources_prompts = InfrastructureResourcesPrompts.get_prompts()
|
|
348
|
-
app_resources_prompts = ApplicationResourcesPrompts.get_prompts()
|
|
291
|
+
try:
|
|
292
|
+
from src.prompts.application.application_alerts import ApplicationAlertsPrompts
|
|
293
|
+
from src.prompts.application.application_metrics import (
|
|
294
|
+
ApplicationMetricsPrompts,
|
|
295
|
+
)
|
|
296
|
+
from src.prompts.application.application_resources import (
|
|
297
|
+
ApplicationResourcesPrompts,
|
|
298
|
+
)
|
|
299
|
+
from src.prompts.application.application_settings import (
|
|
300
|
+
ApplicationSettingsPrompts,
|
|
301
|
+
)
|
|
302
|
+
from src.prompts.application.application_topology import (
|
|
303
|
+
ApplicationTopologyPrompts,
|
|
304
|
+
)
|
|
305
|
+
from src.prompts.events.events_tools import EventsPrompts
|
|
306
|
+
from src.prompts.settings.custom_dashboard import CustomDashboardPrompts
|
|
307
|
+
from src.prompts.website.website_analyze import WebsiteAnalyzePrompts
|
|
308
|
+
from src.prompts.website.website_catalog import WebsiteCatalogPrompts
|
|
309
|
+
from src.prompts.website.website_configuration import (
|
|
310
|
+
WebsiteConfigurationPrompts,
|
|
311
|
+
)
|
|
312
|
+
from src.prompts.website.website_metrics import WebsiteMetricsPrompts
|
|
313
|
+
except ImportError as e:
|
|
314
|
+
logger.warning(f"Could not import prompt classes: {e}")
|
|
315
|
+
return {}
|
|
316
|
+
|
|
317
|
+
# Get prompts from each class
|
|
318
|
+
app_alerts_prompts = ApplicationAlertsPrompts.get_prompts()
|
|
349
319
|
app_metrics_prompts = ApplicationMetricsPrompts.get_prompts()
|
|
350
|
-
|
|
320
|
+
app_resources_prompts = ApplicationResourcesPrompts.get_prompts()
|
|
351
321
|
app_settings_prompts = ApplicationSettingsPrompts.get_prompts()
|
|
352
322
|
app_topology_prompts = ApplicationTopologyPrompts.get_prompts()
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
website_catalog_prompts = WebsiteCatalogPrompts.get_prompts()
|
|
323
|
+
events_prompts = EventsPrompts.get_prompts()
|
|
324
|
+
custom_dashboard_prompts = CustomDashboardPrompts.get_prompts()
|
|
356
325
|
website_analyze_prompts = WebsiteAnalyzePrompts.get_prompts()
|
|
326
|
+
website_catalog_prompts = WebsiteCatalogPrompts.get_prompts()
|
|
357
327
|
website_configuration_prompts = WebsiteConfigurationPrompts.get_prompts()
|
|
358
|
-
|
|
328
|
+
website_metrics_prompts = WebsiteMetricsPrompts.get_prompts()
|
|
359
329
|
|
|
360
|
-
# Return the categories with their prompt groups
|
|
361
330
|
return {
|
|
362
|
-
"infra": [
|
|
363
|
-
('infra_resources_prompts', infra_resources_prompts),
|
|
364
|
-
('infra_catalog_prompts', infra_catalog_prompts),
|
|
365
|
-
('infra_topology_prompts', infra_topology_prompts),
|
|
366
|
-
('infra_analyze_prompts', infra_analyze_prompts),
|
|
367
|
-
('infra_metrics_prompts', infra_metrics_prompts),
|
|
368
|
-
],
|
|
369
331
|
"app": [
|
|
370
|
-
(
|
|
371
|
-
(
|
|
372
|
-
(
|
|
373
|
-
(
|
|
374
|
-
(
|
|
375
|
-
|
|
332
|
+
("Application Alerts", app_alerts_prompts),
|
|
333
|
+
("Application Metrics", app_metrics_prompts),
|
|
334
|
+
("Application Resources", app_resources_prompts),
|
|
335
|
+
("Application Settings", app_settings_prompts),
|
|
336
|
+
("Application Topology", app_topology_prompts),
|
|
337
|
+
],
|
|
338
|
+
"events": [
|
|
339
|
+
("Events Tools", events_prompts),
|
|
376
340
|
],
|
|
377
341
|
"website": [
|
|
378
|
-
(
|
|
379
|
-
(
|
|
380
|
-
(
|
|
381
|
-
(
|
|
342
|
+
("Website Analyze", website_analyze_prompts),
|
|
343
|
+
("Website Catalog", website_catalog_prompts),
|
|
344
|
+
("Website Configuration", website_configuration_prompts),
|
|
345
|
+
("Website Metrics", website_metrics_prompts),
|
|
382
346
|
],
|
|
383
347
|
"settings": [
|
|
384
|
-
(
|
|
385
|
-
]
|
|
348
|
+
("Custom Dashboard", custom_dashboard_prompts),
|
|
349
|
+
]
|
|
386
350
|
}
|
|
387
351
|
|
|
388
352
|
def get_enabled_client_configs(enabled_categories: str):
|
|
@@ -404,6 +368,7 @@ def get_enabled_client_configs(enabled_categories: str):
|
|
|
404
368
|
logger.warning(f"Unknown category '{category}'")
|
|
405
369
|
return enabled_configs
|
|
406
370
|
|
|
371
|
+
@workflow(name="instana_mcp_workflow")
|
|
407
372
|
def main():
|
|
408
373
|
"""Main entry point for the MCP server."""
|
|
409
374
|
try:
|
|
@@ -438,7 +403,7 @@ def main():
|
|
|
438
403
|
"--tools",
|
|
439
404
|
type=str,
|
|
440
405
|
metavar='<categories>',
|
|
441
|
-
help="Comma-separated list of tool categories to enable (--tools infra,app,events,automation,website,
|
|
406
|
+
help="Comma-separated list of tool categories to enable (--tools router,infra,app,events,automation,website,settings). Also controls which prompts are enabled. If not provided, all tools and prompts are enabled. Use 'router' for smart routing across app and infra metrics."
|
|
442
407
|
)
|
|
443
408
|
parser.add_argument(
|
|
444
409
|
"--list-tools",
|
|
@@ -451,6 +416,16 @@ def main():
|
|
|
451
416
|
default=int(os.getenv("PORT", "8080")),
|
|
452
417
|
help="Port to listen on (default: 8080, can be overridden with PORT env var)"
|
|
453
418
|
)
|
|
419
|
+
parser.add_argument(
|
|
420
|
+
"--api-token",
|
|
421
|
+
type=str,
|
|
422
|
+
help="Instana API token (overrides INSTANA_API_TOKEN env var)"
|
|
423
|
+
)
|
|
424
|
+
parser.add_argument(
|
|
425
|
+
"--base-url",
|
|
426
|
+
type=str,
|
|
427
|
+
help="Instana base URL (overrides INSTANA_BASE_URL env var)"
|
|
428
|
+
)
|
|
454
429
|
# Check for help arguments before parsing
|
|
455
430
|
if len(sys.argv) > 1 and any(arg in ['-h','--h','--help','-help'] for arg in sys.argv[1:]):
|
|
456
431
|
# Check if help is combined with other arguments
|
|
@@ -485,7 +460,7 @@ def main():
|
|
|
485
460
|
else:
|
|
486
461
|
set_log_level(args.log_level)
|
|
487
462
|
|
|
488
|
-
all_categories = {"infra", "app", "events", "automation", "website", "settings"}
|
|
463
|
+
all_categories = {"router", "infra", "app", "events", "automation", "website", "settings"}
|
|
489
464
|
|
|
490
465
|
# Handle --list-tools option
|
|
491
466
|
if args.list_tools:
|
|
@@ -513,7 +488,7 @@ def main():
|
|
|
513
488
|
enabled = set(all_categories)
|
|
514
489
|
|
|
515
490
|
if invalid:
|
|
516
|
-
logger.error(f"Error: Unknown category/categories: {', '.join(invalid)}. Available categories: infra, app, events, automation, website, settings")
|
|
491
|
+
logger.error(f"Error: Unknown category/categories: {', '.join(invalid)}. Available categories: router, infra, app, events, automation, website, settings")
|
|
517
492
|
sys.exit(2)
|
|
518
493
|
|
|
519
494
|
# Print enabled tools for user information
|
|
@@ -536,8 +511,9 @@ def main():
|
|
|
536
511
|
f"Total enabled tools: {len(enabled_tool_classes)}"
|
|
537
512
|
)
|
|
538
513
|
|
|
539
|
-
# Get credentials from
|
|
540
|
-
INSTANA_API_TOKEN
|
|
514
|
+
# Get credentials from command line args or environment variables
|
|
515
|
+
INSTANA_API_TOKEN = args.api_token if args.api_token else os.getenv("INSTANA_API_TOKEN", "")
|
|
516
|
+
INSTANA_BASE_URL = args.base_url if args.base_url else os.getenv("INSTANA_BASE_URL", "")
|
|
541
517
|
|
|
542
518
|
if args.transport == "stdio" or args.transport is None:
|
|
543
519
|
if not validate_credentials(INSTANA_API_TOKEN, INSTANA_BASE_URL):
|