mcp_core_rs/
resource.rs

1use chrono::{DateTime, Utc};
2use mcp_error_rs::{Error, Result};
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6use crate::Annotation;
7
8const EPSILON: f32 = 1e-6; // Tolerance for floating point comparison
9
10#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
11#[serde(rename_all = "lowercase")]
12pub enum MimeType {
13    Text,
14    Blob,
15}
16
17/// Represents a resource in the extension with metadata
18#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
19#[serde(rename_all = "camelCase")]
20pub struct Resource {
21    /// URI representing the resource location (e.g., "file:///path/to/file" or "str:///content")
22    pub uri: String,
23    /// Name of the resource
24    pub name: String,
25    /// Optional description of the resource
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub description: Option<String>,
28
29    pub mime_type: MimeType,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub annotation: Option<Annotation>,
32}
33
34#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
35#[serde(rename_all = "camelCase", untagged)]
36pub enum ResourceContents {
37    TextResourceContents {
38        uri: String,
39        #[serde(skip_serializing_if = "Option::is_none")]
40        mime_type: Option<String>,
41        text: String,
42    },
43    BlobResourceContents {
44        uri: String,
45        #[serde(skip_serializing_if = "Option::is_none")]
46        mime_type: Option<String>,
47        blob: String,
48    },
49}
50
51impl Resource {
52    /// Creates a new Resource from a URI with explicit mime type
53    pub fn new<S: AsRef<str>>(uri: S, mime_type: MimeType, name: Option<String>) -> Result<Self> {
54        let uri = uri.as_ref();
55        let url = Url::parse(uri)?;
56
57        // Extract name from the path component of the URI
58        // Use provided name if available, otherwise extract from URI
59        let name = match name {
60            Some(n) => n,
61            None => url
62                .path_segments()
63                .and_then(|segments| segments.last())
64                .unwrap_or("unnamed")
65                .to_string(),
66        };
67
68        Ok(Self {
69            uri: uri.to_string(),
70            name,
71            description: None,
72            mime_type,
73            annotation: Some(Annotation::new_with_priority(0.0)),
74        })
75    }
76
77    /// Creates a new Resource with explicit URI, name, and priority
78    pub fn with_uri<S: Into<String>>(
79        uri: S,
80        name: S,
81        priority: f32,
82        mime_type: MimeType,
83    ) -> Result<Self> {
84        let uri_string: String = uri.into();
85        Url::parse(&uri_string)?;
86
87        Ok(Self {
88            uri: uri_string,
89            name: name.into(),
90            description: None,
91            mime_type,
92            annotation: Some(Annotation::new_with_priority(priority)),
93        })
94    }
95
96    /// Updates the resource's timestamp to the current time
97    pub fn update_timestamp(&mut self) -> Result<()> {
98        if let Some(ann) = &mut self.annotation {
99            ann.update_timestamp();
100            Ok(())
101        } else {
102            Err(Error::System("Missing annotation".into()))
103        }
104    }
105
106    // TODO 所有和annotiaon的方法设计不好需要修改
107
108    /// Sets the priority of the resource and returns self for method chaining
109    pub fn with_priority(mut self, priority: f32) -> Self {
110        self.annotation.as_mut().unwrap().priority = Some(priority);
111        self
112    }
113
114    /// Mark the resource as active, i.e. set its priority to 1.0
115    pub fn mark_active(self) -> Self {
116        self.with_priority(1.0)
117    }
118
119    // Check if the resource is active
120    pub fn is_active(&self) -> bool {
121        if let Some(priority) = self.priority() {
122            (priority - 1.0).abs() < EPSILON
123        } else {
124            false
125        }
126    }
127
128    /// Returns the priority of the resource, if set
129    pub fn priority(&self) -> Option<f32> {
130        self.annotation.as_ref().and_then(|a| a.priority)
131    }
132
133    /// Returns the timestamp of the resource, if set
134    pub fn timestamp(&self) -> Option<DateTime<Utc>> {
135        self.annotation.as_ref().and_then(|a| a.timestamp)
136    }
137
138    /// Returns the scheme of the URI
139    pub fn scheme(&self) -> Result<String> {
140        let url = Url::parse(&self.uri)?;
141        Ok(url.scheme().to_string())
142    }
143
144    /// Sets the description of the resource
145    pub fn with_description<S: Into<String>>(mut self, description: S) -> Self {
146        self.description = Some(description.into());
147        self
148    }
149
150    /// Sets the MIME type of the resource
151    pub fn with_mime_type(mut self, mime_type: MimeType) -> Self {
152        self.mime_type = mime_type;
153        self
154    }
155}