Skip to content

Commit f908837

Browse files
authored
[goose2] MCP Apps: translate ACP host capabilities into MCP initialization (#8623)
Signed-off-by: Andrew Harvard <aharvard@squareup.com>
1 parent 52b3f21 commit f908837

13 files changed

Lines changed: 344 additions & 68 deletions

File tree

crates/goose-cli/src/cli.rs

Lines changed: 2 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")]
@@ -1083,6 +1084,7 @@ async fn handle_serve_command(host: String, port: u16, builtins: Vec<String>) ->
10831084
builtins,
10841085
data_dir: Paths::data_dir(),
10851086
config_dir: Paths::config_dir(),
1087+
goose_platform: GoosePlatform::GooseCli,
10861088
}));
10871089
let router = create_router(server);
10881090

crates/goose/src/acp/server.rs

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::acp::fs::AcpTools;
33
use crate::acp::tools::AcpAwareToolMeta;
44
use crate::acp::{PermissionDecision, ACP_CURRENT_MODEL};
55
use crate::agents::extension::{Envs, PLATFORM_EXTENSIONS};
6-
use crate::agents::mcp_client::McpClientTrait;
6+
use crate::agents::mcp_client::{GooseMcpHostInfo, McpClientTrait};
77
use crate::agents::platform_extensions::developer::DeveloperClient;
88
use crate::agents::{Agent, AgentConfig, ExtensionConfig, GoosePlatform, SessionConfig};
99
use crate::config::base::CONFIG_YAML_NAME;
@@ -59,6 +59,7 @@ use sacp::{
5959
Agent as SacpAgent, ByteStreams, Client, ConnectionTo, Dispatch, HandleDispatchFrom, Handled,
6060
Responder,
6161
};
62+
use serde::Deserialize;
6263
use std::collections::HashMap;
6364
use std::sync::Arc;
6465
use strum::{EnumMessage, VariantNames};
@@ -182,13 +183,15 @@ pub struct GooseAcpAgent {
182183
builtins: Vec<String>,
183184
client_fs_capabilities: OnceCell<FileSystemCapabilities>,
184185
client_terminal: OnceCell<bool>,
186+
client_mcp_host_info: OnceCell<GooseMcpHostInfo>,
185187
config_dir: std::path::PathBuf,
186188
session_manager: Arc<SessionManager>,
187189
thread_manager: Arc<crate::session::ThreadManager>,
188190
permission_manager: Arc<PermissionManager>,
189191
goose_mode: GooseMode,
190192
disable_session_naming: bool,
191193
provider_inventory: ProviderInventoryService,
194+
goose_platform: GoosePlatform,
192195
}
193196

194197
/// Shorten a session/thread id for perf log correlation.
@@ -253,6 +256,52 @@ fn extract_timeout_from_meta(meta: &Option<Meta>) -> Option<u64> {
253256
.and_then(|v| v.as_u64())
254257
}
255258

