Skip to main content

turbomcp_client/client/operations/
resources.rs

1//! Resource operations for MCP client
2//!
3//! This module provides resource-related functionality including listing resources,
4//! reading resource content, and managing resource templates.
5
6use std::sync::atomic::Ordering;
7
8use turbomcp_protocol::types::{
9    Cursor, ListResourceTemplatesRequest, ListResourceTemplatesResult, ListResourcesRequest,
10    ListResourcesResult, ReadResourceRequest, ReadResourceResult, Resource,
11};
12use turbomcp_protocol::{Error, Result};
13
14/// Maximum number of pagination pages to prevent infinite loops from misbehaving servers.
15const MAX_PAGINATION_PAGES: usize = 1000;
16
17impl<T: turbomcp_transport::Transport + 'static> super::super::core::Client<T> {
18    /// List available resources from the MCP server
19    ///
20    /// Returns a list of resources with their full metadata including URIs, names,
21    /// descriptions, MIME types, and other attributes provided by the server.
22    /// Resources represent data or content that can be accessed by the client.
23    ///
24    /// # Returns
25    ///
26    /// Returns a vector of `Resource` objects containing full metadata that can be
27    /// read using `read_resource()`.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if:
32    /// - The client is not initialized
33    /// - The server doesn't support resources
34    /// - The request fails
35    ///
36    /// # Examples
37    ///
38    /// ```rust,no_run
39    /// # use turbomcp_client::Client;
40    /// # use turbomcp_transport::stdio::StdioTransport;
41    /// # async fn example() -> turbomcp_protocol::Result<()> {
42    /// let mut client = Client::new(StdioTransport::new());
43    /// client.initialize().await?;
44    ///
45    /// let resources = client.list_resources().await?;
46    /// for resource in resources {
47    ///     println!("Resource: {} ({})", resource.name, resource.uri);
48    ///     if let Some(desc) = &resource.description {
49    ///         println!("  Description: {}", desc);
50    ///     }
51    /// }
52    /// # Ok(())
53    /// # }
54    /// ```
55    pub async fn list_resources(&self) -> Result<Vec<Resource>> {
56        if !self.inner.initialized.load(Ordering::Relaxed) {
57            return Err(Error::invalid_request("Client not initialized"));
58        }
59
60        let mut all_resources = Vec::new();
61        let mut cursor = None;
62        for _ in 0..MAX_PAGINATION_PAGES {
63            let result = self.list_resources_paginated(cursor).await?;
64            let page_empty = result.resources.is_empty();
65            all_resources.extend(result.resources);
66            match result.next_cursor {
67                Some(c) if !page_empty => cursor = Some(c),
68                _ => break,
69            }
70        }
71        Ok(all_resources)
72    }
73
74    /// List resources with pagination support
75    ///
76    /// Returns the full `ListResourcesResult` including `next_cursor` for manual
77    /// pagination control. Use `list_resources()` for automatic pagination.
78    ///
79    /// # Arguments
80    ///
81    /// * `cursor` - Optional cursor from a previous `ListResourcesResult::next_cursor`
82    pub async fn list_resources_paginated(
83        &self,
84        cursor: Option<Cursor>,
85    ) -> Result<ListResourcesResult> {
86        if !self.inner.initialized.load(Ordering::Relaxed) {
87            return Err(Error::invalid_request("Client not initialized"));
88        }
89
90        let request = ListResourcesRequest {
91            cursor,
92            _meta: None,
93        };
94        let params = if request.cursor.is_some() {
95            Some(serde_json::to_value(&request)?)
96        } else {
97            None
98        };
99        self.inner.protocol.request("resources/list", params).await
100    }
101
102    /// Read the content of a specific resource by URI
103    ///
104    /// Retrieves the content of a resource identified by its URI.
105    /// Resources can contain text, binary data, or structured content.
106    ///
107    /// # Arguments
108    ///
109    /// * `uri` - The URI of the resource to read
110    ///
111    /// # Returns
112    ///
113    /// Returns `ReadResourceResult` containing the resource content and metadata.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if:
118    /// - The client is not initialized
119    /// - The URI is empty or invalid
120    /// - The resource doesn't exist
121    /// - Access to the resource is denied
122    ///
123    /// # Examples
124    ///
125    /// ```rust,no_run
126    /// # use turbomcp_client::Client;
127    /// # use turbomcp_transport::stdio::StdioTransport;
128    /// # async fn example() -> turbomcp_protocol::Result<()> {
129    /// let mut client = Client::new(StdioTransport::new());
130    /// client.initialize().await?;
131    ///
132    /// let result = client.read_resource("file:///path/to/document.txt").await?;
133    /// for content in result.contents {
134    ///     println!("Resource content: {:?}", content);
135    /// }
136    /// # Ok(())
137    /// # }
138    /// ```
139    pub async fn read_resource(&self, uri: &str) -> Result<ReadResourceResult> {
140        if !self.inner.initialized.load(Ordering::Relaxed) {
141            return Err(Error::invalid_request("Client not initialized"));
142        }
143
144        if uri.is_empty() {
145            return Err(Error::invalid_request("Resource URI cannot be empty"));
146        }
147
148        // Send read_resource request
149        let request = ReadResourceRequest {
150            uri: uri.into(),
151            _meta: None,
152        };
153
154        let response: ReadResourceResult = self
155            .inner
156            .protocol
157            .request("resources/read", Some(serde_json::to_value(request)?))
158            .await?;
159        Ok(response)
160    }
161
162    /// List available resource templates from the MCP server
163    ///
164    /// Returns a list of resource template URIs that define patterns for
165    /// generating resource URIs. Templates allow servers to describe
166    /// families of related resources without listing each individual resource.
167    ///
168    /// # Returns
169    ///
170    /// Returns a vector of resource template URI patterns.
171    ///
172    /// # Errors
173    ///
174    /// Returns an error if:
175    /// - The client is not initialized
176    /// - The server doesn't support resource templates
177    /// - The request fails
178    ///
179    /// # Examples
180    ///
181    /// ```rust,no_run
182    /// # use turbomcp_client::Client;
183    /// # use turbomcp_transport::stdio::StdioTransport;
184    /// # async fn example() -> turbomcp_protocol::Result<()> {
185    /// let mut client = Client::new(StdioTransport::new());
186    /// client.initialize().await?;
187    ///
188    /// let templates = client.list_resource_templates().await?;
189    /// for template in templates {
190    ///     println!("Resource template: {}", template);
191    /// }
192    /// # Ok(())
193    /// # }
194    /// ```
195    pub async fn list_resource_templates(&self) -> Result<Vec<String>> {
196        if !self.inner.initialized.load(Ordering::Relaxed) {
197            return Err(Error::invalid_request("Client not initialized"));
198        }
199
200        let mut all_templates = Vec::new();
201        let mut cursor = None;
202        for _ in 0..MAX_PAGINATION_PAGES {
203            let result = self.list_resource_templates_paginated(cursor).await?;
204            let page_empty = result.resource_templates.is_empty();
205            all_templates.extend(
206                result
207                    .resource_templates
208                    .into_iter()
209                    .map(|t| t.uri_template),
210            );
211            match result.next_cursor {
212                Some(c) if !page_empty => cursor = Some(c),
213                _ => break,
214            }
215        }
216        Ok(all_templates)
217    }
218
219    /// List resource templates with pagination support
220    ///
221    /// Returns the full `ListResourceTemplatesResult` including `next_cursor`
222    /// for manual pagination control. Use `list_resource_templates()` for
223    /// automatic pagination.
224    ///
225    /// # Arguments
226    ///
227    /// * `cursor` - Optional cursor from a previous result's `next_cursor`
228    pub async fn list_resource_templates_paginated(
229        &self,
230        cursor: Option<Cursor>,
231    ) -> Result<ListResourceTemplatesResult> {
232        if !self.inner.initialized.load(Ordering::Relaxed) {
233            return Err(Error::invalid_request("Client not initialized"));
234        }
235
236        let request = ListResourceTemplatesRequest {
237            cursor,
238            _meta: None,
239        };
240        let params = if request.cursor.is_some() {
241            Some(serde_json::to_value(&request)?)
242        } else {
243            None
244        };
245        self.inner
246            .protocol
247            .request("resources/templates", params)
248            .await
249    }
250}