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; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
11#[serde(rename_all = "lowercase")]
12pub enum MimeType {
13 Text,
14 Blob,
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
19#[serde(rename_all = "camelCase")]
20pub struct Resource {
21 pub uri: String,
23 pub name: String,
25 #[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 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 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 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 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 pub fn with_priority(mut self, priority: f32) -> Self {
110 self.annotation.as_mut().unwrap().priority = Some(priority);
111 self
112 }
113
114 pub fn mark_active(self) -> Self {
116 self.with_priority(1.0)
117 }
118
119 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 pub fn priority(&self) -> Option<f32> {
130 self.annotation.as_ref().and_then(|a| a.priority)
131 }
132
133 pub fn timestamp(&self) -> Option<DateTime<Utc>> {
135 self.annotation.as_ref().and_then(|a| a.timestamp)
136 }
137
138 pub fn scheme(&self) -> Result<String> {
140 let url = Url::parse(&self.uri)?;
141 Ok(url.scheme().to_string())
142 }
143
144 pub fn with_description<S: Into<String>>(mut self, description: S) -> Self {
146 self.description = Some(description.into());
147 self
148 }
149
150 pub fn with_mime_type(mut self, mime_type: MimeType) -> Self {
152 self.mime_type = mime_type;
153 self
154 }
155}