use std::{borrow::Cow, sync::Arc}; #[cfg(feature = "server")] use schemars::JsonSchema; /// Tools represent a routine that a server can execute /// Tool calls represent requests from the client to execute one use serde::{Deserialize, Serialize}; use serde_json::Value; use super::{Icon, JsonObject, Meta}; /// A tool that can be used by a model. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Tool { /// The name of the tool pub name: Cow<'static, str>, /// A human-readable title for the tool #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// A description of what the tool does #[serde(skip_serializing_if = "Option::is_none")] pub description: Option>, /// A JSON Schema object defining the expected parameters for the tool pub input_schema: Arc, /// An optional JSON Schema object defining the structure of the tool's output #[serde(skip_serializing_if = "Option::is_none")] pub output_schema: Option>, #[serde(skip_serializing_if = "Option::is_none")] /// Optional additional tool information. pub annotations: Option, /// Execution-related configuration including task support mode. #[serde(skip_serializing_if = "Option::is_none")] pub execution: Option, /// Optional list of icons for the tool #[serde(skip_serializing_if = "Option::is_none")] pub icons: Option>, /// Optional additional metadata for this tool #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } /// Per-tool task support mode as defined in the MCP specification. /// /// This enum indicates whether a tool supports task-based invocation, /// allowing clients to know how to properly call the tool. /// /// See [Tool-Level Negotiation](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#tool-level-negotiation). #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum TaskSupport { /// Clients MUST NOT invoke this tool as a task (default behavior). #[default] Forbidden, /// Clients MAY invoke this tool as either a task or a normal call. Optional, /// Clients MUST invoke this tool as a task. Required, } /// Execution-related configuration for a tool. /// /// This struct contains settings that control how a tool should be executed, /// including task support configuration. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ToolExecution { /// Indicates whether this tool supports task-based invocation. /// /// When not present or set to `Forbidden`, clients MUST NOT invoke this tool as a task. /// When set to `Optional`, clients MAY invoke this tool as a task or normal call. /// When set to `Required`, clients MUST invoke this tool as a task. #[serde(skip_serializing_if = "Option::is_none")] pub task_support: Option, } impl ToolExecution { /// Create a new empty ToolExecution configuration. pub fn new() -> Self { Self::default() } /// Set the task support mode. pub fn with_task_support(mut self, task_support: TaskSupport) -> Self { self.task_support = Some(task_support); self } } /// Additional properties describing a Tool to clients. /// /// NOTE: all properties in ToolAnnotations are **hints**. /// They are not guaranteed to provide a faithful description of /// tool behavior (including descriptive properties like `title`). /// /// Clients should never make tool use decisions based on ToolAnnotations /// received from untrusted servers. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ToolAnnotations { /// A human-readable title for the tool. #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, /// If true, the tool does not modify its environment. /// /// Default: false #[serde(skip_serializing_if = "Option::is_none")] pub read_only_hint: Option, /// If true, the tool may perform destructive updates to its environment. /// If false, the tool performs only additive updates. /// /// (This property is meaningful only when `readOnlyHint == false`) /// /// Default: true /// A human-readable description of the tool's purpose. #[serde(skip_serializing_if = "Option::is_none")] pub destructive_hint: Option, /// If true, calling the tool repeatedly with the same arguments /// will have no additional effect on the its environment. /// /// (This property is meaningful only when `readOnlyHint == false`) /// /// Default: false. #[serde(skip_serializing_if = "Option::is_none")] pub idempotent_hint: Option, /// If true, this tool may interact with an "open world" of external /// entities. If false, the tool's domain of interaction is closed. /// For example, the world of a web search tool is open, whereas that /// of a memory tool is not. /// /// Default: true #[serde(skip_serializing_if = "Option::is_none")] pub open_world_hint: Option, } impl ToolAnnotations { pub fn new() -> Self { Self::default() } pub fn with_title(title: T) -> Self where T: Into, { ToolAnnotations { title: Some(title.into()), ..Self::default() } } pub fn read_only(self, read_only: bool) -> Self { ToolAnnotations { read_only_hint: Some(read_only), ..self } } pub fn destructive(self, destructive: bool) -> Self { ToolAnnotations { destructive_hint: Some(destructive), ..self } } pub fn idempotent(self, idempotent: bool) -> Self { ToolAnnotations { idempotent_hint: Some(idempotent), ..self } } pub fn open_world(self, open_world: bool) -> Self { ToolAnnotations { open_world_hint: Some(open_world), ..self } } /// If not set, defaults to true. pub fn is_destructive(&self) -> bool { self.destructive_hint.unwrap_or(true) } /// If not set, defaults to false. pub fn is_idempotent(&self) -> bool { self.idempotent_hint.unwrap_or(false) } } impl Tool { /// Create a new tool with the given name and description pub fn new(name: N, description: D, input_schema: S) -> Self where N: Into>, D: Into>, S: Into>, { Tool { name: name.into(), title: None, description: Some(description.into()), input_schema: input_schema.into(), output_schema: None, annotations: None, execution: None, icons: None, meta: None, } } pub fn annotate(self, annotations: ToolAnnotations) -> Self { Tool { annotations: Some(annotations), ..self } } /// Set the execution configuration for this tool. pub fn with_execution(self, execution: ToolExecution) -> Self { Tool { execution: Some(execution), ..self } } /// Returns the task support mode for this tool. /// /// Returns `TaskSupport::Forbidden` if not explicitly set. pub fn task_support(&self) -> TaskSupport { self.execution .as_ref() .and_then(|e| e.task_support) .unwrap_or_default() } /// Set the output schema using a type that implements JsonSchema /// /// # Panics /// /// Panics if the generated schema does not have root type "object" as required by MCP specification. #[cfg(feature = "server")] pub fn with_output_schema(mut self) -> Self { let schema = crate::handler::server::tool::schema_for_output::() .unwrap_or_else(|e| panic!("Invalid output schema for tool '{}': {}", self.name, e)); self.output_schema = Some(schema); self } /// Set the input schema using a type that implements JsonSchema #[cfg(feature = "server")] pub fn with_input_schema(mut self) -> Self { self.input_schema = crate::handler::server::tool::schema_for_type::(); self } /// Get the schema as json value pub fn schema_as_json_value(&self) -> Value { Value::Object(self.input_schema.as_ref().clone()) } }