use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Tool {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "inputSchema")]
pub input_schema: ToolInputSchema,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolInputSchema {
#[serde(rename = "type")]
pub type_: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(flatten)]
pub additional: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListToolsRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ListToolsResult {
pub tools: Vec<Tool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "nextCursor")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CallToolRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CallToolResult {
pub content: Vec<ToolResultContent>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "isError")]
pub is_error: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ToolResultContent {
#[serde(rename = "text")]
Text {
text: String,
},
#[serde(rename = "image")]
Image {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
},
#[serde(rename = "resource")]
Resource {
resource: String,
},
}
impl Tool {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: Some(description.into()),
input_schema: ToolInputSchema {
type_: "object".to_string(),
properties: None,
required: None,
additional: None,
},
}
}
pub fn with_parameter(
mut self,
name: impl Into<String>,
description: impl Into<String>,
required: bool,
) -> Self {
let name = name.into();
let description = description.into();
if self.input_schema.properties.is_none() {
self.input_schema.properties = Some(serde_json::json!({}));
}
if let Some(Value::Object(ref mut props)) = &mut self.input_schema.properties {
props.insert(
name.clone(),
serde_json::json!({
"type": "string",
"description": description
}),
);
}
if required {
if self.input_schema.required.is_none() {
self.input_schema.required = Some(Vec::new());
}
if let Some(ref mut req) = &mut self.input_schema.required {
req.push(name);
}
}
self
}
}
impl ToolResultContent {
pub fn text(text: impl Into<String>) -> Self {
Self::Text { text: text.into() }
}
pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Image {
data: data.into(),
mime_type: mime_type.into(),
}
}
pub fn resource(uri: impl Into<String>) -> Self {
Self::Resource {
resource: uri.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_tool_creation() {
let tool = Tool::new("calculate", "Perform mathematical calculations")
.with_parameter("expression", "Mathematical expression to evaluate", true);
assert_eq!(tool.name, "calculate");
assert!(tool.description.is_some());
assert!(tool.input_schema.required.is_some());
}
#[test]
fn test_tool_serialization() {
let tool = Tool {
name: "test_tool".to_string(),
description: Some("A test tool".to_string()),
input_schema: ToolInputSchema {
type_: "object".to_string(),
properties: Some(json!({"param": {"type": "string"}})),
required: Some(vec!["param".to_string()]),
additional: None,
},
};
let json = serde_json::to_string(&tool).unwrap();
let deserialized: Tool = serde_json::from_str(&json).unwrap();
assert_eq!(tool, deserialized);
}
#[test]
fn test_tool_result_content() {
let text = ToolResultContent::text("Hello, world!");
let image = ToolResultContent::image("base64data", "image/png");
let resource = ToolResultContent::resource("file:///path/to/file.txt");
let text_json = serde_json::to_string(&text).unwrap();
let image_json = serde_json::to_string(&image).unwrap();
let resource_json = serde_json::to_string(&resource).unwrap();
let _: ToolResultContent = serde_json::from_str(&text_json).unwrap();
let _: ToolResultContent = serde_json::from_str(&image_json).unwrap();
let _: ToolResultContent = serde_json::from_str(&resource_json).unwrap();
}
}