mcp-instana 0.6.2__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 (28) hide show
  1. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +179 -120
  2. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +28 -21
  3. src/application/application_alert_config.py +397 -146
  4. src/application/application_analyze.py +597 -597
  5. src/application/application_call_group.py +528 -0
  6. src/application/application_catalog.py +0 -8
  7. src/application/application_global_alert_config.py +255 -38
  8. src/application/application_metrics.py +377 -237
  9. src/application/application_resources.py +414 -365
  10. src/application/application_settings.py +605 -1651
  11. src/application/application_topology.py +62 -62
  12. src/core/custom_dashboard_smart_router_tool.py +135 -0
  13. src/core/server.py +92 -119
  14. src/core/smart_router_tool.py +574 -0
  15. src/core/utils.py +17 -8
  16. src/custom_dashboard/custom_dashboard_tools.py +422 -0
  17. src/infrastructure/elicitation_handler.py +338 -0
  18. src/infrastructure/entity_registry.py +329 -0
  19. src/infrastructure/infrastructure_analyze_new.py +600 -0
  20. src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
  21. src/infrastructure/infrastructure_catalog.py +7 -28
  22. src/infrastructure/infrastructure_metrics.py +93 -17
  23. src/infrastructure/infrastructure_resources.py +5 -20
  24. src/infrastructure/infrastructure_topology.py +2 -8
  25. src/prompts/application/application_settings.py +58 -0
  26. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +0 -0
  27. {mcp_instana-0.6.2.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
  28. {mcp_instana-0.6.2.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
- 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}"}
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
@@ -65,26 +65,30 @@ from fastmcp import FastMCP
65
65
 
66
66
  @dataclass
67
67
  class MCPState:
68
- """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
69
77
  events_client: Any = None
70
- infra_client: Any = None
71
- app_resource_client: Any = None
72
- app_metrics_client: Any = None
73
- app_alert_client: Any = None
74
- infra_catalog_client: Any = None
75
- infra_topo_client: Any = None
76
- infra_analyze_client: Any = None
77
- infra_metrics_client: Any = None
78
- app_catalog_client: Any = None
79
- app_topology_client: Any = None
80
- app_analyze_client: Any = None
81
- app_settings_client: Any = None
82
- 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
83
84
  website_metrics_client: Any = None
84
85
  website_catalog_client: Any = None
85
86
  website_analyze_client: Any = None
86
87
  website_configuration_client: Any = None
87
88
 
89
+ # Settings tools
90
+ custom_dashboard_client: Any = None
91
+
88
92
  # Global variables to store credentials for lifespan
89
93
  _global_token = None
90
94
  _global_base_url = None
@@ -236,33 +240,15 @@ async def execute_tool(tool_name: str, arguments: dict, clients_state) -> str:
236
240
  def get_client_categories():
237
241
  """Get client categories with lazy imports to avoid circular dependencies"""
238
242
  try:
239
- from src.application.application_alert_config import ApplicationAlertMCPTools
240
- from src.application.application_analyze import ApplicationAnalyzeMCPTools
241
- from src.application.application_catalog import ApplicationCatalogMCPTools
242
- from src.application.application_global_alert_config import (
243
- ApplicationGlobalAlertMCPTools,
244
- )
245
- from src.application.application_metrics import ApplicationMetricsMCPTools
246
- from src.application.application_resources import ApplicationResourcesMCPTools
247
- from src.application.application_settings import ApplicationSettingsMCPTools
248
- from src.application.application_topology import ApplicationTopologyMCPTools
249
243
  from src.automation.action_catalog import ActionCatalogMCPTools
250
244
  from src.automation.action_history import ActionHistoryMCPTools
251
- from src.event.events_tools import AgentMonitoringEventsMCPTools
252
- from src.infrastructure.infrastructure_analyze import (
253
- InfrastructureAnalyzeMCPTools,
254
- )
255
- from src.infrastructure.infrastructure_catalog import (
256
- InfrastructureCatalogMCPTools,
257
- )
258
- from src.infrastructure.infrastructure_metrics import (
259
- InfrastructureMetricsMCPTools,
245
+ from src.core.custom_dashboard_smart_router_tool import (
246
+ CustomDashboardSmartRouterMCPTool,
260
247
  )
261
- from src.infrastructure.infrastructure_resources import (
262
- InfrastructureResourcesMCPTools,
263
- )
264
- from src.infrastructure.infrastructure_topology import (
265
- InfrastructureTopologyMCPTools,
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,
266
252
  )
267
253
  from src.settings.custom_dashboard_tools import CustomDashboardMCPTools
268
254
  from src.website.website_analyze import WebsiteAnalyzeMCPTools
@@ -274,22 +260,12 @@ def get_client_categories():
274
260
  return {}
275
261
 
276
262
  return {
277
- "infra": [
278
- ('infra_client', InfrastructureResourcesMCPTools),
279
- ('infra_catalog_client', InfrastructureCatalogMCPTools),
280
- ('infra_topo_client', InfrastructureTopologyMCPTools),
281
- ('infra_analyze_client', InfrastructureAnalyzeMCPTools),
282
- ('infra_metrics_client', InfrastructureMetricsMCPTools),
263
+ "router": [
264
+ ('smart_router_client', SmartRouterMCPTool),
265
+ ('custom_dashboard_smart_router_client', CustomDashboardSmartRouterMCPTool),
283
266
  ],
284
- "app": [
285
- ('app_resource_client', ApplicationResourcesMCPTools),
286
- ('app_metrics_client', ApplicationMetricsMCPTools),
287
- ('app_alert_client', ApplicationAlertMCPTools),
288
- ('app_catalog_client', ApplicationCatalogMCPTools),
289
- ('app_topology_client', ApplicationTopologyMCPTools),
290
- ('app_analyze_client', ApplicationAnalyzeMCPTools),
291
- ('app_settings_client', ApplicationSettingsMCPTools),
292
- ('app_global_alert_client', ApplicationGlobalAlertMCPTools),
267
+ "infra": [
268
+ ('infra_analyze_new_client', InfrastructureAnalyzeOption2),
293
269
  ],
294
270
  "events": [
295
271
  ('events_client', AgentMonitoringEventsMCPTools),
@@ -312,79 +288,65 @@ def get_client_categories():
312
288
  def get_prompt_categories():
313
289
  """Get prompt categories organized by functionality"""
314
290
  # Import the class-based prompts
315
- from src.prompts.application.application_alerts import ApplicationAlertsPrompts
316
- from src.prompts.application.application_catalog import ApplicationCatalogPrompts
317
- from src.prompts.application.application_metrics import ApplicationMetricsPrompts
318
- from src.prompts.application.application_resources import (
319
- ApplicationResourcesPrompts,
320
- )
321
- from src.prompts.application.application_settings import ApplicationSettingsPrompts
322
- from src.prompts.application.application_topology import ApplicationTopologyPrompts
323
- from src.prompts.infrastructure.infrastructure_analyze import (
324
- InfrastructureAnalyzePrompts,
325
- )
326
- from src.prompts.infrastructure.infrastructure_catalog import (
327
- InfrastructureCatalogPrompts,
328
- )
329
- from src.prompts.infrastructure.infrastructure_metrics import (
330
- InfrastructureMetricsPrompts,
331
- )
332
- from src.prompts.infrastructure.infrastructure_resources import (
333
- InfrastructureResourcesPrompts,
334
- )
335
- from src.prompts.infrastructure.infrastructure_topology import (
336
- InfrastructureTopologyPrompts,
337
- )
338
- from src.prompts.settings.custom_dashboard import CustomDashboardPrompts
339
- from src.prompts.website.website_analyze import WebsiteAnalyzePrompts
340
- from src.prompts.website.website_catalog import WebsiteCatalogPrompts
341
- from src.prompts.website.website_configuration import WebsiteConfigurationPrompts
342
- from src.prompts.website.website_metrics import WebsiteMetricsPrompts
343
-
344
- # Use the get_prompts method to get all prompts from the classes
345
- infra_analyze_prompts = InfrastructureAnalyzePrompts.get_prompts()
346
- infra_metrics_prompts = InfrastructureMetricsPrompts.get_prompts()
347
- infra_catalog_prompts = InfrastructureCatalogPrompts.get_prompts()
348
- infra_topology_prompts = InfrastructureTopologyPrompts.get_prompts()
349
- infra_resources_prompts = InfrastructureResourcesPrompts.get_prompts()
350
- 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()
351
319
  app_metrics_prompts = ApplicationMetricsPrompts.get_prompts()
352
- app_catalog_prompts = ApplicationCatalogPrompts.get_prompts()
320
+ app_resources_prompts = ApplicationResourcesPrompts.get_prompts()
353
321
  app_settings_prompts = ApplicationSettingsPrompts.get_prompts()
354
322
  app_topology_prompts = ApplicationTopologyPrompts.get_prompts()
355
- app_alert_prompts = ApplicationAlertsPrompts.get_prompts()
356
- website_metrics_prompts = WebsiteMetricsPrompts.get_prompts()
357
- website_catalog_prompts = WebsiteCatalogPrompts.get_prompts()
323
+ events_prompts = EventsPrompts.get_prompts()
324
+ custom_dashboard_prompts = CustomDashboardPrompts.get_prompts()
358
325
  website_analyze_prompts = WebsiteAnalyzePrompts.get_prompts()
326
+ website_catalog_prompts = WebsiteCatalogPrompts.get_prompts()
359
327
  website_configuration_prompts = WebsiteConfigurationPrompts.get_prompts()
360
- custom_dashboard_prompts = CustomDashboardPrompts.get_prompts()
328
+ website_metrics_prompts = WebsiteMetricsPrompts.get_prompts()
361
329
 
362
- # Return the categories with their prompt groups
363
330
  return {
364
- "infra": [
365
- ('infra_resources_prompts', infra_resources_prompts),
366
- ('infra_catalog_prompts', infra_catalog_prompts),
367
- ('infra_topology_prompts', infra_topology_prompts),
368
- ('infra_analyze_prompts', infra_analyze_prompts),
369
- ('infra_metrics_prompts', infra_metrics_prompts),
370
- ],
371
331
  "app": [
372
- ('app_resources_prompts', app_resources_prompts),
373
- ('app_metrics_prompts', app_metrics_prompts),
374
- ('app_catalog_prompts', app_catalog_prompts),
375
- ('app_settings_prompts', app_settings_prompts),
376
- ('app_topology_prompts', app_topology_prompts),
377
- ('app_alert_prompts', app_alert_prompts),
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),
378
340
  ],
379
341
  "website": [
380
- ('website_metrics_prompts', website_metrics_prompts),
381
- ('website_catalog_prompts', website_catalog_prompts),
382
- ('website_analyze_prompts', website_analyze_prompts),
383
- ('website_configuration_prompts', website_configuration_prompts),
342
+ ("Website Analyze", website_analyze_prompts),
343
+ ("Website Catalog", website_catalog_prompts),
344
+ ("Website Configuration", website_configuration_prompts),
345
+ ("Website Metrics", website_metrics_prompts),
384
346
  ],
385
347
  "settings": [
386
- ('custom_dashboard_prompts', custom_dashboard_prompts),
387
- ],
348
+ ("Custom Dashboard", custom_dashboard_prompts),
349
+ ]
388
350
  }
389
351
 
390
352
  def get_enabled_client_configs(enabled_categories: str):
@@ -441,7 +403,7 @@ def main():
441
403
  "--tools",
442
404
  type=str,
443
405
  metavar='<categories>',
444
- help="Comma-separated list of tool categories to enable (--tools infra,app,events,automation,website, settings). Also controls which prompts are enabled. If not provided, all tools and prompts are enabled."
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."
445
407
  )
446
408
  parser.add_argument(
447
409
  "--list-tools",
@@ -454,6 +416,16 @@ def main():
454
416
  default=int(os.getenv("PORT", "8080")),
455
417
  help="Port to listen on (default: 8080, can be overridden with PORT env var)"
456
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
+ )
457
429
  # Check for help arguments before parsing
458
430
  if len(sys.argv) > 1 and any(arg in ['-h','--h','--help','-help'] for arg in sys.argv[1:]):
459
431
  # Check if help is combined with other arguments
@@ -488,7 +460,7 @@ def main():
488
460
  else:
489
461
  set_log_level(args.log_level)
490
462
 
491
- all_categories = {"infra", "app", "events", "automation", "website", "settings"}
463
+ all_categories = {"router", "infra", "app", "events", "automation", "website", "settings"}
492
464
 
493
465
  # Handle --list-tools option
494
466
  if args.list_tools:
@@ -516,7 +488,7 @@ def main():
516
488
  enabled = set(all_categories)
517
489
 
518
490
  if invalid:
519
- 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")
520
492
  sys.exit(2)
521
493
 
522
494
  # Print enabled tools for user information
@@ -539,8 +511,9 @@ def main():
539
511
  f"Total enabled tools: {len(enabled_tool_classes)}"
540
512
  )
541
513
 
542
- # Get credentials from environment variables for stdio mode
543
- INSTANA_API_TOKEN, INSTANA_BASE_URL = get_instana_credentials()
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", "")
544
517
 
545
518
  if args.transport == "stdio" or args.transport is None:
546
519
  if not validate_credentials(INSTANA_API_TOKEN, INSTANA_BASE_URL):