1use 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#[derive(Debug, Clone)]
65pub struct ToolRequest {
66 pub ctx: RequestContext,
68 pub args: Value,
70}
71
72impl ToolRequest {
73 pub fn new(ctx: RequestContext, args: Value) -> Self {
75 Self { ctx, args }
76 }
77}
78
79pub type BoxToolService = BoxCloneService<ToolRequest, CallToolResult, Infallible>;
85
86#[doc(hidden)]
92pub struct ToolCatchError<S> {
93 inner: S,
94}
95
96impl<S> ToolCatchError<S> {
97 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 #[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 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#[derive(Clone)]
201pub struct GuardLayer<G> {
202 guard: G,
203}
204
205impl<G> GuardLayer<G> {
206 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#[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#[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 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 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
354pub(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
385pub(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
399pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
401
402pub trait ToolHandler: Send + Sync {
404 fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>>;
406
407 fn call_with_context(
412 &self,
413 _ctx: RequestContext,
414 args: Value,
415 ) -> BoxFuture<'_, Result<CallToolResult>> {
416 self.call(args)
417 }
418
419 fn uses_context(&self) -> bool {
421 false
422 }
423
424 fn input_schema(&self) -> Value;
426}
427
428pub(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
470pub struct Tool {
477 pub name: String,
479 pub title: Option<String>,
481 pub description: Option<String>,
483 pub output_schema: Option<Value>,
485 pub icons: Option<Vec<ToolIcon>>,
487 pub annotations: Option<ToolAnnotations>,
489 pub task_support: TaskSupportMode,
491 pub(crate) service: BoxToolService,
493 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
511unsafe 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 pub fn builder(name: impl Into<String>) -> ToolBuilder {
535 ToolBuilder::new(name)
536 }
537
538 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 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 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 service.oneshot(ToolRequest::new(ctx, args)).await.unwrap()
589 })
590 }
591
592 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 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 #[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
708pub 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 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 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 pub fn title(mut self, title: impl Into<String>) -> Self {
812 self.title = Some(title.into());
813 self
814 }
815
816 pub fn output_schema(mut self, schema: Value) -> Self {
818 self.output_schema = Some(schema);
819 self
820 }
821
822 pub fn input_schema(mut self, schema: Value) -> Self {
875 self.input_schema_override = Some(schema);
876 self
877 }
878
879 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 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 pub fn description(mut self, description: impl Into<String>) -> Self {
908 self.description = Some(description.into());
909 self
910 }
911
912 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 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 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 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 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 pub fn annotations(mut self, annotations: ToolAnnotations) -> Self {
961 self.annotations = Some(annotations);
962 self
963 }
964
965 pub fn task_support(mut self, mode: TaskSupportMode) -> Self {
967 self.task_support = mode;
968 self
969 }
970
971 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 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 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 #[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
1255struct 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#[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#[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 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 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 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#[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 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 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 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 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 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 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#[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)]
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 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 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 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
1620struct 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
1657pub trait McpTool: Send + Sync + 'static {
1698 const NAME: &'static str;
1700 const DESCRIPTION: &'static str;
1702
1703 type Input: JsonSchema + DeserializeOwned + Send;
1705 type Output: Serialize + Send;
1707
1708 fn call(&self, input: Self::Input) -> impl Future<Output = Result<Self::Output>> + Send;
1710
1711 fn annotations(&self) -> Option<ToolAnnotations> {
1713 None
1714 }
1715
1716 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
1746struct 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 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 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 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 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 tokio::task::yield_now().await;
1959 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 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 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 let result = tool.call(serde_json::json!({})).await;
2083 assert!(!result.is_error);
2084
2085 let result = tool.call(serde_json::json!({"unexpected": "value"})).await;
2087 assert!(!result.is_error);
2088
2089 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 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 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 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 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 let result = tool.call(serde_json::json!({"delay_ms": 200})).await;
2230 assert!(result.is_error);
2231 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 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 let prev = current.fetch_add(1, Ordering::SeqCst);
2265 max.fetch_max(prev + 1, Ordering::SeqCst);
2266
2267 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 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 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 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 let tool = ToolBuilder::new("test")
2325 .description("test")
2326 .extractor_handler((), |RawArgs(_args): RawArgs| async {
2327 Ok(CallToolResult::text("ok"))
2328 })
2329 .build();
2330 let _clone = tool.call(serde_json::json!({}));
2332 }
2333
2334 #[test]
2335 fn test_tool_catch_error_debug() {
2336 #[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 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 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 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 let tool = ToolBuilder::new("status")
2411 .description("Get status")
2412 .handler(|_input: NoParams| async move { Ok(CallToolResult::text("OK")) })
2413 .build();
2414
2415 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 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 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 let prefixed = tool.with_name_prefix("db");
2460
2461 assert_eq!(prefixed.name, "db.query");
2463
2464 assert_eq!(prefixed.description.as_deref(), Some("Query something"));
2466 assert_eq!(prefixed.title.as_deref(), Some("Query Tool"));
2467
2468 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 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 #[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 let result = tool.call(serde_json::json!({})).await;
2510 assert!(!result.is_error);
2511 assert_eq!(result.first_text().unwrap(), "OK");
2512
2513 let result = tool.call(serde_json::json!(null)).await;
2515 assert!(!result.is_error);
2516
2517 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 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 #[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 let result = tool.call(serde_json::json!({})).await;
2724 assert!(!result.is_error);
2725 assert_eq!(result.first_text().unwrap(), "ok");
2726
2727 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 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 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 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 let result = guarded.call(serde_json::json!({"name": "admin"})).await;
2864 assert!(!result.is_error);
2865
2866 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 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 let guarded1 = tool1.with_guard(require_auth);
2916 let guarded2 = tool2.with_guard(require_auth);
2917
2918 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 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 #[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 let result = tool
2961 .call(serde_json::json!({"name": "test", "count": 5}))
2962 .await;
2963 assert!(!result.is_error);
2964
2965 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 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 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 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 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 assert_eq!(schema["title"], "GreetOverride");
3036
3037 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 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 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}