[goose2] MCP Apps: translate ACP host capabilities into MCP initialization (#8623)

Signed-off-by: Andrew Harvard <aharvard@squareup.com>
This commit is contained in:
Andrew Harvard
2026-04-28 08:03:44 -04:00
committed by GitHub
parent 52b3f21ef7
commit f908837407
13 changed files with 345 additions and 69 deletions
+2
View File
@@ -1,6 +1,7 @@
use anyhow::Result;
use clap::{Args, CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Shell as ClapShell};
use goose::agents::GoosePlatform;
use goose::builtin_extension::register_builtin_extensions;
use goose::config::{Config, GooseMode};
#[cfg(feature = "telemetry")]
@@ -1083,6 +1084,7 @@ async fn handle_serve_command(host: String, port: u16, builtins: Vec<String>) ->
builtins,
data_dir: Paths::data_dir(),
config_dir: Paths::config_dir(),
goose_platform: GoosePlatform::GooseCli,
}));
let router = create_router(server);
+71 -10
View File
@@ -3,7 +3,7 @@ use crate::acp::fs::AcpTools;
use crate::acp::tools::AcpAwareToolMeta;
use crate::acp::{PermissionDecision, ACP_CURRENT_MODEL};
use crate::agents::extension::{Envs, PLATFORM_EXTENSIONS};
use crate::agents::mcp_client::McpClientTrait;
use crate::agents::mcp_client::{GooseMcpHostInfo, McpClientTrait};
use crate::agents::platform_extensions::developer::DeveloperClient;
use crate::agents::{Agent, AgentConfig, ExtensionConfig, GoosePlatform, SessionConfig};
use crate::config::base::CONFIG_YAML_NAME;
@@ -59,6 +59,7 @@ use sacp::{
Agent as SacpAgent, ByteStreams, Client, ConnectionTo, Dispatch, HandleDispatchFrom, Handled,
Responder,
};
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;
use strum::{EnumMessage, VariantNames};
@@ -182,6 +183,7 @@ pub struct GooseAcpAgent {
builtins: Vec<String>,
client_fs_capabilities: OnceCell<FileSystemCapabilities>,
client_terminal: OnceCell<bool>,
client_mcp_host_info: OnceCell<GooseMcpHostInfo>,
config_dir: std::path::PathBuf,
session_manager: Arc<SessionManager>,
thread_manager: Arc<crate::session::ThreadManager>,
@@ -189,6 +191,7 @@ pub struct GooseAcpAgent {
goose_mode: GooseMode,
disable_session_naming: bool,
provider_inventory: ProviderInventoryService,
goose_platform: GoosePlatform,
}
/// Shorten a session/thread id for perf log correlation.
@@ -253,6 +256,52 @@ fn extract_timeout_from_meta(meta: &Option<Meta>) -> Option<u64> {
.and_then(|v| v.as_u64())
}
#[derive(Debug, Default, Deserialize)]
struct GooseClientMetaEnvelope {
#[serde(default)]
goose: Option<GooseClientMeta>,
}
#[derive(Debug, Default, Deserialize)]
struct GooseClientMeta {
#[serde(rename = "mcpHostCapabilities", default)]
mcp_host_capabilities: Option<GooseMcpHostCapabilities>,
}
#[derive(Debug, Default, Deserialize)]
struct GooseMcpHostCapabilities {
#[serde(default)]
extensions: Option<rmcp::model::ExtensionCapabilities>,
}
fn extract_goose_client_meta(meta: &Meta) -> Option<GooseClientMetaEnvelope> {
serde_json::from_value(serde_json::Value::Object(meta.clone())).ok()
}
fn extract_client_mcp_host_info(args: &InitializeRequest) -> GooseMcpHostInfo {
let host_capabilities = args
.client_capabilities
.meta
.as_ref()
.and_then(extract_goose_client_meta)
.and_then(|meta| meta.goose)
.and_then(|goose| goose.mcp_host_capabilities);
let explicit_extensions = host_capabilities
.as_ref()
.and_then(|capabilities| capabilities.extensions.as_ref())
.is_some();
let extensions = host_capabilities
.and_then(|capabilities| capabilities.extensions)
.unwrap_or_default();
GooseMcpHostInfo {
explicit_extensions,
extensions,
client_name: args.client_info.as_ref().map(|info| info.name.clone()),
client_version: args.client_info.as_ref().map(|info| info.version.clone()),
}
}
fn mcp_server_to_extension_config(mcp_server: McpServer) -> Result<ExtensionConfig, String> {
match mcp_server {
McpServer::Stdio(stdio) => {
@@ -795,6 +844,7 @@ impl GooseAcpAgent {
config_dir: std::path::PathBuf,
goose_mode: GooseMode,
disable_session_naming: bool,
goose_platform: GoosePlatform,
) -> Result<Self> {
let session_manager = Arc::new(SessionManager::new(data_dir));
let thread_manager = Arc::new(crate::session::ThreadManager::new(
@@ -809,6 +859,7 @@ impl GooseAcpAgent {
builtins,
client_fs_capabilities: OnceCell::new(),
client_terminal: OnceCell::new(),
client_mcp_host_info: OnceCell::new(),
config_dir,
session_manager,
thread_manager,
@@ -816,6 +867,7 @@ impl GooseAcpAgent {
goose_mode,
disable_session_naming,
provider_inventory,
goose_platform,
})
}
@@ -986,12 +1038,14 @@ impl GooseAcpAgent {
.cloned()
.unwrap_or_default();
let client_terminal = self.client_terminal.get().copied().unwrap_or(false);
let client_mcp_host_info = self.client_mcp_host_info.get().cloned();
let provider_factory = Arc::clone(&self.provider_factory);
let disable_session_naming = self.disable_session_naming;
let goose_platform = self.goose_platform.clone();
tokio::spawn(async move {
let t_setup = std::time::Instant::now();
debug!(target: "perf", sid = %sid, "perf: agent_setup start (background)");
// Shared config — read once, used by both phases.
let config = match Config::new(config_dir.join(CONFIG_YAML_NAME), "goose") {
Ok(c) => c,
@@ -1005,14 +1059,17 @@ impl GooseAcpAgent {
// ── Phase 1: create agent + init provider (fast, ~55ms) ──────
let phase1: Result<Arc<Agent>, String> = async {
let agent = Arc::new(Agent::with_config(AgentConfig::new(
session_manager,
permission_manager,
None,
goose_mode,
disable_session_naming,
GoosePlatform::GooseCli,
)));
let agent = Arc::new(Agent::with_config(
AgentConfig::new(
session_manager,
permission_manager,
None,
goose_mode,
disable_session_naming,
goose_platform,
)
.with_mcp_host_info(client_mcp_host_info),
));
// Init provider — reuse the pre-resolved name + model when
// available (already computed in on_new_session), otherwise
@@ -1627,6 +1684,9 @@ impl GooseAcpAgent {
.client_fs_capabilities
.set(args.client_capabilities.fs.clone());
let _ = self.client_terminal.set(args.client_capabilities.terminal);
let _ = self
.client_mcp_host_info
.set(extract_client_mcp_host_info(&args));
let capabilities = AgentCapabilities::new()
.load_session(true)
@@ -3962,6 +4022,7 @@ pub async fn run(builtins: Vec<String>) -> Result<()> {
builtins,
data_dir: Paths::data_dir(),
config_dir: Paths::config_dir(),
goose_platform: GoosePlatform::GooseCli,
},
);
let agent = server.create_agent().await?;
+3
View File
@@ -1,4 +1,5 @@
use crate::acp::server::{AcpProviderFactory, GooseAcpAgent};
use crate::agents::GoosePlatform;
use anyhow::Result;
use std::sync::Arc;
use tracing::info;
@@ -7,6 +8,7 @@ pub struct AcpServerFactoryConfig {
pub builtins: Vec<String>,
pub data_dir: std::path::PathBuf,
pub config_dir: std::path::PathBuf,
pub goose_platform: GoosePlatform,
}
pub struct AcpServer {
@@ -44,6 +46,7 @@ impl AcpServer {
self.config.config_dir.clone(),
goose_mode,
disable_session_naming,
self.config.goose_platform.clone(),
)
.await?;
info!("Created new ACP agent");
+25 -4
View File
@@ -12,6 +12,7 @@ use uuid::Uuid;
use super::container::Container;
use super::final_output_tool::FinalOutputTool;
use super::mcp_client::GooseMcpHostInfo;
use super::platform_tools;
use super::tool_confirmation_router::ToolConfirmationRouter;
use super::tool_execution::{ToolCallResult, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE};
@@ -114,6 +115,7 @@ pub struct AgentConfig {
pub goose_mode: GooseMode,
pub disable_session_naming: bool,
pub goose_platform: GoosePlatform,
pub mcp_host_info: Option<GooseMcpHostInfo>,
}
impl AgentConfig {
@@ -132,8 +134,14 @@ impl AgentConfig {
goose_mode,
disable_session_naming,
goose_platform,
mcp_host_info: None,
}
}
pub fn with_mcp_host_info(mut self, mcp_host_info: Option<GooseMcpHostInfo>) -> Self {
self.mcp_host_info = mcp_host_info;
self
}
}
/// The main goose Agent
@@ -223,10 +231,23 @@ impl Agent {
let goose_platform = config.goose_platform.clone();
let initial_mode = config.goose_mode;
let capabilities = match config.goose_platform {
GoosePlatform::GooseDesktop => ExtensionManagerCapabilities { mcpui: true },
GoosePlatform::GooseCli => ExtensionManagerCapabilities { mcpui: false },
let explicit_mcp_host_info = config.mcp_host_info.clone();
let mcpui = explicit_mcp_host_info
.as_ref()
.filter(|host_info| host_info.explicit_extensions)
.map(GooseMcpHostInfo::mcpui_enabled)
.unwrap_or_else(|| match config.goose_platform {
GoosePlatform::GooseDesktop => true,
GoosePlatform::GooseCli => false,
});
let capabilities = ExtensionManagerCapabilities {
mcpui,
host_info: explicit_mcp_host_info.clone(),
};
let client_name = explicit_mcp_host_info
.as_ref()
.and_then(|host_info| host_info.client_name.clone())
.unwrap_or_else(|| goose_platform.to_string());
let session_manager = Arc::clone(&config.session_manager);
let permission_manager = Arc::clone(&config.permission_manager);
Self {
@@ -236,7 +257,7 @@ impl Agent {
extension_manager: Arc::new(ExtensionManager::new(
provider.clone(),
session_manager,
goose_platform.to_string(),
client_name,
capabilities,
)),
final_output_tool: Arc::new(Mutex::new(None)),
+20 -26
View File
@@ -34,7 +34,9 @@ use super::tool_execution::{ToolCallContext, ToolCallResult};
use super::types::SharedProvider;
use crate::agents::extension::{Envs, ProcessExit};
use crate::agents::extension_malware_check;
use crate::agents::mcp_client::{GooseMcpClientCapabilities, McpClient, McpClientTrait};
use crate::agents::mcp_client::{
GooseMcpClientCapabilities, GooseMcpHostInfo, McpClient, McpClientTrait,
};
use crate::builtin_extension::get_builtin_extension;
use crate::config::extensions::name_to_key;
use crate::config::search_path::SearchPaths;
@@ -116,6 +118,7 @@ impl Extension {
pub struct ExtensionManagerCapabilities {
pub mcpui: bool,
pub host_info: Option<GooseMcpHostInfo>,
}
/// Manages goose extensions / MCP clients and their interactions
@@ -591,6 +594,13 @@ async fn create_unix_socket_http_client(
}
impl ExtensionManager {
fn mcp_client_capabilities(&self) -> GooseMcpClientCapabilities {
GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
host_info: self.capabilities.host_info.clone(),
}
}
pub fn new(
provider: SharedProvider,
session_manager: Arc<crate::session::SessionManager>,
@@ -618,7 +628,10 @@ impl ExtensionManager {
Arc::new(Mutex::new(None)),
session_manager,
"goose-cli".to_string(),
ExtensionManagerCapabilities { mcpui: false },
ExtensionManagerCapabilities {
mcpui: false,
host_info: None,
},
)
}
@@ -697,10 +710,6 @@ impl ExtensionManager {
.map(|(k, v)| (k.clone(), substitute_env_vars(v, &all_envs)))
.collect();
let resolved_socket = socket.as_ref().map(|s| substitute_env_vars(s, &all_envs));
let capability = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
create_streamable_http_client(
&resolved_uri,
*timeout,
@@ -709,7 +718,7 @@ impl ExtensionManager {
resolved_socket.as_deref(),
self.provider.clone(),
self.client_name.clone(),
capability,
self.mcp_client_capabilities(),
&effective_working_dir,
)
.await?
@@ -760,10 +769,6 @@ impl ExtensionManager {
.arg(&normalized_name);
});
let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
let client = child_process_client(
command,
&Some(timeout_secs),
@@ -771,7 +776,7 @@ impl ExtensionManager {
&effective_working_dir,
Some(container_id.to_string()),
self.client_name.clone(),
capabilities,
self.mcp_client_capabilities(),
)
.await?;
Box::new(client)
@@ -780,17 +785,13 @@ impl ExtensionManager {
let (client_read, server_write) = tokio::io::duplex(65536);
extension_fn(server_read, server_write);
let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
Box::new(
McpClient::connect(
(client_read, client_write),
Duration::from_secs(timeout_secs),
self.provider.clone(),
self.client_name.clone(),
capabilities,
self.mcp_client_capabilities(),
effective_working_dir.clone(),
)
.await?,
@@ -840,9 +841,6 @@ impl ExtensionManager {
})
};
let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
let client = child_process_client(
command,
timeout,
@@ -850,7 +848,7 @@ impl ExtensionManager {
&effective_working_dir,
container.map(|c| c.id().to_string()),
self.client_name.clone(),
capabilities,
self.mcp_client_capabilities(),
)
.await?;
Box::new(client)
@@ -875,10 +873,6 @@ impl ExtensionManager {
command.arg("python").arg(file_path.to_str().unwrap());
});
let capabilities = GooseMcpClientCapabilities {
mcpui: self.capabilities.mcpui,
};
let client = child_process_client(
command,
timeout,
@@ -886,7 +880,7 @@ impl ExtensionManager {
&effective_working_dir,
container.map(|c| c.id().to_string()),
self.client_name.clone(),
capabilities,
self.mcp_client_capabilities(),
)
.await?;
+160 -22
View File
@@ -37,6 +37,34 @@ pub type BoxError = Box<dyn std::error::Error + Sync + Send>;
pub type Error = rmcp::ServiceError;
const MCP_APPS_UI_EXTENSION_ID: &str = "io.modelcontextprotocol/ui";
const MCP_APPS_UI_MIME_TYPE: &str = "text/html;profile=mcp-app";
fn default_mcp_apps_ui_extensions() -> ExtensionCapabilities {
let mut extensions = ExtensionCapabilities::new();
let mut ui_extension_settings = JsonObject::new();
ui_extension_settings.insert(
"mimeTypes".to_string(),
serde_json::json!([MCP_APPS_UI_MIME_TYPE]),
);
extensions.insert(MCP_APPS_UI_EXTENSION_ID.to_string(), ui_extension_settings);
extensions
}
#[derive(Debug, Clone, Default)]
pub struct GooseMcpHostInfo {
pub explicit_extensions: bool,
pub extensions: ExtensionCapabilities,
pub client_name: Option<String>,
pub client_version: Option<String>,
}
impl GooseMcpHostInfo {
pub fn mcpui_enabled(&self) -> bool {
self.extensions.contains_key(MCP_APPS_UI_EXTENSION_ID)
}
}
#[async_trait::async_trait]
pub trait McpClientTrait: Send + Sync {
async fn list_tools(
@@ -164,6 +192,40 @@ impl GooseClient {
.and_then(|(_, value)| value.as_str())
.map(|value| value.to_string())
}
fn resolved_extensions(&self) -> ExtensionCapabilities {
if let Some(host_info) = &self.capabilities.host_info {
if host_info.explicit_extensions {
return host_info.extensions.clone();
}
}
if self.capabilities.mcpui {
return default_mcp_apps_ui_extensions();
}
ExtensionCapabilities::new()
}
fn resolved_client_info(&self) -> Implementation {
let name = self
.capabilities
.host_info
.as_ref()
.and_then(|host_info| host_info.client_name.clone())
.unwrap_or_else(|| self.client_name.clone());
let version = self
.capabilities
.host_info
.as_ref()
.and_then(|host_info| host_info.client_version.clone())
.unwrap_or_else(|| {
std::env::var("GOOSE_MCP_CLIENT_VERSION")
.unwrap_or(env!("CARGO_PKG_VERSION").to_owned())
});
Implementation::new(name, version)
}
}
fn working_dir_roots(dir: &std::path::Path) -> ListRootsResult {
@@ -340,21 +402,7 @@ impl ClientHandler for GooseClient {
}
fn get_info(&self) -> ClientInfo {
let mut extensions = ExtensionCapabilities::new();
if self.capabilities.mcpui {
// Build MCP Apps UI extension capability
// See: https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx
let mut ui_extension_settings = JsonObject::new();
ui_extension_settings.insert(
"mimeTypes".to_string(),
serde_json::json!(["text/html;profile=mcp-app"]),
);
extensions.insert(
"io.modelcontextprotocol/ui".to_string(),
ui_extension_settings,
);
}
let extensions = self.resolved_extensions();
InitializeRequestParams::new(
ClientCapabilities::builder()
@@ -363,11 +411,7 @@ impl ClientHandler for GooseClient {
.enable_sampling()
.enable_elicitation()
.build(),
Implementation::new(
self.client_name.clone(),
std::env::var("GOOSE_MCP_CLIENT_VERSION")
.unwrap_or(env!("CARGO_PKG_VERSION").to_owned()),
),
self.resolved_client_info(),
)
.with_protocol_version(ProtocolVersion::V_2025_03_26)
}
@@ -376,6 +420,7 @@ impl ClientHandler for GooseClient {
#[derive(Debug, Clone)]
pub struct GooseMcpClientCapabilities {
pub mcpui: bool,
pub host_info: Option<GooseMcpHostInfo>,
}
/// The MCP client is the interface for MCP operations.
@@ -769,8 +814,14 @@ mod tests {
fn new_client(platform: GoosePlatform) -> GooseClient {
let capabilities = match platform {
GoosePlatform::GooseDesktop => GooseMcpClientCapabilities { mcpui: true },
GoosePlatform::GooseCli => GooseMcpClientCapabilities { mcpui: false },
GoosePlatform::GooseDesktop => GooseMcpClientCapabilities {
mcpui: true,
host_info: None,
},
GoosePlatform::GooseCli => GooseMcpClientCapabilities {
mcpui: false,
host_info: None,
},
};
GooseClient::new(
@@ -1000,6 +1051,93 @@ mod tests {
);
}
#[test]
fn test_explicit_host_info_passes_through_client_identity() {
let client = GooseClient::new(
Arc::new(Mutex::new(Vec::new())),
Arc::new(Mutex::new(None)),
GoosePlatform::GooseDesktop.to_string(),
GooseMcpClientCapabilities {
mcpui: true,
host_info: Some(GooseMcpHostInfo {
explicit_extensions: true,
extensions: ExtensionCapabilities::new(),
client_name: Some("goose2".to_string()),
client_version: Some("0.1.0".to_string()),
}),
},
std::env::current_dir().unwrap_or_default(),
);
let info = ClientHandler::get_info(&client);
assert_eq!(info.client_info.name, "goose2");
assert_eq!(info.client_info.version, "0.1.0");
let extensions = info
.capabilities
.extensions
.expect("client should still serialize an extensions object");
assert!(
!extensions.contains_key(MCP_APPS_UI_EXTENSION_ID),
"explicit empty host extensions should disable platform fallback"
);
}
#[test]
fn test_explicit_host_extensions_override_platform_fallback() {
let client = GooseClient::new(
Arc::new(Mutex::new(Vec::new())),
Arc::new(Mutex::new(None)),
GoosePlatform::GooseCli.to_string(),
GooseMcpClientCapabilities {
mcpui: false,
host_info: Some(GooseMcpHostInfo {
explicit_extensions: true,
extensions: default_mcp_apps_ui_extensions(),
client_name: Some("goose2".to_string()),
client_version: Some("0.1.0".to_string()),
}),
},
std::env::current_dir().unwrap_or_default(),
);
let info = ClientHandler::get_info(&client);
let extensions = info
.capabilities
.extensions
.expect("capabilities should have explicit host extensions");
assert!(extensions.contains_key(MCP_APPS_UI_EXTENSION_ID));
assert_eq!(info.client_info.name, "goose2");
}
#[test]
fn test_host_identity_does_not_disable_platform_fallback_without_explicit_extensions() {
let client = GooseClient::new(
Arc::new(Mutex::new(Vec::new())),
Arc::new(Mutex::new(None)),
GoosePlatform::GooseDesktop.to_string(),
GooseMcpClientCapabilities {
mcpui: true,
host_info: Some(GooseMcpHostInfo {
explicit_extensions: false,
extensions: ExtensionCapabilities::new(),
client_name: Some("goose2".to_string()),
client_version: Some("0.1.0".to_string()),
}),
},
std::env::current_dir().unwrap_or_default(),
);
let info = ClientHandler::get_info(&client);
let extensions = info
.capabilities
.extensions
.expect("platform fallback should still advertise MCP Apps UI");
assert!(extensions.contains_key(MCP_APPS_UI_EXTENSION_ID));
assert_eq!(info.client_info.name, "goose2");
}
#[test]
fn test_working_dir_roots_returns_current_dir_as_root() {
let dir = PathBuf::from("/tmp/test-project");
+2
View File
@@ -5,6 +5,7 @@ use async_trait::async_trait;
use fs_err as fs;
use goose::acp::server::{serve, AcpProviderFactory, GooseAcpAgent};
pub use goose::acp::{map_permission_response, PermissionDecision};
use goose::agents::GoosePlatform;
use goose::builtin_extension::register_builtin_extensions;
use goose::config::paths::Paths;
use goose::config::{GooseMode, PermissionManager};
@@ -190,6 +191,7 @@ pub async fn spawn_acp_server_in_process(
data_root.to_path_buf(),
goose_mode,
true,
GoosePlatform::GooseCli,
)
.await
.unwrap();
+4 -1
View File
@@ -261,7 +261,10 @@ async fn test_replayed_session(
provider,
session_manager,
GoosePlatform::GooseDesktop.to_string(),
ExtensionManagerCapabilities { mcpui: true },
ExtensionManagerCapabilities {
mcpui: true,
host_info: None,
},
));
#[allow(clippy::redundant_closure_call)]
+17 -6
View File
@@ -1,5 +1,9 @@
import { invoke } from "@tauri-apps/api/core";
import { GooseClient } from "@aaif/goose-sdk";
import {
DEFAULT_GOOSE_MCP_HOST_CAPABILITIES,
GooseClient,
type GooseInitializeRequest,
} from "@aaif/goose-sdk";
import {
PROTOCOL_VERSION,
type Client,
@@ -7,6 +11,7 @@ import {
type RequestPermissionRequest,
type RequestPermissionResponse,
} from "@agentclientprotocol/sdk";
import packageJson from "../../../package.json";
import { createWebSocketStream } from "./createWebSocketStream";
import { perfLog } from "@/shared/lib/perfLog";
@@ -81,12 +86,18 @@ async function initializeConnection(): Promise<GooseClient> {
const tInit = performance.now();
await client.initialize({
protocolVersion: PROTOCOL_VERSION,
clientCapabilities: {},
clientInfo: {
name: "goose2",
version: "0.1.0",
clientCapabilities: {
_meta: {
goose: {
mcpHostCapabilities: DEFAULT_GOOSE_MCP_HOST_CAPABILITIES,
},
},
},
});
clientInfo: {
name: packageJson.name,
version: packageJson.version,
},
} satisfies GooseInitializeRequest);
perfLog(
`[perf:conn] client.initialize in ${(performance.now() - tInit).toFixed(1)}ms (total ${(performance.now() - tStart).toFixed(1)}ms)`,
);
+3
View File
@@ -648,6 +648,9 @@ importers:
sdk:
dependencies:
'@modelcontextprotocol/ext-apps':
specifier: ^0.3.1
version: 0.3.1(@modelcontextprotocol/sdk@1.27.1(zod@3.25.76))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@3.25.76)
zod:
specifier: ^3.25.76
version: 3.25.76
+1
View File
@@ -40,6 +40,7 @@
"format": "prettier --write src/"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^0.3.1",
"zod": "^3.25.76"
},
"peerDependencies": {
+1
View File
@@ -2,6 +2,7 @@ export * from "./generated/types.gen.js";
export * from "./generated/zod.gen.js";
export { GooseClient } from "./goose-client.js";
export { createHttpStream } from "./http-stream.js";
export * from "./mcp-apps.js";
export {
ClientSideConnection,
+36
View File
@@ -0,0 +1,36 @@
import type {
Implementation,
InitializeRequest,
} from "@agentclientprotocol/sdk";
import { RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps";
export const GOOSE_MCP_UI_EXTENSION_ID = "io.modelcontextprotocol/ui" as const;
export interface GooseMcpUiExtensionSettings {
mimeTypes: string[];
}
export interface GooseMcpHostCapabilities {
extensions: Record<string, GooseMcpUiExtensionSettings>;
}
export interface GooseClientMeta {
goose: {
mcpHostCapabilities: GooseMcpHostCapabilities;
};
}
export type GooseInitializeRequest = InitializeRequest & {
clientCapabilities: NonNullable<InitializeRequest["clientCapabilities"]> & {
_meta: GooseClientMeta;
};
clientInfo: Implementation;
};
export const DEFAULT_GOOSE_MCP_HOST_CAPABILITIES: GooseMcpHostCapabilities = {
extensions: {
[GOOSE_MCP_UI_EXTENSION_ID]: {
mimeTypes: [RESOURCE_MIME_TYPE],
},
},
};