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 output = async {
33//! let mut handler = toolset.tool_handler(name).await?;
34//! handler
35//! .step(ToolHandlerInput::Invoke(ToolInvocation {
36//! call_id: Some("call-1".to_owned()),
37//! arguments: serde_json::json!({}),
38//! }))
39//! .await
40//! }.await;
41//! let cleanup = toolset.shutdown().await;
42//! if let Err(error) = &cleanup {
43//! eprintln!("Cleanup failed: {error}");
44//! }
45//! let ToolHandlerOutput::Completed(result) = output? else { panic!("completion expected") };
46//! assert!(!result.is_error);
47//! assert!(
48//! matches!(result.content.as_slice(), [tools_kit::execution::ToolContent::Text { text, .. }] if text == "pong")
49//! );
50//! cleanup?;
51//! # Ok(())
52//! # }
53//! ```
54//!
55//! # Choose a workflow
56//!
57//! | Audience | Start here |
58//! | --- | --- |
59//! | Application builders | [Local registration and caller interactions](local), [terminal results and failures](execution), [configured MCP clients and servers](mcp) |
60//! | Custom-backend implementers | Complete executable [`ToolsetBackend`] example, then the optional [`Toolset::from_backend`] normalization seam |
61//!
62//! Local construction performs no network request. Configuring an MCP connection is
63//! passive, but connecting or serving requires its external transport. The optional
64//! `mcp-server` feature adds a separately owned server; stopping it never shuts down
65//! the shared executable Toolset. No Handler normalization constructor exists.
66
67#![doc(html_logo_url = "../assets/ai-kit-logo.png")]
68#![forbid(unsafe_code)]
69
70/// Tool names and source-neutral executable declarations.
71pub mod declaration;
72/// Invocation, continuation, and completed-result values.
73pub mod execution;
74/// Portable caller interactions requested during execution.
75pub mod interaction;
76/// In-process tool registration and interaction context.
77pub mod local;
78/// MCP client configuration and optional independent server support.
79pub mod mcp;
80/// JSON Schema values and validation diagnostics.
81pub mod schema;
82
83mod error;
84mod handler;
85mod name;
86mod sources;
87mod toolset;
88
89pub use crate::{
90 error::ToolError,
91 handler::{ToolHandler, ToolHandlerBackend},
92 toolset::{Toolset, ToolsetBackend},
93};
94
95/// Builds a typed local registration.
96///
97/// The macro is intentionally limited to typed-handler ergonomics. It does
98/// not generate dispatch, routing, or lifecycle code.
99///
100/// ```
101/// # #[tokio::main(flavor = "current_thread")]
102/// # async fn main() -> Result<(), tools_kit::ToolError> {
103/// use tools_kit::{Toolset, ToolsetBackend, ToolHandlerBackend};
104/// use tools_kit::declaration::ToolName;
105/// use tools_kit::execution::{ToolHandlerInput, ToolHandlerOutput, ToolInvocation};
106/// let registration = tools_kit::tool!("double", |value: i64| async move { Ok(value * 2) })?;
107/// let tools = Toolset::local(vec![registration])?;
108/// let result = async {
109/// let mut handler = tools.tool_handler(ToolName::new("double")?).await?;
110/// handler.step(ToolHandlerInput::Invoke(ToolInvocation {
111/// call_id: None, arguments: serde_json::json!(3),
112/// })).await
113/// }.await;
114/// let cleanup = tools.shutdown().await;
115/// if let Err(error) = &cleanup {
116/// eprintln!("Cleanup failed: {error}");
117/// }
118/// let ToolHandlerOutput::Completed(result) = result? else { panic!("completion expected") };
119/// assert_eq!(result.structured_content, Some(serde_json::json!(6)));
120/// cleanup?;
121/// # Ok(())
122/// # }
123/// ```
124#[macro_export]
125macro_rules! tool {
126 ($name:expr, $handler:expr $(,)?) => {
127 $crate::local::ToolRegistration::typed($name, $handler)
128 };
129 ($name:expr, description = $description:expr, $handler:expr $(,)?) => {
130 $crate::local::ToolRegistration::typed($name, $handler)
131 .map(|registration| registration.description($description))
132 };
133}