use rmcp::model::{
AnnotateAble, Annotated, RawResource, ReadResourceResult, ResourceContents, Role,
};
pub struct ResourceEntry {
pub group: &'static str,
pub name: &'static str,
pub title: &'static str,
pub description: &'static str,
pub uri: &'static str,
pub content: &'static str,
}
#[allow(clippy::needless_raw_string_hashes)]
mod generated {
use super::ResourceEntry;
include!(concat!(env!("OUT_DIR"), "/resources_generated.rs"));
}
pub use generated::RESOURCES;
pub enum ResourceResult {
Immediate(Result<ReadResourceResult, ResourceError>),
}
#[derive(Debug)]
pub enum ResourceError {
NotFound(String),
}
#[must_use]
pub fn list_resources() -> Vec<Annotated<RawResource>> {
RESOURCES
.iter()
.map(|entry| {
RawResource::new(entry.uri, entry.name)
.with_title(entry.title)
.with_description(entry.description)
.with_mime_type("text/markdown")
.with_size(u32::try_from(entry.content.len()).unwrap_or(u32::MAX))
.no_annotation()
.with_audience(vec![Role::Assistant])
.with_priority(0.8)
})
.collect()
}
#[must_use]
pub fn read_resource(uri: &str) -> ResourceResult {
let result = RESOURCES
.iter()
.find(|entry| entry.uri == uri)
.map(|entry| {
ReadResourceResult::new(vec![
ResourceContents::text(entry.content, entry.uri).with_mime_type("text/markdown"),
])
})
.ok_or_else(|| ResourceError::NotFound(uri.into()));
ResourceResult::Immediate(result)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn generated_resources_are_not_empty() {
assert!(
!RESOURCES.is_empty(),
"build.rs should generate at least one resource"
);
}
#[test]
fn all_entries_have_required_fields() {
for entry in RESOURCES {
assert!(!entry.group.is_empty(), "group should not be empty");
assert!(!entry.name.is_empty(), "name should not be empty");
assert!(!entry.title.is_empty(), "title should not be empty");
assert!(
!entry.description.is_empty(),
"description should not be empty for {}",
entry.name
);
assert!(!entry.uri.is_empty(), "uri should not be empty");
assert!(
!entry.content.is_empty(),
"content should not be empty for {}",
entry.name
);
}
}
#[test]
fn uri_format_matches_group_colon_name() {
for entry in RESOURCES {
let expected = format!("{}:{}", entry.group, entry.name);
assert_eq!(
entry.uri, expected,
"URI should be group:name for {}",
entry.name
);
}
}
#[test]
fn list_resources_returns_all_entries() {
let resources = list_resources();
assert_eq!(resources.len(), RESOURCES.len());
}
#[test]
fn list_resources_sets_annotations() {
let resources = list_resources();
for resource in &resources {
let annotations = resource
.annotations
.as_ref()
.expect("annotations should be set");
assert_eq!(
annotations.audience.as_deref(),
Some(&[Role::Assistant][..])
);
assert_eq!(annotations.priority, Some(0.8));
}
}
#[test]
fn list_resources_sets_mime_type() {
let resources = list_resources();
for resource in &resources {
assert_eq!(resource.raw.mime_type.as_deref(), Some("text/markdown"));
}
}
#[test]
fn read_resource_returns_content_for_valid_uri() {
for entry in RESOURCES {
let result = read_resource(entry.uri);
let ResourceResult::Immediate(Ok(read_result)) = result else {
panic!("expected Immediate(Ok(...)) for URI {}", entry.uri);
};
assert_eq!(read_result.contents.len(), 1);
match &read_result.contents[0] {
ResourceContents::TextResourceContents { uri, text, .. } => {
assert_eq!(uri, entry.uri);
assert_eq!(text, entry.content);
}
ResourceContents::BlobResourceContents { .. } => {
panic!("expected text content, got blob");
}
}
}
}
#[test]
fn read_resource_returns_error_for_unknown_uri() {
let result = read_resource("nonexistent:nothing");
let ResourceResult::Immediate(Err(ResourceError::NotFound(uri))) = result else {
panic!("expected Immediate(Err(NotFound(...)))");
};
assert_eq!(uri, "nonexistent:nothing");
}
#[test]
fn insights_group_exists() {
let has_insights = RESOURCES.iter().any(|e| e.group == "insights");
assert!(has_insights, "should have at least one insights resource");
}
#[test]
fn known_insight_resources_present() {
let names: Vec<&str> = RESOURCES
.iter()
.filter(|e| e.group == "insights")
.map(|e| e.name)
.collect();
assert!(
names.contains(&"shaded-views"),
"should have shaded-views insight"
);
assert!(names.contains(&"sketch"), "should have sketch insight");
}
}