Skip to main content

mcp/
resources.rs

1//! Resource Protocol Abstraction
2//!
3//! This module provides support for MCP Resources - application-provided contextual data
4//! that agents can read and reference.
5//!
6//! Resources complement Tools:
7//! - **Tools**: Model-controlled actions (agent decides when to invoke)
8//! - **Resources**: Application-controlled context (app provides to agent)
9//!
10//! # Architecture
11//!
12//! ```text
13//! Agent → ResourceProtocol → Resource URIs
14//!                         → Read Resource Content
15//! ```
16//!
17//! # Example
18//!
19//! ```rust,ignore
20//! use cloudllm::resource_protocol::{ResourceMetadata, ResourceProtocol};
21//! use std::sync::Arc;
22//!
23//! struct MyResourceProtocol;
24//!
25//! #[async_trait::async_trait]
26//! impl ResourceProtocol for MyResourceProtocol {
27//!     async fn list_resources(&self) -> Result<Vec<ResourceMetadata>, Box<dyn std::error::Error + Send + Sync>> {
28//!         Ok(vec![
29//!             ResourceMetadata::new("file:///config.yaml", "Application configuration"),
30//!             ResourceMetadata::new("schema:///database", "Database schema"),
31//!         ])
32//!     }
33//!
34//!     async fn read_resource(&self, uri: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
35//!         match uri {
36//!             "file:///config.yaml" => Ok("...".to_string()),
37//!             _ => Err("Not found".into()),
38//!         }
39//!     }
40//! }
41//! ```
42
43use async_trait::async_trait;
44use serde::{Deserialize, Serialize};
45use std::collections::HashMap;
46use std::error::Error;
47
48/// Metadata describing a resource
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ResourceMetadata {
51    /// Unique resource identifier (URI)
52    /// Examples: "file:///config.yaml", "schema:///users", "db:///schema.sql"
53    pub uri: String,
54    /// Human-readable description of the resource
55    pub description: String,
56    /// Optional MIME type of the resource content
57    pub mime_type: Option<String>,
58    /// Additional metadata
59    pub metadata: HashMap<String, serde_json::Value>,
60}
61
62impl ResourceMetadata {
63    /// Create a new resource with URI and description
64    pub fn new(uri: impl Into<String>, description: impl Into<String>) -> Self {
65        Self {
66            uri: uri.into(),
67            description: description.into(),
68            mime_type: None,
69            metadata: HashMap::new(),
70        }
71    }
72
73    /// Set the MIME type for this resource
74    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
75        self.mime_type = Some(mime_type.into());
76        self
77    }
78
79    /// Add metadata to the resource
80    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
81        self.metadata.insert(key.into(), value);
82        self
83    }
84}
85
86/// Trait for implementing resource protocols
87///
88/// Resources are application-provided contextual data that agents can read.
89/// Unlike tools (which perform actions), resources provide information.
90#[async_trait]
91pub trait ResourceProtocol: Send + Sync {
92    /// List all available resources
93    async fn list_resources(&self) -> Result<Vec<ResourceMetadata>, Box<dyn Error + Send + Sync>>;
94
95    /// Read the content of a resource by URI
96    async fn read_resource(&self, uri: &str) -> Result<String, Box<dyn Error + Send + Sync>>;
97
98    /// Protocol identifier (e.g., "mcp", "custom")
99    fn protocol_name(&self) -> &str {
100        "resource"
101    }
102
103    /// Initialize/connect to the resource protocol (optional)
104    async fn initialize(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
105        Ok(())
106    }
107
108    /// Cleanup/disconnect from the resource protocol (optional)
109    async fn shutdown(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
110        Ok(())
111    }
112}
113
114/// Error types for resource operations
115#[derive(Debug, Clone)]
116pub enum ResourceError {
117    /// Requested resource is not available
118    NotFound(String),
119    /// Permission denied reading this resource
120    PermissionDenied(String),
121    /// Invalid URI format
122    InvalidUri(String),
123    /// Protocol error
124    ProtocolError(String),
125}
126
127impl std::fmt::Display for ResourceError {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            ResourceError::NotFound(uri) => write!(f, "Resource not found: {}", uri),
131            ResourceError::PermissionDenied(uri) => write!(f, "Permission denied: {}", uri),
132            ResourceError::InvalidUri(uri) => write!(f, "Invalid URI: {}", uri),
133            ResourceError::ProtocolError(msg) => write!(f, "Protocol error: {}", msg),
134        }
135    }
136}
137
138impl std::error::Error for ResourceError {}