Fix MCP settings migration continually adding the same key (#32848)

Release Notes:

- N/A
This commit is contained in:
Ben Brandt 2025-06-17 11:32:08 +02:00 committed by GitHub
parent 2f3acb6185
commit c4355d2905
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 103 additions and 123 deletions

View file

@ -4,149 +4,87 @@ use tree_sitter::{Query, QueryMatch};
use crate::MigrationPatterns;
pub const SETTINGS_PATTERNS: MigrationPatterns = &[
(
SETTINGS_CUSTOM_CONTEXT_SERVER_PATTERN,
migrate_custom_context_server_settings,
),
(
SETTINGS_EXTENSION_CONTEXT_SERVER_PATTERN,
migrate_extension_context_server_settings,
),
(
SETTINGS_EMPTY_CONTEXT_SERVER_PATTERN,
migrate_empty_context_server_settings,
),
];
pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
SETTINGS_CONTEXT_SERVER_PATTERN,
migrate_context_server_settings,
)];
const SETTINGS_CUSTOM_CONTEXT_SERVER_PATTERN: &str = r#"(document
const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document
(object
(pair
key: (string (string_content) @context-servers)
value: (object
(pair
key: (string)
value: (object
(pair
key: (string (string_content) @previous-key)
value: (object)
)*
(pair
key: (string (string_content) @key)
value: (object)
)
(pair
key: (string (string_content) @next-key)
value: (object)
)*
) @server-settings
)
)
)
)
(#eq? @context-servers "context_servers")
(#eq? @key "command")
(#not-eq? @previous-key "source")
(#not-eq? @next-key "source")
)"#;
fn migrate_custom_context_server_settings(
_contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let server_settings_index = query.capture_index_for_name("server-settings")?;
let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
// Move forward 1 to get inside the object
let start = server_settings.start_byte() + 1;
Some((
start..start,
r#"
"source": "custom","#
.to_string(),
))
}
const SETTINGS_EXTENSION_CONTEXT_SERVER_PATTERN: &str = r#"(document
(object
(pair
key: (string (string_content) @context-servers)
value: (object
(pair
key: (string)
value: (object
(pair
key: (string (string_content) @previous-key)
value: (object)
)*
(pair
key: (string (string_content) @key)
value: (object)
)
(pair
key: (string (string_content) @next-key)
value: (object)
)*
) @server-settings
)
)
)
)
(#eq? @context-servers "context_servers")
(#eq? @key "settings")
(#not-match? @previous-key "^command|source$")
(#not-match? @next-key "^command|source$")
)"#;
fn migrate_extension_context_server_settings(
_contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let server_settings_index = query.capture_index_for_name("server-settings")?;
let server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
// Move forward 1 to get inside the object
let start = server_settings.start_byte() + 1;
Some((
start..start,
r#"
"source": "extension","#
.to_string(),
))
}
const SETTINGS_EMPTY_CONTEXT_SERVER_PATTERN: &str = r#"(document
(object
(pair
key: (string (string_content) @context-servers)
value: (object
(pair
key: (string)
key: (string (string_content) @server-name)
value: (object) @server-settings
)
)
)
)
(#eq? @context-servers "context_servers")
(#eq? @server-settings "{}")
)"#;
fn migrate_empty_context_server_settings(
_contents: &str,
fn migrate_context_server_settings(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, 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 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" {
if 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,
_ => 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 {
return Some((
start..start,
format!(
r#"
{indent}"source": "{}",
{indent}"settings": {{}}{}
"#,
source_type,
if other_keys > 0 { "," } else { "" }
),
));
}
Some((
server_settings.byte_range(),
r#"{
"source": "extension",
"settings": {}
}"#
.to_string(),
start..start,
format!(
r#"
{indent}"source": "{}","#,
source_type
),
))
}

View file

@ -1010,4 +1010,46 @@ mod tests {
),
);
}
#[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_settings(settings, None);
}
}