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