fix: extension command with quotes in cli (#8150)

This commit is contained in:
Lifei Zhou 2026-03-27 23:37:26 +11:00 committed by GitHub
parent 88c06ab5dc
commit 98bab02aa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 72 additions and 41 deletions

View file

@ -1106,9 +1106,13 @@ fn configure_stdio_extension() -> anyhow::Result<()> {
let timeout = prompt_extension_timeout()?;
let mut parts = command_str.split_whitespace();
let cmd = parts.next().unwrap_or("").to_string();
let args: Vec<String> = parts.map(String::from).collect();
let mut parts = crate::session::split_quoted(&command_str)?;
let cmd = if parts.is_empty() {
String::new()
} else {
parts.remove(0)
};
let args = parts;
let description = prompt_extension_description()?;
let (envs, env_keys) = collect_env_vars()?;

View file

@ -231,6 +231,36 @@ pub async fn classify_planner_response(
}
}
pub fn split_quoted(input: &str) -> Result<Vec<String>> {
let mut parts = Vec::new();
let mut current = String::new();
let mut in_double_quote = false;
let mut in_single_quote = false;
for c in input.chars() {
match c {
'"' if !in_single_quote => in_double_quote = !in_double_quote,
'\'' if !in_double_quote => in_single_quote = !in_single_quote,
c if c.is_whitespace() && !in_double_quote && !in_single_quote => {
if !current.is_empty() {
parts.push(std::mem::take(&mut current));
}
}
_ => current.push(c),
}
}
if in_double_quote || in_single_quote {
return Err(anyhow::anyhow!("Unmatched quote in command"));
}
if !current.is_empty() {
parts.push(current);
}
Ok(parts)
}
impl CliSession {
#[allow(clippy::too_many_arguments)]
pub async fn new(
@ -273,7 +303,7 @@ impl CliSession {
/// Parse a stdio extension command string into an ExtensionConfig
/// Format: "ENV1=val1 ENV2=val2 command args..."
pub fn parse_stdio_extension(extension_command: &str) -> Result<ExtensionConfig> {
let mut parts: Vec<&str> = extension_command.split_whitespace().collect();
let mut parts = split_quoted(extension_command)?;
let mut envs = HashMap::new();
while let Some(part) = parts.first() {
@ -289,7 +319,7 @@ impl CliSession {
return Err(anyhow::anyhow!("No command provided in extension string"));
}
let cmd = parts.remove(0).to_string();
let cmd = parts.remove(0);
let name = std::path::Path::new(&cmd)
.file_name()
.and_then(|f| f.to_str())
@ -299,7 +329,7 @@ impl CliSession {
Ok(ExtensionConfig::Stdio {
name,
cmd,
args: parts.iter().map(|s| s.to_string()).collect(),
args: parts,
envs: Envs::new(envs),
env_keys: Vec::new(),
description: goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string(),
@ -2014,6 +2044,21 @@ mod tests {
}
; "env_prefix_name_from_cmd"
)]
#[test_case(
r#""/Applications/IntelliJ IDEA.app/Contents/jbr/Contents/Home/bin/java" -classpath "/path/with spaces/lib.jar" Main"#,
ExtensionConfig::Stdio {
name: "java".into(),
cmd: "/Applications/IntelliJ IDEA.app/Contents/jbr/Contents/Home/bin/java".into(),
args: vec!["-classpath".into(), "/path/with spaces/lib.jar".into(), "Main".into()],
envs: Envs::default(),
env_keys: vec![],
description: goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string(),
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
bundled: None,
available_tools: vec![],
}
; "quoted_path_with_spaces"
)]
fn test_parse_stdio_extension(input: &str, expected: ExtensionConfig) {
assert_eq!(CliSession::parse_stdio_extension(input).unwrap(), expected);
}
@ -2023,6 +2068,23 @@ mod tests {
assert!(CliSession::parse_stdio_extension("").is_err());
}
#[test]
fn test_split_quoted_windows_paths() {
assert_eq!(
split_quoted(r"C:\tools\mcp.exe --arg value").unwrap(),
vec![r"C:\tools\mcp.exe", "--arg", "value"]
);
assert_eq!(
split_quoted(r#""C:\Program Files\server\mcp.exe" --arg"#).unwrap(),
vec![r"C:\Program Files\server\mcp.exe", "--arg"]
);
}
#[test]
fn test_split_quoted_unmatched_quote() {
assert!(split_quoted(r#""unmatched"#).is_err());
}
#[test_case(
"https://mcp.kiwi.com", 300,
ExtensionConfig::StreamableHttp {

View file

@ -23,7 +23,6 @@ This tutorial covers how to add the JetBrains extension to integrate with any Je
Versions 2025.2 and later have built-in MCP server support and generate a dynamic configuration specific to your IDE instance. See your IDE's documentation for more details (e.g. [MCP Server](https://www.jetbrains.com/help/idea/mcp-server.html) for IntelliJ IDEA).
<!-- hide until parsing bugs for paths with spaces are fixed in Desktop and CLI
:::tip Quick Install
<Tabs groupId="interface">
<TabItem value="ui" label="goose Desktop" default>
@ -34,7 +33,6 @@ This tutorial covers how to add the JetBrains extension to integrate with any Je
</TabItem>
</Tabs>
:::
-->
<br/>
Configure the extension using your IDE's built-in MCP server support:
@ -49,10 +47,6 @@ This tutorial covers how to add the JetBrains extension to integrate with any Je
2. Add the JetBrains extension to goose using the command from the config:
:::info
If the goose Desktop or goose CLI configuration steps aren't successful, follow the `Config File` steps.
:::
<Tabs groupId="interface">
<TabItem value="ui" label="goose Desktop" default>
1. Click the <PanelLeft className="inline" size={16} /> button in the top-left to open the sidebar
@ -82,35 +76,6 @@ This tutorial covers how to add the JetBrains extension to integrate with any Je
}
/>
</TabItem>
<TabItem value="config" label="Config File">
1. Open your goose [`config.yaml`](/docs/guides/config-files) file
2. In the `extensions` section, add an entry that uses your IDE's Stdio config, for example:
```yaml
extensions:
jetbrains:
enabled: true
type: stdio
name: JetBrains
description: Integrate goose with any JetBrains IDE
cmd: /Applications/IntelliJ IDEA.app/Contents/jbr/Contents/Home/bin/java
args:
- -classpath
- /Applications/IntelliJ IDEA.app/Contents/plugins/mcpserver/lib/mcpserver-frontend.jar:/Applications/IntelliJ IDEA.app/Contents/lib/util-8.jar
- com.intellij.mcpserver.stdio.McpStdioRunnerKt
envs:
IJ_MCP_SERVER_PORT: "63342"
env_keys:
- IJ_MCP_SERVER_PORT
timeout: 300
bundled: null
available_tools: []
```
Make sure to:
- Replace the `cmd` and `args` values in the example to match your JetBrains IDE installation
- Update `IJ_MCP_SERVER_PORT` to match your IDE's MCP server port
</TabItem>
</Tabs>
</TabItem>
<TabItem value="earlier" label="2025.1 and earlier">