Skip to main content

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}