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}