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; pub type Error = rmcp::ServiceError; #[async_trait::async_trait] pub trait McpClientTrait: Send + Sync { async fn list_resources( &self, next_cursor: Option, cancel_token: CancellationToken, ) -> Result; async fn read_resource( &self, uri: &str, cancel_token: CancellationToken, ) -> Result; async fn list_tools( &self, next_cursor: Option, cancel_token: CancellationToken, ) -> Result; async fn call_tool( &self, name: &str, arguments: Option, cancel_token: CancellationToken, ) -> Result; async fn list_prompts( &self, next_cursor: Option, cancel_token: CancellationToken, ) -> Result; async fn get_prompt( &self, name: &str, arguments: Value, cancel_token: CancellationToken, ) -> Result; async fn subscribe(&self) -> mpsc::Receiver; fn get_info(&self) -> Option<&InitializeResult>; } pub struct GooseClient { notification_handlers: Arc>>>, } impl GooseClient { pub fn new(handlers: Arc>>>) -> Self { GooseClient { notification_handlers: handlers, } } } impl ClientHandler for GooseClient { async fn on_progress( &self, params: rmcp::model::ProgressNotificationParam, context: rmcp::service::NotificationContext, ) { 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, ) { 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>, notification_subscribers: Arc>>>, server_info: Option, timeout: std::time::Duration, } impl McpClient { pub async fn connect( transport: T, timeout: std::time::Duration, ) -> Result where T: IntoTransport, E: std::error::Error + From + Send + Sync + 'static, { let notification_subscribers = Arc::new(Mutex::new(Vec::>::new())); let client = GooseClient::new(notification_subscribers.clone()); let client: rmcp::service::RunningService = 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 { 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, timeout: Duration, cancel_token: &CancellationToken, ) -> Result<::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, request_id: RequestId, reason: Option, ) -> 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, cancel_token: CancellationToken, ) -> Result { 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 { 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, cancel_token: CancellationToken, ) -> Result { 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, cancel_token: CancellationToken, ) -> Result { 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, cancel_token: CancellationToken, ) -> Result { 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 { 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 { let (tx, rx) = mpsc::channel(16); self.notification_subscribers.lock().await.push(tx); rx } }