alexhancock/mcp-crate-cleanup (#4885)

This commit is contained in:
Alex Hancock
2025-09-29 17:00:17 -04:00
committed by GitHub
parent 2bd18a757b
commit cb0eca9966
78 changed files with 1039 additions and 1090 deletions
+52 -33
View File
@@ -34,6 +34,7 @@ use crate::agents::types::{FrontendTool, ToolResultReceiver};
use crate::config::{Config, ExtensionConfigManager};
use crate::context_mgmt::auto_compact;
use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation};
use crate::mcp_utils::ToolResult;
use crate::permission::permission_inspector::PermissionInspector;
use crate::permission::permission_judge::PermissionCheckResult;
use crate::permission::PermissionConfirmation;
@@ -45,10 +46,10 @@ use crate::security::security_inspector::SecurityInspector;
use crate::tool_inspection::ToolInspectionManager;
use crate::tool_monitor::RepetitionInspector;
use crate::utils::is_token_cancelled;
use mcp_core::ToolResult;
use regex::Regex;
use rmcp::model::{
Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ServerNotification, Tool,
CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt,
ServerNotification, Tool,
};
use serde_json::Value;
use tokio::sync::{mpsc, Mutex};
@@ -389,14 +390,18 @@ impl Agent {
#[instrument(skip(self, tool_call, request_id), fields(input, output))]
pub async fn dispatch_tool_call(
&self,
tool_call: mcp_core::tool::ToolCall,
tool_call: CallToolRequestParam,
request_id: String,
cancellation_token: Option<CancellationToken>,
session: &Option<SessionConfig>,
) -> (String, Result<ToolCallResult, ErrorData>) {
if tool_call.name == PLATFORM_MANAGE_SCHEDULE_TOOL_NAME {
let arguments = tool_call
.arguments
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));
let result = self
.handle_schedule_management(tool_call.arguments, request_id.clone())
.handle_schedule_management(arguments, request_id.clone())
.await;
return (request_id, Ok(ToolCallResult::from(result)));
}
@@ -404,13 +409,15 @@ impl Agent {
if tool_call.name == PLATFORM_MANAGE_EXTENSIONS_TOOL_NAME {
let extension_name = tool_call
.arguments
.get("extension_name")
.as_ref()
.and_then(|args| args.get("extension_name"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let action = tool_call
.arguments
.get("action")
.as_ref()
.and_then(|args| args.get("action"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
@@ -445,19 +452,25 @@ impl Agent {
.is_sub_recipe_tool(&tool_call.name)
{
let sub_recipe_manager = self.sub_recipe_manager.lock().await;
let arguments = tool_call
.arguments
.clone()
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));
sub_recipe_manager
.dispatch_sub_recipe_tool_call(
&tool_call.name,
tool_call.arguments.clone(),
&self.tasks_manager,
)
.dispatch_sub_recipe_tool_call(&tool_call.name, arguments, &self.tasks_manager)
.await
} else if tool_call.name == SUBAGENT_EXECUTE_TASK_TOOL_NAME {
let provider = self.provider().await.ok();
let arguments = tool_call
.arguments
.clone()
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));
let task_config = TaskConfig::new(provider);
subagent_execute_task_tool::run_tasks(
tool_call.arguments.clone(),
arguments,
task_config,
&self.tasks_manager,
cancellation_token,
@@ -470,29 +483,33 @@ impl Agent {
.list_extensions()
.await
.unwrap_or_default();
create_dynamic_task(
tool_call.arguments.clone(),
&self.tasks_manager,
loaded_extensions,
)
.await
let arguments = tool_call
.arguments
.clone()
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));
create_dynamic_task(arguments, &self.tasks_manager, loaded_extensions).await
} else if tool_call.name == PLATFORM_READ_RESOURCE_TOOL_NAME {
// Check if the tool is read_resource and handle it separately
let arguments = tool_call
.arguments
.clone()
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));
ToolCallResult::from(
self.extension_manager
.read_resource(
tool_call.arguments.clone(),
cancellation_token.unwrap_or_default(),
)
.read_resource(arguments, cancellation_token.unwrap_or_default())
.await,
)
} else if tool_call.name == PLATFORM_LIST_RESOURCES_TOOL_NAME {
let arguments = tool_call
.arguments
.clone()
.map(Value::Object)
.unwrap_or(Value::Object(serde_json::Map::new()));
ToolCallResult::from(
self.extension_manager
.list_resources(
tool_call.arguments.clone(),
cancellation_token.unwrap_or_default(),
)
.list_resources(arguments, cancellation_token.unwrap_or_default())
.await,
)
} else if tool_call.name == PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME {
@@ -522,12 +539,14 @@ impl Agent {
ToolCallResult::from(Ok(vec![Content::text(todo_content)]))
} else if tool_call.name == TODO_WRITE_TOOL_NAME {
// Handle task planner write tool
let content = tool_call
.arguments
.get("content")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let content = match tool_call.arguments {
Some(args) => args
.get("content")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
None => "".to_string(),
};
// Character limit validation
let char_count = content.chars().count();
@@ -592,7 +611,7 @@ impl Agent {
} else if tool_call.name == ROUTER_LLM_SEARCH_TOOL_NAME {
match self
.tool_route_manager
.dispatch_route_search_tool(tool_call.arguments)
.dispatch_route_search_tool(tool_call.arguments.unwrap_or_default())
.await
{
Ok(tool_result) => tool_result,
+1 -1
View File
@@ -1,8 +1,8 @@
use std::collections::HashMap;
use mcp_client::client::Error as ClientError;
use rmcp::model::Tool;
use rmcp::service::ClientInitializeError;
use rmcp::ServiceError as ClientError;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::warn;
+53 -36
View File
@@ -3,8 +3,6 @@ use axum::http::{HeaderMap, HeaderName};
use chrono::{DateTime, Utc};
use futures::stream::{FuturesUnordered, StreamExt};
use futures::{future, FutureExt};
use mcp_core::handler::require_str_parameter;
use mcp_core::ToolCall;
use rmcp::service::ClientInitializeError;
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
use rmcp::transport::{
@@ -27,12 +25,13 @@ use super::extension::{ExtensionConfig, ExtensionError, ExtensionInfo, Extension
use super::tool_execution::ToolCallResult;
use crate::agents::extension::{Envs, ProcessExit};
use crate::agents::extension_malware_check;
use crate::agents::mcp_client::{McpClient, McpClientTrait};
use crate::config::{Config, ExtensionConfigManager};
use crate::oauth::oauth_flow;
use crate::prompt_template;
use mcp_client::client::{McpClient, McpClientTrait};
use rmcp::model::{
Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ResourceContents, ServerInfo, Tool,
CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ResourceContents,
ServerInfo, Tool,
};
use rmcp::transport::auth::AuthClient;
use serde_json::Value;
@@ -135,6 +134,24 @@ fn normalize(input: String) -> String {
result.to_lowercase()
}
fn require_str_parameter<'a>(v: &'a serde_json::Value, name: &str) -> Result<&'a str, ErrorData> {
let v = v.get(name).ok_or_else(|| {
ErrorData::new(
ErrorCode::INVALID_PARAMS,
format!("The parameter {name} is required"),
None,
)
})?;
match v.as_str() {
Some(r) => Ok(r),
None => Err(ErrorData::new(
ErrorCode::INVALID_PARAMS,
format!("The parameter {name} must be a string"),
None,
)),
}
}
pub fn get_parameter_names(tool: &Tool) -> Vec<String> {
tool.input_schema
.get("properties")
@@ -604,6 +621,7 @@ impl ExtensionManager {
cancellation_token: CancellationToken,
) -> Result<Vec<Content>, ErrorData> {
let uri = require_str_parameter(&params, "uri")?;
let extension_name = params.get("extension_name").and_then(|v| v.as_str());
// If extension name is provided, we can just look it up
@@ -805,7 +823,7 @@ impl ExtensionManager {
pub async fn dispatch_tool_call(
&self,
tool_call: ToolCall,
tool_call: CallToolRequestParam,
cancellation_token: CancellationToken,
) -> Result<ToolCallResult> {
// Dispatch tool call based on the prefix naming convention
@@ -1042,10 +1060,9 @@ impl ExtensionManager {
#[cfg(test)]
mod tests {
use super::*;
use mcp_client::client::Error;
use mcp_client::client::McpClientTrait;
use rmcp::model::CallToolResult;
use rmcp::model::InitializeResult;
use rmcp::model::{InitializeResult, JsonObject};
use rmcp::{object, ServiceError as Error};
use rmcp::model::ListPromptsResult;
use rmcp::model::ListResourcesResult;
@@ -1146,7 +1163,7 @@ mod tests {
async fn call_tool(
&self,
name: &str,
_arguments: Value,
_arguments: Option<JsonObject>,
_cancellation_token: CancellationToken,
) -> Result<CallToolResult, Error> {
match name {
@@ -1269,9 +1286,9 @@ mod tests {
.await;
// verify a normal tool call
let tool_call = ToolCall {
name: "test_client__tool".to_string(),
arguments: json!({}),
let tool_call = CallToolRequestParam {
name: "test_client__tool".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1279,9 +1296,9 @@ mod tests {
.await;
assert!(result.is_ok());
let tool_call = ToolCall {
name: "test_client__test__tool".to_string(),
arguments: json!({}),
let tool_call = CallToolRequestParam {
name: "test_client__test__tool".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1290,9 +1307,9 @@ mod tests {
assert!(result.is_ok());
// verify a multiple underscores dispatch
let tool_call = ToolCall {
name: "__cli__ent____tool".to_string(),
arguments: json!({}),
let tool_call = CallToolRequestParam {
name: "__cli__ent____tool".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1301,9 +1318,9 @@ mod tests {
assert!(result.is_ok());
// Test unicode in tool name, "client 🚀" should become "client_"
let tool_call = ToolCall {
name: "client___tool".to_string(),
arguments: json!({}),
let tool_call = CallToolRequestParam {
name: "client___tool".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1311,9 +1328,9 @@ mod tests {
.await;
assert!(result.is_ok());
let tool_call = ToolCall {
name: "client___test__tool".to_string(),
arguments: json!({}),
let tool_call = CallToolRequestParam {
name: "client___test__tool".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1322,9 +1339,9 @@ mod tests {
assert!(result.is_ok());
// this should error out, specifically for an ToolError::ExecutionError
let invalid_tool_call = ToolCall {
name: "client___tools".to_string(),
arguments: json!({}),
let invalid_tool_call = CallToolRequestParam {
name: "client___tools".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1343,9 +1360,9 @@ mod tests {
// this should error out, specifically with an ToolError::NotFound
// this client doesn't exist
let invalid_tool_call = ToolCall {
name: "_client__tools".to_string(),
arguments: json!({}),
let invalid_tool_call = CallToolRequestParam {
name: "_client__tools".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1427,9 +1444,9 @@ mod tests {
.await;
// Try to call an unavailable tool
let unavailable_tool_call = ToolCall {
name: "test_extension__tool".to_string(),
arguments: json!({}),
let unavailable_tool_call = CallToolRequestParam {
name: "test_extension__tool".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
@@ -1446,9 +1463,9 @@ mod tests {
}
// Try to call an available tool - should succeed
let available_tool_call = ToolCall {
name: "test_extension__available_tool".to_string(),
arguments: json!({}),
let available_tool_call = CallToolRequestParam {
name: "test_extension__available_tool".to_string().into(),
arguments: Some(object!({})),
};
let result = extension_manager
+14 -13
View File
@@ -1,8 +1,7 @@
use crate::agents::tool_execution::ToolCallResult;
use crate::recipe::Response;
use indoc::formatdoc;
use mcp_core::ToolCall;
use rmcp::model::{Content, ErrorCode, ErrorData, Tool, ToolAnnotations};
use rmcp::model::{CallToolRequestParam, Content, ErrorCode, ErrorData, Tool, ToolAnnotations};
use serde_json::Value;
use std::borrow::Cow;
@@ -117,10 +116,10 @@ impl FinalOutputTool {
}
}
pub async fn execute_tool_call(&mut self, tool_call: ToolCall) -> ToolCallResult {
match tool_call.name.as_str() {
pub async fn execute_tool_call(&mut self, tool_call: CallToolRequestParam) -> ToolCallResult {
match tool_call.name.to_string().as_str() {
FINAL_OUTPUT_TOOL_NAME => {
let result = self.validate_json_output(&tool_call.arguments).await;
let result = self.validate_json_output(&tool_call.arguments.into()).await;
match result {
Ok(parsed_value) => {
self.final_output = Some(Self::parsed_final_output_string(parsed_value));
@@ -153,6 +152,8 @@ impl FinalOutputTool {
mod tests {
use super::*;
use crate::recipe::Response;
use rmcp::model::CallToolRequestParam;
use rmcp::object;
use serde_json::json;
fn create_complex_test_schema() -> Value {
@@ -226,11 +227,11 @@ mod tests {
};
let mut tool = FinalOutputTool::new(response);
let tool_call = ToolCall {
name: FINAL_OUTPUT_TOOL_NAME.to_string(),
arguments: json!({
let tool_call = CallToolRequestParam {
name: FINAL_OUTPUT_TOOL_NAME.into(),
arguments: Some(object!({
"message": "Hello" // Missing required "count" field
}),
})),
};
let result = tool.execute_tool_call(tool_call).await;
@@ -248,15 +249,15 @@ mod tests {
};
let mut tool = FinalOutputTool::new(response);
let tool_call = ToolCall {
name: FINAL_OUTPUT_TOOL_NAME.to_string(),
arguments: json!({
let tool_call = CallToolRequestParam {
name: FINAL_OUTPUT_TOOL_NAME.into(),
arguments: Some(object!({
"user": {
"name": "John",
"age": 30
},
"tags": ["developer", "rust"]
}),
})),
};
let result = tool.execute_tool_call(tool_call).await;
+387
View File
@@ -0,0 +1,387 @@
use rmcp::model::JsonObject;
/// MCP client implementation for Goose
use rmcp::{
model::{
CallToolRequest, CallToolRequestParam, CallToolResult, CancelledNotification,
CancelledNotificationMethod, CancelledNotificationParam, ClientCapabilities, ClientInfo,
ClientRequest, GetPromptRequest, GetPromptRequestParam, GetPromptResult, Implementation,
InitializeResult, ListPromptsRequest, ListPromptsResult, ListResourcesRequest,
ListResourcesResult, ListToolsRequest, ListToolsResult, LoggingMessageNotification,
LoggingMessageNotificationMethod, PaginatedRequestParam, ProgressNotification,
ProgressNotificationMethod, ProtocolVersion, ReadResourceRequest, ReadResourceRequestParam,
ReadResourceResult, RequestId, ServerNotification, ServerResult,
},
service::{
ClientInitializeError, PeerRequestOptions, RequestHandle, RunningService, ServiceRole,
},
transport::IntoTransport,
ClientHandler, Peer, RoleClient, ServiceError, ServiceExt,
};
use serde_json::Value;
use std::{sync::Arc, time::Duration};
use tokio::sync::{
mpsc::{self, Sender},
Mutex,
};
use tokio_util::sync::CancellationToken;
pub type BoxError = Box<dyn std::error::Error + Sync + Send>;
pub type Error = rmcp::ServiceError;
#[async_trait::async_trait]
pub trait McpClientTrait: Send + Sync {
async fn list_resources(
&self,
next_cursor: Option<String>,
cancel_token: CancellationToken,
) -> Result<ListResourcesResult, Error>;
async fn read_resource(
&self,
uri: &str,
cancel_token: CancellationToken,
) -> Result<ReadResourceResult, Error>;
async fn list_tools(
&self,
next_cursor: Option<String>,
cancel_token: CancellationToken,
) -> Result<ListToolsResult, Error>;
async fn call_tool(
&self,
name: &str,
arguments: Option<JsonObject>,
cancel_token: CancellationToken,
) -> Result<CallToolResult, Error>;
async fn list_prompts(
&self,
next_cursor: Option<String>,
cancel_token: CancellationToken,
) -> Result<ListPromptsResult, Error>;
async fn get_prompt(
&self,
name: &str,
arguments: Value,
cancel_token: CancellationToken,
) -> Result<GetPromptResult, Error>;
async fn subscribe(&self) -> mpsc::Receiver<ServerNotification>;
fn get_info(&self) -> Option<&InitializeResult>;
}
pub struct GooseClient {
notification_handlers: Arc<Mutex<Vec<Sender<ServerNotification>>>>,
}
impl GooseClient {
pub fn new(handlers: Arc<Mutex<Vec<Sender<ServerNotification>>>>) -> Self {
GooseClient {
notification_handlers: handlers,
}
}
}
impl ClientHandler for GooseClient {
async fn on_progress(
&self,
params: rmcp::model::ProgressNotificationParam,
context: rmcp::service::NotificationContext<rmcp::RoleClient>,
) {
self.notification_handlers
.lock()
.await
.iter()
.for_each(|handler| {
let _ = handler.try_send(ServerNotification::ProgressNotification(
ProgressNotification {
params: params.clone(),
method: ProgressNotificationMethod,
extensions: context.extensions.clone(),
},
));
});
}
async fn on_logging_message(
&self,
params: rmcp::model::LoggingMessageNotificationParam,
context: rmcp::service::NotificationContext<rmcp::RoleClient>,
) {
self.notification_handlers
.lock()
.await
.iter()
.for_each(|handler| {
let _ = handler.try_send(ServerNotification::LoggingMessageNotification(
LoggingMessageNotification {
params: params.clone(),
method: LoggingMessageNotificationMethod,
extensions: context.extensions.clone(),
},
));
});
}
fn get_info(&self) -> ClientInfo {
ClientInfo {
protocol_version: ProtocolVersion::V_2025_03_26,
capabilities: ClientCapabilities::builder().build(),
client_info: Implementation {
name: "goose".to_string(),
version: env!("CARGO_PKG_VERSION").to_owned(),
},
}
}
}
/// The MCP client is the interface for MCP operations.
pub struct McpClient {
client: Mutex<RunningService<RoleClient, GooseClient>>,
notification_subscribers: Arc<Mutex<Vec<mpsc::Sender<ServerNotification>>>>,
server_info: Option<InitializeResult>,
timeout: std::time::Duration,
}
impl McpClient {
pub async fn connect<T, E, A>(
transport: T,
timeout: std::time::Duration,
) -> Result<Self, ClientInitializeError>
where
T: IntoTransport<RoleClient, E, A>,
E: std::error::Error + From<std::io::Error> + Send + Sync + 'static,
{
let notification_subscribers =
Arc::new(Mutex::new(Vec::<mpsc::Sender<ServerNotification>>::new()));
let client = GooseClient::new(notification_subscribers.clone());
let client: rmcp::service::RunningService<rmcp::RoleClient, GooseClient> =
client.serve(transport).await?;
let server_info = client.peer_info().cloned();
Ok(Self {
client: Mutex::new(client),
notification_subscribers,
server_info,
timeout,
})
}
async fn send_request(
&self,
request: ClientRequest,
cancel_token: CancellationToken,
) -> Result<ServerResult, Error> {
let handle = self
.client
.lock()
.await
.send_cancellable_request(request, PeerRequestOptions::no_options())
.await?;
await_response(handle, self.timeout, &cancel_token).await
}
}
async fn await_response(
handle: RequestHandle<RoleClient>,
timeout: Duration,
cancel_token: &CancellationToken,
) -> Result<<RoleClient as ServiceRole>::PeerResp, ServiceError> {
let receiver = handle.rx;
let peer = handle.peer;
let request_id = handle.id;
tokio::select! {
result = receiver => {
result.map_err(|_e| ServiceError::TransportClosed)?
}
_ = tokio::time::sleep(timeout) => {
send_cancel_message(&peer, request_id, Some("timed out".to_owned())).await?;
Err(ServiceError::Timeout{timeout})
}
_ = cancel_token.cancelled() => {
send_cancel_message(&peer, request_id, Some("operation cancelled".to_owned())).await?;
Err(ServiceError::Cancelled { reason: None })
}
}
}
async fn send_cancel_message(
peer: &Peer<RoleClient>,
request_id: RequestId,
reason: Option<String>,
) -> Result<(), ServiceError> {
peer.send_notification(
CancelledNotification {
params: CancelledNotificationParam { request_id, reason },
method: CancelledNotificationMethod,
extensions: Default::default(),
}
.into(),
)
.await
}
#[async_trait::async_trait]
impl McpClientTrait for McpClient {
fn get_info(&self) -> Option<&InitializeResult> {
self.server_info.as_ref()
}
async fn list_resources(
&self,
cursor: Option<String>,
cancel_token: CancellationToken,
) -> Result<ListResourcesResult, Error> {
let res = self
.send_request(
ClientRequest::ListResourcesRequest(ListResourcesRequest {
params: Some(PaginatedRequestParam { cursor }),
method: Default::default(),
extensions: Default::default(),
}),
cancel_token,
)
.await?;
match res {
ServerResult::ListResourcesResult(result) => Ok(result),
_ => Err(ServiceError::UnexpectedResponse),
}
}
async fn read_resource(
&self,
uri: &str,
cancel_token: CancellationToken,
) -> Result<ReadResourceResult, Error> {
let res = self
.send_request(
ClientRequest::ReadResourceRequest(ReadResourceRequest {
params: ReadResourceRequestParam {
uri: uri.to_string(),
},
method: Default::default(),
extensions: Default::default(),
}),
cancel_token,
)
.await?;
match res {
ServerResult::ReadResourceResult(result) => Ok(result),
_ => Err(ServiceError::UnexpectedResponse),
}
}
async fn list_tools(
&self,
cursor: Option<String>,
cancel_token: CancellationToken,
) -> Result<ListToolsResult, Error> {
let res = self
.send_request(
ClientRequest::ListToolsRequest(ListToolsRequest {
params: Some(PaginatedRequestParam { cursor }),
method: Default::default(),
extensions: Default::default(),
}),
cancel_token,
)
.await?;
match res {
ServerResult::ListToolsResult(result) => Ok(result),
_ => Err(ServiceError::UnexpectedResponse),
}
}
async fn call_tool(
&self,
name: &str,
arguments: Option<JsonObject>,
cancel_token: CancellationToken,
) -> Result<CallToolResult, Error> {
let res = self
.send_request(
ClientRequest::CallToolRequest(CallToolRequest {
params: CallToolRequestParam {
name: name.to_string().into(),
arguments,
},
method: Default::default(),
extensions: Default::default(),
}),
cancel_token,
)
.await?;
match res {
ServerResult::CallToolResult(result) => Ok(result),
_ => Err(ServiceError::UnexpectedResponse),
}
}
async fn list_prompts(
&self,
cursor: Option<String>,
cancel_token: CancellationToken,
) -> Result<ListPromptsResult, Error> {
let res = self
.send_request(
ClientRequest::ListPromptsRequest(ListPromptsRequest {
params: Some(PaginatedRequestParam { cursor }),
method: Default::default(),
extensions: Default::default(),
}),
cancel_token,
)
.await?;
match res {
ServerResult::ListPromptsResult(result) => Ok(result),
_ => Err(ServiceError::UnexpectedResponse),
}
}
async fn get_prompt(
&self,
name: &str,
arguments: Value,
cancel_token: CancellationToken,
) -> Result<GetPromptResult, Error> {
let arguments = match arguments {
Value::Object(map) => Some(map),
_ => None,
};
let res = self
.send_request(
ClientRequest::GetPromptRequest(GetPromptRequest {
params: GetPromptRequestParam {
name: name.to_string(),
arguments,
},
method: Default::default(),
extensions: Default::default(),
}),
cancel_token,
)
.await?;
match res {
ServerResult::GetPromptResult(result) => Ok(result),
_ => Err(ServiceError::UnexpectedResponse),
}
}
async fn subscribe(&self) -> mpsc::Receiver<ServerNotification> {
let (tx, rx) = mpsc::channel(16);
self.notification_subscribers.lock().await.push(tx);
rx
}
}
+1
View File
@@ -5,6 +5,7 @@ pub mod extension_malware_check;
pub mod extension_manager;
pub mod final_output_tool;
mod large_response_handler;
pub mod mcp_client;
pub mod model_selector;
pub mod platform_tools;
pub mod prompt_manager;
@@ -1,10 +1,9 @@
use rmcp::model::Tool;
use rmcp::model::{Content, ErrorCode, ErrorData};
use rmcp::model::{JsonObject, Tool};
use anyhow::Result;
use async_trait::async_trait;
use serde::Serialize;
use serde_json::Value;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::VecDeque;
@@ -23,7 +22,7 @@ struct ToolSelectorContext {
#[async_trait]
pub trait RouterToolSelector: Send + Sync {
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ErrorData>;
async fn select_tools(&self, params: JsonObject) -> Result<Vec<Content>, ErrorData>;
async fn index_tools(&self, tools: &[Tool], extension_name: &str) -> Result<(), ErrorData>;
async fn remove_tool(&self, tool_name: &str) -> Result<(), ErrorData>;
async fn record_tool_call(&self, tool_name: &str) -> Result<(), ErrorData>;
@@ -48,7 +47,7 @@ impl LLMToolSelector {
#[async_trait]
impl RouterToolSelector for LLMToolSelector {
async fn select_tools(&self, params: Value) -> Result<Vec<Content>, ErrorData> {
async fn select_tools(&self, params: JsonObject) -> Result<Vec<Content>, ErrorData> {
let query = params
.get("query")
.and_then(|v| v.as_str())
+1 -1
View File
@@ -5,8 +5,8 @@
use std::sync::Arc;
use crate::mcp_utils::ToolResult;
use chrono::Utc;
use mcp_core::ToolResult;
use rmcp::model::{Content, ErrorCode, ErrorData};
use crate::recipe::Recipe;
+4 -3
View File
@@ -8,8 +8,8 @@ use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use crate::config::permission::PermissionLevel;
use crate::mcp_utils::ToolResult;
use crate::permission::Permission;
use mcp_core::ToolResult;
use rmcp::model::{Content, ServerNotification};
// ToolCallResult combines the result of a tool call with an optional notification stream that
@@ -70,8 +70,8 @@ impl Agent {
let confirmation = Message::user().with_tool_confirmation_request(
request.id.clone(),
tool_call.name.clone(),
tool_call.arguments.clone(),
tool_call.name.to_string().clone(),
tool_call.arguments.clone().unwrap_or_default(),
security_message,
);
yield confirmation;
@@ -80,6 +80,7 @@ impl Agent {
while let Some((req_id, confirmation)) = rx.recv().await {
if req_id == request.id {
if confirmation.permission == Permission::AllowOnce || confirmation.permission == Permission::AlwaysAllow {
// Clone tool_call to avoid moving it
let (req_id, tool_result) = self.dispatch_tool_call(tool_call.clone(), request.id.clone(), cancellation_token.clone(), &None).await;
let mut futures = tool_futures.lock().await;
@@ -7,8 +7,7 @@ use crate::config::Config;
use crate::conversation::message::ToolRequest;
use crate::providers::base::Provider;
use anyhow::{anyhow, Result};
use rmcp::model::{ErrorCode, ErrorData, Tool};
use serde_json::Value;
use rmcp::model::{ErrorCode, ErrorData, JsonObject, Tool};
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::error;
@@ -46,7 +45,7 @@ impl ToolRouteManager {
pub async fn dispatch_route_search_tool(
&self,
arguments: Value,
arguments: JsonObject,
) -> Result<ToolCallResult, ErrorData> {
let selector = self.router_tool_selector.lock().await.clone();
match selector.as_ref() {
+1 -1
View File
@@ -1,4 +1,4 @@
use mcp_core::ToolResult;
use crate::mcp_utils::ToolResult;
use rmcp::model::{Content, Tool};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;