Skip to main content

tower_mcp/
tool.rs

1//! Tool definition and builder API
2//!
3//! Provides ergonomic ways to define MCP tools:
4//!
5//! 1. **Builder pattern** - Fluent API for defining tools
6//! 2. **Trait-based** - Implement `McpTool` for full control
7//! 3. **Function-based** - Quick tools from async functions
8//!
9//! ## Per-Tool Middleware
10//!
11//! Tools are implemented as Tower services internally, enabling middleware
12//! composition via the `.layer()` method:
13//!
14//! ```rust
15//! use std::time::Duration;
16//! use tower::timeout::TimeoutLayer;
17//! use tower_mcp::{ToolBuilder, CallToolResult};
18//! use schemars::JsonSchema;
19//! use serde::Deserialize;
20//!
21//! #[derive(Debug, Deserialize, JsonSchema)]
22//! struct SearchInput { query: String }
23//!
24//! let tool = ToolBuilder::new("slow_search")
25//!     .description("Search with extended timeout")
26//!     .handler(|input: SearchInput| async move {
27//!         Ok(CallToolResult::text("result"))
28//!     })
29//!     .layer(TimeoutLayer::new(Duration::from_secs(30)))
30//!     .build();
31//! ```
32
33use std::borrow::Cow;
34use std::convert::Infallible;
35use std::fmt;
36use std::future::Future;
37use std::pin::Pin;
38use std::sync::Arc;
39use std::task::{Context, Poll};
40
41use pin_project_lite::pin_project;
42
43use schemars::{JsonSchema, Schema, SchemaGenerator};
44use serde::Serialize;
45use serde::de::DeserializeOwned;
46use serde_json::Value;
47use tower::util::BoxCloneService;
48use tower_service::Service;
49
50use crate::context::RequestContext;
51use crate::error::{Error, Result, ResultExt};
52use crate::protocol::{
53    CallToolResult, TaskSupportMode, ToolAnnotations, ToolDefinition, ToolExecution, ToolIcon,
54};
55
56// =============================================================================
57// Service Types for Per-Tool Middleware
58// =============================================================================
59
60/// Request type for tool services.
61///
62/// Contains the request context (for progress reporting, cancellation, etc.)
63/// and the tool arguments as raw JSON.
64#[derive(Debug, Clone)]
65pub struct ToolRequest {
66    /// Request context for progress reporting, cancellation, and client requests
67    pub ctx: RequestContext,
68    /// Tool arguments as raw JSON
69    pub args: Value,
70}
71
72impl ToolRequest {
73    /// Create a new tool request
74    pub fn new(ctx: RequestContext, args: Value) -> Self {
75        Self { ctx, args }
76    }
77}
78
79/// A boxed, cloneable tool service with `Error = Infallible`.
80///
81/// This is the internal service type that tools use. Middleware errors are
82/// caught and converted to `CallToolResult::error()` responses, so the
83/// service never fails at the Tower level.
84pub type BoxToolService = BoxCloneService<ToolRequest, CallToolResult, Infallible>;
85
86/// Catches errors from the inner service and converts them to `CallToolResult::error()`.
87///
88/// This wrapper ensures that middleware errors (e.g., timeouts, rate limits)
89/// and handler errors are converted to tool-level error responses with
90/// `is_error: true`, rather than propagating as Tower service errors.
91#[doc(hidden)]
92pub struct ToolCatchError<S> {
93    inner: S,
94}
95
96impl<S> ToolCatchError<S> {
97    /// Create a new `ToolCatchError` wrapping the given service.
98    pub fn new(inner: S) -> Self {
99        Self { inner }
100    }
101}
102
103impl<S: Clone> Clone for ToolCatchError<S> {
104    fn clone(&self) -> Self {
105        Self {
106            inner: self.inner.clone(),
107        }
108    }
109}
110
111impl<S: fmt::Debug> fmt::Debug for ToolCatchError<S> {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        f.debug_struct("ToolCatchError")
114            .field("inner", &self.inner)
115            .finish()
116    }
117}
118
119pin_project! {
120    /// Future for [`ToolCatchError`].
121    #[doc(hidden)]
122    pub struct ToolCatchErrorFuture<F> {
123        #[pin]
124        inner: F,
125    }
126}
127
128impl<F, E> Future for ToolCatchErrorFuture<F>
129where
130    F: Future<Output = std::result::Result<CallToolResult, E>>,
131    E: fmt::Display,
132{
133    type Output = std::result::Result<CallToolResult, Infallible>;
134
135    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
136        match self.project().inner.poll(cx) {
137            Poll::Pending => Poll::Pending,
138            Poll::Ready(Ok(result)) => Poll::Ready(Ok(result)),
139            Poll::Ready(Err(err)) => Poll::Ready(Ok(CallToolResult::error(err.to_string()))),
140        }
141    }
142}
143
144impl<S> Service<ToolRequest> for ToolCatchError<S>
145where
146    S: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
147    S::Error: fmt::Display + Send,
148    S::Future: Send,
149{
150    type Response = CallToolResult;
151    type Error = Infallible;
152    type Future = ToolCatchErrorFuture<S::Future>;
153
154    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
155        // Map any readiness error to Infallible (we catch it on call)
156        match self.inner.poll_ready(cx) {
157            Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
158            Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
159            Poll::Pending => Poll::Pending,
160        }
161    }
162
163    fn call(&mut self, req: ToolRequest) -> Self::Future {
164        ToolCatchErrorFuture {
165            inner: self.inner.call(req),
166        }
167    }
168}
169
170/// A tower [`Layer`](tower::Layer) that applies a guard function before the inner service.
171///
172/// Guards run before the tool handler and can short-circuit with an error message.
173/// Use via [`ToolBuilderWithHandler::guard`] or [`Tool::with_guard`] rather than
174/// constructing directly.
175///
176/// # Example
177///
178/// ```rust
179/// use tower_mcp::{ToolBuilder, ToolRequest, CallToolResult};
180/// use schemars::JsonSchema;
181/// use serde::Deserialize;
182///
183/// #[derive(Debug, Deserialize, JsonSchema)]
184/// struct DeleteInput { id: String, confirm: bool }
185///
186/// let tool = ToolBuilder::new("delete")
187///     .description("Delete a record")
188///     .handler(|input: DeleteInput| async move {
189///         Ok(CallToolResult::text(format!("deleted {}", input.id)))
190///     })
191///     .guard(|req: &ToolRequest| {
192///         let confirm = req.args.get("confirm").and_then(|v| v.as_bool()).unwrap_or(false);
193///         if !confirm {
194///             return Err("Must set confirm=true to delete".to_string());
195///         }
196///         Ok(())
197///     })
198///     .build();
199/// ```
200#[derive(Clone)]
201pub struct GuardLayer<G> {
202    guard: G,
203}
204
205impl<G> GuardLayer<G> {
206    /// Create a new guard layer from a closure.
207    ///
208    /// The closure receives a `&ToolRequest` and returns `Ok(())` to proceed
209    /// or `Err(String)` to reject with an error message.
210    pub fn new(guard: G) -> Self {
211        Self { guard }
212    }
213}
214
215impl<G, S> tower::Layer<S> for GuardLayer<G>
216where
217    G: Clone,
218{
219    type Service = GuardService<G, S>;
220
221    fn layer(&self, inner: S) -> Self::Service {
222        GuardService {
223            guard: self.guard.clone(),
224            inner,
225        }
226    }
227}
228
229/// Service wrapper that runs a guard check before calling the inner service.
230///
231/// Created by [`GuardLayer`]. See its documentation for usage.
232#[doc(hidden)]
233#[derive(Clone)]
234pub struct GuardService<G, S> {
235    guard: G,
236    inner: S,
237}
238
239impl<G, S> Service<ToolRequest> for GuardService<G, S>
240where
241    G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
242    S: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
243    S::Error: Into<Error> + Send,
244    S::Future: Send,
245{
246    type Response = CallToolResult;
247    type Error = Error;
248    type Future = Pin<Box<dyn Future<Output = std::result::Result<CallToolResult, Error>> + Send>>;
249
250    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
251        self.inner.poll_ready(cx).map_err(Into::into)
252    }
253
254    fn call(&mut self, req: ToolRequest) -> Self::Future {
255        match (self.guard)(&req) {
256            Ok(()) => {
257                let fut = self.inner.call(req);
258                Box::pin(async move { fut.await.map_err(Into::into) })
259            }
260            Err(msg) => Box::pin(async move { Err(Error::tool(msg)) }),
261        }
262    }
263}
264
265/// A marker type for tools that take no parameters.
266///
267/// Use this instead of `()` when defining tools with no input parameters.
268/// The unit type `()` generates `"type": "null"` in JSON Schema, which many
269/// MCP clients reject. `NoParams` generates `"type": "object"` with no
270/// required properties, which is the correct schema for parameterless tools.
271///
272/// # Example
273///
274/// ```rust
275/// use tower_mcp::{ToolBuilder, CallToolResult, NoParams};
276///
277/// let tool = ToolBuilder::new("get_status")
278///     .description("Get current status")
279///     .handler(|_input: NoParams| async move {
280///         Ok(CallToolResult::text("OK"))
281///     })
282///     .build();
283/// ```
284#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
285pub struct NoParams;
286
287impl<'de> serde::Deserialize<'de> for NoParams {
288    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
289    where
290        D: serde::Deserializer<'de>,
291    {
292        // Accept null, empty object, or any object (ignoring all fields)
293        struct NoParamsVisitor;
294
295        impl<'de> serde::de::Visitor<'de> for NoParamsVisitor {
296            type Value = NoParams;
297
298            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
299                formatter.write_str("null or an object")
300            }
301
302            fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
303            where
304                E: serde::de::Error,
305            {
306                Ok(NoParams)
307            }
308
309            fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
310            where
311                E: serde::de::Error,
312            {
313                Ok(NoParams)
314            }
315
316            fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
317            where
318                D: serde::Deserializer<'de>,
319            {
320                serde::Deserialize::deserialize(deserializer)
321            }
322
323            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
324            where
325                A: serde::de::MapAccess<'de>,
326            {
327                // Drain the map, ignoring all entries
328                while map
329                    .next_entry::<serde::de::IgnoredAny, serde::de::IgnoredAny>()?
330                    .is_some()
331                {}
332                Ok(NoParams)
333            }
334        }
335
336        deserializer.deserialize_any(NoParamsVisitor)
337    }
338}
339
340impl JsonSchema for NoParams {
341    fn schema_name() -> Cow<'static, str> {
342        Cow::Borrowed("NoParams")
343    }
344
345    fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
346        serde_json::json!({
347            "type": "object"
348        })
349        .try_into()
350        .expect("valid schema")
351    }
352}
353
354/// Validate a tool name according to MCP spec (SEP-986).
355///
356/// Tool names must be:
357/// - 1-64 characters long
358/// - Contain only ASCII alphanumeric characters, underscores, hyphens, dots,
359///   and forward slashes
360///
361/// Returns `Ok(())` if valid, `Err` with description if invalid.
362pub(crate) fn validate_tool_name(name: &str) -> Result<()> {
363    if name.is_empty() {
364        return Err(Error::tool("Tool name cannot be empty"));
365    }
366    if name.len() > 64 {
367        return Err(Error::tool(format!(
368            "Tool name '{}' exceeds maximum length of 64 characters (got {})",
369            name,
370            name.len()
371        )));
372    }
373    if let Some(invalid_char) = name
374        .chars()
375        .find(|c| !c.is_ascii_alphanumeric() && *c != '_' && *c != '-' && *c != '.' && *c != '/')
376    {
377        return Err(Error::tool(format!(
378            "Tool name '{}' contains invalid character '{}'. Only alphanumeric, underscore, hyphen, dot, and forward slash are allowed.",
379            name, invalid_char
380        )));
381    }
382    Ok(())
383}
384
385/// Ensure a JSON Schema value has `"type": "object"`.
386///
387/// The MCP spec requires tool input schemas to be JSON objects with a `"type"` field.
388/// Some types (e.g., `serde_json::Value`) generate schemas via schemars that lack
389/// the `"type"` field, which causes MCP clients to reject the tool.
390pub(crate) fn ensure_object_schema(mut schema: Value) -> Value {
391    if let Some(obj) = schema.as_object_mut()
392        && !obj.contains_key("type")
393    {
394        obj.insert("type".to_string(), serde_json::json!("object"));
395    }
396    schema
397}
398
399/// A boxed future for tool handlers
400pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
401
402/// Tool handler trait - the core abstraction for tool execution
403pub trait ToolHandler: Send + Sync {
404    /// Execute the tool with the given arguments
405    fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>>;
406
407    /// Execute the tool with request context for progress/cancellation support
408    ///
409    /// The default implementation ignores the context and calls `call`.
410    /// Override this to receive progress/cancellation context.
411    fn call_with_context(
412        &self,
413        _ctx: RequestContext,
414        args: Value,
415    ) -> BoxFuture<'_, Result<CallToolResult>> {
416        self.call(args)
417    }
418
419    /// Returns true if this handler uses context (for optimization)
420    fn uses_context(&self) -> bool {
421        false
422    }
423
424    /// Get the tool's input schema
425    fn input_schema(&self) -> Value;
426}
427
428/// Adapts a `ToolHandler` to a Tower `Service<ToolRequest>`.
429///
430/// This is an internal adapter that bridges the handler abstraction to the
431/// service abstraction, enabling middleware composition.
432pub(crate) struct ToolHandlerService<H> {
433    handler: Arc<H>,
434}
435
436impl<H> ToolHandlerService<H> {
437    pub(crate) fn new(handler: H) -> Self {
438        Self {
439            handler: Arc::new(handler),
440        }
441    }
442}
443
444impl<H> Clone for ToolHandlerService<H> {
445    fn clone(&self) -> Self {
446        Self {
447            handler: self.handler.clone(),
448        }
449    }
450}
451
452impl<H> Service<ToolRequest> for ToolHandlerService<H>
453where
454    H: ToolHandler + 'static,
455{
456    type Response = CallToolResult;
457    type Error = Error;
458    type Future = Pin<Box<dyn Future<Output = std::result::Result<CallToolResult, Error>> + Send>>;
459
460    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
461        Poll::Ready(Ok(()))
462    }
463
464    fn call(&mut self, req: ToolRequest) -> Self::Future {
465        let handler = self.handler.clone();
466        Box::pin(async move { handler.call_with_context(req.ctx, req.args).await })
467    }
468}
469
470/// A complete tool definition with service-based execution.
471///
472/// Tools are implemented as Tower services internally, enabling middleware
473/// composition via the builder's `.layer()` method. The service is wrapped
474/// in [`ToolCatchError`] to convert any errors (from handlers or middleware)
475/// into `CallToolResult::error()` responses.
476pub struct Tool {
477    /// Tool name (must be 1-128 chars, alphanumeric/underscore/hyphen/dot only)
478    pub name: String,
479    /// Human-readable title for the tool
480    pub title: Option<String>,
481    /// Description of what the tool does
482    pub description: Option<String>,
483    /// JSON Schema for the tool's output (optional)
484    pub output_schema: Option<Value>,
485    /// Icons for the tool
486    pub icons: Option<Vec<ToolIcon>>,
487    /// Tool annotations (hints about behavior)
488    pub annotations: Option<ToolAnnotations>,
489    /// Task support mode for this tool
490    pub task_support: TaskSupportMode,
491    /// The boxed service that executes the tool
492    pub(crate) service: BoxToolService,
493    /// JSON Schema for the tool's input
494    pub(crate) input_schema: Value,
495}
496
497impl std::fmt::Debug for Tool {
498    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
499        f.debug_struct("Tool")
500            .field("name", &self.name)
501            .field("title", &self.title)
502            .field("description", &self.description)
503            .field("output_schema", &self.output_schema)
504            .field("icons", &self.icons)
505            .field("annotations", &self.annotations)
506            .field("task_support", &self.task_support)
507            .finish_non_exhaustive()
508    }
509}
510
511// SAFETY: BoxCloneService is Send + Sync (tower provides unsafe impl Sync),
512// and all other fields in Tool are Send + Sync.
513unsafe impl Send for Tool {}
514unsafe impl Sync for Tool {}
515
516impl Clone for Tool {
517    fn clone(&self) -> Self {
518        Self {
519            name: self.name.clone(),
520            title: self.title.clone(),
521            description: self.description.clone(),
522            output_schema: self.output_schema.clone(),
523            icons: self.icons.clone(),
524            annotations: self.annotations.clone(),
525            task_support: self.task_support,
526            service: self.service.clone(),
527            input_schema: self.input_schema.clone(),
528        }
529    }
530}
531
532impl Tool {
533    /// Create a new tool builder
534    pub fn builder(name: impl Into<String>) -> ToolBuilder {
535        ToolBuilder::new(name)
536    }
537
538    /// Get the tool definition for tools/list
539    pub fn definition(&self) -> ToolDefinition {
540        let execution = match self.task_support {
541            TaskSupportMode::Forbidden => None,
542            mode => Some(ToolExecution {
543                task_support: Some(mode),
544            }),
545        };
546        ToolDefinition {
547            name: self.name.clone(),
548            title: self.title.clone(),
549            description: self.description.clone(),
550            input_schema: self.input_schema.clone(),
551            output_schema: self.output_schema.clone(),
552            icons: self.icons.clone(),
553            annotations: self.annotations.clone(),
554            execution,
555            meta: None,
556        }
557    }
558
559    /// Call the tool without context
560    ///
561    /// Creates a dummy request context. For full context support, use
562    /// [`call_with_context`](Self::call_with_context).
563    pub fn call(&self, args: Value) -> BoxFuture<'static, CallToolResult> {
564        let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
565        self.call_with_context(ctx, args)
566    }
567
568    /// Call the tool with request context
569    ///
570    /// The context provides progress reporting, cancellation support, and
571    /// access to client requests (for sampling, etc.).
572    ///
573    /// # Note
574    ///
575    /// This method returns `CallToolResult` directly (not `Result<CallToolResult>`).
576    /// Any errors from the handler or middleware are converted to
577    /// `CallToolResult::error()` with `is_error: true`.
578    pub fn call_with_context(
579        &self,
580        ctx: RequestContext,
581        args: Value,
582    ) -> BoxFuture<'static, CallToolResult> {
583        use tower::ServiceExt;
584        let service = self.service.clone();
585        Box::pin(async move {
586            // ServiceExt::oneshot properly handles poll_ready before call
587            // Service is Infallible, so unwrap is safe
588            service.oneshot(ToolRequest::new(ctx, args)).await.unwrap()
589        })
590    }
591
592    /// Apply a guard to this built tool.
593    ///
594    /// The guard runs before the handler and can short-circuit with an error.
595    /// This is useful for applying the same guard to multiple tools (per-group
596    /// pattern):
597    ///
598    /// ```rust
599    /// use tower_mcp::{ToolBuilder, CallToolResult};
600    /// use tower_mcp::tool::ToolRequest;
601    /// use schemars::JsonSchema;
602    /// use serde::Deserialize;
603    ///
604    /// #[derive(Debug, Deserialize, JsonSchema)]
605    /// struct Input { value: String }
606    ///
607    /// fn build_tool(name: &str) -> tower_mcp::tool::Tool {
608    ///     ToolBuilder::new(name)
609    ///         .handler(|i: Input| async move { Ok(CallToolResult::text(&i.value)) })
610    ///         .build()
611    /// }
612    ///
613    /// let guard = |_req: &ToolRequest| -> Result<(), String> { Ok(()) };
614    ///
615    /// let tools: Vec<_> = vec![build_tool("a"), build_tool("b")]
616    ///     .into_iter()
617    ///     .map(|t| t.with_guard(guard.clone()))
618    ///     .collect();
619    /// ```
620    pub fn with_guard<G>(self, guard: G) -> Self
621    where
622        G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
623    {
624        let guarded = GuardService {
625            guard,
626            inner: self.service,
627        };
628        let caught = ToolCatchError::new(guarded);
629        Tool {
630            service: BoxCloneService::new(caught),
631            ..self
632        }
633    }
634
635    /// Create a new tool with a prefixed name.
636    ///
637    /// This creates a copy of the tool with its name prefixed by the given
638    /// string and a dot separator. For example, if the tool is named "query"
639    /// and the prefix is "db", the new tool will be named "db.query".
640    ///
641    /// This is used internally by `McpRouter::nest()` to namespace tools.
642    ///
643    /// # Example
644    ///
645    /// ```rust
646    /// use tower_mcp::{ToolBuilder, CallToolResult};
647    /// use schemars::JsonSchema;
648    /// use serde::Deserialize;
649    ///
650    /// #[derive(Debug, Deserialize, JsonSchema)]
651    /// struct Input { value: String }
652    ///
653    /// let tool = ToolBuilder::new("query")
654    ///     .description("Query the database")
655    ///     .handler(|i: Input| async move { Ok(CallToolResult::text(&i.value)) })
656    ///     .build();
657    ///
658    /// let prefixed = tool.with_name_prefix("db");
659    /// assert_eq!(prefixed.name, "db.query");
660    /// ```
661    pub fn with_name_prefix(&self, prefix: &str) -> Self {
662        Self {
663            name: format!("{}.{}", prefix, self.name),
664            title: self.title.clone(),
665            description: self.description.clone(),
666            output_schema: self.output_schema.clone(),
667            icons: self.icons.clone(),
668            annotations: self.annotations.clone(),
669            task_support: self.task_support,
670            service: self.service.clone(),
671            input_schema: self.input_schema.clone(),
672        }
673    }
674
675    /// Create a tool from a handler (internal helper)
676    #[allow(clippy::too_many_arguments)]
677    fn from_handler<H: ToolHandler + 'static>(
678        name: String,
679        title: Option<String>,
680        description: Option<String>,
681        output_schema: Option<Value>,
682        icons: Option<Vec<ToolIcon>>,
683        annotations: Option<ToolAnnotations>,
684        task_support: TaskSupportMode,
685        input_schema_override: Option<Value>,
686        handler: H,
687    ) -> Self {
688        let input_schema =
689            ensure_object_schema(input_schema_override.unwrap_or_else(|| handler.input_schema()));
690        let handler_service = ToolHandlerService::new(handler);
691        let catch_error = ToolCatchError::new(handler_service);
692        let service = BoxCloneService::new(catch_error);
693
694        Self {
695            name,
696            title,
697            description,
698            output_schema,
699            icons,
700            annotations,
701            task_support,
702            service,
703            input_schema,
704        }
705    }
706}
707
708// =============================================================================
709// Builder API
710// =============================================================================
711
712/// Builder for creating tools with a fluent API
713///
714/// # Example
715///
716/// ```rust
717/// use tower_mcp::{ToolBuilder, CallToolResult};
718/// use schemars::JsonSchema;
719/// use serde::Deserialize;
720///
721/// #[derive(Debug, Deserialize, JsonSchema)]
722/// struct GreetInput {
723///     name: String,
724/// }
725///
726/// let tool = ToolBuilder::new("greet")
727///     .description("Greet someone by name")
728///     .handler(|input: GreetInput| async move {
729///         Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
730///     })
731///     .build();
732///
733/// assert_eq!(tool.name, "greet");
734/// ```
735pub struct ToolBuilder {
736    name: String,
737    title: Option<String>,
738    description: Option<String>,
739    output_schema: Option<Value>,
740    input_schema_override: Option<Value>,
741    icons: Option<Vec<ToolIcon>>,
742    annotations: Option<ToolAnnotations>,
743    task_support: TaskSupportMode,
744}
745
746impl ToolBuilder {
747    /// Create a new tool builder with the given name.
748    ///
749    /// Tool names must be 1-64 characters and contain only ASCII alphanumeric
750    /// characters, underscores, hyphens, dots, and forward slashes (per
751    /// [SEP-986](https://github.com/modelcontextprotocol/specification/issues/986)).
752    ///
753    /// Use [`try_new`](Self::try_new) if the name comes from runtime input.
754    ///
755    /// # Panics
756    ///
757    /// Panics if `name` is empty, exceeds 64 characters, or contains
758    /// characters other than ASCII alphanumerics, `_`, `-`, `.`, and `/`.
759    pub fn new(name: impl Into<String>) -> Self {
760        let name = name.into();
761        if let Err(e) = validate_tool_name(&name) {
762            panic!("{e}");
763        }
764        Self {
765            name,
766            title: None,
767            description: None,
768            output_schema: None,
769            input_schema_override: None,
770            icons: None,
771            annotations: None,
772            task_support: TaskSupportMode::default(),
773        }
774    }
775
776    /// Create a new tool builder, returning an error if the name is invalid.
777    ///
778    /// This is the fallible alternative to [`new`](Self::new) for cases where
779    /// the tool name comes from runtime input (e.g., user configuration or
780    /// database).
781    pub fn try_new(name: impl Into<String>) -> Result<Self> {
782        let name = name.into();
783        validate_tool_name(&name)?;
784        Ok(Self {
785            name,
786            title: None,
787            description: None,
788            output_schema: None,
789            input_schema_override: None,
790            icons: None,
791            annotations: None,
792            task_support: TaskSupportMode::default(),
793        })
794    }
795
796    /// Set a human-readable title for the tool.
797    ///
798    /// The title is displayed by MCP clients (e.g., Claude Code's `/mcp` tool list)
799    /// as a friendly label instead of the raw tool name. For example, a tool named
800    /// `search_crates` with title `"Search Crates"` will display the title in UIs
801    /// that support it.
802    ///
803    /// ```
804    /// # use tower_mcp::ToolBuilder;
805    /// let tool = ToolBuilder::new("search_crates")
806    ///     .title("Search Crates")
807    ///     .description("Search for Rust crates on crates.io")
808    ///     .handler(|()| async { Ok(tower_mcp::CallToolResult::text("results")) })
809    ///     .build();
810    /// ```
811    pub fn title(mut self, title: impl Into<String>) -> Self {
812        self.title = Some(title.into());
813        self
814    }
815
816    /// Set the output schema (JSON Schema for structured output)
817    pub fn output_schema(mut self, schema: Value) -> Self {
818        self.output_schema = Some(schema);
819        self
820    }
821
822    /// Override the input schema (JSON Schema for tool arguments).
823    ///
824    /// By default, the input schema is auto-generated from the handler's input
825    /// type via [`schemars::JsonSchema`]. Calling this method overrides that
826    /// auto-generation with an explicit schema. This is particularly useful for
827    /// handlers that use [`RawArgs`](crate::extract::RawArgs) (which has no typed
828    /// input struct) but still need to declare a non-trivial schema, or to
829    /// supply richer JSON Schema 2020-12 constructs (`oneOf`, `anyOf`,
830    /// `if`/`then`, `$ref`, etc.) that schemars cannot express.
831    ///
832    /// The supplied schema is normalized via the same `type: "object"` check
833    /// the auto-generated schemas go through, so MCP-spec compliance is
834    /// preserved.
835    ///
836    /// When called alongside a typed handler (`.handler(|x: Foo| ...)` or a
837    /// [`Json<T>`](crate::extract::Json) extractor), the explicit schema wins
838    /// over the schemars-generated one.
839    ///
840    /// # Example
841    ///
842    /// ```rust
843    /// use serde_json::json;
844    /// use tower_mcp::{CallToolResult, ToolBuilder};
845    /// use tower_mcp::extract::RawArgs;
846    ///
847    /// let tool = ToolBuilder::new("query")
848    ///     .description("Query with a conditional schema")
849    ///     .input_schema(json!({
850    ///         "type": "object",
851    ///         "properties": {
852    ///             "filter": {
853    ///                 "oneOf": [
854    ///                     { "type": "string" },
855    ///                     {
856    ///                         "type": "object",
857    ///                         "properties": { "field": { "type": "string" } },
858    ///                         "required": ["field"]
859    ///                     }
860    ///                 ]
861    ///             }
862    ///         },
863    ///         "required": ["filter"]
864    ///     }))
865    ///     .extractor_handler((), |RawArgs(args): RawArgs| async move {
866    ///         Ok(CallToolResult::json(args))
867    ///     })
868    ///     .build();
869    ///
870    /// let schema = tool.definition().input_schema;
871    /// assert_eq!(schema["type"], "object");
872    /// assert!(schema["properties"]["filter"]["oneOf"].is_array());
873    /// ```
874    pub fn input_schema(mut self, schema: Value) -> Self {
875        self.input_schema_override = Some(schema);
876        self
877    }
878
879    /// Add an icon for the tool
880    pub fn icon(mut self, src: impl Into<String>) -> Self {
881        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
882            src: src.into(),
883            mime_type: None,
884            sizes: None,
885            theme: None,
886        });
887        self
888    }
889
890    /// Add an icon with metadata
891    pub fn icon_with_meta(
892        mut self,
893        src: impl Into<String>,
894        mime_type: Option<String>,
895        sizes: Option<Vec<String>>,
896    ) -> Self {
897        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
898            src: src.into(),
899            mime_type,
900            sizes,
901            theme: None,
902        });
903        self
904    }
905
906    /// Set the tool description
907    pub fn description(mut self, description: impl Into<String>) -> Self {
908        self.description = Some(description.into());
909        self
910    }
911
912    /// Mark the tool as read-only (does not modify state)
913    pub fn read_only(mut self) -> Self {
914        self.annotations
915            .get_or_insert_with(ToolAnnotations::default)
916            .read_only_hint = true;
917        self
918    }
919
920    /// Mark the tool as non-destructive
921    pub fn non_destructive(mut self) -> Self {
922        self.annotations
923            .get_or_insert_with(ToolAnnotations::default)
924            .destructive_hint = false;
925        self
926    }
927
928    /// Mark the tool as destructive (may perform irreversible operations)
929    pub fn destructive(mut self) -> Self {
930        self.annotations
931            .get_or_insert_with(ToolAnnotations::default)
932            .destructive_hint = true;
933        self
934    }
935
936    /// Mark the tool as idempotent (same args = same effect)
937    pub fn idempotent(mut self) -> Self {
938        self.annotations
939            .get_or_insert_with(ToolAnnotations::default)
940            .idempotent_hint = true;
941        self
942    }
943
944    /// Mark the tool as read-only, idempotent, and non-destructive.
945    ///
946    /// This is a convenience method for safe, side-effect-free tools.
947    /// For finer control, use `.read_only()`, `.idempotent()`, and
948    /// `.non_destructive()` individually.
949    pub fn read_only_safe(mut self) -> Self {
950        let ann = self
951            .annotations
952            .get_or_insert_with(ToolAnnotations::default);
953        ann.read_only_hint = true;
954        ann.idempotent_hint = true;
955        ann.destructive_hint = false;
956        self
957    }
958
959    /// Set tool annotations directly
960    pub fn annotations(mut self, annotations: ToolAnnotations) -> Self {
961        self.annotations = Some(annotations);
962        self
963    }
964
965    /// Set the task support mode for this tool
966    pub fn task_support(mut self, mode: TaskSupportMode) -> Self {
967        self.task_support = mode;
968        self
969    }
970
971    /// Create a tool that takes no parameters.
972    ///
973    /// This is a convenience method for tools that don't require any input.
974    /// It generates the correct `{"type": "object"}` schema that MCP clients expect.
975    ///
976    /// # Example
977    ///
978    /// ```rust
979    /// use tower_mcp::{ToolBuilder, CallToolResult};
980    ///
981    /// let tool = ToolBuilder::new("get_status")
982    ///     .description("Get current status")
983    ///     .no_params_handler(|| async {
984    ///         Ok(CallToolResult::text("OK"))
985    ///     })
986    ///     .build();
987    /// ```
988    pub fn no_params_handler<F, Fut>(self, handler: F) -> ToolBuilderWithNoParamsHandler<F>
989    where
990        F: Fn() -> Fut + Send + Sync + 'static,
991        Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
992    {
993        ToolBuilderWithNoParamsHandler {
994            name: self.name,
995            title: self.title,
996            description: self.description,
997            output_schema: self.output_schema,
998            input_schema_override: self.input_schema_override,
999            icons: self.icons,
1000            annotations: self.annotations,
1001            task_support: self.task_support,
1002            handler,
1003        }
1004    }
1005
1006    /// Specify input type and handler.
1007    ///
1008    /// The input type must implement `JsonSchema` and `DeserializeOwned`.
1009    /// The handler receives the deserialized input and returns a `CallToolResult`.
1010    ///
1011    /// # State Sharing
1012    ///
1013    /// To share state across tool calls (e.g., database connections, API clients),
1014    /// wrap your state in an `Arc` and clone it into the async block:
1015    ///
1016    /// ```rust
1017    /// use std::sync::Arc;
1018    /// use tower_mcp::{ToolBuilder, CallToolResult};
1019    /// use schemars::JsonSchema;
1020    /// use serde::Deserialize;
1021    ///
1022    /// struct AppState {
1023    ///     api_key: String,
1024    /// }
1025    ///
1026    /// #[derive(Debug, Deserialize, JsonSchema)]
1027    /// struct MyInput {
1028    ///     query: String,
1029    /// }
1030    ///
1031    /// let state = Arc::new(AppState { api_key: "secret".to_string() });
1032    ///
1033    /// let tool = ToolBuilder::new("my_tool")
1034    ///     .description("A tool that uses shared state")
1035    ///     .handler(move |input: MyInput| {
1036    ///         let state = state.clone(); // Clone Arc for the async block
1037    ///         async move {
1038    ///             // Use state.api_key here...
1039    ///             Ok(CallToolResult::text(format!("Query: {}", input.query)))
1040    ///         }
1041    ///     })
1042    ///     .build();
1043    /// ```
1044    ///
1045    /// The `move` keyword on the closure captures the `Arc<AppState>`, and
1046    /// cloning it inside the closure body allows each async invocation to
1047    /// have its own reference to the shared state.
1048    pub fn handler<I, F, Fut>(self, handler: F) -> ToolBuilderWithHandler<I, F>
1049    where
1050        I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
1051        F: Fn(I) -> Fut + Send + Sync + 'static,
1052        Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
1053    {
1054        ToolBuilderWithHandler {
1055            name: self.name,
1056            title: self.title,
1057            description: self.description,
1058            output_schema: self.output_schema,
1059            input_schema_override: self.input_schema_override,
1060            icons: self.icons,
1061            annotations: self.annotations,
1062            task_support: self.task_support,
1063            handler,
1064            _phantom: std::marker::PhantomData,
1065        }
1066    }
1067
1068    /// Create a tool using the extractor pattern.
1069    ///
1070    /// This method provides an axum-inspired way to define handlers where state,
1071    /// context, and input are extracted declaratively from function parameters.
1072    /// This reduces the combinatorial explosion of handler variants like
1073    /// `handler_with_state`, `handler_with_context`, etc.
1074    ///
1075    /// # Schema Auto-Detection
1076    ///
1077    /// When a [`Json<T>`](crate::extract::Json) extractor is used, the proper JSON
1078    /// schema is automatically generated from `T`'s `JsonSchema` implementation.
1079    /// No turbofish is needed -- the schema type is inferred from the closure
1080    /// parameters.
1081    ///
1082    /// # Extractors
1083    ///
1084    /// Built-in extractors available in [`crate::extract`]:
1085    /// - [`Json<T>`](crate::extract::Json) - Deserialize JSON arguments to type `T`
1086    /// - [`State<T>`](crate::extract::State) - Extract cloned state
1087    /// - [`Extension<T>`](crate::extract::Extension) - Extract router-level state
1088    /// - [`Context`](crate::extract::Context) - Extract request context
1089    /// - [`RawArgs`](crate::extract::RawArgs) - Extract raw JSON arguments
1090    ///
1091    /// # Per-Tool Middleware
1092    ///
1093    /// The returned builder supports `.layer()` to apply Tower middleware:
1094    ///
1095    /// ```rust
1096    /// use std::sync::Arc;
1097    /// use std::time::Duration;
1098    /// use tower::timeout::TimeoutLayer;
1099    /// use tower_mcp::{ToolBuilder, CallToolResult};
1100    /// use tower_mcp::extract::{Json, State};
1101    /// use schemars::JsonSchema;
1102    /// use serde::Deserialize;
1103    ///
1104    /// #[derive(Clone)]
1105    /// struct Database { url: String }
1106    ///
1107    /// #[derive(Debug, Deserialize, JsonSchema)]
1108    /// struct QueryInput { query: String }
1109    ///
1110    /// let db = Arc::new(Database { url: "postgres://...".to_string() });
1111    ///
1112    /// let tool = ToolBuilder::new("search")
1113    ///     .description("Search the database")
1114    ///     .extractor_handler(db, |
1115    ///         State(db): State<Arc<Database>>,
1116    ///         Json(input): Json<QueryInput>,
1117    ///     | async move {
1118    ///         Ok(CallToolResult::text(format!("Searched {} with: {}", db.url, input.query)))
1119    ///     })
1120    ///     .layer(TimeoutLayer::new(Duration::from_secs(30)))
1121    ///     .build();
1122    /// ```
1123    ///
1124    /// # Example
1125    ///
1126    /// ```rust
1127    /// use std::sync::Arc;
1128    /// use tower_mcp::{ToolBuilder, CallToolResult};
1129    /// use tower_mcp::extract::{Json, State, Context};
1130    /// use schemars::JsonSchema;
1131    /// use serde::Deserialize;
1132    ///
1133    /// #[derive(Clone)]
1134    /// struct Database { url: String }
1135    ///
1136    /// #[derive(Debug, Deserialize, JsonSchema)]
1137    /// struct QueryInput { query: String }
1138    ///
1139    /// let db = Arc::new(Database { url: "postgres://...".to_string() });
1140    ///
1141    /// let tool = ToolBuilder::new("search")
1142    ///     .description("Search the database")
1143    ///     .extractor_handler(db, |
1144    ///         State(db): State<Arc<Database>>,
1145    ///         ctx: Context,
1146    ///         Json(input): Json<QueryInput>,
1147    ///     | async move {
1148    ///         if ctx.is_cancelled() {
1149    ///             return Ok(CallToolResult::error("Cancelled"));
1150    ///         }
1151    ///         ctx.report_progress(0.5, Some(1.0), Some("Searching...")).await;
1152    ///         Ok(CallToolResult::text(format!("Searched {} with: {}", db.url, input.query)))
1153    ///     })
1154    ///     .build();
1155    /// ```
1156    ///
1157    /// # Type Inference
1158    ///
1159    /// The compiler infers extractor types from the function signature. Make sure
1160    /// to annotate the extractor types explicitly in the closure parameters.
1161    pub fn extractor_handler<S, F, T>(
1162        self,
1163        state: S,
1164        handler: F,
1165    ) -> crate::extract::ToolBuilderWithExtractor<S, F, T>
1166    where
1167        S: Clone + Send + Sync + 'static,
1168        F: crate::extract::ExtractorHandler<S, T> + Clone,
1169        T: Send + Sync + 'static,
1170    {
1171        let input_schema = ensure_object_schema(
1172            self.input_schema_override
1173                .unwrap_or_else(|| F::input_schema()),
1174        );
1175        crate::extract::ToolBuilderWithExtractor {
1176            name: self.name,
1177            title: self.title,
1178            description: self.description,
1179            output_schema: self.output_schema,
1180            icons: self.icons,
1181            annotations: self.annotations,
1182            task_support: self.task_support,
1183            state,
1184            handler,
1185            input_schema,
1186            _phantom: std::marker::PhantomData,
1187        }
1188    }
1189
1190    /// Create a tool using the extractor pattern with typed JSON input.
1191    ///
1192    /// # Deprecated
1193    ///
1194    /// Use [`extractor_handler`](Self::extractor_handler) instead. It auto-detects
1195    /// the JSON schema from `Json<T>` extractors, producing identical results
1196    /// without requiring a turbofish.
1197    ///
1198    /// ```rust
1199    /// # use std::sync::Arc;
1200    /// # use tower_mcp::{ToolBuilder, CallToolResult};
1201    /// # use tower_mcp::extract::{Json, State};
1202    /// # use schemars::JsonSchema;
1203    /// # use serde::Deserialize;
1204    /// # #[derive(Clone)]
1205    /// # struct AppState { prefix: String }
1206    /// # #[derive(Debug, Deserialize, JsonSchema)]
1207    /// # struct GreetInput { name: String }
1208    /// # let state = Arc::new(AppState { prefix: "Hello".to_string() });
1209    /// // Before (deprecated):
1210    /// // .extractor_handler_typed::<_, _, _, GreetInput>(state, handler)
1211    ///
1212    /// // After:
1213    /// let tool = ToolBuilder::new("greet")
1214    ///     .description("Greet someone")
1215    ///     .extractor_handler(state, |
1216    ///         State(app): State<Arc<AppState>>,
1217    ///         Json(input): Json<GreetInput>,
1218    ///     | async move {
1219    ///         Ok(CallToolResult::text(format!("{}, {}!", app.prefix, input.name)))
1220    ///     })
1221    ///     .build();
1222    /// ```
1223    #[deprecated(
1224        since = "0.8.0",
1225        note = "Use `extractor_handler` instead -- it auto-detects JSON schema from `Json<T>` extractors without requiring a turbofish"
1226    )]
1227    #[allow(deprecated)]
1228    pub fn extractor_handler_typed<S, F, T, I>(
1229        self,
1230        state: S,
1231        handler: F,
1232    ) -> crate::extract::ToolBuilderWithTypedExtractor<S, F, T, I>
1233    where
1234        S: Clone + Send + Sync + 'static,
1235        F: crate::extract::TypedExtractorHandler<S, T, I> + Clone,
1236        T: Send + Sync + 'static,
1237        I: schemars::JsonSchema + Send + Sync + 'static,
1238    {
1239        crate::extract::ToolBuilderWithTypedExtractor {
1240            name: self.name,
1241            title: self.title,
1242            description: self.description,
1243            output_schema: self.output_schema,
1244            input_schema_override: self.input_schema_override,
1245            icons: self.icons,
1246            annotations: self.annotations,
1247            task_support: self.task_support,
1248            state,
1249            handler,
1250            _phantom: std::marker::PhantomData,
1251        }
1252    }
1253}
1254
1255/// Handler for tools with no parameters.
1256///
1257/// Used internally by [`ToolBuilder::no_params_handler`].
1258struct NoParamsTypedHandler<F> {
1259    handler: F,
1260}
1261
1262impl<F, Fut> ToolHandler for NoParamsTypedHandler<F>
1263where
1264    F: Fn() -> Fut + Send + Sync + 'static,
1265    Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
1266{
1267    fn call(&self, _args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
1268        Box::pin(async move { (self.handler)().await })
1269    }
1270
1271    fn input_schema(&self) -> Value {
1272        serde_json::json!({ "type": "object" })
1273    }
1274}
1275
1276/// Builder state after handler is specified
1277#[doc(hidden)]
1278pub struct ToolBuilderWithHandler<I, F> {
1279    name: String,
1280    title: Option<String>,
1281    description: Option<String>,
1282    output_schema: Option<Value>,
1283    input_schema_override: Option<Value>,
1284    icons: Option<Vec<ToolIcon>>,
1285    annotations: Option<ToolAnnotations>,
1286    task_support: TaskSupportMode,
1287    handler: F,
1288    _phantom: std::marker::PhantomData<I>,
1289}
1290
1291/// Builder state for tools with no parameters.
1292///
1293/// Created by [`ToolBuilder::no_params_handler`].
1294#[doc(hidden)]
1295pub struct ToolBuilderWithNoParamsHandler<F> {
1296    name: String,
1297    title: Option<String>,
1298    description: Option<String>,
1299    output_schema: Option<Value>,
1300    input_schema_override: Option<Value>,
1301    icons: Option<Vec<ToolIcon>>,
1302    annotations: Option<ToolAnnotations>,
1303    task_support: TaskSupportMode,
1304    handler: F,
1305}
1306
1307impl<F, Fut> ToolBuilderWithNoParamsHandler<F>
1308where
1309    F: Fn() -> Fut + Send + Sync + 'static,
1310    Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
1311{
1312    /// Build the tool.
1313    pub fn build(self) -> Tool {
1314        Tool::from_handler(
1315            self.name,
1316            self.title,
1317            self.description,
1318            self.output_schema,
1319            self.icons,
1320            self.annotations,
1321            self.task_support,
1322            self.input_schema_override,
1323            NoParamsTypedHandler {
1324                handler: self.handler,
1325            },
1326        )
1327    }
1328
1329    /// Apply a Tower layer (middleware) to this tool.
1330    ///
1331    /// See [`ToolBuilderWithHandler::layer`] for details.
1332    pub fn layer<L>(self, layer: L) -> ToolBuilderWithNoParamsHandlerLayer<F, L> {
1333        ToolBuilderWithNoParamsHandlerLayer {
1334            name: self.name,
1335            title: self.title,
1336            description: self.description,
1337            output_schema: self.output_schema,
1338            input_schema_override: self.input_schema_override,
1339            icons: self.icons,
1340            annotations: self.annotations,
1341            task_support: self.task_support,
1342            handler: self.handler,
1343            layer,
1344        }
1345    }
1346
1347    /// Apply a guard to this tool.
1348    ///
1349    /// See [`ToolBuilderWithHandler::guard`] for details.
1350    pub fn guard<G>(self, guard: G) -> ToolBuilderWithNoParamsHandlerLayer<F, GuardLayer<G>>
1351    where
1352        G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1353    {
1354        self.layer(GuardLayer::new(guard))
1355    }
1356}
1357
1358/// Builder state after a layer has been applied to a no-params handler.
1359#[doc(hidden)]
1360pub struct ToolBuilderWithNoParamsHandlerLayer<F, L> {
1361    name: String,
1362    title: Option<String>,
1363    description: Option<String>,
1364    output_schema: Option<Value>,
1365    input_schema_override: Option<Value>,
1366    icons: Option<Vec<ToolIcon>>,
1367    annotations: Option<ToolAnnotations>,
1368    task_support: TaskSupportMode,
1369    handler: F,
1370    layer: L,
1371}
1372
1373#[allow(private_bounds)]
1374impl<F, Fut, L> ToolBuilderWithNoParamsHandlerLayer<F, L>
1375where
1376    F: Fn() -> Fut + Send + Sync + 'static,
1377    Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
1378    L: tower::Layer<ToolHandlerService<NoParamsTypedHandler<F>>> + Clone + Send + Sync + 'static,
1379    L::Service: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
1380    <L::Service as Service<ToolRequest>>::Error: fmt::Display + Send,
1381    <L::Service as Service<ToolRequest>>::Future: Send,
1382{
1383    /// Build the tool with the applied layer(s).
1384    pub fn build(self) -> Tool {
1385        let input_schema = ensure_object_schema(
1386            self.input_schema_override
1387                .unwrap_or_else(|| serde_json::json!({ "type": "object" })),
1388        );
1389
1390        let handler_service = ToolHandlerService::new(NoParamsTypedHandler {
1391            handler: self.handler,
1392        });
1393        let layered = self.layer.layer(handler_service);
1394        let catch_error = ToolCatchError::new(layered);
1395        let service = BoxCloneService::new(catch_error);
1396
1397        Tool {
1398            name: self.name,
1399            title: self.title,
1400            description: self.description,
1401            output_schema: self.output_schema,
1402            icons: self.icons,
1403            annotations: self.annotations,
1404            task_support: self.task_support,
1405            service,
1406            input_schema,
1407        }
1408    }
1409
1410    /// Apply an additional Tower layer (middleware).
1411    pub fn layer<L2>(
1412        self,
1413        layer: L2,
1414    ) -> ToolBuilderWithNoParamsHandlerLayer<F, tower::layer::util::Stack<L2, L>> {
1415        ToolBuilderWithNoParamsHandlerLayer {
1416            name: self.name,
1417            title: self.title,
1418            description: self.description,
1419            output_schema: self.output_schema,
1420            input_schema_override: self.input_schema_override,
1421            icons: self.icons,
1422            annotations: self.annotations,
1423            task_support: self.task_support,
1424            handler: self.handler,
1425            layer: tower::layer::util::Stack::new(layer, self.layer),
1426        }
1427    }
1428
1429    /// Apply a guard to this tool.
1430    ///
1431    /// See [`ToolBuilderWithHandler::guard`] for details.
1432    pub fn guard<G>(
1433        self,
1434        guard: G,
1435    ) -> ToolBuilderWithNoParamsHandlerLayer<F, tower::layer::util::Stack<GuardLayer<G>, L>>
1436    where
1437        G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1438    {
1439        self.layer(GuardLayer::new(guard))
1440    }
1441}
1442
1443impl<I, F, Fut> ToolBuilderWithHandler<I, F>
1444where
1445    I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
1446    F: Fn(I) -> Fut + Send + Sync + 'static,
1447    Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
1448{
1449    /// Build the tool.
1450    pub fn build(self) -> Tool {
1451        Tool::from_handler(
1452            self.name,
1453            self.title,
1454            self.description,
1455            self.output_schema,
1456            self.icons,
1457            self.annotations,
1458            self.task_support,
1459            self.input_schema_override,
1460            TypedHandler {
1461                handler: self.handler,
1462                _phantom: std::marker::PhantomData,
1463            },
1464        )
1465    }
1466
1467    /// Apply a Tower layer (middleware) to this tool.
1468    ///
1469    /// The layer wraps the tool's handler service, enabling functionality like
1470    /// timeouts, rate limiting, and metrics collection at the per-tool level.
1471    ///
1472    /// # Example
1473    ///
1474    /// ```rust
1475    /// use std::time::Duration;
1476    /// use tower::timeout::TimeoutLayer;
1477    /// use tower_mcp::{ToolBuilder, CallToolResult};
1478    /// use schemars::JsonSchema;
1479    /// use serde::Deserialize;
1480    ///
1481    /// #[derive(Debug, Deserialize, JsonSchema)]
1482    /// struct Input { query: String }
1483    ///
1484    /// let tool = ToolBuilder::new("search")
1485    ///     .description("Search with timeout")
1486    ///     .handler(|input: Input| async move {
1487    ///         Ok(CallToolResult::text("result"))
1488    ///     })
1489    ///     .layer(TimeoutLayer::new(Duration::from_secs(30)))
1490    ///     .build();
1491    /// ```
1492    pub fn layer<L>(self, layer: L) -> ToolBuilderWithLayer<I, F, L> {
1493        ToolBuilderWithLayer {
1494            name: self.name,
1495            title: self.title,
1496            description: self.description,
1497            output_schema: self.output_schema,
1498            input_schema_override: self.input_schema_override,
1499            icons: self.icons,
1500            annotations: self.annotations,
1501            task_support: self.task_support,
1502            handler: self.handler,
1503            layer,
1504            _phantom: std::marker::PhantomData,
1505        }
1506    }
1507
1508    /// Apply a guard to this tool.
1509    ///
1510    /// The guard runs before the handler and can short-circuit with an error
1511    /// message. This is syntactic sugar for `.layer(GuardLayer::new(f))`.
1512    ///
1513    /// See [`GuardLayer`] for a full example.
1514    pub fn guard<G>(self, guard: G) -> ToolBuilderWithLayer<I, F, GuardLayer<G>>
1515    where
1516        G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1517    {
1518        self.layer(GuardLayer::new(guard))
1519    }
1520}
1521
1522/// Builder state after a layer has been applied to the handler.
1523///
1524/// This builder allows chaining additional layers and building the final tool.
1525#[doc(hidden)]
1526pub struct ToolBuilderWithLayer<I, F, L> {
1527    name: String,
1528    title: Option<String>,
1529    description: Option<String>,
1530    output_schema: Option<Value>,
1531    input_schema_override: Option<Value>,
1532    icons: Option<Vec<ToolIcon>>,
1533    annotations: Option<ToolAnnotations>,
1534    task_support: TaskSupportMode,
1535    handler: F,
1536    layer: L,
1537    _phantom: std::marker::PhantomData<I>,
1538}
1539
1540// Allow private_bounds because these internal types (ToolHandlerService, TypedHandler, etc.)
1541// are implementation details that users don't interact with directly.
1542#[allow(private_bounds)]
1543impl<I, F, Fut, L> ToolBuilderWithLayer<I, F, L>
1544where
1545    I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
1546    F: Fn(I) -> Fut + Send + Sync + 'static,
1547    Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
1548    L: tower::Layer<ToolHandlerService<TypedHandler<I, F>>> + Clone + Send + Sync + 'static,
1549    L::Service: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
1550    <L::Service as Service<ToolRequest>>::Error: fmt::Display + Send,
1551    <L::Service as Service<ToolRequest>>::Future: Send,
1552{
1553    /// Build the tool with the applied layer(s).
1554    pub fn build(self) -> Tool {
1555        let input_schema = self.input_schema_override.unwrap_or_else(|| {
1556            let input_schema = schemars::schema_for!(I);
1557            serde_json::to_value(input_schema)
1558                .unwrap_or_else(|_| serde_json::json!({ "type": "object" }))
1559        });
1560        let input_schema = ensure_object_schema(input_schema);
1561
1562        let handler_service = ToolHandlerService::new(TypedHandler {
1563            handler: self.handler,
1564            _phantom: std::marker::PhantomData,
1565        });
1566        let layered = self.layer.layer(handler_service);
1567        let catch_error = ToolCatchError::new(layered);
1568        let service = BoxCloneService::new(catch_error);
1569
1570        Tool {
1571            name: self.name,
1572            title: self.title,
1573            description: self.description,
1574            output_schema: self.output_schema,
1575            icons: self.icons,
1576            annotations: self.annotations,
1577            task_support: self.task_support,
1578            service,
1579            input_schema,
1580        }
1581    }
1582
1583    /// Apply an additional Tower layer (middleware).
1584    ///
1585    /// Layers are applied in order, with earlier layers wrapping later ones.
1586    /// This means the first layer added is the outermost middleware.
1587    pub fn layer<L2>(
1588        self,
1589        layer: L2,
1590    ) -> ToolBuilderWithLayer<I, F, tower::layer::util::Stack<L2, L>> {
1591        ToolBuilderWithLayer {
1592            name: self.name,
1593            title: self.title,
1594            description: self.description,
1595            output_schema: self.output_schema,
1596            input_schema_override: self.input_schema_override,
1597            icons: self.icons,
1598            annotations: self.annotations,
1599            task_support: self.task_support,
1600            handler: self.handler,
1601            layer: tower::layer::util::Stack::new(layer, self.layer),
1602            _phantom: std::marker::PhantomData,
1603        }
1604    }
1605
1606    /// Apply a guard to this tool.
1607    ///
1608    /// See [`ToolBuilderWithHandler::guard`] for details.
1609    pub fn guard<G>(
1610        self,
1611        guard: G,
1612    ) -> ToolBuilderWithLayer<I, F, tower::layer::util::Stack<GuardLayer<G>, L>>
1613    where
1614        G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1615    {
1616        self.layer(GuardLayer::new(guard))
1617    }
1618}
1619
1620// =============================================================================
1621// Handler implementations
1622// =============================================================================
1623
1624/// Handler that deserializes input to a specific type
1625struct TypedHandler<I, F> {
1626    handler: F,
1627    _phantom: std::marker::PhantomData<I>,
1628}
1629
1630impl<I, F, Fut> ToolHandler for TypedHandler<I, F>
1631where
1632    I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
1633    F: Fn(I) -> Fut + Send + Sync + 'static,
1634    Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
1635{
1636    fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
1637        Box::pin(async move {
1638            let input: I = match serde_json::from_value(args) {
1639                Ok(input) => input,
1640                Err(e) => return Ok(CallToolResult::error(format!("Invalid input: {e}"))),
1641            };
1642            (self.handler)(input).await
1643        })
1644    }
1645
1646    fn input_schema(&self) -> Value {
1647        let schema = schemars::schema_for!(I);
1648        let schema = serde_json::to_value(schema).unwrap_or_else(|_| {
1649            serde_json::json!({
1650                "type": "object"
1651            })
1652        });
1653        ensure_object_schema(schema)
1654    }
1655}
1656
1657// =============================================================================
1658// Trait-based tool definition
1659// =============================================================================
1660
1661/// Trait for defining tools with full control
1662///
1663/// Implement this trait when you need more control than the builder provides,
1664/// or when you want to define tools as standalone types.
1665///
1666/// # Example
1667///
1668/// ```rust
1669/// use tower_mcp::tool::McpTool;
1670/// use tower_mcp::error::Result;
1671/// use schemars::JsonSchema;
1672/// use serde::{Deserialize, Serialize};
1673///
1674/// #[derive(Debug, Deserialize, JsonSchema)]
1675/// struct AddInput {
1676///     a: i64,
1677///     b: i64,
1678/// }
1679///
1680/// struct AddTool;
1681///
1682/// impl McpTool for AddTool {
1683///     const NAME: &'static str = "add";
1684///     const DESCRIPTION: &'static str = "Add two numbers";
1685///
1686///     type Input = AddInput;
1687///     type Output = i64;
1688///
1689///     async fn call(&self, input: Self::Input) -> Result<Self::Output> {
1690///         Ok(input.a + input.b)
1691///     }
1692/// }
1693///
1694/// let tool = AddTool.into_tool();
1695/// assert_eq!(tool.name, "add");
1696/// ```
1697pub trait McpTool: Send + Sync + 'static {
1698    /// The tool name (must be unique within the router).
1699    const NAME: &'static str;
1700    /// A human-readable description of the tool.
1701    const DESCRIPTION: &'static str;
1702
1703    /// The input type, deserialized from tool call arguments.
1704    type Input: JsonSchema + DeserializeOwned + Send;
1705    /// The output type, serialized into the tool call result.
1706    type Output: Serialize + Send;
1707
1708    /// Execute the tool with the given input.
1709    fn call(&self, input: Self::Input) -> impl Future<Output = Result<Self::Output>> + Send;
1710
1711    /// Optional annotations for the tool
1712    fn annotations(&self) -> Option<ToolAnnotations> {
1713        None
1714    }
1715
1716    /// Convert to a [`Tool`] instance.
1717    ///
1718    /// # Panics
1719    ///
1720    /// Panics if [`NAME`](Self::NAME) is not a valid tool name. Since `NAME`
1721    /// is a `&'static str`, invalid names are caught immediately during
1722    /// development.
1723    fn into_tool(self) -> Tool
1724    where
1725        Self: Sized,
1726    {
1727        if let Err(e) = validate_tool_name(Self::NAME) {
1728            panic!("{e}");
1729        }
1730        let annotations = self.annotations();
1731        let tool = Arc::new(self);
1732        Tool::from_handler(
1733            Self::NAME.to_string(),
1734            None,
1735            Some(Self::DESCRIPTION.to_string()),
1736            None,
1737            None,
1738            annotations,
1739            TaskSupportMode::default(),
1740            None,
1741            McpToolHandler { tool },
1742        )
1743    }
1744}
1745
1746/// Wrapper to make McpTool implement ToolHandler
1747struct McpToolHandler<T: McpTool> {
1748    tool: Arc<T>,
1749}
1750
1751impl<T: McpTool> ToolHandler for McpToolHandler<T> {
1752    fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
1753        let tool = self.tool.clone();
1754        Box::pin(async move {
1755            let input: T::Input = match serde_json::from_value(args) {
1756                Ok(input) => input,
1757                Err(e) => return Ok(CallToolResult::error(format!("Invalid input: {e}"))),
1758            };
1759            let output = tool.call(input).await?;
1760            let value = serde_json::to_value(output).tool_context("Failed to serialize output")?;
1761            Ok(CallToolResult::json(value))
1762        })
1763    }
1764
1765    fn input_schema(&self) -> Value {
1766        let schema = schemars::schema_for!(T::Input);
1767        let schema = serde_json::to_value(schema).unwrap_or_else(|_| {
1768            serde_json::json!({
1769                "type": "object"
1770            })
1771        });
1772        ensure_object_schema(schema)
1773    }
1774}
1775
1776#[cfg(test)]
1777mod tests {
1778    use super::*;
1779    use crate::extract::{Context, Json, RawArgs, State};
1780    use crate::protocol::Content;
1781    use schemars::JsonSchema;
1782    use serde::Deserialize;
1783
1784    #[derive(Debug, Deserialize, JsonSchema)]
1785    struct GreetInput {
1786        name: String,
1787    }
1788
1789    #[tokio::test]
1790    async fn test_builder_tool() {
1791        let tool = ToolBuilder::new("greet")
1792            .description("Greet someone")
1793            .handler(|input: GreetInput| async move {
1794                Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
1795            })
1796            .build();
1797
1798        assert_eq!(tool.name, "greet");
1799        assert_eq!(tool.description.as_deref(), Some("Greet someone"));
1800
1801        let result = tool.call(serde_json::json!({"name": "World"})).await;
1802
1803        assert!(!result.is_error);
1804    }
1805
1806    #[tokio::test]
1807    async fn test_raw_handler() {
1808        let tool = ToolBuilder::new("echo")
1809            .description("Echo input")
1810            .extractor_handler((), |RawArgs(args): RawArgs| async move {
1811                Ok(CallToolResult::json(args))
1812            })
1813            .build();
1814
1815        let result = tool.call(serde_json::json!({"foo": "bar"})).await;
1816
1817        assert!(!result.is_error);
1818    }
1819
1820    #[test]
1821    fn test_invalid_tool_name_empty() {
1822        let err = ToolBuilder::try_new("").err().expect("should fail");
1823        assert!(err.to_string().contains("cannot be empty"));
1824    }
1825
1826    #[test]
1827    fn test_invalid_tool_name_too_long() {
1828        let long_name = "a".repeat(65);
1829        let err = ToolBuilder::try_new(long_name).err().expect("should fail");
1830        assert!(err.to_string().contains("exceeds maximum"));
1831    }
1832
1833    #[test]
1834    fn test_invalid_tool_name_bad_chars() {
1835        let err = ToolBuilder::try_new("my tool!").err().expect("should fail");
1836        assert!(err.to_string().contains("invalid character"));
1837    }
1838
1839    #[test]
1840    #[should_panic(expected = "cannot be empty")]
1841    fn test_new_panics_on_empty_name() {
1842        ToolBuilder::new("");
1843    }
1844
1845    #[test]
1846    #[should_panic(expected = "exceeds maximum")]
1847    fn test_new_panics_on_too_long_name() {
1848        ToolBuilder::new("a".repeat(65));
1849    }
1850
1851    #[test]
1852    #[should_panic(expected = "invalid character")]
1853    fn test_new_panics_on_invalid_chars() {
1854        ToolBuilder::new("my tool!");
1855    }
1856
1857    #[test]
1858    fn test_valid_tool_names() {
1859        // All valid characters per SEP-986
1860        let names = [
1861            "my_tool",
1862            "my-tool",
1863            "my.tool",
1864            "my/tool",
1865            "user-profile/update",
1866            "MyTool123",
1867            "a",
1868            &"a".repeat(64),
1869        ];
1870        for name in names {
1871            assert!(
1872                ToolBuilder::try_new(name).is_ok(),
1873                "Expected '{}' to be valid",
1874                name
1875            );
1876        }
1877    }
1878
1879    #[tokio::test]
1880    async fn test_context_aware_handler() {
1881        use crate::context::notification_channel;
1882        use crate::protocol::{ProgressToken, RequestId};
1883
1884        #[derive(Debug, Deserialize, JsonSchema)]
1885        struct ProcessInput {
1886            count: i32,
1887        }
1888
1889        let tool = ToolBuilder::new("process")
1890            .description("Process with context")
1891            .extractor_handler(
1892                (),
1893                |ctx: Context, Json(input): Json<ProcessInput>| async move {
1894                    // Simulate progress reporting
1895                    for i in 0..input.count {
1896                        if ctx.is_cancelled() {
1897                            return Ok(CallToolResult::error("Cancelled"));
1898                        }
1899                        ctx.report_progress(i as f64, Some(input.count as f64), None)
1900                            .await;
1901                    }
1902                    Ok(CallToolResult::text(format!(
1903                        "Processed {} items",
1904                        input.count
1905                    )))
1906                },
1907            )
1908            .build();
1909
1910        assert_eq!(tool.name, "process");
1911
1912        // Test with a context that has progress token and notification sender
1913        let (tx, mut rx) = notification_channel(10);
1914        let ctx = RequestContext::new(RequestId::Number(1))
1915            .with_progress_token(ProgressToken::Number(42))
1916            .with_notification_sender(tx);
1917
1918        let result = tool
1919            .call_with_context(ctx, serde_json::json!({"count": 3}))
1920            .await;
1921
1922        assert!(!result.is_error);
1923
1924        // Check that progress notifications were sent
1925        let mut progress_count = 0;
1926        while rx.try_recv().is_ok() {
1927            progress_count += 1;
1928        }
1929        assert_eq!(progress_count, 3);
1930    }
1931
1932    #[tokio::test]
1933    async fn test_context_aware_handler_cancellation() {
1934        use crate::protocol::RequestId;
1935        use std::sync::atomic::{AtomicI32, Ordering};
1936
1937        #[derive(Debug, Deserialize, JsonSchema)]
1938        struct LongRunningInput {
1939            iterations: i32,
1940        }
1941
1942        let iterations_completed = Arc::new(AtomicI32::new(0));
1943        let iterations_ref = iterations_completed.clone();
1944
1945        let tool = ToolBuilder::new("long_running")
1946            .description("Long running task")
1947            .extractor_handler(
1948                (),
1949                move |ctx: Context, Json(input): Json<LongRunningInput>| {
1950                    let completed = iterations_ref.clone();
1951                    async move {
1952                        for i in 0..input.iterations {
1953                            if ctx.is_cancelled() {
1954                                return Ok(CallToolResult::error("Cancelled"));
1955                            }
1956                            completed.fetch_add(1, Ordering::SeqCst);
1957                            // Simulate work
1958                            tokio::task::yield_now().await;
1959                            // Cancel after iteration 2
1960                            if i == 2 {
1961                                ctx.cancellation_token().cancel();
1962                            }
1963                        }
1964                        Ok(CallToolResult::text("Done"))
1965                    }
1966                },
1967            )
1968            .build();
1969
1970        let ctx = RequestContext::new(RequestId::Number(1));
1971
1972        let result = tool
1973            .call_with_context(ctx, serde_json::json!({"iterations": 10}))
1974            .await;
1975
1976        // Should have been cancelled after 3 iterations (0, 1, 2)
1977        // The next iteration (3) checks cancellation and returns
1978        assert!(result.is_error);
1979        assert_eq!(iterations_completed.load(Ordering::SeqCst), 3);
1980    }
1981
1982    #[tokio::test]
1983    async fn test_tool_builder_with_enhanced_fields() {
1984        let output_schema = serde_json::json!({
1985            "type": "object",
1986            "properties": {
1987                "greeting": {"type": "string"}
1988            }
1989        });
1990
1991        let tool = ToolBuilder::new("greet")
1992            .title("Greeting Tool")
1993            .description("Greet someone")
1994            .output_schema(output_schema.clone())
1995            .icon("https://example.com/icon.png")
1996            .icon_with_meta(
1997                "https://example.com/icon-large.png",
1998                Some("image/png".to_string()),
1999                Some(vec!["96x96".to_string()]),
2000            )
2001            .handler(|input: GreetInput| async move {
2002                Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
2003            })
2004            .build();
2005
2006        assert_eq!(tool.name, "greet");
2007        assert_eq!(tool.title.as_deref(), Some("Greeting Tool"));
2008        assert_eq!(tool.description.as_deref(), Some("Greet someone"));
2009        assert_eq!(tool.output_schema, Some(output_schema));
2010        assert!(tool.icons.is_some());
2011        assert_eq!(tool.icons.as_ref().unwrap().len(), 2);
2012
2013        // Test definition includes new fields
2014        let def = tool.definition();
2015        assert_eq!(def.title.as_deref(), Some("Greeting Tool"));
2016        assert!(def.output_schema.is_some());
2017        assert!(def.icons.is_some());
2018    }
2019
2020    #[tokio::test]
2021    async fn test_handler_with_state() {
2022        let shared = Arc::new("shared-state".to_string());
2023
2024        let tool = ToolBuilder::new("stateful")
2025            .description("Uses shared state")
2026            .extractor_handler(
2027                shared,
2028                |State(state): State<Arc<String>>, Json(input): Json<GreetInput>| async move {
2029                    Ok(CallToolResult::text(format!(
2030                        "{}: Hello, {}!",
2031                        state, input.name
2032                    )))
2033                },
2034            )
2035            .build();
2036
2037        let result = tool.call(serde_json::json!({"name": "World"})).await;
2038        assert!(!result.is_error);
2039    }
2040
2041    #[tokio::test]
2042    async fn test_handler_with_state_and_context() {
2043        use crate::protocol::RequestId;
2044
2045        let shared = Arc::new(42_i32);
2046
2047        let tool =
2048            ToolBuilder::new("stateful_ctx")
2049                .description("Uses state and context")
2050                .extractor_handler(
2051                    shared,
2052                    |State(state): State<Arc<i32>>,
2053                     _ctx: Context,
2054                     Json(input): Json<GreetInput>| async move {
2055                        Ok(CallToolResult::text(format!(
2056                            "{}: Hello, {}!",
2057                            state, input.name
2058                        )))
2059                    },
2060                )
2061                .build();
2062
2063        let ctx = RequestContext::new(RequestId::Number(1));
2064        let result = tool
2065            .call_with_context(ctx, serde_json::json!({"name": "World"}))
2066            .await;
2067        assert!(!result.is_error);
2068    }
2069
2070    #[tokio::test]
2071    async fn test_handler_no_params() {
2072        let tool = ToolBuilder::new("no_params")
2073            .description("Takes no parameters")
2074            .extractor_handler((), |Json(_): Json<NoParams>| async {
2075                Ok(CallToolResult::text("no params result"))
2076            })
2077            .build();
2078
2079        assert_eq!(tool.name, "no_params");
2080
2081        // Should work with empty args
2082        let result = tool.call(serde_json::json!({})).await;
2083        assert!(!result.is_error);
2084
2085        // Should also work with unexpected args (ignored)
2086        let result = tool.call(serde_json::json!({"unexpected": "value"})).await;
2087        assert!(!result.is_error);
2088
2089        // Check input schema includes type: object
2090        let schema = tool.definition().input_schema;
2091        assert_eq!(schema.get("type").unwrap().as_str().unwrap(), "object");
2092    }
2093
2094    #[tokio::test]
2095    async fn test_handler_with_state_no_params() {
2096        let shared = Arc::new("shared_value".to_string());
2097
2098        let tool = ToolBuilder::new("with_state_no_params")
2099            .description("Takes no parameters but has state")
2100            .extractor_handler(
2101                shared,
2102                |State(state): State<Arc<String>>, Json(_): Json<NoParams>| async move {
2103                    Ok(CallToolResult::text(format!("state: {}", state)))
2104                },
2105            )
2106            .build();
2107
2108        assert_eq!(tool.name, "with_state_no_params");
2109
2110        // Should work with empty args
2111        let result = tool.call(serde_json::json!({})).await;
2112        assert!(!result.is_error);
2113        assert_eq!(result.first_text().unwrap(), "state: shared_value");
2114
2115        // Check input schema includes type: object
2116        let schema = tool.definition().input_schema;
2117        assert_eq!(schema.get("type").unwrap().as_str().unwrap(), "object");
2118    }
2119
2120    #[tokio::test]
2121    async fn test_handler_no_params_with_context() {
2122        let tool = ToolBuilder::new("no_params_with_context")
2123            .description("Takes no parameters but has context")
2124            .extractor_handler((), |_ctx: Context, Json(_): Json<NoParams>| async move {
2125                Ok(CallToolResult::text("context available"))
2126            })
2127            .build();
2128
2129        assert_eq!(tool.name, "no_params_with_context");
2130
2131        let result = tool.call(serde_json::json!({})).await;
2132        assert!(!result.is_error);
2133        assert_eq!(result.first_text().unwrap(), "context available");
2134    }
2135
2136    #[tokio::test]
2137    async fn test_handler_with_state_and_context_no_params() {
2138        let shared = Arc::new("shared".to_string());
2139
2140        let tool = ToolBuilder::new("state_context_no_params")
2141            .description("Has state and context, no params")
2142            .extractor_handler(
2143                shared,
2144                |State(state): State<Arc<String>>,
2145                 _ctx: Context,
2146                 Json(_): Json<NoParams>| async move {
2147                    Ok(CallToolResult::text(format!("state: {}", state)))
2148                },
2149            )
2150            .build();
2151
2152        assert_eq!(tool.name, "state_context_no_params");
2153
2154        let result = tool.call(serde_json::json!({})).await;
2155        assert!(!result.is_error);
2156        assert_eq!(result.first_text().unwrap(), "state: shared");
2157    }
2158
2159    #[tokio::test]
2160    async fn test_raw_handler_with_state() {
2161        let prefix = Arc::new("prefix:".to_string());
2162
2163        let tool = ToolBuilder::new("raw_with_state")
2164            .description("Raw handler with state")
2165            .extractor_handler(
2166                prefix,
2167                |State(state): State<Arc<String>>, RawArgs(args): RawArgs| async move {
2168                    Ok(CallToolResult::text(format!("{} {}", state, args)))
2169                },
2170            )
2171            .build();
2172
2173        assert_eq!(tool.name, "raw_with_state");
2174
2175        let result = tool.call(serde_json::json!({"key": "value"})).await;
2176        assert!(!result.is_error);
2177        assert!(result.first_text().unwrap().starts_with("prefix:"));
2178    }
2179
2180    #[tokio::test]
2181    async fn test_raw_handler_with_state_and_context() {
2182        let prefix = Arc::new("prefix:".to_string());
2183
2184        let tool = ToolBuilder::new("raw_state_context")
2185            .description("Raw handler with state and context")
2186            .extractor_handler(
2187                prefix,
2188                |State(state): State<Arc<String>>,
2189                 _ctx: Context,
2190                 RawArgs(args): RawArgs| async move {
2191                    Ok(CallToolResult::text(format!("{} {}", state, args)))
2192                },
2193            )
2194            .build();
2195
2196        assert_eq!(tool.name, "raw_state_context");
2197
2198        let result = tool.call(serde_json::json!({"key": "value"})).await;
2199        assert!(!result.is_error);
2200        assert!(result.first_text().unwrap().starts_with("prefix:"));
2201    }
2202
2203    #[tokio::test]
2204    async fn test_tool_with_timeout_layer() {
2205        use std::time::Duration;
2206        use tower::timeout::TimeoutLayer;
2207
2208        #[derive(Debug, Deserialize, JsonSchema)]
2209        struct SlowInput {
2210            delay_ms: u64,
2211        }
2212
2213        // Create a tool with a short timeout
2214        let tool = ToolBuilder::new("slow_tool")
2215            .description("A slow tool")
2216            .handler(|input: SlowInput| async move {
2217                tokio::time::sleep(Duration::from_millis(input.delay_ms)).await;
2218                Ok(CallToolResult::text("completed"))
2219            })
2220            .layer(TimeoutLayer::new(Duration::from_millis(50)))
2221            .build();
2222
2223        // Fast call should succeed
2224        let result = tool.call(serde_json::json!({"delay_ms": 10})).await;
2225        assert!(!result.is_error);
2226        assert_eq!(result.first_text().unwrap(), "completed");
2227
2228        // Slow call should timeout and return an error result
2229        let result = tool.call(serde_json::json!({"delay_ms": 200})).await;
2230        assert!(result.is_error);
2231        // Tower's timeout error message is "request timed out"
2232        let msg = result.first_text().unwrap().to_lowercase();
2233        assert!(
2234            msg.contains("timed out") || msg.contains("timeout") || msg.contains("elapsed"),
2235            "Expected timeout error, got: {}",
2236            msg
2237        );
2238    }
2239
2240    #[tokio::test]
2241    async fn test_tool_with_concurrency_limit_layer() {
2242        use std::sync::atomic::{AtomicU32, Ordering};
2243        use std::time::Duration;
2244        use tower::limit::ConcurrencyLimitLayer;
2245
2246        #[derive(Debug, Deserialize, JsonSchema)]
2247        struct WorkInput {
2248            id: u32,
2249        }
2250
2251        let max_concurrent = Arc::new(AtomicU32::new(0));
2252        let current_concurrent = Arc::new(AtomicU32::new(0));
2253        let max_ref = max_concurrent.clone();
2254        let current_ref = current_concurrent.clone();
2255
2256        // Create a tool with concurrency limit of 2
2257        let tool = ToolBuilder::new("concurrent_tool")
2258            .description("A concurrent tool")
2259            .handler(move |input: WorkInput| {
2260                let max = max_ref.clone();
2261                let current = current_ref.clone();
2262                async move {
2263                    // Track concurrency
2264                    let prev = current.fetch_add(1, Ordering::SeqCst);
2265                    max.fetch_max(prev + 1, Ordering::SeqCst);
2266
2267                    // Simulate work
2268                    tokio::time::sleep(Duration::from_millis(50)).await;
2269
2270                    current.fetch_sub(1, Ordering::SeqCst);
2271                    Ok(CallToolResult::text(format!("completed {}", input.id)))
2272                }
2273            })
2274            .layer(ConcurrencyLimitLayer::new(2))
2275            .build();
2276
2277        // Launch 4 concurrent calls
2278        let handles: Vec<_> = (0..4)
2279            .map(|i| {
2280                let t = tool.call(serde_json::json!({"id": i}));
2281                tokio::spawn(t)
2282            })
2283            .collect();
2284
2285        for handle in handles {
2286            let result = handle.await.unwrap();
2287            assert!(!result.is_error);
2288        }
2289
2290        // Max concurrent should not exceed 2
2291        assert!(max_concurrent.load(Ordering::SeqCst) <= 2);
2292    }
2293
2294    #[tokio::test]
2295    async fn test_tool_with_multiple_layers() {
2296        use std::time::Duration;
2297        use tower::limit::ConcurrencyLimitLayer;
2298        use tower::timeout::TimeoutLayer;
2299
2300        #[derive(Debug, Deserialize, JsonSchema)]
2301        struct Input {
2302            value: String,
2303        }
2304
2305        // Create a tool with multiple layers stacked
2306        let tool = ToolBuilder::new("multi_layer_tool")
2307            .description("Tool with multiple layers")
2308            .handler(|input: Input| async move {
2309                Ok(CallToolResult::text(format!("processed: {}", input.value)))
2310            })
2311            .layer(TimeoutLayer::new(Duration::from_secs(5)))
2312            .layer(ConcurrencyLimitLayer::new(10))
2313            .build();
2314
2315        let result = tool.call(serde_json::json!({"value": "test"})).await;
2316        assert!(!result.is_error);
2317        assert_eq!(result.first_text().unwrap(), "processed: test");
2318    }
2319
2320    #[test]
2321    fn test_tool_catch_error_clone() {
2322        // ToolCatchError should be Clone when inner is Clone
2323        // Use a simple tool that we can clone
2324        let tool = ToolBuilder::new("test")
2325            .description("test")
2326            .extractor_handler((), |RawArgs(_args): RawArgs| async {
2327                Ok(CallToolResult::text("ok"))
2328            })
2329            .build();
2330        // The tool contains a BoxToolService which is cloneable
2331        let _clone = tool.call(serde_json::json!({}));
2332    }
2333
2334    #[test]
2335    fn test_tool_catch_error_debug() {
2336        // ToolCatchError implements Debug when inner implements Debug
2337        // Since our internal services don't require Debug, just verify
2338        // that ToolCatchError has a Debug impl for appropriate types
2339        #[derive(Debug, Clone)]
2340        struct DebugService;
2341
2342        impl Service<ToolRequest> for DebugService {
2343            type Response = CallToolResult;
2344            type Error = crate::error::Error;
2345            type Future = Pin<
2346                Box<
2347                    dyn Future<Output = std::result::Result<CallToolResult, crate::error::Error>>
2348                        + Send,
2349                >,
2350            >;
2351
2352            fn poll_ready(
2353                &mut self,
2354                _cx: &mut std::task::Context<'_>,
2355            ) -> Poll<std::result::Result<(), Self::Error>> {
2356                Poll::Ready(Ok(()))
2357            }
2358
2359            fn call(&mut self, _req: ToolRequest) -> Self::Future {
2360                Box::pin(async { Ok(CallToolResult::text("ok")) })
2361            }
2362        }
2363
2364        let catch_error = ToolCatchError::new(DebugService);
2365        let debug = format!("{:?}", catch_error);
2366        assert!(debug.contains("ToolCatchError"));
2367    }
2368
2369    #[test]
2370    fn test_tool_request_new() {
2371        use crate::protocol::RequestId;
2372
2373        let ctx = RequestContext::new(RequestId::Number(42));
2374        let args = serde_json::json!({"key": "value"});
2375        let req = ToolRequest::new(ctx.clone(), args.clone());
2376
2377        assert_eq!(req.args, args);
2378    }
2379
2380    #[test]
2381    fn test_no_params_schema() {
2382        // NoParams should produce a schema with type: "object"
2383        let schema = schemars::schema_for!(NoParams);
2384        let schema_value = serde_json::to_value(&schema).unwrap();
2385        assert_eq!(
2386            schema_value.get("type").and_then(|v| v.as_str()),
2387            Some("object"),
2388            "NoParams should generate type: object schema"
2389        );
2390    }
2391
2392    #[test]
2393    fn test_no_params_deserialize() {
2394        // NoParams should deserialize from various inputs
2395        let from_empty_object: NoParams = serde_json::from_str("{}").unwrap();
2396        assert_eq!(from_empty_object, NoParams);
2397
2398        let from_null: NoParams = serde_json::from_str("null").unwrap();
2399        assert_eq!(from_null, NoParams);
2400
2401        // Should also accept objects with unexpected fields (ignored)
2402        let from_object_with_fields: NoParams =
2403            serde_json::from_str(r#"{"unexpected": "value"}"#).unwrap();
2404        assert_eq!(from_object_with_fields, NoParams);
2405    }
2406
2407    #[tokio::test]
2408    async fn test_no_params_type_in_handler() {
2409        // NoParams can be used as a handler input type
2410        let tool = ToolBuilder::new("status")
2411            .description("Get status")
2412            .handler(|_input: NoParams| async move { Ok(CallToolResult::text("OK")) })
2413            .build();
2414
2415        // Check schema has type: object (not type: null like () would produce)
2416        let schema = tool.definition().input_schema;
2417        assert_eq!(
2418            schema.get("type").and_then(|v| v.as_str()),
2419            Some("object"),
2420            "NoParams handler should produce type: object schema"
2421        );
2422
2423        // Should work with empty input
2424        let result = tool.call(serde_json::json!({})).await;
2425        assert!(!result.is_error);
2426    }
2427
2428    #[tokio::test]
2429    async fn test_serde_json_value_handler_has_type_object() {
2430        // serde_json::Value generates a schema without "type" via schemars.
2431        // We must ensure "type": "object" is added for MCP compliance.
2432        let tool = ToolBuilder::new("any_input")
2433            .description("Accepts any input")
2434            .handler(|_input: serde_json::Value| async move { Ok(CallToolResult::text("ok")) })
2435            .build();
2436
2437        let schema = tool.definition().input_schema;
2438        assert_eq!(
2439            schema.get("type").and_then(|v| v.as_str()),
2440            Some("object"),
2441            "serde_json::Value handler should produce schema with type: object"
2442        );
2443    }
2444
2445    #[tokio::test]
2446    async fn test_tool_with_name_prefix() {
2447        #[derive(Debug, Deserialize, JsonSchema)]
2448        struct Input {
2449            value: String,
2450        }
2451
2452        let tool = ToolBuilder::new("query")
2453            .description("Query something")
2454            .title("Query Tool")
2455            .handler(|input: Input| async move { Ok(CallToolResult::text(&input.value)) })
2456            .build();
2457
2458        // Create prefixed version
2459        let prefixed = tool.with_name_prefix("db");
2460
2461        // Check name is prefixed
2462        assert_eq!(prefixed.name, "db.query");
2463
2464        // Check other fields are preserved
2465        assert_eq!(prefixed.description.as_deref(), Some("Query something"));
2466        assert_eq!(prefixed.title.as_deref(), Some("Query Tool"));
2467
2468        // Check the tool still works
2469        let result = prefixed
2470            .call(serde_json::json!({"value": "test input"}))
2471            .await;
2472        assert!(!result.is_error);
2473        match &result.content[0] {
2474            Content::Text { text, .. } => assert_eq!(text, "test input"),
2475            _ => panic!("Expected text content"),
2476        }
2477    }
2478
2479    #[tokio::test]
2480    async fn test_tool_with_name_prefix_multiple_levels() {
2481        let tool = ToolBuilder::new("action")
2482            .description("Do something")
2483            .handler(|_: NoParams| async move { Ok(CallToolResult::text("done")) })
2484            .build();
2485
2486        // Apply multiple prefixes
2487        let prefixed = tool.with_name_prefix("level1");
2488        assert_eq!(prefixed.name, "level1.action");
2489
2490        let double_prefixed = prefixed.with_name_prefix("level0");
2491        assert_eq!(double_prefixed.name, "level0.level1.action");
2492    }
2493
2494    // =============================================================================
2495    // no_params_handler tests
2496    // =============================================================================
2497
2498    #[tokio::test]
2499    async fn test_no_params_handler_basic() {
2500        let tool = ToolBuilder::new("get_status")
2501            .description("Get current status")
2502            .no_params_handler(|| async { Ok(CallToolResult::text("OK")) })
2503            .build();
2504
2505        assert_eq!(tool.name, "get_status");
2506        assert_eq!(tool.description.as_deref(), Some("Get current status"));
2507
2508        // Should work with empty args
2509        let result = tool.call(serde_json::json!({})).await;
2510        assert!(!result.is_error);
2511        assert_eq!(result.first_text().unwrap(), "OK");
2512
2513        // Should also work with null args
2514        let result = tool.call(serde_json::json!(null)).await;
2515        assert!(!result.is_error);
2516
2517        // Check input schema has type: object
2518        let schema = tool.definition().input_schema;
2519        assert_eq!(schema.get("type").and_then(|v| v.as_str()), Some("object"));
2520    }
2521
2522    #[tokio::test]
2523    async fn test_no_params_handler_with_captured_state() {
2524        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2525        let counter_ref = counter.clone();
2526
2527        let tool = ToolBuilder::new("increment")
2528            .description("Increment counter")
2529            .no_params_handler(move || {
2530                let c = counter_ref.clone();
2531                async move {
2532                    let prev = c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2533                    Ok(CallToolResult::text(format!("Incremented from {}", prev)))
2534                }
2535            })
2536            .build();
2537
2538        // Call multiple times
2539        let _ = tool.call(serde_json::json!({})).await;
2540        let _ = tool.call(serde_json::json!({})).await;
2541        let result = tool.call(serde_json::json!({})).await;
2542
2543        assert!(!result.is_error);
2544        assert_eq!(result.first_text().unwrap(), "Incremented from 2");
2545        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 3);
2546    }
2547
2548    #[tokio::test]
2549    async fn test_no_params_handler_with_layer() {
2550        use std::time::Duration;
2551        use tower::timeout::TimeoutLayer;
2552
2553        let tool = ToolBuilder::new("slow_status")
2554            .description("Slow status check")
2555            .no_params_handler(|| async {
2556                tokio::time::sleep(Duration::from_millis(10)).await;
2557                Ok(CallToolResult::text("done"))
2558            })
2559            .layer(TimeoutLayer::new(Duration::from_secs(1)))
2560            .build();
2561
2562        let result = tool.call(serde_json::json!({})).await;
2563        assert!(!result.is_error);
2564        assert_eq!(result.first_text().unwrap(), "done");
2565    }
2566
2567    #[tokio::test]
2568    async fn test_no_params_handler_timeout() {
2569        use std::time::Duration;
2570        use tower::timeout::TimeoutLayer;
2571
2572        let tool = ToolBuilder::new("very_slow_status")
2573            .description("Very slow status check")
2574            .no_params_handler(|| async {
2575                tokio::time::sleep(Duration::from_millis(200)).await;
2576                Ok(CallToolResult::text("done"))
2577            })
2578            .layer(TimeoutLayer::new(Duration::from_millis(50)))
2579            .build();
2580
2581        let result = tool.call(serde_json::json!({})).await;
2582        assert!(result.is_error);
2583        let msg = result.first_text().unwrap().to_lowercase();
2584        assert!(
2585            msg.contains("timed out") || msg.contains("timeout") || msg.contains("elapsed"),
2586            "Expected timeout error, got: {}",
2587            msg
2588        );
2589    }
2590
2591    #[tokio::test]
2592    async fn test_no_params_handler_with_multiple_layers() {
2593        use std::time::Duration;
2594        use tower::limit::ConcurrencyLimitLayer;
2595        use tower::timeout::TimeoutLayer;
2596
2597        let tool = ToolBuilder::new("multi_layer_status")
2598            .description("Status with multiple layers")
2599            .no_params_handler(|| async { Ok(CallToolResult::text("status ok")) })
2600            .layer(TimeoutLayer::new(Duration::from_secs(5)))
2601            .layer(ConcurrencyLimitLayer::new(10))
2602            .build();
2603
2604        let result = tool.call(serde_json::json!({})).await;
2605        assert!(!result.is_error);
2606        assert_eq!(result.first_text().unwrap(), "status ok");
2607    }
2608
2609    // =========================================================================
2610    // Guard tests
2611    // =========================================================================
2612
2613    #[tokio::test]
2614    async fn test_guard_allows_request() {
2615        #[derive(Debug, Deserialize, JsonSchema)]
2616        #[allow(dead_code)]
2617        struct DeleteInput {
2618            id: String,
2619            confirm: bool,
2620        }
2621
2622        let tool = ToolBuilder::new("delete")
2623            .description("Delete a record")
2624            .handler(|input: DeleteInput| async move {
2625                Ok(CallToolResult::text(format!("deleted {}", input.id)))
2626            })
2627            .guard(|req: &ToolRequest| {
2628                let confirm = req
2629                    .args
2630                    .get("confirm")
2631                    .and_then(|v| v.as_bool())
2632                    .unwrap_or(false);
2633                if !confirm {
2634                    return Err("Must set confirm=true to delete".to_string());
2635                }
2636                Ok(())
2637            })
2638            .build();
2639
2640        let result = tool
2641            .call(serde_json::json!({"id": "abc", "confirm": true}))
2642            .await;
2643        assert!(!result.is_error);
2644        assert_eq!(result.first_text().unwrap(), "deleted abc");
2645    }
2646
2647    #[tokio::test]
2648    async fn test_guard_rejects_request() {
2649        #[derive(Debug, Deserialize, JsonSchema)]
2650        #[allow(dead_code)]
2651        struct DeleteInput2 {
2652            id: String,
2653            confirm: bool,
2654        }
2655
2656        let tool = ToolBuilder::new("delete2")
2657            .description("Delete a record")
2658            .handler(|input: DeleteInput2| async move {
2659                Ok(CallToolResult::text(format!("deleted {}", input.id)))
2660            })
2661            .guard(|req: &ToolRequest| {
2662                let confirm = req
2663                    .args
2664                    .get("confirm")
2665                    .and_then(|v| v.as_bool())
2666                    .unwrap_or(false);
2667                if !confirm {
2668                    return Err("Must set confirm=true to delete".to_string());
2669                }
2670                Ok(())
2671            })
2672            .build();
2673
2674        let result = tool
2675            .call(serde_json::json!({"id": "abc", "confirm": false}))
2676            .await;
2677        assert!(result.is_error);
2678        assert!(
2679            result
2680                .first_text()
2681                .unwrap()
2682                .contains("Must set confirm=true")
2683        );
2684    }
2685
2686    #[tokio::test]
2687    async fn test_guard_with_layer() {
2688        use std::time::Duration;
2689        use tower::timeout::TimeoutLayer;
2690
2691        let tool = ToolBuilder::new("guarded_timeout")
2692            .description("Guarded with timeout")
2693            .handler(|input: GreetInput| async move {
2694                Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
2695            })
2696            .layer(TimeoutLayer::new(Duration::from_secs(5)))
2697            .guard(|_req: &ToolRequest| Ok(()))
2698            .build();
2699
2700        let result = tool.call(serde_json::json!({"name": "World"})).await;
2701        assert!(!result.is_error);
2702        assert_eq!(result.first_text().unwrap(), "Hello, World!");
2703    }
2704
2705    #[tokio::test]
2706    async fn test_guard_on_no_params_handler() {
2707        let allowed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
2708        let allowed_clone = allowed.clone();
2709
2710        let tool = ToolBuilder::new("status")
2711            .description("Get status")
2712            .no_params_handler(|| async { Ok(CallToolResult::text("ok")) })
2713            .guard(move |_req: &ToolRequest| {
2714                if allowed_clone.load(std::sync::atomic::Ordering::Relaxed) {
2715                    Ok(())
2716                } else {
2717                    Err("Access denied".to_string())
2718                }
2719            })
2720            .build();
2721
2722        // Allowed
2723        let result = tool.call(serde_json::json!({})).await;
2724        assert!(!result.is_error);
2725        assert_eq!(result.first_text().unwrap(), "ok");
2726
2727        // Denied
2728        allowed.store(false, std::sync::atomic::Ordering::Relaxed);
2729        let result = tool.call(serde_json::json!({})).await;
2730        assert!(result.is_error);
2731        assert!(result.first_text().unwrap().contains("Access denied"));
2732    }
2733
2734    #[tokio::test]
2735    async fn test_guard_on_no_params_handler_with_layer() {
2736        use std::time::Duration;
2737        use tower::timeout::TimeoutLayer;
2738
2739        let tool = ToolBuilder::new("status_layered")
2740            .description("Get status with layers")
2741            .no_params_handler(|| async { Ok(CallToolResult::text("ok")) })
2742            .layer(TimeoutLayer::new(Duration::from_secs(5)))
2743            .guard(|_req: &ToolRequest| Ok(()))
2744            .build();
2745
2746        let result = tool.call(serde_json::json!({})).await;
2747        assert!(!result.is_error);
2748        assert_eq!(result.first_text().unwrap(), "ok");
2749    }
2750
2751    #[tokio::test]
2752    async fn test_guard_on_extractor_handler() {
2753        use std::sync::Arc;
2754
2755        #[derive(Clone)]
2756        struct AppState {
2757            prefix: String,
2758        }
2759
2760        #[derive(Debug, Deserialize, JsonSchema)]
2761        struct QueryInput {
2762            query: String,
2763        }
2764
2765        let state = Arc::new(AppState {
2766            prefix: "db".to_string(),
2767        });
2768
2769        let tool = ToolBuilder::new("search")
2770            .description("Search")
2771            .extractor_handler(
2772                state,
2773                |State(app): State<Arc<AppState>>, Json(input): Json<QueryInput>| async move {
2774                    Ok(CallToolResult::text(format!(
2775                        "{}: {}",
2776                        app.prefix, input.query
2777                    )))
2778                },
2779            )
2780            .guard(|req: &ToolRequest| {
2781                let query = req.args.get("query").and_then(|v| v.as_str()).unwrap_or("");
2782                if query.is_empty() {
2783                    return Err("Query cannot be empty".to_string());
2784                }
2785                Ok(())
2786            })
2787            .build();
2788
2789        // Valid query
2790        let result = tool.call(serde_json::json!({"query": "hello"})).await;
2791        assert!(!result.is_error);
2792        assert_eq!(result.first_text().unwrap(), "db: hello");
2793
2794        // Empty query rejected by guard
2795        let result = tool.call(serde_json::json!({"query": ""})).await;
2796        assert!(result.is_error);
2797        assert!(
2798            result
2799                .first_text()
2800                .unwrap()
2801                .contains("Query cannot be empty")
2802        );
2803    }
2804
2805    #[tokio::test]
2806    async fn test_guard_on_extractor_handler_with_layer() {
2807        use std::sync::Arc;
2808        use std::time::Duration;
2809        use tower::timeout::TimeoutLayer;
2810
2811        #[derive(Clone)]
2812        struct AppState2 {
2813            prefix: String,
2814        }
2815
2816        #[derive(Debug, Deserialize, JsonSchema)]
2817        struct QueryInput2 {
2818            query: String,
2819        }
2820
2821        let state = Arc::new(AppState2 {
2822            prefix: "db".to_string(),
2823        });
2824
2825        let tool = ToolBuilder::new("search2")
2826            .description("Search with layer and guard")
2827            .extractor_handler(
2828                state,
2829                |State(app): State<Arc<AppState2>>, Json(input): Json<QueryInput2>| async move {
2830                    Ok(CallToolResult::text(format!(
2831                        "{}: {}",
2832                        app.prefix, input.query
2833                    )))
2834                },
2835            )
2836            .layer(TimeoutLayer::new(Duration::from_secs(5)))
2837            .guard(|_req: &ToolRequest| Ok(()))
2838            .build();
2839
2840        let result = tool.call(serde_json::json!({"query": "hello"})).await;
2841        assert!(!result.is_error);
2842        assert_eq!(result.first_text().unwrap(), "db: hello");
2843    }
2844
2845    #[tokio::test]
2846    async fn test_tool_with_guard_post_build() {
2847        let tool = ToolBuilder::new("admin_action")
2848            .description("Admin action")
2849            .handler(|_input: GreetInput| async move { Ok(CallToolResult::text("done")) })
2850            .build();
2851
2852        // Apply guard after building
2853        let guarded = tool.with_guard(|req: &ToolRequest| {
2854            let name = req.args.get("name").and_then(|v| v.as_str()).unwrap_or("");
2855            if name == "admin" {
2856                Ok(())
2857            } else {
2858                Err("Only admin allowed".to_string())
2859            }
2860        });
2861
2862        // Admin passes
2863        let result = guarded.call(serde_json::json!({"name": "admin"})).await;
2864        assert!(!result.is_error);
2865
2866        // Non-admin blocked
2867        let result = guarded.call(serde_json::json!({"name": "user"})).await;
2868        assert!(result.is_error);
2869        assert!(result.first_text().unwrap().contains("Only admin allowed"));
2870    }
2871
2872    #[tokio::test]
2873    async fn test_with_guard_preserves_tool_metadata() {
2874        let tool = ToolBuilder::new("my_tool")
2875            .description("A tool")
2876            .title("My Tool")
2877            .read_only()
2878            .handler(|_input: GreetInput| async move { Ok(CallToolResult::text("done")) })
2879            .build();
2880
2881        let guarded = tool.with_guard(|_req: &ToolRequest| Ok(()));
2882
2883        assert_eq!(guarded.name, "my_tool");
2884        assert_eq!(guarded.description.as_deref(), Some("A tool"));
2885        assert_eq!(guarded.title.as_deref(), Some("My Tool"));
2886        assert!(guarded.annotations.is_some());
2887    }
2888
2889    #[tokio::test]
2890    async fn test_guard_group_pattern() {
2891        // Demonstrate applying the same guard to multiple tools (per-group pattern)
2892        let require_auth = |req: &ToolRequest| {
2893            let token = req
2894                .args
2895                .get("_token")
2896                .and_then(|v| v.as_str())
2897                .unwrap_or("");
2898            if token == "valid" {
2899                Ok(())
2900            } else {
2901                Err("Authentication required".to_string())
2902            }
2903        };
2904
2905        let tool1 = ToolBuilder::new("action1")
2906            .description("Action 1")
2907            .handler(|_input: GreetInput| async move { Ok(CallToolResult::text("action1")) })
2908            .build();
2909        let tool2 = ToolBuilder::new("action2")
2910            .description("Action 2")
2911            .handler(|_input: GreetInput| async move { Ok(CallToolResult::text("action2")) })
2912            .build();
2913
2914        // Apply same guard to both
2915        let guarded1 = tool1.with_guard(require_auth);
2916        let guarded2 = tool2.with_guard(require_auth);
2917
2918        // Without auth
2919        let r1 = guarded1
2920            .call(serde_json::json!({"name": "test", "_token": "invalid"}))
2921            .await;
2922        let r2 = guarded2
2923            .call(serde_json::json!({"name": "test", "_token": "invalid"}))
2924            .await;
2925        assert!(r1.is_error);
2926        assert!(r2.is_error);
2927
2928        // With auth
2929        let r1 = guarded1
2930            .call(serde_json::json!({"name": "test", "_token": "valid"}))
2931            .await;
2932        let r2 = guarded2
2933            .call(serde_json::json!({"name": "test", "_token": "valid"}))
2934            .await;
2935        assert!(!r1.is_error);
2936        assert!(!r2.is_error);
2937    }
2938
2939    #[tokio::test]
2940    async fn test_input_validation_returns_tool_error() {
2941        // Per SEP-1303: input validation errors should be returned as
2942        // CallToolResult with isError=true, not as protocol errors.
2943        #[derive(Debug, Deserialize, JsonSchema)]
2944        struct StrictInput {
2945            name: String,
2946            count: u32,
2947        }
2948
2949        let tool = ToolBuilder::new("strict_tool")
2950            .description("requires specific input")
2951            .handler(|input: StrictInput| async move {
2952                Ok(CallToolResult::text(format!(
2953                    "{}: {}",
2954                    input.name, input.count
2955                )))
2956            })
2957            .build();
2958
2959        // Valid input works
2960        let result = tool
2961            .call(serde_json::json!({"name": "test", "count": 5}))
2962            .await;
2963        assert!(!result.is_error);
2964
2965        // Missing required field returns isError, not protocol error
2966        let result = tool.call(serde_json::json!({"name": "test"})).await;
2967        assert!(result.is_error);
2968        let text = result.first_text().unwrap();
2969        assert!(text.contains("Invalid input"), "got: {text}");
2970
2971        // Wrong type returns isError, not protocol error
2972        let result = tool
2973            .call(serde_json::json!({"name": "test", "count": "not_a_number"}))
2974            .await;
2975        assert!(result.is_error);
2976        let text = result.first_text().unwrap();
2977        assert!(text.contains("Invalid input"), "got: {text}");
2978    }
2979
2980    #[tokio::test]
2981    async fn test_input_schema_override_with_raw_args() {
2982        // With a RawArgs handler there is no typed input struct, so the
2983        // builder normally falls back to `{ "type": "object" }`. The
2984        // `input_schema` setter must let users declare a richer schema.
2985        let custom = serde_json::json!({
2986            "type": "object",
2987            "properties": {
2988                "query": { "type": "string", "minLength": 1 }
2989            },
2990            "required": ["query"]
2991        });
2992
2993        let tool = ToolBuilder::new("query")
2994            .description("Query with a custom schema")
2995            .input_schema(custom.clone())
2996            .extractor_handler((), |RawArgs(args): RawArgs| async move {
2997                Ok(CallToolResult::json(args))
2998            })
2999            .build();
3000
3001        let schema = tool.definition().input_schema;
3002        assert_eq!(schema, custom);
3003
3004        // The handler still executes against the raw args.
3005        let result = tool.call(serde_json::json!({"query": "hello"})).await;
3006        assert!(!result.is_error);
3007    }
3008
3009    #[tokio::test]
3010    async fn test_input_schema_override_wins_over_typed_handler() {
3011        // When both `.input_schema(...)` and a typed `.handler(|x: Foo|)` are
3012        // provided, the explicit schema must win over the schemars-generated
3013        // one.
3014        let custom = serde_json::json!({
3015            "type": "object",
3016            "title": "GreetOverride",
3017            "properties": {
3018                "name": { "type": "string", "minLength": 1, "maxLength": 64 }
3019            },
3020            "required": ["name"],
3021            "additionalProperties": false
3022        });
3023
3024        let tool = ToolBuilder::new("greet")
3025            .description("Greet someone with a hand-tuned schema")
3026            .input_schema(custom.clone())
3027            .handler(|input: GreetInput| async move {
3028                Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
3029            })
3030            .build();
3031
3032        let schema = tool.definition().input_schema;
3033        assert_eq!(schema, custom);
3034        // Confirm the schemars-generated `GreetInput` schema did not leak in.
3035        assert_eq!(schema["title"], "GreetOverride");
3036
3037        // Handler still dispatches via the typed deserialization.
3038        let result = tool.call(serde_json::json!({"name": "World"})).await;
3039        assert!(!result.is_error);
3040    }
3041
3042    #[tokio::test]
3043    async fn test_input_schema_override_preserves_2020_12_constructs() {
3044        // Schemars cannot express `oneOf` in property positions directly;
3045        // overriding the schema must keep those advanced constructs intact.
3046        let custom = serde_json::json!({
3047            "type": "object",
3048            "properties": {
3049                "filter": {
3050                    "oneOf": [
3051                        { "type": "string" },
3052                        {
3053                            "type": "object",
3054                            "properties": { "field": { "type": "string" } },
3055                            "required": ["field"]
3056                        }
3057                    ]
3058                }
3059            },
3060            "required": ["filter"]
3061        });
3062
3063        let tool = ToolBuilder::new("filter_tool")
3064            .description("Demonstrates oneOf preservation")
3065            .input_schema(custom.clone())
3066            .extractor_handler((), |RawArgs(args): RawArgs| async move {
3067                Ok(CallToolResult::json(args))
3068            })
3069            .build();
3070
3071        let schema = tool.definition().input_schema;
3072        assert_eq!(schema, custom);
3073        let one_of = schema["properties"]["filter"]["oneOf"]
3074            .as_array()
3075            .expect("oneOf must survive as an array");
3076        assert_eq!(one_of.len(), 2);
3077        assert_eq!(one_of[0]["type"], "string");
3078        assert_eq!(one_of[1]["type"], "object");
3079    }
3080
3081    #[tokio::test]
3082    async fn test_input_schema_override_adds_type_object_if_missing() {
3083        // `ensure_object_schema` must still run against the user-supplied
3084        // schema, so MCP-spec `type: "object"` is added when omitted.
3085        let custom_no_type = serde_json::json!({
3086            "properties": {
3087                "x": { "type": "number" }
3088            }
3089        });
3090
3091        let tool = ToolBuilder::new("typeless")
3092            .description("Schema missing top-level type")
3093            .input_schema(custom_no_type)
3094            .extractor_handler((), |RawArgs(args): RawArgs| async move {
3095                Ok(CallToolResult::json(args))
3096            })
3097            .build();
3098
3099        let schema = tool.definition().input_schema;
3100        assert_eq!(schema["type"], "object");
3101        assert!(schema["properties"]["x"].is_object());
3102    }
3103}