claude_agent/mcp/
resources.rs1use super::{McpContent, McpError, McpManager, McpResourceDefinition, McpResult};
6
7pub struct ResourceSubscription {
9 pub server_name: String,
11 pub uri: String,
13 pub active: bool,
15}
16
17pub struct ResourceManager {
19 manager: std::sync::Arc<McpManager>,
21 subscriptions: Vec<ResourceSubscription>,
23}
24
25impl ResourceManager {
26 pub fn new(manager: std::sync::Arc<McpManager>) -> Self {
28 Self {
29 manager,
30 subscriptions: Vec::new(),
31 }
32 }
33
34 pub async fn list_all(&self) -> Vec<(String, McpResourceDefinition)> {
36 self.manager.list_resources().await
37 }
38
39 #[cfg(feature = "mcp")]
41 pub async fn list_from_server(
42 &self,
43 server_name: &str,
44 ) -> McpResult<Vec<McpResourceDefinition>> {
45 let all_resources = self.manager.list_resources().await;
46 let resources: Vec<_> = all_resources
47 .into_iter()
48 .filter(|(name, _)| name == server_name)
49 .map(|(_, r)| r)
50 .collect();
51
52 if resources.is_empty() {
53 let servers = self.manager.list_servers().await;
55 if !servers.contains(&server_name.to_string()) {
56 return Err(McpError::ServerNotFound {
57 name: server_name.to_string(),
58 });
59 }
60 }
61
62 Ok(resources)
63 }
64
65 #[cfg(not(feature = "mcp"))]
67 pub async fn list_from_server(
68 &self,
69 _server_name: &str,
70 ) -> McpResult<Vec<McpResourceDefinition>> {
71 Ok(Vec::new())
72 }
73
74 pub async fn read(&self, server_name: &str, uri: &str) -> McpResult<Vec<McpContent>> {
76 self.manager.read_resource(server_name, uri).await
77 }
78
79 pub async fn read_text(&self, server_name: &str, uri: &str) -> McpResult<String> {
81 let contents = self.read(server_name, uri).await?;
82
83 let text: Vec<String> = contents
84 .iter()
85 .filter_map(|c| c.as_text().map(|s| s.to_string()))
86 .collect();
87
88 if text.is_empty() {
89 Err(McpError::ResourceNotFound {
90 uri: format!("{} (no text content)", uri),
91 })
92 } else {
93 Ok(text.join("\n"))
94 }
95 }
96
97 pub fn subscribe(&mut self, server_name: &str, uri: &str) -> usize {
102 let subscription = ResourceSubscription {
103 server_name: server_name.to_string(),
104 uri: uri.to_string(),
105 active: true,
106 };
107 self.subscriptions.push(subscription);
108 self.subscriptions.len() - 1
109 }
110
111 pub fn get_subscription(&self, index: usize) -> Option<&ResourceSubscription> {
113 self.subscriptions.get(index)
114 }
115
116 pub fn unsubscribe(&mut self, server_name: &str, uri: &str) {
118 self.subscriptions
119 .retain(|s| !(s.server_name == server_name && s.uri == uri));
120 }
121
122 pub fn subscriptions(&self) -> &[ResourceSubscription] {
124 &self.subscriptions
125 }
126
127 pub async fn find_by_pattern(&self, pattern: &str) -> Vec<(String, McpResourceDefinition)> {
129 let all_resources = self.manager.list_resources().await;
130
131 let is_prefix_match = pattern.ends_with('*');
133 let pattern = pattern.trim_end_matches('*');
134
135 all_resources
136 .into_iter()
137 .filter(|(_, r)| {
138 if is_prefix_match {
139 r.uri.starts_with(pattern)
140 } else {
141 r.uri == pattern
142 }
143 })
144 .collect()
145 }
146}
147
148pub struct ResourceQuery {
150 server: Option<String>,
152 pattern: Option<String>,
154 mime_type: Option<String>,
156}
157
158impl ResourceQuery {
159 pub fn new() -> Self {
161 Self {
162 server: None,
163 pattern: None,
164 mime_type: None,
165 }
166 }
167
168 pub fn server(mut self, name: impl Into<String>) -> Self {
170 self.server = Some(name.into());
171 self
172 }
173
174 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
176 self.pattern = Some(pattern.into());
177 self
178 }
179
180 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
182 self.mime_type = Some(mime_type.into());
183 self
184 }
185
186 pub async fn execute(&self, manager: &ResourceManager) -> Vec<(String, McpResourceDefinition)> {
188 let mut results = manager.list_all().await;
189
190 if let Some(ref server) = self.server {
192 results.retain(|(s, _)| s == server);
193 }
194
195 if let Some(ref pattern) = self.pattern {
197 let is_prefix = pattern.ends_with('*');
198 let pattern = pattern.trim_end_matches('*');
199 results.retain(|(_, r)| {
200 if is_prefix {
201 r.uri.starts_with(pattern)
202 } else {
203 r.uri == pattern
204 }
205 });
206 }
207
208 if let Some(ref mime_type) = self.mime_type {
210 results.retain(|(_, r)| {
211 r.mime_type
212 .as_ref()
213 .map(|m| m == mime_type)
214 .unwrap_or(false)
215 });
216 }
217
218 results
219 }
220}
221
222impl Default for ResourceQuery {
223 fn default() -> Self {
224 Self::new()
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_resource_query_builder() {
234 let query = ResourceQuery::new()
235 .server("filesystem")
236 .pattern("file://*")
237 .mime_type("text/plain");
238
239 assert_eq!(query.server, Some("filesystem".to_string()));
240 assert_eq!(query.pattern, Some("file://*".to_string()));
241 assert_eq!(query.mime_type, Some("text/plain".to_string()));
242 }
243}