tools_kit/lib.rs
1//! Executable Local and MCP tools.
2//!
3//! The public hierarchy is [`Toolset`] → [`ToolHandler`]. Toolset is cloneable
4//! and owns shared source lifecycle; each ToolHandler is a unique resumable
5//! invocation state machine. Callers implement [`ToolsetBackend`] directly and
6//! retain its exact associated handler. [`Toolset::from_backend`] optionally
7//! normalizes that hierarchy into supplied handles at the sole public root seam.
8//!
9//! Supporting API is grouped under [`declaration`], [`execution`],
10//! [`interaction`], [`local`], [`mcp`], and [`schema`].
11//!
12//! ```
13//! use tools_kit::{
14//! ToolHandlerBackend, Toolset, ToolsetBackend,
15//! declaration::{ToolInfo, ToolName},
16//! execution::{ToolHandlerInput, ToolHandlerOutput, ToolInvocation, ToolResult},
17//! local::ToolRegistration,
18//! schema::JsonSchema,
19//! };
20//!
21//! # #[tokio::main(flavor = "current_thread")]
22//! # async fn main() -> Result<(), tools_kit::ToolError> {
23//! let name = ToolName::new("ping")?;
24//! let info = ToolInfo::new(
25//! name.clone(),
26//! JsonSchema::strict(serde_json::json!({"type": "object"})),
27//! );
28//! let toolset = Toolset::local(vec![ToolRegistration::function(
29//! info,
30//! |_invocation| async { Ok(ToolResult::text("pong")) },
31//! )])?;
32//! let mut handler = toolset.tool_handler(name).await?;
33//! let output = handler
34//! .step(ToolHandlerInput::Invoke(ToolInvocation {
35//! call_id: Some("call-1".to_owned()),
36//! arguments: serde_json::json!({}),
37//! }))
38//! .await;
39//! let cleanup = toolset.shutdown().await;
40//! let ToolHandlerOutput::Completed(result) = output? else { panic!("completion expected") };
41//! assert!(!result.is_error);
42//! assert!(matches!(result.content.as_slice(), [tools_kit::execution::ToolContent::Text { text, .. }] if text == "pong"));
43//! cleanup?;
44//! # Ok(())
45//! # }
46//! ```
47//!
48//! # Choose a workflow
49//!
50//! | Audience | Start here |
51//! | --- | --- |
52//! | Application builders | [Local registration and caller interactions](local), [terminal results and failures](execution), [configured MCP clients and servers](mcp) |
53//! | Custom-backend implementers | Complete executable [`ToolsetBackend`] example, then the optional [`Toolset::from_backend`] normalization seam |
54//!
55//! Local construction performs no network request. Configuring an MCP connection is
56//! passive, but connecting or serving requires its external transport. The optional
57//! `mcp-server` feature adds a separately owned server; stopping it never shuts down
58//! the shared executable Toolset. No Handler normalization constructor exists.
59
60#![doc(html_logo_url = "../assets/ai-kit-logo.png")]
61#![forbid(unsafe_code)]
62
63/// Tool names and source-neutral executable declarations.
64pub mod declaration;
65/// Invocation, continuation, and completed-result values.
66pub mod execution;
67/// Portable caller interactions requested during execution.
68pub mod interaction;
69/// In-process tool registration and interaction context.
70pub mod local;
71/// MCP client configuration and optional independent server support.
72pub mod mcp;
73/// JSON Schema values and validation diagnostics.
74pub mod schema;
75
76mod error;
77mod handler;
78mod name;
79mod sources;
80mod toolset;
81
82pub use crate::{
83 error::ToolError,
84 handler::{ToolHandler, ToolHandlerBackend},
85 toolset::{Toolset, ToolsetBackend},
86};
87
88/// Builds a typed local registration.
89///
90/// The macro is intentionally limited to typed-handler ergonomics. It does
91/// not generate dispatch, routing, or lifecycle code.
92///
93/// ```
94/// # #[tokio::main(flavor = "current_thread")]
95/// # async fn main() -> Result<(), tools_kit::ToolError> {
96/// use tools_kit::{Toolset, ToolsetBackend, ToolHandlerBackend};
97/// use tools_kit::declaration::ToolName;
98/// use tools_kit::execution::{ToolHandlerInput, ToolHandlerOutput, ToolInvocation};
99/// let registration = tools_kit::tool!("double", |value: i64| async move { Ok(value * 2) })?;
100/// let tools = Toolset::local(vec![registration])?;
101/// let mut handler = tools.tool_handler(ToolName::new("double")?).await?;
102/// let result = handler.step(ToolHandlerInput::Invoke(ToolInvocation {
103/// call_id: None, arguments: serde_json::json!(3),
104/// })).await;
105/// let cleanup = tools.shutdown().await;
106/// let ToolHandlerOutput::Completed(result) = result? else { panic!("completion expected") };
107/// assert_eq!(result.structured_content, Some(serde_json::json!(6)));
108/// cleanup?;
109/// # Ok(())
110/// # }
111/// ```
112#[macro_export]
113macro_rules! tool {
114 ($name:expr, $handler:expr $(,)?) => {
115 $crate::local::ToolRegistration::typed($name, $handler)
116 };
117 ($name:expr, description = $description:expr, $handler:expr $(,)?) => {
118 $crate::local::ToolRegistration::typed($name, $handler)
119 .map(|registration| registration.description($description))
120 };
121}