Skip to content

Commit 493d6c9

Browse files
committed
translate ACP host capabilities into MCP initialization
Signed-off-by: Andrew Harvard <aharvard@squareup.com>
1 parent bd14186 commit 493d6c9

13 files changed

Lines changed: 354 additions & 71 deletions

File tree

crates/goose-acp/src/server.rs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use fs_err as fs;
66
use futures::future::BoxFuture;
77
use goose::acp::{PermissionDecision, ACP_CURRENT_MODEL};
88
use goose::agents::extension::{Envs, PLATFORM_EXTENSIONS};
9-
use goose::agents::mcp_client::McpClientTrait;
9+
use goose::agents::mcp_client::{GooseMcpHostInfo, McpClientTrait};
1010
use goose::agents::platform_extensions::developer::DeveloperClient;
1111
use goose::agents::{Agent, AgentConfig, ExtensionConfig, GoosePlatform, SessionConfig};
1212
use goose::builtin_extension::register_builtin_extensions;
@@ -47,6 +47,7 @@ use sacp::{
4747
Agent as SacpAgent, ByteStreams, Client, ConnectionTo, Dispatch, HandleDispatchFrom, Handled,
4848
Responder,
4949
};
50+
use serde::Deserialize;
5051
use std::collections::HashMap;
5152
use std::sync::Arc;
5253
use strum::{EnumMessage, VariantNames};
@@ -120,12 +121,14 @@ pub struct GooseAcpAgent {
120121
builtins: Vec<String>,
121122
client_fs_capabilities: OnceCell<FileSystemCapabilities>,
122123
client_terminal: OnceCell<bool>,
124+
client_mcp_host_info: OnceCell<GooseMcpHostInfo>,
123125
config_dir: std::path::PathBuf,
124126
session_manager: Arc<SessionManager>,
125127
thread_manager: Arc<goose::session::ThreadManager>,
126128
permission_manager: Arc<PermissionManager>,
127129
goose_mode: GooseMode,
128130
disable_session_naming: bool,
131+
goose_platform: GoosePlatform,
129132
}
130133

