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}