use std::sync::Arc; use anyhow::Context as _; use context_server::ContextServerId; use extension::{ContextServerConfiguration, ExtensionManifest}; use fs::Fs; use gpui::Task; use language::LanguageRegistry; use project::{ context_server_store::registry::ContextServerDescriptorRegistry, project_settings::ProjectSettings, }; use settings::update_settings_file; use ui::prelude::*; use util::ResultExt; use workspace::Workspace; use crate::agent_configuration::ConfigureContextServerModal; pub(crate) fn init(language_registry: Arc, fs: Arc, cx: &mut App) { cx.observe_new(move |_: &mut Workspace, window, cx| { let Some(window) = window else { return; }; if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() { cx.subscribe_in(extension_events, window, { let language_registry = language_registry.clone(); let fs = fs.clone(); move |workspace, _, event, window, cx| match event { extension::Event::ExtensionInstalled(manifest) => { show_configure_mcp_modal( language_registry.clone(), manifest, workspace, window, cx, ); } extension::Event::ExtensionUninstalled(manifest) => { remove_context_server_settings( manifest.context_servers.keys().cloned().collect(), fs.clone(), cx, ); } extension::Event::ConfigureExtensionRequested(manifest) => { if !manifest.context_servers.is_empty() { show_configure_mcp_modal( language_registry.clone(), manifest, workspace, window, cx, ); } } _ => {} } }) .detach(); } else { log::info!( "No extension events global found. Skipping context server configuration wizard" ); } }) .detach(); } fn remove_context_server_settings( context_server_ids: Vec>, fs: Arc, cx: &mut App, ) { update_settings_file::(fs, cx, move |settings, _| { settings .context_servers .retain(|server_id, _| !context_server_ids.contains(server_id)); }); } pub enum Configuration { NotAvailable(ContextServerId, Option), Required( ContextServerId, Option, ContextServerConfiguration, ), } fn show_configure_mcp_modal( language_registry: Arc, manifest: &Arc, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<'_, Workspace>, ) { if !window.is_window_active() { return; } let context_server_store = workspace.project().read(cx).context_server_store(); let repository: Option = manifest.repository.as_ref().map(|s| s.clone().into()); let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx); let worktree_store = workspace.project().read(cx).worktree_store(); let configuration_tasks = manifest .context_servers .keys() .cloned() .map({ |key| { let Some(descriptor) = registry.context_server_descriptor(&key) else { return Task::ready(Configuration::NotAvailable( ContextServerId(key), repository.clone(), )); }; cx.spawn({ let repository_url = repository.clone(); let worktree_store = worktree_store.clone(); async move |_, cx| { let configuration = descriptor .configuration(worktree_store.clone(), &cx) .await .context("Failed to resolve context server configuration") .log_err() .flatten(); match configuration { Some(config) => Configuration::Required( ContextServerId(key), repository_url, config, ), None => { Configuration::NotAvailable(ContextServerId(key), repository_url) } } } }) } }) .collect::>(); let jsonc_language = language_registry.language_for_name("jsonc"); cx.spawn_in(window, async move |this, cx| { let configurations = futures::future::join_all(configuration_tasks).await; let jsonc_language = jsonc_language.await.ok(); this.update_in(cx, |this, window, cx| { let workspace = cx.entity().downgrade(); this.toggle_modal(window, cx, |window, cx| { ConfigureContextServerModal::new( configurations.into_iter(), context_server_store, jsonc_language, language_registry, workspace, window, cx, ) }); }) }) .detach(); }