131134
fn extract_timeout_from_meta(meta: &Option<Meta>) -> Option<u64> {
@@ -134,6 +137,52 @@ fn extract_timeout_from_meta(meta: &Option<Meta>) -> Option<u64> {
134137
.and_then(|v| v.as_u64())
135138
}
136139

140+
#[derive(Debug, Default, Deserialize)]
141+
struct GooseClientMetaEnvelope {
142+
#[serde(default)]
143+
goose: Option<GooseClientMeta>,
144+
}
145+
146+
#[derive(Debug, Default, Deserialize)]
147+
struct GooseClientMeta {
148+
#[serde(rename = "mcpHostCapabilities", default)]
149+
mcp_host_capabilities: Option<GooseMcpHostCapabilities>,
150+
}
151+
152+
#[derive(Debug, Default, Deserialize)]
153+
struct GooseMcpHostCapabilities {
154+
#[serde(default)]
155+
extensions: Option<rmcp::model::ExtensionCapabilities>,
156+
}
157+
158+
fn extract_goose_client_meta(meta: &Meta) -> Option<GooseClientMetaEnvelope> {
159+
serde_json::from_value(serde_json::Value::Object(meta.clone())).ok()
160+
}
161+
162+
fn extract_client_mcp_host_info(args: &InitializeRequest) -> GooseMcpHostInfo {
163+
let host_capabilities = args
164+
.client_capabilities
165+
.meta
166+
.as_ref()
167+
.and_then(extract_goose_client_meta)
168+
.and_then(|meta| meta.goose)
169+
.and_then(|goose| goose.mcp_host_capabilities);
170+
let explicit_extensions = host_capabilities
171+
.as_ref()
172+
.and_then(|capabilities| capabilities.extensions.as_ref())
173+
.is_some();
174+
let extensions = host_capabilities
175+
.and_then(|capabilities| capabilities.extensions)
176+
.unwrap_or_default();
177+
178+
GooseMcpHostInfo {
179+
explicit_extensions,
180+
extensions,
181+
client_name: args.client_info.as_ref().map(|info| info.name.clone()),
182+
client_version: args.client_info.as_ref().map(|info| info.version.clone()),
183+
}
184+
}
185+
137186
fn mcp_server_to_extension_config(mcp_server: McpServer) -> Result<ExtensionConfig, String> {
138187
match mcp_server {
139188
McpServer::Stdio(stdio) => {
@@ -608,6 +657,7 @@ impl GooseAcpAgent {
608657
config_dir: std::path::PathBuf,
609658
goose_mode: GooseMode,
610659
disable_session_naming: bool,
660+
goose_platform: GoosePlatform,
611661
) -> Result<Self> {
612662
let session_manager = Arc::new(SessionManager::new(data_dir));
613663
let thread_manager = Arc::new(goose::session::ThreadManager::new(
@@ -621,12 +671,14 @@ impl GooseAcpAgent {
621671
builtins,
622672
client_fs_capabilities: OnceCell::new(),
623673
client_terminal: OnceCell::new(),
674+
client_mcp_host_info: OnceCell::new(),
624675
config_dir,
625676
session_manager,
626677
thread_manager,
627678
permission_manager,
628679
goose_mode,
629680
disable_session_naming,
681+
goose_platform,
630682
})
631683
}
632684

@@ -672,19 +724,24 @@ impl GooseAcpAgent {
672724
.cloned()
673725
.unwrap_or_default();
674726
let client_terminal = self.client_terminal.get().copied().unwrap_or(false);
727+
let client_mcp_host_info = self.client_mcp_host_info.get().cloned();
675728
let provider_factory = Arc::clone(&self.provider_factory);
676729
let disable_session_naming = self.disable_session_naming;
730+
let goose_platform = self.goose_platform.clone();
677731

678732
tokio::spawn(async move {
679733
let result: Result<(), String> = async {
680-
let agent = Arc::new(Agent::with_config(AgentConfig::new(
681-
session_manager,
682-
permission_manager,
683-
None,
684-
goose_mode,
685-
disable_session_naming,
686-
GoosePlatform::GooseCli,
687-
)));
734+
let agent = Arc::new(Agent::with_config(
735+
AgentConfig::new(
736+
session_manager,
737+
permission_manager,
738+
None,
739+
goose_mode,
740+
disable_session_naming,
741+
goose_platform,
742+
)
743+
.with_mcp_host_info(client_mcp_host_info),
744+
));
688745

689746
let config_path = config_dir.join(CONFIG_YAML_NAME);
690747
let mut extensions = Config::new(&config_path, "goose")
@@ -1236,6 +1293,9 @@ impl GooseAcpAgent {
12361293
.client_fs_capabilities
12371294
.set(args.client_capabilities.fs.clone());
12381295
let _ = self.client_terminal.set(args.client_capabilities.terminal);
1296+
let _ = self
1297+
.client_mcp_host_info
1298+
.set(extract_client_mcp_host_info(&args));
12391299

12401300
let capabilities = AgentCapabilities::new()
12411301
.load_session(true)
@@ -2901,6 +2961,7 @@ pub async fn run(builtins: Vec<String>) -> Result<()> {
29012961
builtins,
29022962
data_dir: Paths::data_dir(),
29032963
config_dir: Paths::config_dir(),
2964+
goose_platform: GoosePlatform::GooseCli,
29042965
});
29052966
let agent = server.create_agent().await?;
29062967
serve(agent, incoming, outgoing).await

crates/goose-acp/src/server_factory.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::Result;
2+
use goose::agents::GoosePlatform;
23
use std::sync::Arc;
34
use tracing::info;
45

@@ -8,6 +9,7 @@ pub struct AcpServerFactoryConfig {
89
pub builtins: Vec<String>,
910
pub data_dir: std::path::PathBuf,
1011
pub config_dir: std::path::PathBuf,
12+
pub goose_platform: GoosePlatform,
1113
}
1214

1315
pub struct AcpServer {
@@ -45,6 +47,7 @@ impl AcpServer {
4547
self.config.config_dir.clone(),
4648
goose_mode,
4749
disable_session_naming,
50+
self.config.goose_platform.clone(),
4851
)
4952
.await?;
5053
info!("Created new ACP agent");

crates/goose-acp/tests/fixtures/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use async_trait::async_trait;
55
use fs_err as fs;
66
pub use goose::acp::{map_permission_response, PermissionDecision};
7+
use goose::agents::GoosePlatform;
78
use goose::builtin_extension::register_builtin_extensions;
89
use goose::config::paths::Paths;
910
use goose::config::{GooseMode, PermissionManager};
@@ -190,6 +191,7 @@ pub async fn spawn_acp_server_in_process(
190191
data_root.to_path_buf(),
191192
goose_mode,
192193
true,
194+
GoosePlatform::GooseCli,
193195
)
194196
.await
195197
.unwrap();

crates/goose-cli/src/cli.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Result;
22
use clap::{Args, CommandFactory, Parser, Subcommand};
33
use clap_complete::{generate, Shell as ClapShell};
4+
use goose::agents::GoosePlatform;
45
use goose::builtin_extension::register_builtin_extensions;
56
use goose::config::{Config, GooseMode};
67
#[cfg(feature = "telemetry")]
@@ -1075,11 +1076,20 @@ async fn handle_serve_command(host: String, port: u16, builtins: Vec<String>) ->
10751076
} else {
10761077
builtins
10771078
};
1079+
let goose_platform = match std::env::var("GOOSE_PLATFORM").ok().as_deref() {
1080+
Some("desktop") | Some("goose-desktop") => GoosePlatform::GooseDesktop,
1081+
Some("cli") | Some("goose-cli") | None => GoosePlatform::GooseCli,
1082+
Some(other) => {
1083+
warn!("Unknown GOOSE_PLATFORM value '{other}', defaulting ACP server to goose-cli");
1084+
GoosePlatform::GooseCli
1085+
}
1086+
};
10781087

10791088
let server = Arc::new(AcpServer::new(AcpServerFactoryConfig {
10801089
builtins,
10811090
data_dir: Paths::data_dir(),
10821091
config_dir: Paths::config_dir(),
1092+
goose_platform,
10831093
}));
10841094
let router = goose_acp::transport::create_router(server);
10851095

crates/goose/src/agents/agent.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use uuid::Uuid;
1212

1313
use super::container::Container;
1414
use super::final_output_tool::FinalOutputTool;
15+
use super::mcp_client::GooseMcpHostInfo;
1516
use super::platform_tools;
1617
use super::tool_confirmation_router::ToolConfirmationRouter;
1718
use super::tool_execution::{ToolCallResult, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE};
@@ -113,6 +114,7 @@ pub struct AgentConfig {
113114
pub goose_mode: GooseMode,
114115
pub disable_session_naming: bool,
115116
pub goose_platform: GoosePlatform,
117+
pub mcp_host_info: Option<GooseMcpHostInfo>,
116118
}
117119

118120
impl AgentConfig {
@@ -131,8 +133,14 @@ impl AgentConfig {
131133
goose_mode,
132134
disable_session_naming,
133135
goose_platform,
136+
mcp_host_info: None,
134137
}
135138
}
139+
140+
pub fn with_mcp_host_info(mut self, mcp_host_info: Option<GooseMcpHostInfo>) -> Self {
141+
self.mcp_host_info = mcp_host_info;
142+
self
143+
}
136144
}
137145

138146
/// The main goose Agent
@@ -222,10 +230,23 @@ impl Agent {
222230

223231
let goose_platform = config.goose_platform.clone();
224232
let initial_mode = config.goose_mode;
225-
let capabilities = match config.goose_platform {
226-
GoosePlatform::GooseDesktop => ExtensionManagerCapabilities { mcpui: true },
227-
GoosePlatform::GooseCli => ExtensionManagerCapabilities { mcpui: false },
233+
let explicit_mcp_host_info = config.mcp_host_info.clone();
234+
let mcpui = explicit_mcp_host_info
235+
.as_ref()
236+
.filter(|host_info| host_info.explicit_extensions)
237+
.map(GooseMcpHostInfo::mcpui_enabled)
238+
.unwrap_or_else(|| match config.goose_platform {
239+
GoosePlatform::GooseDesktop => true,
240+
GoosePlatform::GooseCli => false,
241+
});
242+
let capabilities = ExtensionManagerCapabilities {
243+
mcpui,
244+
host_info: explicit_mcp_host_info.clone(),
228245
};
246+
let client_name = explicit_mcp_host_info
247+
.as_ref()
248+
.and_then(|host_info| host_info.client_name.clone())
249+
.unwrap_or_else(|| goose_platform.to_string());
229250
let session_manager = Arc::clone(&config.session_manager);
230251
let permission_manager = Arc::clone(&config.permission_manager);
231252
Self {
@@ -235,7 +256,7 @@ impl Agent {
235256
extension_manager: Arc::new(ExtensionManager::new(
236257
provider.clone(),
237258
session_manager,
238-
goose_platform.to_string(),
259+
client_name,
239260
capabilities,
240261
)),
241262
final_output_tool: Arc::new(Mutex::new(None)),

0 commit comments

Comments
 (0)