diff --git a/Cargo.lock b/Cargo.lock index 053c989970..38d73288a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10924,6 +10924,7 @@ dependencies = [ "anyhow", "collections", "convert_case 0.11.0", + "indoc", "log", "pretty_assertions", "serde_json", diff --git a/crates/migrator/Cargo.toml b/crates/migrator/Cargo.toml index 1ba6f704d0..a531873d3d 100644 --- a/crates/migrator/Cargo.toml +++ b/crates/migrator/Cargo.toml @@ -26,5 +26,6 @@ settings_content.workspace = true settings_json = { workspace = true, features = ["editing"] } [dev-dependencies] +indoc.workspace = true pretty_assertions.workspace = true unindent.workspace = true diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index 97240d5592..5592449c87 100644 --- a/crates/migrator/src/migrations.rs +++ b/crates/migrator/src/migrations.rs @@ -197,12 +197,6 @@ pub(crate) mod m_2025_05_08 { pub(crate) use settings::SETTINGS_PATTERNS; } -pub(crate) mod m_2025_06_16 { - mod settings; - - pub(crate) use settings::SETTINGS_PATTERNS; -} - pub(crate) mod m_2025_06_25 { mod settings; diff --git a/crates/migrator/src/migrations/m_2025_06_16/settings.rs b/crates/migrator/src/migrations/m_2025_06_16/settings.rs deleted file mode 100644 index 1fe490287e..0000000000 --- a/crates/migrator/src/migrations/m_2025_06_16/settings.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::ops::Range; - -use tree_sitter::{Query, QueryMatch}; - -use crate::MigrationPatterns; - -pub const SETTINGS_PATTERNS: MigrationPatterns = &[( - SETTINGS_CONTEXT_SERVER_PATTERN, - migrate_context_server_settings, -)]; - -const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document - (object - (pair - key: (string (string_content) @context-servers) - value: (object - (pair - key: (string (string_content) @server-name) - value: (object) @server-settings - ) - ) - ) - ) - (#eq? @context-servers "context_servers") -)"#; - -fn migrate_context_server_settings( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let server_settings_index = query.capture_index_for_name("server-settings")?; - let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?; - - let mut has_command = false; - let mut has_settings = false; - let mut has_url = false; - let mut other_keys = 0; - let mut column = None; - - // Parse the server settings to check what keys it contains - let mut cursor = server_settings.walk(); - for child in server_settings.children(&mut cursor) { - if child.kind() == "pair" - && let Some(key_node) = child.child_by_field_name("key") - { - if let (None, Some(quote_content)) = (column, key_node.child(0)) { - column = Some(quote_content.start_position().column); - } - if let Some(string_content) = key_node.child(1) { - let key = &contents[string_content.byte_range()]; - match key { - // If it already has a source key, don't modify it - "source" => return None, - "command" => has_command = true, - "settings" => has_settings = true, - "url" => has_url = true, - _ => other_keys += 1, - } - } - } - } - - let source_type = if has_command { "custom" } else { "extension" }; - - // Insert the source key at the beginning of the object - let start = server_settings.start_byte() + 1; - let indent = " ".repeat(column.unwrap_or(12)); - - if !has_command && !has_settings && !has_url { - return Some(( - start..start, - format!( - r#" -{indent}"source": "{}", -{indent}"settings": {{}}{} - "#, - source_type, - if other_keys > 0 { "," } else { "" } - ), - )); - } - - Some(( - start..start, - format!( - r#" -{indent}"source": "{}","#, - source_type - ), - )) -} diff --git a/crates/migrator/src/migrations/m_2025_06_27/settings.rs b/crates/migrator/src/migrations/m_2025_06_27/settings.rs index e3e951b1a6..d675e7b2e9 100644 --- a/crates/migrator/src/migrations/m_2025_06_27/settings.rs +++ b/crates/migrator/src/migrations/m_2025_06_27/settings.rs @@ -16,10 +16,6 @@ const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document (pair key: (string (string_content) @server-name) value: (object - (pair - key: (string (string_content) @source-key) - value: (string (string_content) @source-value) - ) (pair key: (string (string_content) @command-key) value: (object) @command-object @@ -30,8 +26,6 @@ const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document ) ) (#eq? @context-servers "context_servers") - (#eq? @source-key "source") - (#eq? @source-value "custom") (#eq? @command-key "command") )"#; diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index a5cf349e9e..acf5289e0c 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -194,10 +194,6 @@ pub fn migrate_settings(text: &str) -> Result> { migrations::m_2025_05_08::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_05_08, ), - MigrationType::TreeSitter( - migrations::m_2025_06_16::SETTINGS_PATTERNS, - &SETTINGS_QUERY_2025_06_16, - ), MigrationType::TreeSitter( migrations::m_2025_06_25::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_25, @@ -353,10 +349,6 @@ define_query!( SETTINGS_QUERY_2025_05_08, migrations::m_2025_05_08::SETTINGS_PATTERNS ); -define_query!( - SETTINGS_QUERY_2025_06_16, - migrations::m_2025_06_16::SETTINGS_PATTERNS -); define_query!( SETTINGS_QUERY_2025_06_25, migrations::m_2025_06_25::SETTINGS_PATTERNS @@ -418,6 +410,7 @@ static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock = LazyLock::new #[cfg(test)] mod tests { use super::*; + use indoc::indoc; use unindent::Unindent as _; #[track_caller] @@ -997,207 +990,6 @@ mod tests { ); } - #[test] - fn test_mcp_settings_migration() { - assert_migrate_with_migrations( - &[MigrationType::TreeSitter( - migrations::m_2025_06_16::SETTINGS_PATTERNS, - &SETTINGS_QUERY_2025_06_16, - )], - r#"{ - "context_servers": { - "empty_server": {}, - "extension_server": { - "settings": { - "foo": "bar" - } - }, - "custom_server": { - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - } - }, - "invalid_server": { - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, - "settings": { - "foo": "bar" - } - }, - "empty_server2": {}, - "extension_server2": { - "foo": "bar", - "settings": { - "foo": "bar" - }, - "bar": "foo" - }, - "custom_server2": { - "foo": "bar", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, - "bar": "foo" - }, - "invalid_server2": { - "foo": "bar", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, - "bar": "foo", - "settings": { - "foo": "bar" - } - } - } -}"#, - Some( - r#"{ - "context_servers": { - "empty_server": { - "source": "extension", - "settings": {} - }, - "extension_server": { - "source": "extension", - "settings": { - "foo": "bar" - } - }, - "custom_server": { - "source": "custom", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - } - }, - "invalid_server": { - "source": "custom", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, - "settings": { - "foo": "bar" - } - }, - "empty_server2": { - "source": "extension", - "settings": {} - }, - "extension_server2": { - "source": "extension", - "foo": "bar", - "settings": { - "foo": "bar" - }, - "bar": "foo" - }, - "custom_server2": { - "source": "custom", - "foo": "bar", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, - "bar": "foo" - }, - "invalid_server2": { - "source": "custom", - "foo": "bar", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, - "bar": "foo", - "settings": { - "foo": "bar" - } - } - } -}"#, - ), - ); - } - - #[test] - fn test_mcp_settings_migration_doesnt_change_valid_settings() { - let settings = r#"{ - "context_servers": { - "empty_server": { - "source": "extension", - "settings": {} - }, - "extension_server": { - "source": "extension", - "settings": { - "foo": "bar" - } - }, - "custom_server": { - "source": "custom", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - } - }, - "invalid_server": { - "source": "custom", - "command": { - "path": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, - "settings": { - "foo": "bar" - } - } - } -}"#; - assert_migrate_with_migrations( - &[MigrationType::TreeSitter( - migrations::m_2025_06_16::SETTINGS_PATTERNS, - &SETTINGS_QUERY_2025_06_16, - )], - settings, - None, - ); - } - #[test] fn test_custom_agent_server_settings_migration() { assert_migrate_with_migrations( @@ -1324,6 +1116,44 @@ mod tests { ); } + #[test] + fn test_flatten_context_server_command_alongside_source() { + assert_migrate_settings( + indoc! {r#" + { + "context_servers": { + "custom_server": { + "source": "custom", + "command": { + "path": "npx", + "args": ["-y", "some-mcp-server"] + } + }, + "hand_edited_server": { + "source": "extension", + "command": { + "path": "other-server" + } + } + } + } + "#}, + Some(indoc! {r#" + { + "context_servers": { + "custom_server": { + "command": "npx", + "args": ["-y", "some-mcp-server"] + }, + "hand_edited_server": { + "command": "other-server" + } + } + } + "#}), + ); + } + #[test] fn test_flatten_context_server_command() { assert_migrate_settings( @@ -5096,48 +4926,32 @@ mod tests { } #[test] - fn test_mcp_settings_migration_adds_settings_to_extension_servers() { + fn test_context_server_types_report_no_migration() { assert_migrate_settings( - r#"{ - "context_servers": { - "extension_server": {}, - "stdio_server": { - "command": "npx", - "args": ["-y", "some-server"] - }, - "http_server": { - "url": "https://example.com/mcp" - }, - "http_server_with_headers": { - "url": "https://example.com/mcp", - "headers": { - "Authorization": "Bearer token" - } - } - } -}"#, - Some( - r#"{ - "context_servers": { - "extension_server": { - "settings": {} - }, - "stdio_server": { - "command": "npx", - "args": ["-y", "some-server"] - }, - "http_server": { - "url": "https://example.com/mcp" - }, - "http_server_with_headers": { - "url": "https://example.com/mcp", - "headers": { - "Authorization": "Bearer token" - } - } - } -}"#, - ), + indoc! {r#" + { + "context_servers": { + "extension_server": {}, + "disabled_extension_server": { + "enabled": false + }, + "stdio_server": { + "command": "npx", + "args": ["-y", "some-server"] + }, + "http_server": { + "url": "https://example.com/mcp" + }, + "http_server_with_headers": { + "url": "https://example.com/mcp", + "headers": { + "Authorization": "Bearer token" + } + } + } + } + "#}, + None, ); } @@ -5483,6 +5297,46 @@ mod tests { assert_migrate_settings(&r#"{ "theme": "One Dark" }"#.unindent(), None); } + #[test] + fn test_url_only_context_servers_are_left_alone() { + assert_migrate_settings( + indoc! {r#" + { + "context_servers": { "grep": { "url": "https://mcp.grep.app" } } + } + "#}, + None, + ); + + assert_migrate_settings( + indoc! {r#" + { + "context_servers": { + "grep": { "url": "https://mcp.grep.app" }, + "local": { + "source": "custom", + "command": { + "path": "npx", + "args": ["-y", "some-mcp-server"] + } + } + } + } + "#}, + Some(indoc! {r#" + { + "context_servers": { + "grep": { "url": "https://mcp.grep.app" }, + "local": { + "command": "npx", + "args": ["-y", "some-mcp-server"] + } + } + } + "#}), + ) + } + #[test] fn test_rename_folder_icons_to_folder_indicator_in_all_panels() { assert_migrate_with_migrations( diff --git a/crates/settings_content/src/project.rs b/crates/settings_content/src/project.rs index fc5eb11c2d..0335a26499 100644 --- a/crates/settings_content/src/project.rs +++ b/crates/settings_content/src/project.rs @@ -470,6 +470,7 @@ pub enum ContextServerSettingsContent { /// /// Consult the documentation for the context server to see what settings /// are supported. + #[serde(default)] settings: serde_json::Value, }, }