context_server_configuration.rs

  1use std::sync::Arc;
  2
  3use anyhow::Context as _;
  4use context_server::ContextServerId;
  5use extension::{ContextServerConfiguration, ExtensionManifest};
  6use fs::Fs;
  7use gpui::Task;
  8use language::LanguageRegistry;
  9use project::{
 10    context_server_store::registry::ContextServerDescriptorRegistry,
 11    project_settings::ProjectSettings,
 12};
 13use settings::update_settings_file;
 14use ui::prelude::*;
 15use util::ResultExt;
 16use workspace::Workspace;
 17
 18use crate::agent_configuration::ConfigureContextServerModal;
 19
 20pub(crate) fn init(language_registry: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, cx: &mut App) {
 21    cx.observe_new(move |_: &mut Workspace, window, cx| {
 22        let Some(window) = window else {
 23            return;
 24        };
 25
 26        if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
 27            cx.subscribe_in(extension_events, window, {
 28                let language_registry = language_registry.clone();
 29                let fs = fs.clone();
 30                move |workspace, _, event, window, cx| match event {
 31                    extension::Event::ExtensionInstalled(manifest) => {
 32                        show_configure_mcp_modal(
 33                            language_registry.clone(),
 34                            manifest,
 35                            workspace,
 36                            window,
 37                            cx,
 38                        );
 39                    }
 40                    extension::Event::ExtensionUninstalled(manifest) => {
 41                        remove_context_server_settings(
 42                            manifest.context_servers.keys().cloned().collect(),
 43                            fs.clone(),
 44                            cx,
 45                        );
 46                    }
 47                    extension::Event::ConfigureExtensionRequested(manifest) => {
 48                        if !manifest.context_servers.is_empty() {
 49                            show_configure_mcp_modal(
 50                                language_registry.clone(),
 51                                manifest,
 52                                workspace,
 53                                window,
 54                                cx,
 55                            );
 56                        }
 57                    }
 58                    _ => {}
 59                }
 60            })
 61            .detach();
 62        } else {
 63            log::info!(
 64                "No extension events global found. Skipping context server configuration wizard"
 65            );
 66        }
 67    })
 68    .detach();
 69}
 70
 71fn remove_context_server_settings(
 72    context_server_ids: Vec<Arc<str>>,
 73    fs: Arc<dyn Fs>,
 74    cx: &mut App,
 75) {
 76    update_settings_file::<ProjectSettings>(fs, cx, move |settings, _| {
 77        settings
 78            .context_servers
 79            .retain(|server_id, _| !context_server_ids.contains(server_id));
 80    });
 81}
 82
 83pub enum Configuration {
 84    NotAvailable(ContextServerId, Option<SharedString>),
 85    Required(
 86        ContextServerId,
 87        Option<SharedString>,
 88        ContextServerConfiguration,
 89    ),
 90}
 91
 92fn show_configure_mcp_modal(
 93    language_registry: Arc<LanguageRegistry>,
 94    manifest: &Arc<ExtensionManifest>,
 95    workspace: &mut Workspace,
 96    window: &mut Window,
 97    cx: &mut Context<'_, Workspace>,
 98) {
 99    if !window.is_window_active() {
100        return;
101    }
102
103    let context_server_store = workspace.project().read(cx).context_server_store();
104    let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());
105
106    let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
107    let worktree_store = workspace.project().read(cx).worktree_store();
108    let configuration_tasks = manifest
109        .context_servers
110        .keys()
111        .cloned()
112        .map({
113            |key| {
114                let Some(descriptor) = registry.context_server_descriptor(&key) else {
115                    return Task::ready(Configuration::NotAvailable(
116                        ContextServerId(key),
117                        repository.clone(),
118                    ));
119                };
120                cx.spawn({
121                    let repository_url = repository.clone();
122                    let worktree_store = worktree_store.clone();
123                    async move |_, cx| {
124                        let configuration = descriptor
125                            .configuration(worktree_store.clone(), &cx)
126                            .await
127                            .context("Failed to resolve context server configuration")
128                            .log_err()
129                            .flatten();
130
131                        match configuration {
132                            Some(config) => Configuration::Required(
133                                ContextServerId(key),
134                                repository_url,
135                                config,
136                            ),
137                            None => {
138                                Configuration::NotAvailable(ContextServerId(key), repository_url)
139                            }
140                        }
141                    }
142                })
143            }
144        })
145        .collect::<Vec<_>>();
146
147    let jsonc_language = language_registry.language_for_name("jsonc");
148
149    cx.spawn_in(window, async move |this, cx| {
150        let configurations = futures::future::join_all(configuration_tasks).await;
151        let jsonc_language = jsonc_language.await.ok();
152
153        this.update_in(cx, |this, window, cx| {
154            let workspace = cx.entity().downgrade();
155            this.toggle_modal(window, cx, |window, cx| {
156                ConfigureContextServerModal::new(
157                    configurations.into_iter(),
158                    context_server_store,
159                    jsonc_language,
160                    language_registry,
161                    workspace,
162                    window,
163                    cx,
164                )
165            });
166        })
167    })
168    .detach();
169}