From 32f075eb13229f85a31022fb76242537267c7b60 Mon Sep 17 00:00:00 2001 From: dcieslak19973 Date: Fri, 4 Jul 2025 01:34:38 -0500 Subject: [PATCH 1/5] Add support in goose configure for streaming http mcp tools --- crates/goose-cli/src/commands/configure.rs | 132 ++++++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 526aa4477163..701ae0bf1da0 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -498,8 +498,13 @@ pub fn configure_extensions_dialog() -> Result<(), Box> { ) .item( "sse", - "Remote Extension", - "Connect to a remote extension via SSE", + "Remote Extension (SSE)", + "Connect to a remote extension via Server-Sent Events", + ) + .item( + "streamable_http", + "Remote Extension (Streaming HTTP)", + "Connect to a remote extension via MCP Streaming HTTP", ) .interact()?; @@ -767,6 +772,129 @@ pub fn configure_extensions_dialog() -> Result<(), Box> { cliclack::outro(format!("Added {} extension", style(name).green()))?; } + "streamable_http" => { + let extensions = ExtensionConfigManager::get_all_names()?; + let name: String = cliclack::input("What would you like to call this extension?") + .placeholder("my-remote-extension") + .validate(move |input: &String| { + if input.is_empty() { + Err("Please enter a name") + } else if extensions.contains(input) { + Err("An extension with this name already exists") + } else { + Ok(()) + } + }) + .interact()?; + + let uri: String = cliclack::input("What is the Streaming HTTP endpoint URI?") + .placeholder("http://localhost:8000/messages") + .validate(|input: &String| { + if input.is_empty() { + Err("Please enter a URI") + } else if !input.starts_with("http") { + Err("URI should start with http:// or https://") + } else { + Ok(()) + } + }) + .interact()?; + + let timeout: u64 = cliclack::input("Please set the timeout for this tool (in secs):") + .placeholder(&goose::config::DEFAULT_EXTENSION_TIMEOUT.to_string()) + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => Err("Please enter a valid timeout"), + }) + .interact()?; + + let add_desc = cliclack::confirm("Would you like to add a description?").interact()?; + + let description = if add_desc { + let desc = cliclack::input("Enter a description for this extension:") + .placeholder("Description") + .validate(|input: &String| match input.parse::() { + Ok(_) => Ok(()), + Err(_) => Err("Please enter a valid description"), + }) + .interact()?; + Some(desc) + } else { + None + }; + + let add_headers = cliclack::confirm("Would you like to add custom headers?").interact()?; + + let mut headers = HashMap::new(); + if add_headers { + loop { + let key: String = cliclack::input("Header name:") + .placeholder("Authorization") + .interact()?; + + let value: String = cliclack::input("Header value:") + .placeholder("Bearer token123") + .interact()?; + + headers.insert(key, value); + + if !cliclack::confirm("Add another header?").interact()? { + break; + } + } + } + + let add_env = cliclack::confirm("Would you like to add environment variables?").interact()?; + + let mut envs = HashMap::new(); + let mut env_keys = Vec::new(); + let config = Config::global(); + + if add_env { + loop { + let key: String = cliclack::input("Environment variable name:") + .placeholder("API_KEY") + .interact()?; + + let value: String = cliclack::password("Environment variable value:") + .mask('▪') + .interact()?; + + // Try to store in keychain + let keychain_key = key.to_string(); + match config.set_secret(&keychain_key, Value::String(value.clone())) { + Ok(_) => { + // Successfully stored in keychain, add to env_keys + env_keys.push(keychain_key); + } + Err(_) => { + // Failed to store in keychain, store directly in envs + envs.insert(key, value); + } + } + + if !cliclack::confirm("Add another environment variable?").interact()? { + break; + } + } + } + + ExtensionConfigManager::set(ExtensionEntry { + enabled: true, + config: ExtensionConfig::StreamableHttp { + name: name.clone(), + uri, + envs: Envs::new(envs), + env_keys, + headers, + description, + timeout: Some(timeout), + bundled: None, + }, + })?; + + cliclack::outro(format!("Added {} extension", style(name).green()))?; + } _ => unreachable!(), }; From 578fb4ebf0d1a2d32647878657762b0b5fea6bf2 Mon Sep 17 00:00:00 2001 From: dcieslak19973 Date: Fri, 4 Jul 2025 12:37:28 -0500 Subject: [PATCH 2/5] Update crates/goose-cli/src/commands/configure.rs Stronger validation on http input Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/goose-cli/src/commands/configure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 701ae0bf1da0..206ca5bb48a0 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -792,7 +792,7 @@ pub fn configure_extensions_dialog() -> Result<(), Box> { .validate(|input: &String| { if input.is_empty() { Err("Please enter a URI") - } else if !input.starts_with("http") { + } else if !(input.starts_with("http://") || input.starts_with("https://")) { Err("URI should start with http:// or https://") } else { Ok(()) From b5aa7d10a560476db0f1f6ce8c5f95966d4aff16 Mon Sep 17 00:00:00 2001 From: dcieslak19973 Date: Fri, 4 Jul 2025 12:38:19 -0500 Subject: [PATCH 3/5] Update crates/goose-cli/src/commands/configure.rs Improve validation Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/goose-cli/src/commands/configure.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 206ca5bb48a0..17dfaa5df0b8 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -813,9 +813,12 @@ pub fn configure_extensions_dialog() -> Result<(), Box> { let description = if add_desc { let desc = cliclack::input("Enter a description for this extension:") .placeholder("Description") - .validate(|input: &String| match input.parse::() { - Ok(_) => Ok(()), - Err(_) => Err("Please enter a valid description"), + .validate(|input: &String| { + if input.trim().is_empty() { + Err("Please enter a valid description") + } else { + Ok(()) + } }) .interact()?; Some(desc) From fb5a496f92b9dd1962567fd2e13067ca0510c65c Mon Sep 17 00:00:00 2001 From: dcieslak19973 Date: Fri, 4 Jul 2025 20:09:32 -0500 Subject: [PATCH 4/5] cli supports Streaming HTTP MCP from goose config --- crates/goose-cli/src/commands/configure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 701ae0bf1da0..0970b17eecab 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -844,7 +844,7 @@ pub fn configure_extensions_dialog() -> Result<(), Box> { } } - let add_env = cliclack::confirm("Would you like to add environment variables?").interact()?; + let add_env = false; // No env prompt for Streaming HTTP let mut envs = HashMap::new(); let mut env_keys = Vec::new(); From 20f1f34d1e2157944cd2e244549e3a3bf37bff37 Mon Sep 17 00:00:00 2001 From: dcieslak19973 Date: Mon, 7 Jul 2025 09:26:06 -0500 Subject: [PATCH 5/5] Ran cargo fmt --- crates/goose-cli/src/commands/configure.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 7b7df2888cf9..0fa5cc475e26 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -826,7 +826,8 @@ pub fn configure_extensions_dialog() -> Result<(), Box> { None }; - let add_headers = cliclack::confirm("Would you like to add custom headers?").interact()?; + let add_headers = + cliclack::confirm("Would you like to add custom headers?").interact()?; let mut headers = HashMap::new(); if add_headers {