From ba3aea0a5558424473bc57d08165a6e1d553aea6 Mon Sep 17 00:00:00 2001 From: Oliver Azevedo Barnes Date: Fri, 6 Mar 2026 16:21:44 +0000 Subject: [PATCH] agent: Don't connect to MCP servers when AI is globally disabled (#47857) Closes #46846 When `disable_ai: true` is set in user settings, Zed was still connecting to configured MCP (context) servers and sending initialization requests. This change adds checks for `DisableAiSettings` in `ContextServerStore` to: - Skip server connections when AI is disabled - Disconnect from running servers when AI becomes disabled - Connect to servers when AI is re-enabled - Prevent registry changes from triggering connections while AI is disabled The fix tracks `ai_disabled` state to detect transitions and properly manage server connections when AI is toggled. Release Notes: - Fixed Zed connecting to MCP servers when AI is disabled. --------- Co-authored-by: Bennet Bo Fenner --- crates/project/src/context_server_store.rs | 31 ++++- .../tests/integration/context_server_store.rs | 113 +++++++++++++++++- 2 files changed, 138 insertions(+), 6 deletions(-) diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 88dc64fcbe8795ae4826dcaa2813744f525b9258..ed8d31ea79cc8cb8537f8cff2edbf2a899794d19 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -222,6 +222,7 @@ pub struct ContextServerStore { update_servers_task: Option>>, context_server_factory: Option, needs_server_update: bool, + ai_disabled: bool, _subscriptions: Vec, } @@ -377,23 +378,42 @@ impl ContextServerStore { cx: &mut Context, ) -> Self { let mut subscriptions = vec![cx.observe_global::(move |this, cx| { + let ai_disabled = DisableAiSettings::get_global(cx).disable_ai; + let ai_was_disabled = this.ai_disabled; + this.ai_disabled = ai_disabled; + let settings = &Self::resolve_project_settings(&this.worktree_store, cx).context_servers; - if &this.context_server_settings == settings { + let settings_changed = &this.context_server_settings != settings; + + if settings_changed { + this.context_server_settings = settings.clone(); + } + + // When AI is disabled, stop all running servers + if ai_disabled { + let server_ids: Vec<_> = this.servers.keys().cloned().collect(); + for id in server_ids { + this.stop_server(&id, cx).log_err(); + } return; } - this.context_server_settings = settings.clone(); - if maintain_server_loop { + + // Trigger updates if AI was re-enabled or settings changed + if maintain_server_loop && (ai_was_disabled || settings_changed) { this.available_context_servers_changed(cx); } })]; if maintain_server_loop { subscriptions.push(cx.observe(®istry, |this, _registry, cx| { - this.available_context_servers_changed(cx); + if !DisableAiSettings::get_global(cx).disable_ai { + this.available_context_servers_changed(cx); + } })); } + let ai_disabled = DisableAiSettings::get_global(cx).disable_ai; let mut this = Self { state, _subscriptions: subscriptions, @@ -404,12 +424,13 @@ impl ContextServerStore { project: weak_project, registry, needs_server_update: false, + ai_disabled, servers: HashMap::default(), server_ids: Default::default(), update_servers_task: None, context_server_factory, }; - if maintain_server_loop { + if maintain_server_loop && !DisableAiSettings::get_global(cx).disable_ai { this.available_context_servers_changed(cx); } this diff --git a/crates/project/tests/integration/context_server_store.rs b/crates/project/tests/integration/context_server_store.rs index 56bdaed41cd77b665d316491e051582c7ccc078a..5b68e11bb95a8b9178a8febf91849ba3a65f76e6 100644 --- a/crates/project/tests/integration/context_server_store.rs +++ b/crates/project/tests/integration/context_server_store.rs @@ -8,10 +8,11 @@ use project::context_server_store::*; use project::project_settings::ContextServerSettings; use project::worktree_store::WorktreeStore; use project::{ - FakeFs, Project, context_server_store::registry::ContextServerDescriptor, + DisableAiSettings, FakeFs, Project, context_server_store::registry::ContextServerDescriptor, project_settings::ProjectSettings, }; use serde_json::json; +use settings::settings_content::SaturatingBool; use settings::{ContextServerCommand, Settings, SettingsStore}; use std::sync::Arc; use std::{cell::RefCell, path::PathBuf, rc::Rc}; @@ -553,6 +554,116 @@ async fn test_context_server_enabled_disabled(cx: &mut TestAppContext) { } } +#[gpui::test] +async fn test_context_server_respects_disable_ai(cx: &mut TestAppContext) { + const SERVER_1_ID: &str = "mcp-1"; + + let server_1_id = ContextServerId(SERVER_1_ID.into()); + + // Set up SettingsStore with disable_ai: true in user settings BEFORE creating project + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + DisableAiSettings::register(cx); + // Set disable_ai via user settings (not override_global) so it persists through recompute_values + SettingsStore::update_global(cx, |store, cx| { + store.update_user_settings(cx, |content| { + content.project.disable_ai = Some(SaturatingBool(true)); + }); + }); + }); + + // Now create the project (ContextServerStore will see disable_ai = true) + let fs = FakeFs::new(cx.executor()); + fs.insert_tree(path!("/test"), json!({"code.rs": ""})).await; + let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await; + + let executor = cx.executor(); + let store = project.read_with(cx, |project, _| project.context_server_store()); + store.update(cx, |store, _| { + store.set_context_server_factory(Box::new(move |id, _| { + Arc::new(ContextServer::new( + id.clone(), + Arc::new(create_fake_transport(id.0.to_string(), executor.clone())), + )) + })); + }); + + set_context_server_configuration( + vec![( + server_1_id.0.clone(), + settings::ContextServerSettingsContent::Stdio { + enabled: true, + remote: false, + command: ContextServerCommand { + path: "somebinary".into(), + args: vec!["arg".to_string()], + env: None, + timeout: None, + }, + }, + )], + cx, + ); + + cx.run_until_parked(); + + // Verify that no server started because AI is disabled + cx.update(|cx| { + assert_eq!( + store.read(cx).status_for_server(&server_1_id), + None, + "Server should not start when disable_ai is true" + ); + }); + + // Enable AI and verify server starts + { + let _server_events = assert_server_events( + &store, + vec![ + (server_1_id.clone(), ContextServerStatus::Starting), + (server_1_id.clone(), ContextServerStatus::Running), + ], + cx, + ); + cx.update(|cx| { + SettingsStore::update_global(cx, |store, cx| { + store.update_user_settings(cx, |content| { + content.project.disable_ai = Some(SaturatingBool(false)); + }); + }); + }); + cx.run_until_parked(); + } + + // Disable AI again and verify server stops + { + let _server_events = assert_server_events( + &store, + vec![(server_1_id.clone(), ContextServerStatus::Stopped)], + cx, + ); + cx.update(|cx| { + SettingsStore::update_global(cx, |store, cx| { + store.update_user_settings(cx, |content| { + content.project.disable_ai = Some(SaturatingBool(true)); + }); + }); + }); + cx.run_until_parked(); + } + + // Verify server is stopped + cx.update(|cx| { + assert_eq!( + store.read(cx).status_for_server(&server_1_id), + Some(ContextServerStatus::Stopped), + "Server should be stopped when disable_ai is true" + ); + }); +} + #[gpui::test] async fn test_server_ids_includes_disabled_servers(cx: &mut TestAppContext) { const ENABLED_SERVER_ID: &str = "enabled-server";