mcp_commune/
client.rs

1//! # Client Module
2//!
3//! This module provides the client-side functionality for the Commune library.
4//! It includes structures and implementations for building and managing clients
5//! that can interact with peers in a distributed network.
6
7use crate::{
8    error::Error,
9    peer::{Peer, PeerPrompt, PeerResource, RemotePeerBuilder},
10    tool::Tool,
11};
12use mcp_sdk_rs::types::ClientCapabilities;
13
14/// A builder for creating `Client` instances with customizable configurations.
15#[derive(Default)]
16pub struct ClientBuilder {
17    peers: Vec<Peer>,
18    tools: Vec<Tool>,
19    capabilities: ClientCapabilities,
20}
21
22impl ClientBuilder {
23    /// Creates a new `ClientBuilder` instance.
24    pub fn new() -> ClientBuilder {
25        ClientBuilder::default()
26    }
27
28    /// Adds multiple peers to the client configuration.
29    pub fn with_peers(mut self, peers: Vec<Peer>) -> ClientBuilder {
30        self.peers.extend(peers);
31        self
32    }
33
34    /// Adds a single peer to the client configuration.
35    pub fn with_peer(mut self, peer: Peer) -> ClientBuilder {
36        self.peers.push(peer);
37        self
38    }
39
40    /// Adds multiple tools to the client configuration.
41    /// Use this to add local tools.
42    pub fn with_tools(mut self, tools: Vec<Tool>) -> ClientBuilder {
43        self.tools.extend(tools);
44        self
45    }
46
47    /// Adds a single tool to the client configuration.
48    /// Use this to add a local tool.
49    pub fn with_tool(mut self, tool: Tool) -> ClientBuilder {
50        self.tools.push(tool);
51        self
52    }
53
54    /// Sets the capabilities for the client.
55    pub fn with_capabilities(mut self, capabilities: ClientCapabilities) -> ClientBuilder {
56        self.capabilities = capabilities;
57        self
58    }
59
60    /// Builds the `Client` instance based on the configured parameters.
61    ///
62    /// # Errors
63    /// Returns an error if the peer list is empty or if there's an issue retrieving peers.
64    pub async fn build(self) -> Result<Client, Error> {
65        Ok(Client {
66            peers: self.get_peers().await?,
67            local_tools: self.tools,
68        })
69    }
70
71    /// Retrieves and aggregates peers, including those from Commune servers.
72    ///
73    /// # Errors
74    /// Returns an error if there's an issue communicating with peers or parsing their responses.
75    async fn get_peers(&self) -> Result<Vec<Peer>, Error> {
76        let mut new_peers = self.peers.clone();
77        for peer in &self.peers {
78            match peer {
79                Peer::Local {
80                    name: _,
81                    description: _,
82                    cmd: _,
83                    args: _,
84                    env: _,
85                    capabilities,
86                    client,
87                } => {
88                    if let Some(ref client) = client {
89                        if let Some(ref caps) = capabilities.experimental {
90                            if let Some(x) = caps.as_object() {
91                                if x.get("peers").is_some() {
92                                    // log::debug!("{} is a commune server, getting peers", peer.url);
93                                    let r =
94                                        client.request("peers/list", None).await.map_err(|_| {
95                                            Error::McpClient("failed to list peers".to_string())
96                                        })?;
97                                    let remote_peers: Vec<RemotePeerBuilder> =
98                                        serde_json::from_value(r)
99                                            .map_err(|_| Error::InvalidResponse)?;
100                                    for pb in remote_peers {
101                                        new_peers.push(pb.build().await?);
102                                    }
103                                }
104                            }
105                        }
106                    }
107                }
108                Peer::Remote {
109                    name: _,
110                    description: _,
111                    url: _,
112                    capabilities,
113                    client,
114                } => {
115                    if let Some(ref client) = client {
116                        if let Some(ref caps) = capabilities.experimental {
117                            if let Some(x) = caps.as_object() {
118                                if x.get("peers").is_some() {
119                                    // log::debug!("{} is a commune server, getting peers", peer.url);
120                                    let r =
121                                        client.request("peers/list", None).await.map_err(|_| {
122                                            Error::McpClient("failed to list peers".to_string())
123                                        })?;
124                                    let remote_peers: Vec<RemotePeerBuilder> =
125                                        serde_json::from_value(r)
126                                            .map_err(|_| Error::InvalidResponse)?;
127                                    for pb in remote_peers {
128                                        new_peers.push(pb.build().await?);
129                                    }
130                                }
131                            }
132                        }
133                    }
134                }
135            }
136        }
137        Ok(new_peers)
138    }
139}
140
141/// Represents a client in the Commune network, capable of interacting with multiple peers.
142pub struct Client {
143    pub peers: Vec<Peer>,
144    pub local_tools: Vec<Tool>,
145}
146
147impl Client {
148    /// Lists all tools available across all connected peers.
149    ///
150    /// # Errors
151    /// Returns an error if there's an issue communicating with any peer.
152    pub async fn all_tools(&self) -> Result<Vec<Tool>, Error> {
153        let mut res = self.local_tools.clone();
154        for peer in &self.peers {
155            match peer {
156                Peer::Local {
157                    name: _,
158                    description: _,
159                    cmd: _,
160                    args: _,
161                    env: _,
162                    capabilities,
163                    client: _,
164                } => {
165                    if capabilities.tools.is_some() {
166                        for tool in peer.list_tools().await? {
167                            // local peers speak MCP protocol and, as such, their tools are implemented as Tool::Remote
168                            res.push(Tool::Remote {
169                                peer: peer.clone(),
170                                tool,
171                            })
172                        }
173                    }
174                }
175                Peer::Remote {
176                    name: _,
177                    description: _,
178                    url: _,
179                    capabilities,
180                    client: _,
181                } => {
182                    if capabilities.tools.is_some() {
183                        for tool in peer.list_tools().await? {
184                            res.push(Tool::Remote {
185                                peer: peer.clone(),
186                                tool,
187                            })
188                        }
189                    }
190                }
191            }
192        }
193        Ok(res)
194    }
195
196    /// Lists all resources available across all connected peers.
197    ///
198    /// # Errors
199    /// Returns an error if there's an issue communicating with any peer.
200    pub async fn all_resources(&self) -> Result<Vec<PeerResource>, Error> {
201        let mut res = vec![];
202        for peer in &self.peers {
203            match peer {
204                Peer::Local {
205                    name: _,
206                    description: _,
207                    cmd: _,
208                    args: _,
209                    env: _,
210                    capabilities,
211                    client: _,
212                } => {
213                    if capabilities.resources.is_some() {
214                        for resource in peer.list_resources().await? {
215                            res.push(PeerResource {
216                                peer: peer.clone(),
217                                resource,
218                            })
219                        }
220                    }
221                }
222                Peer::Remote {
223                    name: _,
224                    description: _,
225                    url: _,
226                    capabilities,
227                    client: _,
228                } => {
229                    if capabilities.resources.is_some() {
230                        for resource in peer.list_resources().await? {
231                            res.push(PeerResource {
232                                peer: peer.clone(),
233                                resource,
234                            })
235                        }
236                    }
237                }
238            }
239        }
240        Ok(res)
241    }
242
243    /// Lists all prompts available across all connected peers.
244    ///
245    /// # Errors
246    /// Returns an error if there's an issue communicating with any peer.
247    pub async fn all_prompts(&self) -> Result<Vec<PeerPrompt>, Error> {
248        let mut res = vec![];
249        for peer in &self.peers {
250            match peer {
251                Peer::Local {
252                    name: _,
253                    description: _,
254                    cmd: _,
255                    args: _,
256                    env: _,
257                    capabilities,
258                    client: _,
259                } => {
260                    if capabilities.prompts.is_some() {
261                        for prompt in peer.list_prompts().await? {
262                            res.push(PeerPrompt {
263                                peer: peer.clone(),
264                                prompt,
265                            })
266                        }
267                    }
268                }
269                Peer::Remote {
270                    name: _,
271                    description: _,
272                    url: _,
273                    capabilities,
274                    client: _,
275                } => {
276                    if capabilities.prompts.is_some() {
277                        for prompt in peer.list_prompts().await? {
278                            res.push(PeerPrompt {
279                                peer: peer.clone(),
280                                prompt,
281                            })
282                        }
283                    }
284                }
285            }
286        }
287        Ok(res)
288    }
289}