Skip to main content

claude_agent/mcp/
resources.rs

1//! MCP Resource management
2//!
3//! This module provides utilities for working with MCP resources.
4
5use super::{McpContent, McpError, McpManager, McpResourceDefinition, McpResult};
6
7/// Resource manager for handling MCP resource operations
8pub struct ResourceManager {
9    manager: std::sync::Arc<McpManager>,
10}
11
12impl ResourceManager {
13    /// Create a new resource manager
14    pub fn new(manager: std::sync::Arc<McpManager>) -> Self {
15        Self { manager }
16    }
17
18    /// List all available resources from all connected servers
19    pub async fn list_all(&self) -> Vec<(String, McpResourceDefinition)> {
20        self.manager.list_resources().await
21    }
22
23    /// List resources from a specific server
24    #[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    /// List resources from a specific server (stub when feature disabled)
49    #[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    /// Read a resource
58    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    /// Read a resource as text
63    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    /// Find resources by URI pattern
81    pub async fn find_by_pattern(&self, pattern: &str) -> Vec<(String, McpResourceDefinition)> {
82        let all_resources = self.manager.list_resources().await;
83
84        // Simple glob-like pattern matching
85        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
101/// Builder for resource queries
102pub struct ResourceQuery {
103    /// Server filter
104    server: Option<String>,
105    /// URI pattern
106    pattern: Option<String>,
107    /// MIME type filter
108    mime_type: Option<String>,
109}
110
111impl ResourceQuery {
112    /// Create a new resource query
113    pub fn new() -> Self {
114        Self {
115            server: None,
116            pattern: None,
117            mime_type: None,
118        }
119    }
120
121    /// Filter by server name
122    pub fn server(mut self, name: impl Into<String>) -> Self {
123        self.server = Some(name.into());
124        self
125    }
126
127    /// Filter by URI pattern
128    pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
129        self.pattern = Some(pattern.into());
130        self
131    }
132
133    /// Filter by MIME type
134    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    /// Execute the query against a resource manager
140    pub async fn execute(&self, manager: &ResourceManager) -> Vec<(String, McpResourceDefinition)> {
141        let mut results = manager.list_all().await;
142
143        // Filter by server
144        if let Some(ref server) = self.server {
145            results.retain(|(s, _)| s == server);
146        }
147
148        // Filter by pattern
149        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        // Filter by MIME type
162        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}