use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ToolDescription {
pub name: String,
pub description: String,
#[serde(rename = "inputSchema")]
pub input_schema: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceDescription {
pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PromptArgument {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PromptDescription {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub arguments: Vec<PromptArgument>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tool_description_serializes_camelcase_input_schema() {
let tool = ToolDescription {
name: "search".into(),
description: "Search documents".into(),
input_schema: serde_json::json!({"type": "object"}),
};
let json = serde_json::to_value(&tool).unwrap();
assert!(json.get("inputSchema").is_some(), "expected camelCase key");
assert!(json.get("input_schema").is_none());
}
#[test]
fn resource_description_serializes_camelcase_mime_type() {
let res = ResourceDescription {
uri: "docs://readme".into(),
name: "README".into(),
description: Some("Project readme".into()),
mime_type: Some("text/markdown".into()),
};
let json = serde_json::to_value(&res).unwrap();
assert_eq!(json["mimeType"], "text/markdown");
assert!(json.get("mime_type").is_none());
}
#[test]
fn resource_description_roundtrip() {
let res = ResourceDescription {
uri: "docs://readme".into(),
name: "README".into(),
description: Some("Project readme".into()),
mime_type: Some("text/markdown".into()),
};
let json = serde_json::to_string(&res).unwrap();
let back: ResourceDescription = serde_json::from_str(&json).unwrap();
assert_eq!(back.uri, "docs://readme");
assert_eq!(back.mime_type.as_deref(), Some("text/markdown"));
}
#[test]
fn prompt_description_serializes() {
let prompt = PromptDescription {
name: "summarize".into(),
description: Some("Summarize a document".into()),
arguments: vec![PromptArgument {
name: "text".into(),
description: Some("The text to summarize".into()),
required: true,
}],
};
let json = serde_json::to_value(&prompt).unwrap();
assert_eq!(json["name"], "summarize");
assert_eq!(json["arguments"][0]["name"], "text");
assert_eq!(json["arguments"][0]["required"], true);
}
#[test]
fn prompt_argument_omits_false_required() {
let arg = PromptArgument {
name: "opt".into(),
description: None,
required: false,
};
let json = serde_json::to_value(&arg).unwrap();
assert!(json.get("required").is_none(), "false required is omitted");
assert!(json.get("description").is_none());
}
}