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 {}