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}