assistant2: Add context server list to configuration view (#27028)

Marshall Bowers created

This PR adds a context server list to the configuration view in
Assistant2:

<img width="1394" alt="Screenshot 2025-03-18 at 5 26 23 PM"
src="https://github.com/user-attachments/assets/58bf3920-1e35-4cb8-a32a-5ae9f98ce387"
/>

Release Notes:

- N/A

Change summary

crates/assistant2/src/assistant_configuration.rs | 51 ++++++++++++++++-
crates/assistant2/src/assistant_panel.rs         |  5 +
crates/assistant2/src/thread_store.rs            |  4 +
3 files changed, 56 insertions(+), 4 deletions(-)

Detailed changes

crates/assistant2/src/assistant_configuration.rs 🔗

@@ -1,19 +1,25 @@
 use std::sync::Arc;
 
 use collections::HashMap;
-use gpui::{Action, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
+use context_server::manager::ContextServerManager;
+use gpui::{Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription};
 use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
-use ui::{prelude::*, Divider, DividerColor, ElevationIndex};
+use ui::{prelude::*, Divider, DividerColor, ElevationIndex, Indicator};
 use zed_actions::assistant::DeployPromptLibrary;
 
 pub struct AssistantConfiguration {
     focus_handle: FocusHandle,
     configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
+    context_server_manager: Entity<ContextServerManager>,
     _registry_subscription: Subscription,
 }
 
 impl AssistantConfiguration {
-    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
+    pub fn new(
+        context_server_manager: Entity<ContextServerManager>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Self {
         let focus_handle = cx.focus_handle();
 
         let registry_subscription = cx.subscribe_in(
@@ -36,6 +42,7 @@ impl AssistantConfiguration {
         let mut this = Self {
             focus_handle,
             configuration_views_by_provider: HashMap::default(),
+            context_server_manager,
             _registry_subscription: registry_subscription,
         };
         this.build_provider_configuration_views(window, cx);
@@ -143,6 +150,42 @@ impl AssistantConfiguration {
                     }),
             )
     }
+
+    fn render_context_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
+        let context_servers = self.context_server_manager.read(cx).servers().clone();
+
+        const SUBHEADING: &str = "Connect to context servers via the Model Context Protocol either via Zed extensions or directly.";
+
+        v_flex()
+            .p(DynamicSpacing::Base16.rems(cx))
+            .mt_1()
+            .gap_6()
+            .flex_1()
+            .child(
+                v_flex()
+                    .gap_0p5()
+                    .child(Headline::new("Context Servers (MCP)").size(HeadlineSize::Small))
+                    .child(Label::new(SUBHEADING).color(Color::Muted)),
+            )
+            .children(context_servers.into_iter().map(|context_server| {
+                let is_running = context_server.client().is_some();
+
+                h_flex()
+                    .gap_2()
+                    .px_2()
+                    .py_1()
+                    .border_1()
+                    .rounded_sm()
+                    .border_color(cx.theme().colors().border)
+                    .bg(cx.theme().colors().editor_background)
+                    .child(Indicator::dot().color(if is_running {
+                        Color::Success
+                    } else {
+                        Color::Error
+                    }))
+                    .child(Label::new(context_server.id()))
+            }))
+    }
 }
 
 impl Render for AssistantConfiguration {
@@ -182,6 +225,8 @@ impl Render for AssistantConfiguration {
                     ),
             )
             .child(Divider::horizontal().color(DividerColor::Border))
+            .child(self.render_context_servers_section(cx))
+            .child(Divider::horizontal().color(DividerColor::Border))
             .child(
                 v_flex()
                     .p(DynamicSpacing::Base16.rems(cx))

crates/assistant2/src/assistant_panel.rs 🔗

@@ -415,8 +415,11 @@ impl AssistantPanel {
     }
 
     pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        let context_server_manager = self.thread_store.read(cx).context_server_manager();
+
         self.active_view = ActiveView::Configuration;
-        self.configuration = Some(cx.new(|cx| AssistantConfiguration::new(window, cx)));
+        self.configuration =
+            Some(cx.new(|cx| AssistantConfiguration::new(context_server_manager, window, cx)));
 
         if let Some(configuration) = self.configuration.as_ref() {
             self.configuration_subscription = Some(cx.subscribe_in(

crates/assistant2/src/thread_store.rs 🔗

@@ -65,6 +65,10 @@ impl ThreadStore {
         Ok(this)
     }
 
+    pub fn context_server_manager(&self) -> Entity<ContextServerManager> {
+        self.context_server_manager.clone()
+    }
+
     /// Returns the number of threads.
     pub fn thread_count(&self) -> usize {
         self.threads.len()