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}