@@ -115,13 +115,18 @@ func (dt *disabledTools) addTools(s *server.MCPServer) {
115115func (dt * disabledTools ) addToolsDynamically (s * server.MCPServer ) * mcpgrafana.DynamicToolManager {
116116 dtm := mcpgrafana .NewDynamicToolManager (s )
117117
118- enabledTools := strings .Split (dt .enabledTools , "," )
118+ // Split and clean up the enabled tools list (trim whitespace, filter empty strings)
119+ rawTools := strings .Split (dt .enabledTools , "," )
120+ enabledTools := make ([]string , 0 , len (rawTools ))
121+ for _ , tool := range rawTools {
122+ trimmed := strings .TrimSpace (tool )
123+ if trimmed != "" {
124+ enabledTools = append (enabledTools , trimmed )
125+ }
126+ }
127+ enableWriteTools := ! dt .write
119128
120129 isEnabled := func (toolName string ) bool {
121- // If enabledTools is empty string, no tools should be available
122- if dt .enabledTools == "" {
123- return false
124- }
125130 return slices .Contains (enabledTools , toolName )
126131 }
127132
@@ -134,18 +139,20 @@ func (dt *disabledTools) addToolsDynamically(s *server.MCPServer) *mcpgrafana.Dy
134139 }{
135140 {"search" , "Tools for searching dashboards, folders, and other Grafana resources" , []string {"search_dashboards" , "search_folders" }, tools .AddSearchTools },
136141 {"datasource" , "Tools for listing and fetching datasource details" , []string {"list_datasources" , "get_datasource_by_uid" , "get_datasource_by_name" }, tools .AddDatasourceTools },
137- {"incident" , "Tools for managing Grafana Incident (create, update, search incidents)" , []string {"list_incidents" , "create_incident" , "add_activity_to_incident" , "get_incident" }, tools .AddIncidentTools },
142+ {"incident" , "Tools for managing Grafana Incident (create, update, search incidents)" , []string {"list_incidents" , "create_incident" , "add_activity_to_incident" , "get_incident" }, func ( mcp * server. MCPServer ) { tools .AddIncidentTools ( mcp , enableWriteTools ) } },
138143 {"prometheus" , "Tools for querying Prometheus metrics and metadata" , []string {"list_prometheus_metric_metadata" , "query_prometheus" , "list_prometheus_metric_names" , "list_prometheus_label_names" , "list_prometheus_label_values" }, tools .AddPrometheusTools },
139144 {"loki" , "Tools for querying Loki logs and labels" , []string {"list_loki_label_names" , "list_loki_label_values" , "query_loki_stats" , "query_loki_logs" }, tools .AddLokiTools },
140- {"alerting" , "Tools for managing alert rules and notification contact points" , []string {"list_alert_rules" , "get_alert_rule_by_uid" , "list_contact_points" , "create_alert_rule" , "update_alert_rule" , "delete_alert_rule" }, tools .AddAlertingTools },
141- {"dashboard" , "Tools for managing Grafana dashboards (get, update, extract queries)" , []string {"get_dashboard_by_uid" , "update_dashboard" , "get_dashboard_panel_queries" , "get_dashboard_property" , "get_dashboard_summary" }, tools .AddDashboardTools },
142- {"folder" , "Tools for managing Grafana folders" , []string {"create_folder" }, tools .AddFolderTools },
145+ {"alerting" , "Tools for managing alert rules and notification contact points" , []string {"list_alert_rules" , "get_alert_rule_by_uid" , "list_contact_points" , "create_alert_rule" , "update_alert_rule" , "delete_alert_rule" }, func ( mcp * server. MCPServer ) { tools .AddAlertingTools ( mcp , enableWriteTools ) } },
146+ {"dashboard" , "Tools for managing Grafana dashboards (get, update, extract queries)" , []string {"get_dashboard_by_uid" , "update_dashboard" , "get_dashboard_panel_queries" , "get_dashboard_property" , "get_dashboard_summary" }, func ( mcp * server. MCPServer ) { tools .AddDashboardTools ( mcp , enableWriteTools ) } },
147+ {"folder" , "Tools for managing Grafana folders" , []string {"create_folder" }, func ( mcp * server. MCPServer ) { tools .AddFolderTools ( mcp , enableWriteTools ) } },
143148 {"oncall" , "Tools for managing OnCall schedules, shifts, teams, and users" , []string {"list_oncall_schedules" , "get_oncall_shift" , "get_current_oncall_users" , "list_oncall_teams" , "list_oncall_users" , "list_alert_groups" , "get_alert_group" }, tools .AddOnCallTools },
144149 {"asserts" , "Tools for Grafana Asserts cloud functionality" , []string {"get_assertions" }, tools .AddAssertsTools },
145- {"sift" , "Tools for Sift investigations (analyze logs/traces, find errors, detect slow requests)" , []string {"get_sift_investigation" , "get_sift_analysis" , "list_sift_investigations" , "find_error_pattern_logs" , "find_slow_requests" }, tools .AddSiftTools },
150+ {"sift" , "Tools for Sift investigations (analyze logs/traces, find errors, detect slow requests)" , []string {"get_sift_investigation" , "get_sift_analysis" , "list_sift_investigations" , "find_error_pattern_logs" , "find_slow_requests" }, func ( mcp * server. MCPServer ) { tools .AddSiftTools ( mcp , enableWriteTools ) } },
146151 {"admin" , "Tools for administrative tasks (list teams, manage users)" , []string {"list_teams" , "list_users_by_org" }, tools .AddAdminTools },
147152 {"pyroscope" , "Tools for profiling applications with Pyroscope" , []string {"list_pyroscope_label_names" , "list_pyroscope_label_values" , "list_pyroscope_profile_types" , "fetch_pyroscope_profile" }, tools .AddPyroscopeTools },
148153 {"navigation" , "Tools for generating deeplink URLs to Grafana resources" , []string {"generate_deeplink" }, tools .AddNavigationTools },
154+ {"annotations" , "Tools for managing annotations" , []string {"get_annotations" , "create_annotation" , "create_graphite_annotation" , "update_annotation" , "patch_annotation" , "get_annotation_tags" }, func (mcp * server.MCPServer ) { tools .AddAnnotationTools (mcp , enableWriteTools ) }},
155+ {"rendering" , "Tools for rendering dashboard panels as images" , []string {"get_panel_image" }, tools .AddRenderingTools },
149156 }
150157
151158 // Only register toolsets that are enabled
@@ -270,11 +277,6 @@ Note that some of these capabilities may be disabled. Do not try to use features
270277 stm = mcpgrafana .NewToolManager (sm , s , mcpgrafana .WithProxiedTools (! dt .proxied ))
271278
272279 if dt .dynamicTools {
273- // Validate that enabled-tools is not empty when using dynamic toolsets
274- // An empty list would result in a non-functional server with no toolsets to enable
275- if dt .enabledTools == "" {
276- return nil , nil , errors .New ("--enabled-tools cannot be empty when using --dynamic-toolsets (would result in no toolsets available)" )
277- }
278280 // For dynamic toolsets, start with only discovery tools
279281 // Tools will be added dynamically when toolsets are enabled
280282 dt .addToolsDynamically (s )
@@ -349,6 +351,13 @@ func handleHealthz(w http.ResponseWriter, r *http.Request) {
349351
350352func run (transport , addr , basePath , endpointPath string , logLevel slog.Level , dt disabledTools , gc mcpgrafana.GrafanaConfig , tls tlsConfig ) error {
351353 slog .SetDefault (slog .New (slog .NewTextHandler (os .Stderr , & slog.HandlerOptions {Level : logLevel })))
354+
355+ // Validate that enabled-tools is not empty when using dynamic toolsets
356+ // An empty list would result in a non-functional server with no toolsets to enable
357+ if dt .dynamicTools && dt .enabledTools == "" {
358+ return errors .New ("--enabled-tools cannot be empty when using --dynamic-toolsets (would result in no toolsets available)" )
359+ }
360+
352361 s , tm := newServer (transport , dt )
353362
354363 // Create a context that will be cancelled on shutdown
0 commit comments