diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index 843a35068b..6a32b48860 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -6,8 +6,7 @@ use editor::{Editor, EditorElement, EditorStyle}; use extension_host::ExtensionStore; use gpui::{ AsyncWindowContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, - Subscription, Task, TaskExt, TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, - prelude::*, + Subscription, Task, TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, prelude::*, }; use language::{Language, LanguageRegistry}; use markdown::{Markdown, MarkdownElement, MarkdownStyle}; @@ -32,12 +31,7 @@ use ui::{ use util::ResultExt as _; use workspace::{ModalView, Workspace}; -use crate::{AddContextServer, ContextServerType}; - enum ConfigurationTarget { - New { - server_type: ContextServerType, - }, Existing { id: ContextServerId, command: ContextServerCommand, @@ -56,14 +50,15 @@ enum ConfigurationTarget { }, } +enum ExistingServerType { + Local, + Remote, +} + enum ConfigurationSource { - New { - editor: Entity, - server_type: ContextServerType, - }, Existing { editor: Entity, - server_type: ContextServerType, + server_type: ExistingServerType, }, Extension { id: ContextServerId, @@ -79,10 +74,6 @@ impl ConfigurationSource { !matches!(self, ConfigurationSource::Extension { editor: None, .. }) } - fn is_new(&self) -> bool { - matches!(self, ConfigurationSource::New { .. }) - } - fn from_target( target: ConfigurationTarget, language_registry: Arc, @@ -109,18 +100,6 @@ impl ConfigurationSource { } match target { - ConfigurationTarget::New { server_type } => ConfigurationSource::New { - editor: create_editor( - match server_type { - ContextServerType::Remote => context_server_http_input(None), - ContextServerType::Local => context_server_input(None), - }, - jsonc_language, - window, - cx, - ), - server_type, - }, ConfigurationTarget::Existing { id, command } => ConfigurationSource::Existing { editor: create_editor( context_server_input(Some((id, command))), @@ -128,7 +107,7 @@ impl ConfigurationSource { window, cx, ), - server_type: ContextServerType::Local, + server_type: ExistingServerType::Local, }, ConfigurationTarget::ExistingHttp { id, @@ -142,7 +121,7 @@ impl ConfigurationSource { window, cx, ), - server_type: ContextServerType::Remote, + server_type: ExistingServerType::Remote, }, ConfigurationTarget::Extension { @@ -180,15 +159,11 @@ impl ConfigurationSource { fn output(&self, cx: &mut App) -> Result<(ContextServerId, ContextServerSettings)> { match self { - ConfigurationSource::New { - editor, - server_type, - } - | ConfigurationSource::Existing { + ConfigurationSource::Existing { editor, server_type, } => match *server_type { - ContextServerType::Remote => { + ExistingServerType::Remote => { parse_http_input(&editor.read(cx).text(cx)).map(|(id, url, auth, oauth)| { ( id, @@ -202,7 +177,7 @@ impl ConfigurationSource { ) }) } - ContextServerType::Local => { + ExistingServerType::Local => { parse_input(&editor.read(cx).text(cx)).map(|(id, command)| { ( id, @@ -459,13 +434,10 @@ impl ConfigureContextServerModal { target: &ConfigurationTarget, cx: &App, ) -> State { - let Some(server_id) = (match target { + let server_id = match target { ConfigurationTarget::Existing { id, .. } | ConfigurationTarget::ExistingHttp { id, .. } - | ConfigurationTarget::Extension { id, .. } => Some(id), - ConfigurationTarget::New { .. } => None, - }) else { - return State::Idle; + | ConfigurationTarget::Extension { id, .. } => id, }; match context_server_store.read(cx).status_for_server(server_id) { @@ -490,32 +462,6 @@ impl ConfigureContextServerModal { } } - pub fn register( - workspace: &mut Workspace, - language_registry: Arc, - _window: Option<&mut Window>, - _cx: &mut Context, - ) { - workspace.register_action({ - move |_workspace, action: &AddContextServer, window, cx| { - let workspace_handle = cx.weak_entity(); - let language_registry = language_registry.clone(); - let server_type = action.context_server_type; - window - .spawn(cx, async move |cx| { - Self::show_modal( - ConfigurationTarget::New { server_type }, - language_registry, - workspace_handle, - cx, - ) - .await - }) - .detach_and_log_err(cx); - } - }); - } - pub fn show_modal_for_existing_server( server_id: ContextServerId, language_registry: Arc, @@ -600,12 +546,11 @@ impl ConfigureContextServerModal { workspace: workspace_handle, state: Self::initial_state(&context_server_store, &target, cx), - original_server_id: match &target { - ConfigurationTarget::Existing { id, .. } => Some(id.clone()), - ConfigurationTarget::ExistingHttp { id, .. } => Some(id.clone()), - ConfigurationTarget::Extension { id, .. } => Some(id.clone()), - ConfigurationTarget::New { .. } => None, - }, + original_server_id: Some(match &target { + ConfigurationTarget::Existing { id, .. } + | ConfigurationTarget::ExistingHttp { id, .. } + | ConfigurationTarget::Extension { id, .. } => id.clone(), + }), source: ConfigurationSource::from_target( target, language_registry, @@ -835,7 +780,6 @@ impl ModalView for ConfigureContextServerModal {} impl Focusable for ConfigureContextServerModal { fn focus_handle(&self, cx: &App) -> FocusHandle { match &self.source { - ConfigurationSource::New { editor, .. } => editor.focus_handle(cx), ConfigurationSource::Existing { editor, .. } => editor.focus_handle(cx), ConfigurationSource::Extension { editor, .. } => editor .as_ref() @@ -850,7 +794,6 @@ impl EventEmitter for ConfigureContextServerModal {} impl ConfigureContextServerModal { fn render_modal_header(&self) -> ModalHeader { let text: SharedString = match &self.source { - ConfigurationSource::New { .. } => "Add MCP Server".into(), ConfigurationSource::Existing { .. } => "Configure MCP Server".into(), ConfigurationSource::Extension { id, .. } => format!("Configure {}", id.0).into(), }; @@ -881,79 +824,8 @@ impl ConfigureContextServerModal { } } - fn render_tab_bar(&self, cx: &mut Context) -> Option { - let is_http = match &self.source { - ConfigurationSource::New { server_type, .. } => { - *server_type == ContextServerType::Remote - } - _ => return None, - }; - - let tab = |label: &'static str, active: bool| { - div() - .id(label) - .cursor_pointer() - .p_1() - .text_sm() - .border_b_1() - .when_else( - active, - |this| this.border_color(cx.theme().colors().border_focused), - |this| { - this.border_color(gpui::transparent_black()) - .text_color(cx.theme().colors().text_muted) - .hover(|s| s.text_color(cx.theme().colors().text)) - }, - ) - .child(label) - }; - - Some( - h_flex() - .pt_1() - .mb_2p5() - .gap_1() - .border_b_1() - .border_color(cx.theme().colors().border.opacity(0.5)) - .child( - tab("Local", !is_http).on_click(cx.listener(|this, _, window, cx| { - if let ConfigurationSource::New { - editor, - server_type, - } = &mut this.source - && *server_type != ContextServerType::Local - { - *server_type = ContextServerType::Local; - let new_text = context_server_input(None); - editor.update(cx, |editor, cx| { - editor.set_text(new_text, window, cx); - }); - } - })), - ) - .child( - tab("Remote", is_http).on_click(cx.listener(|this, _, window, cx| { - if let ConfigurationSource::New { - editor, - server_type, - } = &mut this.source - && *server_type != ContextServerType::Remote - { - *server_type = ContextServerType::Remote; - let new_text = context_server_http_input(None); - editor.update(cx, |editor, cx| { - editor.set_text(new_text, window, cx); - }); - } - })), - ) - .into_any_element(), - ) - } - fn render_modal_content(&self, cx: &App) -> AnyElement { let editor = match &self.source { - ConfigurationSource::New { editor, .. } => editor, ConfigurationSource::Existing { editor, .. } => editor, ConfigurationSource::Extension { editor, .. } => { let Some(editor) = editor else { @@ -1053,24 +925,15 @@ impl ConfigureContextServerModal { ), ) .children(self.source.has_configuration_options().then(|| { - Button::new( - "add-server", - if self.source.is_new() { - "Add Server" - } else { - "Configure Server" - }, - ) - .disabled(is_busy) - .key_binding( - KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx) - .map(|kb| kb.size(rems_from_px(12.))), - ) - .on_click( - cx.listener(|this, _event, _window, cx| { + Button::new("configure-server", "Configure Server") + .disabled(is_busy) + .key_binding( + KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx) + .map(|kb| kb.size(rems_from_px(12.))), + ) + .on_click(cx.listener(|this, _event, _window, cx| { this.confirm(&menu::Confirm, cx) - }), - ) + })) })), ) } @@ -1278,7 +1141,6 @@ impl Render for ConfigureContextServerModal { .overflow_y_scroll() .track_scroll(&self.scroll_handle) .child(self.render_modal_description(window, cx)) - .children(self.render_tab_bar(cx)) .child(self.render_modal_content(cx)) .child(match &self.state { State::Idle => div(), diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 01b380044a..7fe2d2c0b9 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -44,19 +44,19 @@ use crate::terminal_thread_metadata_store::{ }; use crate::thread_metadata_store::{ThreadId, ThreadMetadataStore, ThreadMetadataStoreEvent}; use crate::{ - AddContextServer, AgentDiffPane, ConversationView, CopyThreadToClipboard, Follow, - LoadThreadFromClipboard, NewTerminalThread, NewThread, OpenActiveThreadAsMarkdown, - OpenAgentDiff, ResetFastModeWarnings, ResetTrialEndUpsell, ResetTrialUpsell, - ShowAllSidebarThreadMetadata, ShowThreadMetadata, ToggleNewThreadMenu, ToggleOptionsMenu, + Agent, AgentInitialContent, AgentThreadSource, ExternalSourcePrompt, NewExternalAgentThread, + NewNativeAgentThreadFromSummary, +}; +use crate::{ + AgentDiffPane, ConversationView, CopyThreadToClipboard, Follow, LoadThreadFromClipboard, + NewTerminalThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, ResetFastModeWarnings, + ResetTrialEndUpsell, ResetTrialUpsell, ShowAllSidebarThreadMetadata, ShowThreadMetadata, + ToggleNewThreadMenu, ToggleOptionsMenu, conversation_view::{ AcpThreadViewEvent, RootThreadUpdated, ThreadView, reset_fast_mode_warnings, }, ui::{AgentNotification, AgentNotificationEvent, EndTrialUpsell}, }; -use crate::{ - Agent, AgentInitialContent, AgentThreadSource, ExternalSourcePrompt, NewExternalAgentThread, - NewNativeAgentThreadFromSummary, -}; use agent_settings::AgentSettings; use ai_onboarding::AgentPanelOnboarding; use anyhow::{Context as _, Result, anyhow}; @@ -5560,7 +5560,13 @@ impl AgentPanel { if !showing_terminal { menu = menu .header("MCP Servers") - .action("Add Server…", Box::new(AddContextServer::local())) + .action( + "Add Server…", + Box::new(zed_actions::OpenSettingsAt { + path: "context_servers".to_string(), + target: None, + }), + ) .action( "Install New Servers…", Box::new(zed_actions::Extensions { diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index ad5940d21c..1949a5965a 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -67,7 +67,7 @@ use std::any::TypeId; use std::path::{Path, PathBuf}; use workspace::Workspace; -use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal}; +use crate::agent_configuration::ManageProfilesModal; pub use crate::agent_connection_store::{ActiveAcpConnection, AgentConnectionStore}; pub use crate::agent_panel::{ AgentPanel, AgentPanelEvent, AgentPanelTerminalInfo, MaxIdleRetainedThreads, TerminalId, @@ -353,49 +353,6 @@ pub struct ToggleCommandPattern { #[serde(deny_unknown_fields)] pub struct NewThread; -/// The kind of context server to configure when adding a new one. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ContextServerType { - /// A context server that runs locally via stdin/stdout. - #[default] - Local, - /// A context server that is connected to over HTTP. - Remote, -} - -/// Adds a context server to the configuration. -#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)] -#[action(namespace = agent)] -#[serde(deny_unknown_fields)] -pub struct AddContextServer { - /// The kind of context server to add. - #[serde(default)] - pub context_server_type: ContextServerType, -} - -impl Default for AddContextServer { - fn default() -> Self { - Self::local() - } -} - -impl AddContextServer { - /// Returns an action that adds a local (stdin/stdout) context server. - pub fn local() -> Self { - Self { - context_server_type: ContextServerType::Local, - } - } - - /// Returns an action that adds a remote (HTTP) context server. - pub fn remote() -> Self { - Self { - context_server_type: ContextServerType::Remote, - } - } -} - /// Creates a new external agent conversation thread. #[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)] #[action(namespace = agent)] @@ -628,16 +585,12 @@ pub fn init( init_language_model_settings(cx); } agent_panel::init(cx); - context_server_configuration::init(language_registry.clone(), fs.clone(), cx); + context_server_configuration::init(language_registry, fs.clone(), cx); thread_metadata_store::init(cx); terminal_thread_metadata_store::init(cx); inline_assistant::init(fs.clone(), prompt_builder.clone(), cx); terminal_inline_assistant::init(fs.clone(), prompt_builder, cx); - cx.observe_new(move |workspace, window, cx| { - ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx) - }) - .detach(); cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action( move |workspace: &mut Workspace, diff --git a/docs/src/ai/mcp.md b/docs/src/ai/mcp.md index 83c456be0b..777fd0e9b0 100644 --- a/docs/src/ai/mcp.md +++ b/docs/src/ai/mcp.md @@ -51,7 +51,7 @@ Popular servers available as an extension include: ### As Custom Servers Creating an extension is not the only way to use MCP servers in Zed. -You can connect both local and remote MCP servers easily utilizing the MCP server modal, which you can open by invoking the {#action agent::AddContextServer} action. Your specified configuration will create entries in your settings file (which you can open with {#action zed::OpenSettingsFile}) similar to the ones below: +You can connect both local and remote MCP servers from **Settings → AI → MCP Servers** (also accessible via the {#action agent::OpenSettings} action, then selecting `MCP Servers`). Click `Add Server` in the page header, then choose `Add Local Server` or `Add Remote Server`. Your specified configuration will create entries in your settings file (which you can open with {#action zed::OpenSettingsFile}) similar to the ones below: ```json [settings] { @@ -72,8 +72,6 @@ You can connect both local and remote MCP servers easily utilizing the MCP serve } ``` -Alternatively, you can open the modal from **Settings → AI → MCP Servers** (also accessible via the {#action agent::OpenSettings} action, then selecting `MCP Servers`) and clicking the `Add Server` button in the page header, then choosing `Add Local Server` or `Add Remote Server`. - > Note: When a remote MCP server has no configured `"Authorization"` header, Zed will prompt you to authenticate yourself against the MCP server using the standard MCP OAuth flow. ## Using MCP Servers