feat: Adding streamable-http transport support for backend, desktop and cli (#2942)
This commit is contained in:
@@ -15,7 +15,7 @@ use crate::config::permission::PermissionLevel;
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ExtensionError {
|
||||
#[error("Failed to start the MCP server from configuration `{0}` `{1}`")]
|
||||
Initialization(ExtensionConfig, ClientError),
|
||||
Initialization(Box<ExtensionConfig>, ClientError),
|
||||
#[error("Failed a client call to an MCP server: {0}")]
|
||||
Client(#[from] ClientError),
|
||||
#[error("User Message exceeded context-limit. History could not be truncated to accommodate.")]
|
||||
@@ -54,7 +54,7 @@ impl Envs {
|
||||
"LD_AUDIT", // Loads a monitoring library that can intercept execution
|
||||
"LD_DEBUG", // Enables verbose linker logging (information disclosure risk)
|
||||
"LD_BIND_NOW", // Forces immediate symbol resolution, affecting ASLR
|
||||
"LD_ASSUME_KERNEL", // Tricks linker into thinking it’s running on an older kernel
|
||||
"LD_ASSUME_KERNEL", // Tricks linker into thinking it's running on an older kernel
|
||||
// 🍎 macOS dynamic linker variables
|
||||
"DYLD_LIBRARY_PATH", // Same as LD_LIBRARY_PATH but for macOS
|
||||
"DYLD_INSERT_LIBRARIES", // macOS equivalent of LD_PRELOAD
|
||||
@@ -168,6 +168,26 @@ pub enum ExtensionConfig {
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
},
|
||||
/// Streamable HTTP client with a URI endpoint using MCP Streamable HTTP specification
|
||||
#[serde(rename = "streamable_http")]
|
||||
StreamableHttp {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
uri: String,
|
||||
#[serde(default)]
|
||||
envs: Envs,
|
||||
#[serde(default)]
|
||||
env_keys: Vec<String>,
|
||||
#[serde(default)]
|
||||
headers: HashMap<String, String>,
|
||||
description: Option<String>,
|
||||
// NOTE: set timeout to be optional for compatibility.
|
||||
// However, new configurations should include this field.
|
||||
timeout: Option<u64>,
|
||||
/// Whether this extension is bundled with Goose
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
},
|
||||
/// Frontend-provided tools that will be called through the frontend
|
||||
#[serde(rename = "frontend")]
|
||||
Frontend {
|
||||
@@ -207,6 +227,24 @@ impl ExtensionConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn streamable_http<S: Into<String>, T: Into<u64>>(
|
||||
name: S,
|
||||
uri: S,
|
||||
description: S,
|
||||
timeout: T,
|
||||
) -> Self {
|
||||
Self::StreamableHttp {
|
||||
name: name.into(),
|
||||
uri: uri.into(),
|
||||
envs: Envs::default(),
|
||||
env_keys: Vec::new(),
|
||||
headers: HashMap::new(),
|
||||
description: Some(description.into()),
|
||||
timeout: Some(timeout.into()),
|
||||
bundled: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stdio<S: Into<String>, T: Into<u64>>(
|
||||
name: S,
|
||||
cmd: S,
|
||||
@@ -263,6 +301,7 @@ impl ExtensionConfig {
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
Self::Sse { name, .. } => name,
|
||||
Self::StreamableHttp { name, .. } => name,
|
||||
Self::Stdio { name, .. } => name,
|
||||
Self::Builtin { name, .. } => name,
|
||||
Self::Frontend { name, .. } => name,
|
||||
@@ -275,6 +314,9 @@ impl std::fmt::Display for ExtensionConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExtensionConfig::Sse { name, uri, .. } => write!(f, "SSE({}: {})", name, uri),
|
||||
ExtensionConfig::StreamableHttp { name, uri, .. } => {
|
||||
write!(f, "StreamableHttp({}: {})", name, uri)
|
||||
}
|
||||
ExtensionConfig::Stdio {
|
||||
name, cmd, args, ..
|
||||
} => {
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::agents::extension::Envs;
|
||||
use crate::config::{Config, ExtensionConfigManager};
|
||||
use crate::prompt_template;
|
||||
use mcp_client::client::{ClientCapabilities, ClientInfo, McpClient, McpClientTrait};
|
||||
use mcp_client::transport::{SseTransport, StdioTransport, Transport};
|
||||
use mcp_client::transport::{SseTransport, StdioTransport, StreamableHttpTransport, Transport};
|
||||
use mcp_core::{prompt::Prompt, Content, Tool, ToolCall, ToolError};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -195,6 +195,28 @@ impl ExtensionManager {
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
ExtensionConfig::StreamableHttp {
|
||||
uri,
|
||||
envs,
|
||||
env_keys,
|
||||
headers,
|
||||
timeout,
|
||||
..
|
||||
} => {
|
||||
let all_envs = merge_environments(envs, env_keys, &sanitized_name).await?;
|
||||
let transport =
|
||||
StreamableHttpTransport::with_headers(uri, all_envs, headers.clone());
|
||||
let handle = transport.start().await?;
|
||||
Box::new(
|
||||
McpClient::connect(
|
||||
handle,
|
||||
Duration::from_secs(
|
||||
timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT),
|
||||
),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
ExtensionConfig::Stdio {
|
||||
cmd,
|
||||
args,
|
||||
@@ -256,7 +278,7 @@ impl ExtensionManager {
|
||||
let init_result = client
|
||||
.initialize(info, capabilities)
|
||||
.await
|
||||
.map_err(|e| ExtensionError::Initialization(config.clone(), e))?;
|
||||
.map_err(|e| ExtensionError::Initialization(Box::new(config.clone()), e))?;
|
||||
|
||||
if let Some(instructions) = init_result.instructions {
|
||||
self.instructions
|
||||
@@ -752,10 +774,13 @@ impl ExtensionManager {
|
||||
ExtensionConfig::Sse {
|
||||
description, name, ..
|
||||
}
|
||||
| ExtensionConfig::StreamableHttp {
|
||||
description, name, ..
|
||||
}
|
||||
| ExtensionConfig::Stdio {
|
||||
description, name, ..
|
||||
} => {
|
||||
// For SSE/Stdio, use description if available
|
||||
// For SSE/StreamableHttp/Stdio, use description if available
|
||||
description
|
||||
.as_ref()
|
||||
.map(|s| s.to_string())
|
||||
|
||||
Reference in New Issue
Block a user