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}