259+
#[derive(Debug, Default, Deserialize)]
260+
struct GooseClientMetaEnvelope {
261+
#[serde(default)]
262+
goose: Option<GooseClientMeta>,
263+
}
264+
265+
#[derive(Debug, Default, Deserialize)]
266+
struct GooseClientMeta {
267+
#[serde(rename = "mcpHostCapabilities", default)]
268+
mcp_host_capabilities: Option<GooseMcpHostCapabilities>,
269+
}
270+
271+
#[derive(Debug, Default, Deserialize)]
272+
struct GooseMcpHostCapabilities {
273+
#[serde(default)]
274+
extensions: Option<rmcp::model::ExtensionCapabilities>,
275+
}
276+
277+
fn extract_goose_client_meta(meta: &Meta) -> Option<GooseClientMetaEnvelope> {
278+
serde_json::from_value(serde_json::Value::Object(meta.clone())).ok()
279+
}
280+
281+
fn extract_client_mcp_host_info(args: &InitializeRequest) -> GooseMcpHostInfo {
282+
let host_capabilities = args
283+
.client_capabilities
284+
.meta
285+
.as_ref()
286+
.and_then(extract_goose_client_meta)
287+
.and_then(|meta| meta.goose)
288+
.and_then(|goose| goose.mcp_host_capabilities);
289+
let explicit_extensions = host_capabilities
290+
.as_ref()
291+
.and_then(|capabilities| capabilities.extensions.as_ref())
292+
.is_some();
293+
let extensions = host_capabilities
294+
.and_then(|capabilities| capabilities.extensions)
295+
.unwrap_or_default();
296+
297+
GooseMcpHostInfo {
298+
explicit_extensions,
299+
extensions,
300+
client_name: args.client_info.as_ref().map(|info| info.name.clone()),
301+
client_version: args.client_info.as_ref().map(|info| info.version.clone()),
302+
}
303+
}
304+
256305
fn mcp_server_to_extension_config(mcp_server: McpServer) -> Result<ExtensionConfig, String> {
257306
match mcp_server {
258307
McpServer::Stdio(stdio) => {
@@ -795,6 +844,7 @@ impl GooseAcpAgent {
795844
config_dir: std::path::PathBuf,
796845
goose_mode: GooseMode,
797846
disable_session_naming: bool,
847+
goose_platform: GoosePlatform,
798848
) -> Result<Self> {
799849
let session_manager = Arc::new(SessionManager::new(data_dir));
800850
let thread_manager = Arc::new(crate::session::ThreadManager::new(
@@ -809,13 +859,15 @@ impl GooseAcpAgent {
809859
builtins,
810860
client_fs_capabilities: OnceCell::new(),
811861
client_terminal: OnceCell::new(),
862+
client_mcp_host_info: OnceCell::new(),
812863
config_dir,
813864
session_manager,
814865
thread_manager,
815866
permission_manager,
816867
goose_mode,
817868
disable_session_naming,
818869
provider_inventory,
870+
goose_platform,
819871
})
820872
}
821873

@@ -986,12 +1038,14 @@ impl GooseAcpAgent {
9861038
.cloned()
9871039
.unwrap_or_default();
9881040
let client_terminal = self.client_terminal.get().copied().unwrap_or(false);
1041+
let client_mcp_host_info = self.client_mcp_host_info.get().cloned();
9891042
let provider_factory = Arc::clone(&self.provider_factory);
9901043
let disable_session_naming = self.disable_session_naming;
1044+
let goose_platform = self.goose_platform.clone();
9911045

9921046
tokio::spawn(async move {
9931047
let t_setup = std::time::Instant::now();
994-
1048+
debug!(target: "perf", sid = %sid, "perf: agent_setup start (background)");
9951049
// Shared config — read once, used by both phases.
9961050
let config = match Config::new(config_dir.join(CONFIG_YAML_NAME), "goose") {
9971051
Ok(c) => c,
@@ -1005,14 +1059,17 @@ impl GooseAcpAgent {
10051059

10061060
// ── Phase 1: create agent + init provider (fast, ~55ms) ──────
10071061
let phase1: Result<Arc<Agent>, String> = async {
1008-
let agent = Arc::new(Agent::with_config(AgentConfig::new(
1009-
session_manager,
1010-
permission_manager,
1011-
None,
1012-
goose_mode,
1013-
disable_session_naming,
1014-
GoosePlatform::GooseCli,
1015-
)));
1062+
let agent = Arc::new(Agent::with_config(
1063+
AgentConfig::new(
1064+
session_manager,
1065+
permission_manager,
1066+
None,
1067+
goose_mode,
1068+
disable_session_naming,
1069+
goose_platform,
1070+
)
1071+
.with_mcp_host_info(client_mcp_host_info),
1072+
));
10161073

10171074
// Init provider — reuse the pre-resolved name + model when
10181075
// available (already computed in on_new_session), otherwise
@@ -1627,6 +1684,9 @@ impl GooseAcpAgent {
16271684
.client_fs_capabilities
16281685
.set(args.client_capabilities.fs.clone());
16291686
let _ = self.client_terminal.set(args.client_capabilities.terminal);
1687+
let _ = self
1688+
.client_mcp_host_info
1689+
.set(extract_client_mcp_host_info(&args));
16301690

16311691
let capabilities = AgentCapabilities::new()
16321692
.load_session(true)
@@ -3962,6 +4022,7 @@ pub async fn run(builtins: Vec<String>) -> Result<()> {
39624022
builtins,
39634023
data_dir: Paths::data_dir(),
39644024
config_dir: Paths::config_dir(),
4025+
goose_platform: GoosePlatform::GooseCli,
39654026
},
39664027
);
39674028
let agent = server.create_agent().await?;

crates/goose/src/acp/server_factory.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::acp::server::{AcpProviderFactory, GooseAcpAgent};
2+
use crate::agents::GoosePlatform;
23
use anyhow::Result;
34
use std::sync::Arc;
45
use tracing::info;
@@ -7,6 +8,7 @@ pub struct AcpServerFactoryConfig {
78
pub builtins: Vec<String>,
89
pub data_dir: std::path::PathBuf,
910
pub config_dir: std::path::PathBuf,
11+
pub goose_platform: GoosePlatform,
1012
}
1113

1214
pub struct AcpServer {
@@ -44,6 +46,7 @@ impl AcpServer {
4446
self.config.config_dir.clone(),
4547
goose_mode,
4648
disable_session_naming,
49+
self.config.goose_platform.clone(),
4750
)
4851
.await?;
4952
info!("Created new ACP agent");

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};
@@ -114,6 +115,7 @@ pub struct AgentConfig {
114115
pub goose_mode: GooseMode,
115116
pub disable_session_naming: bool,
116117
pub goose_platform: GoosePlatform,
118+
pub mcp_host_info: Option<GooseMcpHostInfo>,
117119
}
118120

119121
impl AgentConfig {
@@ -132,8 +134,14 @@ impl AgentConfig {
132134
goose_mode,
133135
disable_session_naming,
134136
goose_platform,
137+
mcp_host_info: None,
135138
}
136139
}
140+
141+
pub fn with_mcp_host_info(mut self, mcp_host_info: Option<GooseMcpHostInfo>) -> Self {
142+
self.mcp_host_info = mcp_host_info;
143+
self
144+
}
137145
}
138146

139147
/// The main goose Agent
@@ -223,10 +231,23 @@ impl Agent {
223231

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

0 commit comments

Comments
 (0)