Skip to main content

tools_kit/
declaration.rs

1//! Tool identities and complete executable declarations.
2//!
3//! [`ToolInfo`](crate::declaration::ToolInfo) carries schemas, interaction capabilities and model-visible metadata.
4//! [`ToolName`](crate::declaration::ToolName) is the rendered identity; native MCP routing stays private.
5
6use std::collections::BTreeMap;
7
8use serde::{Deserialize, Serialize};
9
10pub use crate::name::ToolName;
11
12use crate::{
13    ToolError,
14    execution::ToolResult,
15    interaction::ToolInteractionCapabilities,
16    schema::{JsonSchema, ValidationIssue},
17};
18
19/// Complete source-neutral declaration for one executable tool.
20/// A declared output schema requires structured content on every successful result.
21/// Missing content fails; explicit JSON null is validated against the schema.
22/// Model-visible error results (`is_error = true`) are exempt from output-schema validation.
23///
24/// # Examples
25///
26/// ```
27/// use tools_kit::{declaration::{ToolInfo, ToolName}, schema::JsonSchema};
28/// let info = ToolInfo::new(ToolName::new("ping")?, JsonSchema::strict(serde_json::json!({"type":"object"})));
29/// assert!(info.output_schema.is_none());
30///
31/// # Ok::<(), tools_kit::ToolError>(())
32/// ```
33///
34#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct ToolInfo {
37    /// Stable caller-facing name.
38    pub name: ToolName,
39    /// Optional display label.
40    pub title: Option<String>,
41    /// Model-visible behavior description.
42    pub description: Option<String>,
43    /// JSON Schema for invocation arguments.
44    pub input_schema: JsonSchema,
45    /// Optional JSON Schema for structured output.
46    pub output_schema: Option<JsonSchema>,
47    /// Portable behavior hints.
48    pub annotations: ToolAnnotations,
49    /// Optional visual metadata.
50    pub icons: Vec<ToolIcon>,
51    /// Supported source-neutral caller interactions.
52    pub interactions: ToolInteractionCapabilities,
53    /// Uninterpreted source-neutral metadata.
54    pub metadata: BTreeMap<String, serde_json::Value>,
55}
56
57impl ToolInfo {
58    /// Creates a complete declaration with no optional metadata.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// use tools_kit::{declaration::{ToolInfo, ToolName}, schema::JsonSchema};
64    /// let info = ToolInfo::new(ToolName::new("ping")?, JsonSchema::strict(serde_json::json!({"type":"object"})));
65    /// assert!(info.output_schema.is_none());
66    ///
67    /// # Ok::<(), tools_kit::ToolError>(())
68    /// ```
69    ///
70    #[must_use]
71    pub fn new(name: ToolName, input_schema: JsonSchema) -> Self {
72        Self {
73            name,
74            title: None,
75            description: None,
76            input_schema,
77            output_schema: None,
78            annotations: ToolAnnotations::default(),
79            icons: Vec::new(),
80            interactions: ToolInteractionCapabilities::default(),
81            metadata: BTreeMap::new(),
82        }
83    }
84
85    pub(crate) fn validate_output(&self, result: &ToolResult) -> Result<(), ToolError> {
86        if result.is_error {
87            return Ok(());
88        }
89        let Some(schema) = &self.output_schema else {
90            return Ok(());
91        };
92        let value = result
93            .structured_content
94            .as_ref()
95            .ok_or_else(|| ToolError::InvalidOutput {
96                message: format!(
97                    "successful output from `{}` omitted structured content",
98                    self.name
99                ),
100                issues: vec![ValidationIssue {
101                    path: String::new(),
102                    message: "structured content is required by the declared output schema"
103                        .to_owned(),
104                }],
105            })?;
106        let issues = schema.validate(value)?;
107        if issues.is_empty() {
108            Ok(())
109        } else {
110            Err(ToolError::InvalidOutput {
111                message: format!(
112                    "structured output from `{}` failed schema validation",
113                    self.name
114                ),
115                issues,
116            })
117        }
118    }
119}
120
121/// Source-neutral tool behavior hints.
122/// These hints describe expected behavior; they do not enforce permissions or retry safety.
123///
124/// # Examples
125///
126/// ```
127/// use tools_kit::declaration::ToolAnnotations;
128/// let hints = ToolAnnotations { read_only_hint: Some(true), ..Default::default() };
129/// assert_eq!(hints.read_only_hint, Some(true));
130/// ```
131///
132#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase", deny_unknown_fields)]
134pub struct ToolAnnotations {
135    /// Optional model-visible title.
136    pub title: Option<String>,
137    /// Whether execution is expected to avoid environmental mutation.
138    pub read_only_hint: Option<bool>,
139    /// Whether execution may perform destructive changes.
140    pub destructive_hint: Option<bool>,
141    /// Whether repeating the same invocation is expected to be safe.
142    pub idempotent_hint: Option<bool>,
143    /// Whether execution may interact with entities outside a closed domain.
144    pub open_world_hint: Option<bool>,
145}
146
147/// Source-neutral icon metadata.
148///
149/// # Examples
150///
151/// ```
152/// use tools_kit::declaration::ToolIcon;
153/// let icon = ToolIcon { uri: "https://example.com/icon.png".into(), mime_type: Some("image/png".into()) };
154/// assert_eq!(icon.mime_type.as_deref(), Some("image/png"));
155/// ```
156///
157#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase", deny_unknown_fields)]
159pub struct ToolIcon {
160    /// URI identifying or containing the icon resource.
161    pub uri: String,
162    /// Optional media type for the icon resource.
163    pub mime_type: Option<String>,
164}