use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use anyhow::{anyhow, Result};
use tokio::sync::{RwLock, broadcast};
use mcp_protocol::types::resource::{
Resource, ResourceContent, ResourceTemplate
};
use mcp_protocol::types::completion::CompletionItem;
const DEFAULT_PAGE_SIZE: usize = 50;
pub type ResourceContentProvider = Arc<dyn Fn() -> Result<Vec<ResourceContent>> + Send + Sync>;
pub type TemplateCompletionProvider = Arc<dyn Fn(String, String, Option<String>) -> Result<Vec<CompletionItem>> + Send + Sync>;
pub type TemplateExpanderFn = Arc<dyn Fn(String, HashMap<String, String>) -> Result<String> + Send + Sync>;
pub struct ResourceManager {
resources: Arc<RwLock<HashMap<String, (Resource, ResourceContentProvider)>>>,
templates: Arc<RwLock<HashMap<String, (ResourceTemplate, TemplateExpanderFn)>>>,
subscriptions: Arc<RwLock<HashMap<String, HashSet<String>>>>, update_tx: broadcast::Sender<String>, completion_providers: Arc<RwLock<HashMap<String, TemplateCompletionProvider>>>,
}
impl ResourceManager {
pub fn new() -> Self {
let (update_tx, _) = broadcast::channel(100);
Self {
resources: Arc::new(RwLock::new(HashMap::new())),
templates: Arc::new(RwLock::new(HashMap::new())),
subscriptions: Arc::new(RwLock::new(HashMap::new())),
update_tx,
completion_providers: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn register_resource(
&self,
resource: Resource,
content_provider: impl Fn() -> Result<Vec<ResourceContent>> + Send + Sync + 'static
) {
let resources = self.resources.clone();
let content_provider = Arc::new(content_provider);
tokio::spawn(async move {
let mut resources = resources.write().await;
resources.insert(resource.uri.clone(), (resource, content_provider));
});
}
pub async fn list_resources(&self, cursor: Option<String>) -> (Vec<Resource>, Option<String>) {
let resources = self.resources.read().await;
let all_resources: Vec<Resource> = resources.values().map(|(resource, _)| resource.clone()).collect();
let start_pos = match cursor {
Some(cursor) => {
let pos = all_resources.iter().position(|r| r.uri == cursor);
pos.map(|p| p + 1).unwrap_or(0)
},
None => 0,
};
let end_pos = std::cmp::min(start_pos + DEFAULT_PAGE_SIZE, all_resources.len());
let page = all_resources[start_pos..end_pos].to_vec();
let next_cursor = if end_pos < all_resources.len() {
Some(all_resources[end_pos - 1].uri.clone())
} else {
None
};
(page, next_cursor)
}
pub async fn get_resource_content(&self, uri: &str) -> Result<Vec<ResourceContent>> {
let resources = self.resources.read().await;
if let Some((_, content_provider)) = resources.get(uri) {
return content_provider();
}
let templates = self.templates.read().await;
for (template_uri, (_, _expander)) in templates.iter() {
if uri.starts_with(template_uri.split('{').next().unwrap_or("")) {
if let Some((_, content_provider)) = resources.get(uri) {
return content_provider();
}
}
}
Err(anyhow!("Resource not found: {}", uri))
}
pub fn register_template(
&self,
template: ResourceTemplate,
expander: impl Fn(String, HashMap<String, String>) -> Result<String> + Send + Sync + 'static,
) {
let templates = self.templates.clone();
let expander = Arc::new(expander);
tokio::spawn(async move {
let mut templates = templates.write().await;
templates.insert(template.uri_template.clone(), (template, expander));
});
}
pub fn register_completion_provider(
&self,
template_uri: &str,
provider: impl Fn(String, String, Option<String>) -> Result<Vec<CompletionItem>> + Send + Sync + 'static,
) {
let providers = self.completion_providers.clone();
let template_uri = template_uri.to_string();
let provider = Arc::new(provider);
tokio::spawn(async move {
let mut providers = providers.write().await;
providers.insert(template_uri, provider);
});
}
pub async fn get_completions(
&self,
template_uri: &str,
parameter: &str,
value: Option<String>,
) -> Result<Vec<CompletionItem>> {
let providers = self.completion_providers.read().await;
if let Some(provider) = providers.get(template_uri) {
return provider(template_uri.to_string(), parameter.to_string(), value);
}
Ok(Vec::new())
}
pub async fn list_templates(&self, cursor: Option<String>) -> (Vec<ResourceTemplate>, Option<String>) {
let templates = self.templates.read().await;
let all_templates: Vec<ResourceTemplate> = templates.values().map(|(template, _)| template.clone()).collect();
let start_pos = match cursor {
Some(cursor) => {
let pos = all_templates.iter().position(|t| t.uri_template == cursor);
pos.map(|p| p + 1).unwrap_or(0)
},
None => 0,
};
let end_pos = std::cmp::min(start_pos + DEFAULT_PAGE_SIZE, all_templates.len());
let page = all_templates[start_pos..end_pos].to_vec();
let next_cursor = if end_pos < all_templates.len() {
Some(all_templates[end_pos - 1].uri_template.clone())
} else {
None
};
(page, next_cursor)
}
pub async fn subscribe(&self, client_id: &str, uri: &str) -> Result<()> {
{
let resources = self.resources.read().await;
if !resources.contains_key(uri) {
return Err(anyhow::anyhow!("Resource not found: {}", uri));
}
}
let mut subscriptions = self.subscriptions.write().await;
let subscribers = subscriptions.entry(uri.to_string()).or_insert_with(HashSet::new);
subscribers.insert(client_id.to_string());
Ok(())
}
pub async fn unsubscribe(&self, client_id: &str, uri: &str) -> Result<()> {
let mut subscriptions = self.subscriptions.write().await;
if let Some(subscribers) = subscriptions.get_mut(uri) {
subscribers.remove(client_id);
if subscribers.is_empty() {
subscriptions.remove(uri);
}
}
Ok(())
}
pub async fn update_resource(
&self,
resource: Resource,
content_provider: impl Fn() -> Result<Vec<ResourceContent>> + Send + Sync + 'static
) -> Result<()> {
{
let mut resources = self.resources.write().await;
resources.insert(resource.uri.clone(), (resource.clone(), Arc::new(content_provider)));
}
let _ = self.update_tx.send(resource.uri.clone());
Ok(())
}
pub fn subscribe_to_updates(&self) -> broadcast::Receiver<String> {
self.update_tx.subscribe()
}
pub fn parse_template_parameters(&self, template: &str, uri: &str) -> HashMap<String, String> {
let mut params = HashMap::new();
let template_parts: Vec<&str> = template.split('{')
.flat_map(|part| part.split('}')).collect();
let mut uri_cursor = uri;
for (i, part) in template_parts.iter().enumerate() {
if i % 2 == 0 {
if uri_cursor.starts_with(part) {
uri_cursor = &uri_cursor[part.len()..];
}
} else {
let param_name = *part;
let next_literal = if i + 1 < template_parts.len() {
template_parts[i + 1]
} else {
""
};
let param_value = if next_literal.is_empty() {
uri_cursor.to_string()
} else if let Some(pos) = uri_cursor.find(next_literal) {
let value = &uri_cursor[..pos];
uri_cursor = &uri_cursor[pos + next_literal.len()..];
value.to_string()
} else {
uri_cursor.to_string()
};
params.insert(param_name.to_string(), param_value);
}
}
params
}
pub async fn expand_template(&self, template_uri: &str, params: HashMap<String, String>) -> Result<String> {
let templates = self.templates.read().await;
if let Some((_, expander)) = templates.get(template_uri) {
return expander(template_uri.to_string(), params);
}
let mut result = template_uri.to_string();
for (name, value) in params {
result = result.replace(&format!("{{{}}}", name), &value);
}
Ok(result)
}
}
impl Default for ResourceManager {
fn default() -> Self {
Self::new()
}
}