Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use workspace::{ModalView, Workspace};
use crate::AddContextServer;

enum ConfigurationTarget {
New,
New { server_type: ServerType },
Existing {
id: ContextServerId,
command: ContextServerCommand,
Expand All @@ -46,6 +46,7 @@ enum ConfigurationTarget {
enum ConfigurationSource {
New {
editor: Entity<Editor>,
current_template_type: ServerType,
},
Existing {
editor: Entity<Editor>,
Expand Down Expand Up @@ -94,8 +95,9 @@ impl ConfigurationSource {
}

match target {
ConfigurationTarget::New => ConfigurationSource::New {
editor: create_editor(context_server_input(None), jsonc_language, window, cx),
ConfigurationTarget::New { server_type } => ConfigurationSource::New {
editor: create_editor(context_server_input_template(server_type), jsonc_language, window, cx),
current_template_type: server_type,
},
ConfigurationTarget::Existing { id, command } => ConfigurationSource::Existing {
editor: create_editor(
Expand Down Expand Up @@ -140,7 +142,7 @@ impl ConfigurationSource {

fn output(&self, cx: &mut App) -> Result<(ContextServerId, ContextServerSettings)> {
match self {
ConfigurationSource::New { editor } | ConfigurationSource::Existing { editor } => {
ConfigurationSource::New { editor, .. } | ConfigurationSource::Existing { editor } => {
parse_input(&editor.read(cx).text(cx)).map(|(id, command)| {
(
id,
Expand Down Expand Up @@ -211,6 +213,55 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
)
}

#[derive(Clone, Copy, PartialEq)]
enum ServerType {
Local,
Remote,
}

fn context_server_input_template(server_type: ServerType) -> String {
match server_type {
ServerType::Local => {
format!(
r#"{{
/// The name of your MCP server
"some-mcp-server": {{
/// The command which runs the MCP server
"command": "",
/// The arguments to pass to the MCP server
"args": [],
/// The environment variables to set
"env": {{}}
}}
}}"#
)
}
ServerType::Remote => {
format!(
r#"{{
/// The name of your MCP server
"some-mcp-server": {{
/// The command which runs the MCP server
"command": "npx",
/// The arguments to pass to the MCP server
"args": [
"-y",
"mcp-remote",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this doesn't seem right IMO. We should either add our own http transport or simply use the rust-sdk official lib.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the benefit here is it allows people to use any transport if needed as sometimes you'll need special behaviour baked into a custom transport layer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah also the main purpose of this is to guide users into a quick workaround for remote servers until built-in support is added.

"https://mcp.atlassian.com/v1/sse",
"--header",
"Authorization:${{AUTH_HEADER}}" // note no spaces around ':'
],
/// The environment variables to set
"env": {{
"AUTH_HEADER": "Bearer <auth-token>" // spaces OK in env vars
}}
}}
}}"#
)
}
}
}

fn resolve_context_server_extension(
id: ContextServerId,
worktree_store: Entity<WorktreeStore>,
Expand Down Expand Up @@ -262,13 +313,14 @@ impl ConfigureContextServerModal {
_cx: &mut Context<Workspace>,
) {
workspace.register_action({
let language_registry = language_registry.clone();
move |_workspace, _: &AddContextServer, window, cx| {
let workspace_handle = cx.weak_entity();
let language_registry = language_registry.clone();
window
.spawn(cx, async move |cx| {
Self::show_modal(
ConfigurationTarget::New,
ConfigurationTarget::New { server_type: ServerType::Local },
language_registry,
workspace_handle,
cx,
Expand Down Expand Up @@ -352,7 +404,7 @@ impl ConfigureContextServerModal {
original_server_id: match &target {
ConfigurationTarget::Existing { id, .. } => Some(id.clone()),
ConfigurationTarget::Extension { id, .. } => Some(id.clone()),
ConfigurationTarget::New => None,
ConfigurationTarget::New { .. } => None,
},
source: ConfigurationSource::from_target(
target,
Expand Down Expand Up @@ -418,7 +470,7 @@ impl ConfigureContextServerModal {
ProjectSettings::get_global(cx).context_servers.get(&id.0) != Some(&settings);

if settings_changed {
// When we write the settings to the file, the context server will be restarted.
// When we write the settings to the file, the context server will be restarted automatically.
workspace.update(cx, |workspace, cx| {
let fs = workspace.app_state().fs.clone();
let original_server_id = self.original_server_id.clone();
Expand All @@ -445,6 +497,26 @@ impl ConfigureContextServerModal {
cx.emit(DismissEvent);
}

fn switch_template(&mut self, new_type: ServerType, cx: &mut Context<Self>) {
if let ConfigurationSource::New { editor, current_template_type } = &mut self.source {
if *current_template_type != new_type {
*current_template_type = new_type;

// Update the editor content with the new template
let new_template = context_server_input_template(new_type);
editor.update(cx, |editor, cx| {
editor.buffer().update(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
let range = 0..snapshot.len();
buffer.edit([(range, new_template)], None, cx);
});
});

cx.notify();
}
}
}

fn show_configured_context_server_toast(&self, id: ContextServerId, cx: &mut App) {
self.workspace
.update(cx, {
Expand Down Expand Up @@ -479,7 +551,7 @@ 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::New { editor, .. } => editor.focus_handle(cx),
ConfigurationSource::Existing { editor, .. } => editor.focus_handle(cx),
ConfigurationSource::Extension { editor, .. } => editor
.as_ref()
Expand Down Expand Up @@ -524,9 +596,9 @@ impl ConfigureContextServerModal {
}
}

fn render_modal_content(&self, cx: &App) -> AnyElement {
fn render_modal_content(&self, cx: &mut Context<Self>) -> AnyElement {
let editor = match &self.source {
ConfigurationSource::New { editor } => editor,
ConfigurationSource::New { editor, .. } => editor,
ConfigurationSource::Existing { editor } => editor,
ConfigurationSource::Extension { editor, .. } => {
let Some(editor) = editor else {
Expand All @@ -536,37 +608,85 @@ impl ConfigureContextServerModal {
}
};

div()
.p_2()
.rounded_md()
.border_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};
EditorElement::new(
editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
syntax: cx.theme().syntax().clone(),
..Default::default()
},
)
})
v_flex()
.gap_2()
.when(
matches!(&self.source, ConfigurationSource::New { .. }),
|content| content.child(self.render_template_switcher(cx))
)
.child(
div()
.p_2()
.rounded_md()
.border_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};
EditorElement::new(
editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
syntax: cx.theme().syntax().clone(),
..Default::default()
},
)
})
)
.into_any_element()
}

fn render_template_switcher(&self, cx: &mut Context<Self>) -> impl IntoElement {
let current_type = if let ConfigurationSource::New { current_template_type, .. } = &self.source {
*current_template_type
} else {
ServerType::Local // fallback, shouldn't happen
};

h_flex()
.gap_2()
.child(
Label::new("Template:")
.size(LabelSize::Small)
.color(Color::Muted)
)
.child(
Button::new("template-local", "Local Server")
.style(if current_type == ServerType::Local {
ButtonStyle::Filled
} else {
ButtonStyle::Subtle
})
.size(ButtonSize::Compact)
.on_click(cx.listener(|this, _event, _window, cx| {
this.switch_template(ServerType::Local, cx);
}))
)
.child(
Button::new("template-remote", "Remote Server")
.style(if current_type == ServerType::Remote {
ButtonStyle::Filled
} else {
ButtonStyle::Subtle
})
.size(ButtonSize::Compact)
.on_click(cx.listener(|this, _event, _window, cx| {
this.switch_template(ServerType::Remote, cx);
}))
)
}

fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
let focus_handle = self.focus_handle(cx);
let is_connecting = matches!(self.state, State::Waiting);
Expand Down
Loading