use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Resource {
pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub widget_renderer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mind_section: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trust_level: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceTemplate {
pub uri_template: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceContents {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResourcesParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResourcesResult {
pub resources: Vec<Resource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadResourceParams {
pub uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadResourceResult {
pub contents: Vec<ResourceContents>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribeResourceParams {
pub uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResourceTemplatesResult {
pub resource_templates: Vec<ResourceTemplate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_roundtrip() {
let resource = Resource {
uri: "omegon://scribe/engagements/recro".to_string(),
name: "Recro Engagement".to_string(),
description: Some("Current engagement data".to_string()),
mime_type: Some("application/json".to_string()),
widget_renderer: Some("table".to_string()),
mind_section: Some("Engagements".to_string()),
trust_level: Some("internal".to_string()),
};
let json = serde_json::to_string(&resource).unwrap();
let parsed: Resource = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.uri, "omegon://scribe/engagements/recro");
assert_eq!(parsed.name, "Recro Engagement");
assert_eq!(parsed.widget_renderer.as_deref(), Some("table"));
assert_eq!(parsed.mind_section.as_deref(), Some("Engagements"));
}
#[test]
fn test_resource_minimal() {
let json = r#"{"uri":"omegon://ext/data","name":"Data"}"#;
let parsed: Resource = serde_json::from_str(json).unwrap();
assert_eq!(parsed.uri, "omegon://ext/data");
assert!(parsed.description.is_none());
assert!(parsed.widget_renderer.is_none());
assert!(parsed.mind_section.is_none());
}
#[test]
fn test_resource_omegon_fields_stripped_for_mcp() {
let resource = Resource {
uri: "omegon://scribe/issues".to_string(),
name: "Issues".to_string(),
description: None,
mime_type: None,
widget_renderer: Some("table".to_string()),
mind_section: Some("Issues".to_string()),
trust_level: Some("internal".to_string()),
};
let json = serde_json::to_value(&resource).unwrap();
let obj = json.as_object().unwrap();
assert!(obj.contains_key("widget_renderer"));
assert!(obj.contains_key("mind_section"));
assert!(obj.contains_key("trust_level"));
assert!(obj.contains_key("uri"));
assert!(obj.contains_key("name"));
}
#[test]
fn test_resource_template_roundtrip() {
let template = ResourceTemplate {
uri_template: "omegon://scribe/engagements/{client_name}".to_string(),
name: "Client Engagement".to_string(),
description: Some("Engagement data for a specific client".to_string()),
mime_type: Some("application/json".to_string()),
};
let json = serde_json::to_string(&template).unwrap();
let parsed: ResourceTemplate = serde_json::from_str(&json).unwrap();
assert_eq!(
parsed.uri_template,
"omegon://scribe/engagements/{client_name}"
);
}
#[test]
fn test_resource_contents_text() {
let contents = ResourceContents {
uri: "omegon://scribe/engagements/recro".to_string(),
mime_type: Some("application/json".to_string()),
text: Some(r#"{"client":"Recro","status":"active"}"#.to_string()),
blob: None,
};
let json = serde_json::to_string(&contents).unwrap();
let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
assert!(parsed.text.is_some());
assert!(parsed.blob.is_none());
}
#[test]
fn test_resource_contents_blob() {
let contents = ResourceContents {
uri: "omegon://scry/images/latest".to_string(),
mime_type: Some("image/png".to_string()),
text: None,
blob: Some("iVBORw0KGgo=".to_string()),
};
let json = serde_json::to_string(&contents).unwrap();
let parsed: ResourceContents = serde_json::from_str(&json).unwrap();
assert!(parsed.text.is_none());
assert!(parsed.blob.is_some());
}
#[test]
fn test_list_resources_result_pagination() {
let result = ListResourcesResult {
resources: vec![Resource {
uri: "omegon://ext/data".to_string(),
name: "Data".to_string(),
description: None,
mime_type: None,
widget_renderer: None,
mind_section: None,
trust_level: None,
}],
next_cursor: Some("page2".to_string()),
};
let json = serde_json::to_string(&result).unwrap();
let parsed: ListResourcesResult = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.resources.len(), 1);
assert_eq!(parsed.next_cursor.as_deref(), Some("page2"));
}
#[test]
fn test_list_resources_result_last_page() {
let result = ListResourcesResult {
resources: vec![],
next_cursor: None,
};
let json = serde_json::to_string(&result).unwrap();
assert!(!json.contains("next_cursor"));
}
#[test]
fn test_read_resource_params() {
let params = ReadResourceParams {
uri: "omegon://scribe/issues".to_string(),
};
let json = serde_json::to_string(¶ms).unwrap();
let parsed: ReadResourceParams = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.uri, "omegon://scribe/issues");
}
#[test]
fn test_subscribe_params() {
let params = SubscribeResourceParams {
uri: "omegon://scribe/issues".to_string(),
};
let json = serde_json::to_string(¶ms).unwrap();
let parsed: SubscribeResourceParams = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.uri, "omegon://scribe/issues");
}
}