agent_ui: Open MCP settings UI instead of modal (#60188)
This makes it so that we show open the settings UI instead of the modal when adding an MCP server when clicking on `Add server`: <img width="240" height="320" alt="image" src="https://github.com/user-attachments/assets/bc5e9059-1053-45d5-8fd5-acfb7da48a35" /> We still show the modal when installing an extension. Since we're hopefully replacing MCP extensions with MCP registry support soon, we can leave it as is for now. Release Notes: - N/A
This commit is contained in:
1 parent
e4e2c9d3d5
commit
eef824cce5
4 files changed
+45
-226
No files matched your search
@@ -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<Editor>,
|
||||
server_type: ContextServerType,
|
||||
},
|
||||
Existing {
|
||||
editor: Entity<Editor>,
|
||||
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<LanguageRegistry>,
|
||||
@@ -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<LanguageRegistry>,
|
||||
_window: Option<&mut Window>,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) {
|
||||
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<LanguageRegistry>,
|
||||
@@ -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<DismissEvent> 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<Self>) -> Option<AnyElement> {
|
||||
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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-3
@@ -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
|
||||
|
||||
Reference in new issue
Block a user