From f1792dea241480d611d6bcd83c22f3fd93021e22 Mon Sep 17 00:00:00 2001 From: Chaitanya Rahalkar Date: Tue, 24 Jun 2025 09:26:44 -0500 Subject: [PATCH 1/2] feat: add elicitation support for Goose MCP --- crates/mcp-client/src/client.rs | 82 ++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/crates/mcp-client/src/client.rs b/crates/mcp-client/src/client.rs index 5cade18d66ee..635d48f3a844 100644 --- a/crates/mcp-client/src/client.rs +++ b/crates/mcp-client/src/client.rs @@ -66,9 +66,23 @@ pub struct ClientInfo { pub version: String, } -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize)] pub struct ClientCapabilities { - // Add fields as needed. For now, empty capabilities are fine. + /// Advertise support for elicitation so that MCP servers can issue + /// `elicitation/create` requests. Currently `goose` automatically + /// rejects such requests, but declaring the capability prevents + /// protocol negotiations from failing. + #[serde(skip_serializing_if = "Option::is_none")] + pub elicitation: Option, +} + +// Provide a manual Default impl to include the elicitation capability +impl Default for ClientCapabilities { + fn default() -> Self { + Self { + elicitation: Some(serde_json::json!({})), + } + } } #[derive(Serialize, Deserialize)] @@ -135,6 +149,70 @@ where Ok(message) => { tracing::info!("Received message: {:?}", message); match message { + // Interactive handling for `elicitation/create`. + JsonRpcMessage::Request(ref req) if req.method == "elicitation/create" => { + // Clone pieces we need before we await. + let id_opt = req.id.clone(); + let params_val = req.params.clone().unwrap_or_default(); + + // Spawn a blocking task so we don't block the async runtime while waiting on stdin. + let user_response = tokio::task::spawn_blocking(move || { + use std::io::{self, Write}; + println!("\n--- ELICITATION REQUEST ------------------------------------------------"); + if let Some(msg) = params_val.get("message").and_then(|v| v.as_str()) { + println!("{}", msg); + } + + // Show requested schema for transparency + if let Some(schema) = params_val.get("requestedSchema") { + println!("Requested schema: {}", schema); + } + + println!("Enter JSON value to accept, or type 'reject' to deny, 'cancel' to cancel:"); + print!("> "); + let _ = io::stdout().flush(); + + let mut input = String::new(); + if let Err(e) = io::stdin().read_line(&mut input) { + eprintln!("Failed to read input: {}", e); + return serde_json::json!({"action": "reject"}); + } + let trimmed = input.trim(); + if trimmed.eq_ignore_ascii_case("reject") { + return serde_json::json!({"action": "reject"}); + } + if trimmed.eq_ignore_ascii_case("cancel") { + return serde_json::json!({"action": "cancel"}); + } + + // Try parse JSON + match serde_json::from_str::(trimmed) { + Ok(v) => serde_json::json!({"action": "accept", "content": v}), + Err(_) => { + // treat as simple string + serde_json::json!({"action": "accept", "content": trimmed}) + } + } + }).await.unwrap_or_else(|_| serde_json::json!({"action": "reject"})); + + if let Some(id) = id_opt { + let response = JsonRpcMessage::Response(JsonRpcResponse { + jsonrpc: "2.0".to_string(), + id: Some(id), + result: Some(user_response), + error: None, + }); + + if let Err(e) = transport.send(response).await { + tracing::error!(error = ?e, "Failed to send elicitation response"); + service_ptr.hangup(e).await; + } + } + + // Forward to subscribers as before + let mut subs = subscribers_ptr.lock().await; + subs.retain(|sub| sub.try_send(message.clone()).is_ok()); + } JsonRpcMessage::Response(JsonRpcResponse { id: Some(id), .. }) | JsonRpcMessage::Error(JsonRpcError { id: Some(id), .. }) => { service_ptr.respond(&id.to_string(), Ok(message)).await; From b6502b484ba7e4bc11195a0ce4d63007fecd1b9c Mon Sep 17 00:00:00 2001 From: Chaitanya Rahalkar Date: Tue, 24 Jun 2025 09:28:19 -0500 Subject: [PATCH 2/2] rust formatting --- crates/mcp-client/src/client.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/mcp-client/src/client.rs b/crates/mcp-client/src/client.rs index 635d48f3a844..efaabb214a3d 100644 --- a/crates/mcp-client/src/client.rs +++ b/crates/mcp-client/src/client.rs @@ -150,7 +150,9 @@ where tracing::info!("Received message: {:?}", message); match message { // Interactive handling for `elicitation/create`. - JsonRpcMessage::Request(ref req) if req.method == "elicitation/create" => { + JsonRpcMessage::Request(ref req) + if req.method == "elicitation/create" => + { // Clone pieces we need before we await. let id_opt = req.id.clone(); let params_val = req.params.clone().unwrap_or_default();