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