use crate::error::FastMCPError;
use crate::mcp::types::ContentBlock;
use crate::server::context::Context;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json::json;
use std::collections::HashSet;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug, Serialize, Deserialize)]
pub struct ToolResult {
pub content: Vec<ContentBlock>,
#[serde(skip_serializing_if = "Option::is_none")]
pub structured_content: Option<Value>,
}
pub type ToolHandler = Box<
dyn Fn(Context, Value) -> Pin<Box<dyn Future<Output = Result<ToolResult, FastMCPError>> + Send>>
+ Send
+ Sync,
>;
fn default_handler() -> Arc<ToolHandler> {
Arc::new(Box::new(|_, _| {
Box::pin(async {
Err(FastMCPError::new(
"Tool handler not set (deserialized tool has no handler)".to_string(),
))
})
}))
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ToolFunction {
pub name: String,
pub description: Option<String>,
pub input_schema: Value,
pub output_schema: Option<Value>,
#[serde(skip, default = "default_handler")]
pub fn_handler: Arc<ToolHandler>,
#[serde(skip)]
pub compiled_schema: Option<Arc<Value>>,
}
#[derive(Clone, Serialize, Deserialize)]
pub enum ToolKind {
Function(ToolFunction),
Transformed { original: ToolFunction },
}
pub type Tool = crate::util::component::Component<ToolKind>;
impl Tool {
pub fn new(name: &str, description: &str) -> Self {
Self {
name: name.to_string(),
title: Some(name.to_string()), description: Some(description.to_string()),
enabled: true,
key: None,
tags: HashSet::new(),
meta: None,
data: ToolKind::Function(ToolFunction {
name: name.to_string(),
description: Some(description.to_string()),
input_schema: json!({
"type": "object",
"properties": {},
"required": []
}),
output_schema: None,
fn_handler: default_handler(),
compiled_schema: None,
}),
}
}
pub fn add_parameter(mut self, name: &str, type_: &str, description: &str) -> Self {
if let ToolKind::Function(ref mut func) = self.data {
if let Some(props) = func
.input_schema
.as_object_mut()
.and_then(|obj| obj.get_mut("properties"))
.and_then(|v| v.as_object_mut())
{
props.insert(
name.to_string(),
json!({
"type": type_,
"description": description
}),
);
}
}
self
}
pub fn with_handler(mut self, handler: ToolHandler) -> Self {
if let ToolKind::Function(ref mut func) = self.data {
func.fn_handler = Arc::new(handler);
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_new_defaults() {
let tool = Tool::new("greet", "Says hello");
assert_eq!(tool.name, "greet");
assert_eq!(tool.description.as_deref(), Some("Says hello"));
assert!(tool.enabled);
assert!(tool.tags.is_empty());
assert!(tool.key.is_none());
assert_eq!(tool.title.as_deref(), Some("greet"));
}
#[test]
fn test_add_parameter_modifies_schema() {
let tool = Tool::new("calc", "calculator")
.add_parameter("x", "number", "first operand")
.add_parameter("op", "string", "operator");
if let ToolKind::Function(func) = &tool.data {
let props = func.input_schema["properties"].as_object().unwrap();
assert!(props.contains_key("x"));
assert_eq!(props["x"]["type"], "number");
assert!(props.contains_key("op"));
assert_eq!(props["op"]["type"], "string");
} else {
panic!("Expected Function tool kind");
}
}
#[test]
fn test_with_handler_replaces_handler() {
let tool = Tool::new("test", "test tool").with_handler(Box::new(|_ctx, _args| {
Box::pin(async {
Ok(ToolResult {
content: vec![],
structured_content: None,
})
})
}));
assert_eq!(tool.name, "test");
}
#[test]
fn test_tool_function_input_schema_structure() {
let tool = Tool::new("noop", "does nothing");
if let ToolKind::Function(func) = &tool.data {
assert_eq!(func.input_schema["type"], "object");
assert!(
func.input_schema["properties"]
.as_object()
.unwrap()
.is_empty()
);
assert!(func.compiled_schema.is_none());
} else {
panic!("Expected Function tool kind");
}
}
}