claude_agent/mcp/
resources.rs1use super::{McpContent, McpError, McpManager, McpResourceDefinition, McpResult};
6
7pub struct ResourceManager {
9 manager: std::sync::Arc<McpManager>,
10}
11
12impl ResourceManager {
13 pub fn new(manager: std::sync::Arc<McpManager>) -> Self {
15 Self { manager }
16 }
17
18 pub async fn list_all(&self) -> Vec<(String, McpResourceDefinition)> {
20 self.manager.list_resources().await
21 }
22
23 #[cfg(feature = "mcp")]
25 pub async fn list_from_server(
26 &self,
27 server_name: &str,
28 ) -> McpResult<Vec<McpResourceDefinition>> {
29 let all_resources = self.manager.list_resources().await;
30 let resources: Vec<_> = all_resources
31 .into_iter()
32 .filter(|(name, _)| name == server_name)
33 .map(|(_, r)| r)
34 .collect();
35
36 if resources.is_empty() {
37 let servers = self.manager.list_servers().await;
38 if !servers.contains(&server_name.to_string()) {
39 return Err(McpError::ServerNotFound {
40 name: server_name.to_string(),
41 });
42 }
43 }
44
45 Ok(resources)
46 }
47
48 #[cfg(not(feature = "mcp"))]
50 pub async fn list_from_server(
51 &self,
52 _server_name: &str,
53 ) -> McpResult<Vec<McpResourceDefinition>> {
54 Ok(Vec::new())
55 }
56
57 pub async fn read(&self, server_name: &str, uri: &str) -> McpResult<Vec<McpContent>> {
59 self.manager.read_resource(server_name, uri).await
60 }
61
62 pub async fn read_text(&self, server_name: &str, uri: &str) -> McpResult<String> {
64 let contents = self.read(server_name, uri).await?;
65
66 let text: Vec<String> = contents
67 .iter()
68 .filter_map(|c| c.as_text().map(|s| s.to_string()))
69 .collect();
70
71 if text.is_empty() {
72 Err(McpError::ResourceNotFound {
73 uri: format!("{} (no text content)", uri),
74 })
75 } else {
76 Ok(text.join("\n"))
77 }
78 }
79
80 pub async fn find_by_pattern(&self, pattern: &str) -> Vec<(String, McpResourceDefinition)> {
82 let all_resources = self.manager.list_resources().await;
83
84 let is_prefix_match = pattern.ends_with('*');
86 let pattern = pattern.trim_end_matches('*');
87
88 all_resources
89 .into_iter()
90 .filter(|(_, r)| {
91 if is_prefix_match {
92 r.uri.starts_with(pattern)
93 } else {
94 r.uri == pattern
95 }
96 })
97 .collect()
98 }
99}
100
101pub struct ResourceQuery {
103 server: Option<String>,
105 pattern: Option<String>,
107 mime_type: Option<String>,
109}
110
111impl ResourceQuery {
112 pub fn new() -> Self {
114 Self {
115 server: None,
116 pattern: None,
117 mime_type: None,
118 }
119 }
120
121 pub fn server(mut self, name: impl Into<String>) -> Self {
123 self.server = Some(name.into());
124 self
125 }
126
127 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
129 self.pattern = Some(pattern.into());
130 self
131 }
132
133 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
135 self.mime_type = Some(mime_type.into());
136 self
137 }
138
139 pub async fn execute(&self, manager: &ResourceManager) -> Vec<(String, McpResourceDefinition)> {
141 let mut results = manager.list_all().await;
142
143 if let Some(ref server) = self.server {
145 results.retain(|(s, _)| s == server);
146 }
147
148 if let Some(ref pattern) = self.pattern {
150 let is_prefix = pattern.ends_with('*');
151 let pattern = pattern.trim_end_matches('*');
152 results.retain(|(_, r)| {
153 if is_prefix {
154 r.uri.starts_with(pattern)
155 } else {
156 r.uri == pattern
157 }
158 });
159 }
160
161 if let Some(ref mime_type) = self.mime_type {
163 results.retain(|(_, r)| {
164 r.mime_type
165 .as_ref()
166 .map(|m| m == mime_type)
167 .unwrap_or(false)
168 });
169 }
170
171 results
172 }
173}
174
175impl Default for ResourceQuery {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_resource_query_builder() {
187 let query = ResourceQuery::new()
188 .server("filesystem")
189 .pattern("file://*")
190 .mime_type("text/plain");
191
192 assert_eq!(query.server, Some("filesystem".to_string()));
193 assert_eq!(query.pattern, Some("file://*".to_string()));
194 assert_eq!(query.mime_type, Some("text/plain".to_string()));
195 }
196}