1use std::future::Future;
84use std::marker::PhantomData;
85use std::ops::Deref;
86use std::pin::Pin;
87
88use schemars::JsonSchema;
89use serde::de::DeserializeOwned;
90use serde_json::Value;
91
92use crate::context::RequestContext;
93use crate::error::{Error, Result};
94use crate::protocol::CallToolResult;
95
96#[derive(Debug, Clone)]
106pub struct Rejection {
107 message: String,
108}
109
110impl Rejection {
111 pub fn new(message: impl Into<String>) -> Self {
113 Self {
114 message: message.into(),
115 }
116 }
117
118 pub fn message(&self) -> &str {
120 &self.message
121 }
122}
123
124impl std::fmt::Display for Rejection {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "{}", self.message)
127 }
128}
129
130impl std::error::Error for Rejection {}
131
132impl From<Rejection> for Error {
133 fn from(rejection: Rejection) -> Self {
134 Error::tool(rejection.message)
135 }
136}
137
138#[derive(Debug, Clone)]
152pub struct JsonRejection {
153 message: String,
154 path: Option<String>,
156}
157
158impl JsonRejection {
159 pub fn new(message: impl Into<String>) -> Self {
161 Self {
162 message: message.into(),
163 path: None,
164 }
165 }
166
167 pub fn with_path(message: impl Into<String>, path: impl Into<String>) -> Self {
169 Self {
170 message: message.into(),
171 path: Some(path.into()),
172 }
173 }
174
175 pub fn message(&self) -> &str {
177 &self.message
178 }
179
180 pub fn path(&self) -> Option<&str> {
182 self.path.as_deref()
183 }
184}
185
186impl std::fmt::Display for JsonRejection {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 if let Some(path) = &self.path {
189 write!(f, "Invalid input at `{}`: {}", path, self.message)
190 } else {
191 write!(f, "Invalid input: {}", self.message)
192 }
193 }
194}
195
196impl std::error::Error for JsonRejection {}
197
198impl From<JsonRejection> for Error {
199 fn from(rejection: JsonRejection) -> Self {
200 Error::tool(rejection.to_string())
201 }
202}
203
204impl From<serde_json::Error> for JsonRejection {
205 fn from(err: serde_json::Error) -> Self {
206 let path = if err.is_data() {
208 None
211 } else {
212 None
213 };
214
215 Self {
216 message: err.to_string(),
217 path,
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
236pub struct ExtensionRejection {
237 type_name: &'static str,
238}
239
240impl ExtensionRejection {
241 pub fn not_found<T>() -> Self {
243 Self {
244 type_name: std::any::type_name::<T>(),
245 }
246 }
247
248 pub fn type_name(&self) -> &'static str {
250 self.type_name
251 }
252}
253
254impl std::fmt::Display for ExtensionRejection {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 write!(
257 f,
258 "Extension of type `{name}` not found. Did you call `router.with_state()`, \
259 `router.with_extension()`, or `transport.bridge_extension::<{name}>()` for a \
260 type a tower layer inserts into the request?",
261 name = self.type_name
262 )
263 }
264}
265
266impl std::error::Error for ExtensionRejection {}
267
268impl From<ExtensionRejection> for Error {
269 fn from(rejection: ExtensionRejection) -> Self {
270 Error::tool(rejection.to_string())
271 }
272}
273
274pub trait FromToolRequest<S = ()>: Sized {
305 type Rejection: Into<Error>;
307
308 fn from_tool_request(
316 ctx: &RequestContext,
317 state: &S,
318 args: &Value,
319 ) -> std::result::Result<Self, Self::Rejection>;
320}
321
322#[derive(Debug, Clone, Copy)]
353pub struct Json<T>(pub T);
354
355impl<T> Deref for Json<T> {
356 type Target = T;
357
358 fn deref(&self) -> &Self::Target {
359 &self.0
360 }
361}
362
363impl<S, T> FromToolRequest<S> for Json<T>
364where
365 T: DeserializeOwned,
366{
367 type Rejection = JsonRejection;
368
369 fn from_tool_request(
370 _ctx: &RequestContext,
371 _state: &S,
372 args: &Value,
373 ) -> std::result::Result<Self, Self::Rejection> {
374 serde_json::from_value(args.clone())
375 .map(Json)
376 .map_err(JsonRejection::from)
377 }
378}
379
380#[derive(Debug, Clone, Copy)]
405pub struct State<T>(pub T);
406
407impl<T> Deref for State<T> {
408 type Target = T;
409
410 fn deref(&self) -> &Self::Target {
411 &self.0
412 }
413}
414
415impl<S: Clone> FromToolRequest<S> for State<S> {
416 type Rejection = Rejection;
417
418 fn from_tool_request(
419 _ctx: &RequestContext,
420 state: &S,
421 _args: &Value,
422 ) -> std::result::Result<Self, Self::Rejection> {
423 Ok(State(state.clone()))
424 }
425}
426
427#[derive(Debug, Clone)]
448pub struct Context(RequestContext);
449
450impl Context {
451 pub fn into_inner(self) -> RequestContext {
453 self.0
454 }
455}
456
457impl Deref for Context {
458 type Target = RequestContext;
459
460 fn deref(&self) -> &Self::Target {
461 &self.0
462 }
463}
464
465impl<S> FromToolRequest<S> for Context {
466 type Rejection = Rejection;
467
468 fn from_tool_request(
469 ctx: &RequestContext,
470 _state: &S,
471 _args: &Value,
472 ) -> std::result::Result<Self, Self::Rejection> {
473 Ok(Context(ctx.clone()))
474 }
475}
476
477#[derive(Debug, Clone)]
495pub struct RawArgs(pub Value);
496
497impl Deref for RawArgs {
498 type Target = Value;
499
500 fn deref(&self) -> &Self::Target {
501 &self.0
502 }
503}
504
505impl<S> FromToolRequest<S> for RawArgs {
506 type Rejection = Rejection;
507
508 fn from_tool_request(
509 _ctx: &RequestContext,
510 _state: &S,
511 args: &Value,
512 ) -> std::result::Result<Self, Self::Rejection> {
513 Ok(RawArgs(args.clone()))
514 }
515}
516
517#[derive(Debug, Clone)]
570pub struct Extension<T>(pub T);
571
572impl<T> Deref for Extension<T> {
573 type Target = T;
574
575 fn deref(&self) -> &Self::Target {
576 &self.0
577 }
578}
579
580impl<S, T> FromToolRequest<S> for Extension<T>
581where
582 T: Clone + Send + Sync + 'static,
583{
584 type Rejection = ExtensionRejection;
585
586 fn from_tool_request(
587 ctx: &RequestContext,
588 _state: &S,
589 _args: &Value,
590 ) -> std::result::Result<Self, Self::Rejection> {
591 ctx.extension::<T>()
592 .cloned()
593 .map(Extension)
594 .ok_or_else(ExtensionRejection::not_found::<T>)
595 }
596}
597
598#[diagnostic::on_unimplemented(
608 message = "`{Self}` is not a valid extractor handler",
609 note = "each closure argument must be an extractor (`Json<T>`, `State<S>`, `Context`, `Extension<T>`, `RawArgs`) and the return type must be `Result<impl Into<CallToolResult>, ToolError>`",
610 note = "for a `Json<T>` argument, `T` must implement `serde::Deserialize` and `schemars::JsonSchema`",
611 note = "if `T` derives `JsonSchema` but this still fails, check for a `schemars` major-version mismatch: the derive must come from the same `schemars` version tower-mcp uses (>=1). Depend on it via the `tower_mcp::schemars` re-export to stay aligned"
612)]
613pub trait ExtractorHandler<S, T>: Clone + Send + Sync + 'static {
614 type Future: Future<Output = Result<CallToolResult>> + Send;
616
617 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future;
619
620 fn input_schema() -> Value;
624}
625
626impl<S, F, Fut, T1> ExtractorHandler<S, (T1,)> for F
628where
629 S: Clone + Send + Sync + 'static,
630 F: Fn(T1) -> Fut + Clone + Send + Sync + 'static,
631 Fut: Future<Output = Result<CallToolResult>> + Send,
632 T1: FromToolRequest<S> + HasSchema + Send,
633{
634 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
635
636 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
637 Box::pin(async move {
638 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
639 self(t1).await
640 })
641 }
642
643 fn input_schema() -> Value {
644 if let Some(schema) = T1::schema() {
645 return schema;
646 }
647 serde_json::json!({
648 "type": "object",
649 "additionalProperties": true
650 })
651 }
652}
653
654impl<S, F, Fut, T1, T2> ExtractorHandler<S, (T1, T2)> for F
656where
657 S: Clone + Send + Sync + 'static,
658 F: Fn(T1, T2) -> Fut + Clone + Send + Sync + 'static,
659 Fut: Future<Output = Result<CallToolResult>> + Send,
660 T1: FromToolRequest<S> + HasSchema + Send,
661 T2: FromToolRequest<S> + HasSchema + Send,
662{
663 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
664
665 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
666 Box::pin(async move {
667 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
668 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
669 self(t1, t2).await
670 })
671 }
672
673 fn input_schema() -> Value {
674 if let Some(schema) = T2::schema() {
675 return schema;
676 }
677 if let Some(schema) = T1::schema() {
678 return schema;
679 }
680 serde_json::json!({
681 "type": "object",
682 "additionalProperties": true
683 })
684 }
685}
686
687impl<S, F, Fut, T1, T2, T3> ExtractorHandler<S, (T1, T2, T3)> for F
689where
690 S: Clone + Send + Sync + 'static,
691 F: Fn(T1, T2, T3) -> Fut + Clone + Send + Sync + 'static,
692 Fut: Future<Output = Result<CallToolResult>> + Send,
693 T1: FromToolRequest<S> + HasSchema + Send,
694 T2: FromToolRequest<S> + HasSchema + Send,
695 T3: FromToolRequest<S> + HasSchema + Send,
696{
697 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
698
699 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
700 Box::pin(async move {
701 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
702 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
703 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
704 self(t1, t2, t3).await
705 })
706 }
707
708 fn input_schema() -> Value {
709 if let Some(schema) = T3::schema() {
710 return schema;
711 }
712 if let Some(schema) = T2::schema() {
713 return schema;
714 }
715 if let Some(schema) = T1::schema() {
716 return schema;
717 }
718 serde_json::json!({
719 "type": "object",
720 "additionalProperties": true
721 })
722 }
723}
724
725impl<S, F, Fut, T1, T2, T3, T4> ExtractorHandler<S, (T1, T2, T3, T4)> for F
727where
728 S: Clone + Send + Sync + 'static,
729 F: Fn(T1, T2, T3, T4) -> Fut + Clone + Send + Sync + 'static,
730 Fut: Future<Output = Result<CallToolResult>> + Send,
731 T1: FromToolRequest<S> + HasSchema + Send,
732 T2: FromToolRequest<S> + HasSchema + Send,
733 T3: FromToolRequest<S> + HasSchema + Send,
734 T4: FromToolRequest<S> + HasSchema + Send,
735{
736 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
737
738 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
739 Box::pin(async move {
740 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
741 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
742 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
743 let t4 = T4::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
744 self(t1, t2, t3, t4).await
745 })
746 }
747
748 fn input_schema() -> Value {
749 if let Some(schema) = T4::schema() {
750 return schema;
751 }
752 if let Some(schema) = T3::schema() {
753 return schema;
754 }
755 if let Some(schema) = T2::schema() {
756 return schema;
757 }
758 if let Some(schema) = T1::schema() {
759 return schema;
760 }
761 serde_json::json!({
762 "type": "object",
763 "additionalProperties": true
764 })
765 }
766}
767
768impl<S, F, Fut, T1, T2, T3, T4, T5> ExtractorHandler<S, (T1, T2, T3, T4, T5)> for F
770where
771 S: Clone + Send + Sync + 'static,
772 F: Fn(T1, T2, T3, T4, T5) -> Fut + Clone + Send + Sync + 'static,
773 Fut: Future<Output = Result<CallToolResult>> + Send,
774 T1: FromToolRequest<S> + HasSchema + Send,
775 T2: FromToolRequest<S> + HasSchema + Send,
776 T3: FromToolRequest<S> + HasSchema + Send,
777 T4: FromToolRequest<S> + HasSchema + Send,
778 T5: FromToolRequest<S> + HasSchema + Send,
779{
780 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
781
782 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
783 Box::pin(async move {
784 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
785 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
786 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
787 let t4 = T4::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
788 let t5 = T5::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
789 self(t1, t2, t3, t4, t5).await
790 })
791 }
792
793 fn input_schema() -> Value {
794 if let Some(schema) = T5::schema() {
795 return schema;
796 }
797 if let Some(schema) = T4::schema() {
798 return schema;
799 }
800 if let Some(schema) = T3::schema() {
801 return schema;
802 }
803 if let Some(schema) = T2::schema() {
804 return schema;
805 }
806 if let Some(schema) = T1::schema() {
807 return schema;
808 }
809 serde_json::json!({
810 "type": "object",
811 "additionalProperties": true
812 })
813 }
814}
815
816#[diagnostic::on_unimplemented(
822 message = "`{Self}` does not implement `HasSchema`",
823 note = "for `Json<T>` this means `T: schemars::JsonSchema` is not satisfied",
824 note = "a common cause is a `schemars` major-version mismatch: the derive on `T` must come from the same `schemars` version tower-mcp uses (>=1)",
825 note = "depend on `schemars` via the `tower_mcp::schemars` re-export to keep the versions aligned"
826)]
827pub trait HasSchema {
828 fn schema() -> Option<Value>;
830}
831
832impl<T: JsonSchema> HasSchema for Json<T> {
833 fn schema() -> Option<Value> {
834 let schema = schemars::schema_for!(T);
835 serde_json::to_value(schema)
836 .ok()
837 .map(crate::tool::ensure_object_schema)
838 }
839}
840
841impl HasSchema for Context {
843 fn schema() -> Option<Value> {
844 None
845 }
846}
847
848impl HasSchema for RawArgs {
849 fn schema() -> Option<Value> {
850 None
851 }
852}
853
854impl<T> HasSchema for State<T> {
855 fn schema() -> Option<Value> {
856 None
857 }
858}
859
860impl<T> HasSchema for Extension<T> {
861 fn schema() -> Option<Value> {
862 None
863 }
864}
865
866#[deprecated(
875 since = "0.8.0",
876 note = "Use `ExtractorHandler` instead -- `extractor_handler` auto-detects JSON schema from `Json<T>` extractors"
877)]
878pub trait TypedExtractorHandler<S, T, I>: Clone + Send + Sync + 'static
879where
880 I: JsonSchema,
881{
882 type Future: Future<Output = Result<CallToolResult>> + Send;
884
885 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future;
887}
888
889#[allow(deprecated)]
891impl<S, F, Fut, T> TypedExtractorHandler<S, (Json<T>,), T> for F
892where
893 S: Clone + Send + Sync + 'static,
894 F: Fn(Json<T>) -> Fut + Clone + Send + Sync + 'static,
895 Fut: Future<Output = Result<CallToolResult>> + Send,
896 T: DeserializeOwned + JsonSchema + Send,
897{
898 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
899
900 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
901 Box::pin(async move {
902 let t1 =
903 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
904 self(t1).await
905 })
906 }
907}
908
909#[allow(deprecated)]
911impl<S, F, Fut, T1, T> TypedExtractorHandler<S, (T1, Json<T>), T> for F
912where
913 S: Clone + Send + Sync + 'static,
914 F: Fn(T1, Json<T>) -> Fut + Clone + Send + Sync + 'static,
915 Fut: Future<Output = Result<CallToolResult>> + Send,
916 T1: FromToolRequest<S> + Send,
917 T: DeserializeOwned + JsonSchema + Send,
918{
919 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
920
921 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
922 Box::pin(async move {
923 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
924 let t2 =
925 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
926 self(t1, t2).await
927 })
928 }
929}
930
931#[allow(deprecated)]
933impl<S, F, Fut, T1, T2, T> TypedExtractorHandler<S, (T1, T2, Json<T>), T> for F
934where
935 S: Clone + Send + Sync + 'static,
936 F: Fn(T1, T2, Json<T>) -> Fut + Clone + Send + Sync + 'static,
937 Fut: Future<Output = Result<CallToolResult>> + Send,
938 T1: FromToolRequest<S> + Send,
939 T2: FromToolRequest<S> + Send,
940 T: DeserializeOwned + JsonSchema + Send,
941{
942 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
943
944 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
945 Box::pin(async move {
946 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
947 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
948 let t3 =
949 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
950 self(t1, t2, t3).await
951 })
952 }
953}
954
955#[allow(deprecated)]
957impl<S, F, Fut, T1, T2, T3, T> TypedExtractorHandler<S, (T1, T2, T3, Json<T>), T> for F
958where
959 S: Clone + Send + Sync + 'static,
960 F: Fn(T1, T2, T3, Json<T>) -> Fut + Clone + Send + Sync + 'static,
961 Fut: Future<Output = Result<CallToolResult>> + Send,
962 T1: FromToolRequest<S> + Send,
963 T2: FromToolRequest<S> + Send,
964 T3: FromToolRequest<S> + Send,
965 T: DeserializeOwned + JsonSchema + Send,
966{
967 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
968
969 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
970 Box::pin(async move {
971 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
972 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
973 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
974 let t4 =
975 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
976 self(t1, t2, t3, t4).await
977 })
978 }
979}
980
981use crate::tool::{
986 BoxFuture, GuardLayer, Tool, ToolCatchError, ToolHandler, ToolHandlerService, ToolRequest,
987};
988use tower::util::BoxCloneService;
989use tower_service::Service;
990
991pub(crate) struct ExtractorToolHandler<S, F, T> {
993 state: S,
994 handler: F,
995 input_schema: Value,
996 _phantom: PhantomData<T>,
997}
998
999impl<S, F, T> ToolHandler for ExtractorToolHandler<S, F, T>
1000where
1001 S: Clone + Send + Sync + 'static,
1002 F: ExtractorHandler<S, T> + Clone,
1003 T: Send + Sync + 'static,
1004{
1005 fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
1006 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
1007 self.call_with_context(ctx, args)
1008 }
1009
1010 fn call_with_context(
1011 &self,
1012 ctx: RequestContext,
1013 args: Value,
1014 ) -> BoxFuture<'_, Result<CallToolResult>> {
1015 let state = self.state.clone();
1016 let handler = self.handler.clone();
1017 Box::pin(async move { handler.call(ctx, state, args).await })
1018 }
1019
1020 fn uses_context(&self) -> bool {
1021 true
1022 }
1023
1024 fn input_schema(&self) -> Value {
1025 self.input_schema.clone()
1026 }
1027}
1028
1029#[doc(hidden)]
1031pub struct ToolBuilderWithExtractor<S, F, T> {
1032 pub(crate) name: String,
1033 pub(crate) title: Option<String>,
1034 pub(crate) description: Option<String>,
1035 pub(crate) output_schema: Option<Value>,
1036 pub(crate) icons: Option<Vec<crate::protocol::ToolIcon>>,
1037 pub(crate) annotations: Option<crate::protocol::ToolAnnotations>,
1038 pub(crate) task_support: crate::protocol::TaskSupportMode,
1039 pub(crate) state: S,
1040 pub(crate) handler: F,
1041 pub(crate) input_schema: Value,
1042 pub(crate) _phantom: PhantomData<T>,
1043}
1044
1045impl<S, F, T> ToolBuilderWithExtractor<S, F, T>
1046where
1047 S: Clone + Send + Sync + 'static,
1048 F: ExtractorHandler<S, T> + Clone,
1049 T: Send + Sync + 'static,
1050{
1051 pub fn build(self) -> Tool {
1053 let handler = ExtractorToolHandler {
1054 state: self.state,
1055 handler: self.handler,
1056 input_schema: self.input_schema.clone(),
1057 _phantom: PhantomData,
1058 };
1059
1060 let handler_service = ToolHandlerService::new(handler);
1061 let catch_error = ToolCatchError::new(handler_service);
1062 let service = BoxCloneService::new(catch_error);
1063
1064 Tool {
1065 live_handler: None,
1066 name: self.name,
1067 title: self.title,
1068 description: self.description,
1069 output_schema: self.output_schema,
1070 icons: self.icons,
1071 annotations: self.annotations,
1072 meta: None,
1073 task_support: self.task_support,
1074 required_client_capabilities: None,
1075 task_preparer: None,
1076 service: Some(service),
1077 #[cfg(feature = "stateless")]
1078 mrtr_handler: None,
1079 input_schema: self.input_schema,
1080 }
1081 }
1082
1083 pub fn layer<L>(self, layer: L) -> ToolBuilderWithExtractorLayer<S, F, T, L> {
1119 ToolBuilderWithExtractorLayer {
1120 name: self.name,
1121 title: self.title,
1122 description: self.description,
1123 output_schema: self.output_schema,
1124 icons: self.icons,
1125 annotations: self.annotations,
1126 task_support: self.task_support,
1127 state: self.state,
1128 handler: self.handler,
1129 input_schema: self.input_schema,
1130 layer,
1131 _phantom: PhantomData,
1132 }
1133 }
1134
1135 pub fn guard<G>(self, guard: G) -> ToolBuilderWithExtractorLayer<S, F, T, GuardLayer<G>>
1139 where
1140 G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1141 {
1142 self.layer(GuardLayer::new(guard))
1143 }
1144}
1145
1146#[doc(hidden)]
1150pub struct ToolBuilderWithExtractorLayer<S, F, T, L> {
1151 name: String,
1152 title: Option<String>,
1153 description: Option<String>,
1154 output_schema: Option<Value>,
1155 icons: Option<Vec<crate::protocol::ToolIcon>>,
1156 annotations: Option<crate::protocol::ToolAnnotations>,
1157 task_support: crate::protocol::TaskSupportMode,
1158 state: S,
1159 handler: F,
1160 input_schema: Value,
1161 layer: L,
1162 _phantom: PhantomData<T>,
1163}
1164
1165#[allow(private_bounds)]
1166impl<S, F, T, L> ToolBuilderWithExtractorLayer<S, F, T, L>
1167where
1168 S: Clone + Send + Sync + 'static,
1169 F: ExtractorHandler<S, T> + Clone,
1170 T: Send + Sync + 'static,
1171 L: tower::Layer<ToolHandlerService<ExtractorToolHandler<S, F, T>>>
1172 + Clone
1173 + Send
1174 + Sync
1175 + 'static,
1176 L::Service: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
1177 <L::Service as Service<ToolRequest>>::Error: std::fmt::Display + Send,
1178 <L::Service as Service<ToolRequest>>::Future: Send,
1179{
1180 pub fn build(self) -> Tool {
1182 let handler = ExtractorToolHandler {
1183 state: self.state,
1184 handler: self.handler,
1185 input_schema: self.input_schema.clone(),
1186 _phantom: PhantomData,
1187 };
1188
1189 let handler_service = ToolHandlerService::new(handler);
1190 let layered = self.layer.layer(handler_service);
1191 let catch_error = ToolCatchError::new(layered);
1192 let service = BoxCloneService::new(catch_error);
1193
1194 Tool {
1195 live_handler: None,
1196 name: self.name,
1197 title: self.title,
1198 description: self.description,
1199 output_schema: self.output_schema,
1200 icons: self.icons,
1201 annotations: self.annotations,
1202 meta: None,
1203 task_support: self.task_support,
1204 required_client_capabilities: None,
1205 task_preparer: None,
1206 service: Some(service),
1207 #[cfg(feature = "stateless")]
1208 mrtr_handler: None,
1209 input_schema: self.input_schema,
1210 }
1211 }
1212
1213 pub fn layer<L2>(
1218 self,
1219 layer: L2,
1220 ) -> ToolBuilderWithExtractorLayer<S, F, T, tower::layer::util::Stack<L2, L>> {
1221 ToolBuilderWithExtractorLayer {
1222 name: self.name,
1223 title: self.title,
1224 description: self.description,
1225 output_schema: self.output_schema,
1226 icons: self.icons,
1227 annotations: self.annotations,
1228 task_support: self.task_support,
1229 state: self.state,
1230 handler: self.handler,
1231 input_schema: self.input_schema,
1232 layer: tower::layer::util::Stack::new(layer, self.layer),
1233 _phantom: PhantomData,
1234 }
1235 }
1236
1237 pub fn guard<G>(
1241 self,
1242 guard: G,
1243 ) -> ToolBuilderWithExtractorLayer<S, F, T, tower::layer::util::Stack<GuardLayer<G>, L>>
1244 where
1245 G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1246 {
1247 self.layer(GuardLayer::new(guard))
1248 }
1249}
1250
1251#[doc(hidden)]
1253#[deprecated(
1254 since = "0.8.0",
1255 note = "Use `ToolBuilderWithExtractor` via `extractor_handler` instead"
1256)]
1257pub struct ToolBuilderWithTypedExtractor<S, F, T, I> {
1258 pub(crate) name: String,
1259 pub(crate) title: Option<String>,
1260 pub(crate) description: Option<String>,
1261 pub(crate) output_schema: Option<Value>,
1262 pub(crate) input_schema_override: Option<Value>,
1263 pub(crate) icons: Option<Vec<crate::protocol::ToolIcon>>,
1264 pub(crate) annotations: Option<crate::protocol::ToolAnnotations>,
1265 pub(crate) task_support: crate::protocol::TaskSupportMode,
1266 pub(crate) state: S,
1267 pub(crate) handler: F,
1268 pub(crate) _phantom: PhantomData<(T, I)>,
1269}
1270
1271#[allow(deprecated)]
1272impl<S, F, T, I> ToolBuilderWithTypedExtractor<S, F, T, I>
1273where
1274 S: Clone + Send + Sync + 'static,
1275 F: TypedExtractorHandler<S, T, I> + Clone,
1276 T: Send + Sync + 'static,
1277 I: JsonSchema + Send + Sync + 'static,
1278{
1279 pub fn build(self) -> Tool {
1281 let input_schema = {
1282 let schema = self.input_schema_override.unwrap_or_else(|| {
1283 let schema = schemars::schema_for!(I);
1284 serde_json::to_value(schema).unwrap_or_else(|_| {
1285 serde_json::json!({
1286 "type": "object"
1287 })
1288 })
1289 });
1290 crate::tool::ensure_object_schema(schema)
1291 };
1292
1293 let handler = TypedExtractorToolHandler {
1294 state: self.state,
1295 handler: self.handler,
1296 input_schema: input_schema.clone(),
1297 _phantom: PhantomData,
1298 };
1299
1300 let handler_service = crate::tool::ToolHandlerService::new(handler);
1301 let catch_error = ToolCatchError::new(handler_service);
1302 let service = BoxCloneService::new(catch_error);
1303
1304 Tool {
1305 live_handler: None,
1306 name: self.name,
1307 title: self.title,
1308 description: self.description,
1309 output_schema: self.output_schema,
1310 icons: self.icons,
1311 annotations: self.annotations,
1312 meta: None,
1313 task_support: self.task_support,
1314 required_client_capabilities: None,
1315 task_preparer: None,
1316 service: Some(service),
1317 #[cfg(feature = "stateless")]
1318 mrtr_handler: None,
1319 input_schema,
1320 }
1321 }
1322}
1323
1324struct TypedExtractorToolHandler<S, F, T, I> {
1326 state: S,
1327 handler: F,
1328 input_schema: Value,
1329 _phantom: PhantomData<(T, I)>,
1330}
1331
1332#[allow(deprecated)]
1333impl<S, F, T, I> ToolHandler for TypedExtractorToolHandler<S, F, T, I>
1334where
1335 S: Clone + Send + Sync + 'static,
1336 F: TypedExtractorHandler<S, T, I> + Clone,
1337 T: Send + Sync + 'static,
1338 I: JsonSchema + Send + Sync + 'static,
1339{
1340 fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
1341 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
1342 self.call_with_context(ctx, args)
1343 }
1344
1345 fn call_with_context(
1346 &self,
1347 ctx: RequestContext,
1348 args: Value,
1349 ) -> BoxFuture<'_, Result<CallToolResult>> {
1350 let state = self.state.clone();
1351 let handler = self.handler.clone();
1352 Box::pin(async move { handler.call(ctx, state, args).await })
1353 }
1354
1355 fn uses_context(&self) -> bool {
1356 true
1357 }
1358
1359 fn input_schema(&self) -> Value {
1360 self.input_schema.clone()
1361 }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366 use super::*;
1367 use crate::protocol::RequestId;
1368 use schemars::JsonSchema;
1369 use serde::Deserialize;
1370 use std::sync::Arc;
1371
1372 #[derive(Debug, Deserialize, JsonSchema)]
1373 struct TestInput {
1374 name: String,
1375 count: i32,
1376 }
1377
1378 #[derive(Debug, Deserialize, JsonSchema)]
1383 #[schemars(crate = "crate::schemars")]
1384 struct ReexportInput {
1385 field: String,
1386 }
1387
1388 #[test]
1389 fn reexported_schemars_derive_produces_schema() {
1390 let schema = <Json<ReexportInput> as HasSchema>::schema()
1391 .expect("re-exported schemars derive should yield a schema");
1392 assert_eq!(schema["type"], "object");
1393 assert!(schema["properties"].get("field").is_some());
1394
1395 let ctx = RequestContext::new(RequestId::Number(1));
1397 let args = serde_json::json!({"field": "value"});
1398 let Json(input) = Json::<ReexportInput>::from_tool_request(&ctx, &(), &args)
1399 .expect("deserialization should succeed");
1400 assert_eq!(input.field, "value");
1401 }
1402
1403 #[test]
1404 fn test_json_extraction() {
1405 let args = serde_json::json!({"name": "test", "count": 42});
1406 let ctx = RequestContext::new(RequestId::Number(1));
1407
1408 let result = Json::<TestInput>::from_tool_request(&ctx, &(), &args);
1409 assert!(result.is_ok());
1410 let Json(input) = result.unwrap();
1411 assert_eq!(input.name, "test");
1412 assert_eq!(input.count, 42);
1413 }
1414
1415 #[test]
1416 fn test_json_extraction_error() {
1417 let args = serde_json::json!({"name": "test"}); let ctx = RequestContext::new(RequestId::Number(1));
1419
1420 let result = Json::<TestInput>::from_tool_request(&ctx, &(), &args);
1421 assert!(result.is_err());
1422 let rejection = result.unwrap_err();
1423 assert!(rejection.message().contains("count"));
1425 }
1426
1427 #[test]
1428 fn test_state_extraction() {
1429 let args = serde_json::json!({});
1430 let ctx = RequestContext::new(RequestId::Number(1));
1431 let state = Arc::new("my-state".to_string());
1432
1433 let result = State::<Arc<String>>::from_tool_request(&ctx, &state, &args);
1434 assert!(result.is_ok());
1435 let State(extracted) = result.unwrap();
1436 assert_eq!(*extracted, "my-state");
1437 }
1438
1439 #[test]
1440 fn test_context_extraction() {
1441 let args = serde_json::json!({});
1442 let ctx = RequestContext::new(RequestId::Number(42));
1443
1444 let result = Context::from_tool_request(&ctx, &(), &args);
1445 assert!(result.is_ok());
1446 let extracted = result.unwrap();
1447 assert_eq!(*extracted.request_id(), RequestId::Number(42));
1448 }
1449
1450 #[test]
1451 fn test_raw_args_extraction() {
1452 let args = serde_json::json!({"foo": "bar", "baz": 123});
1453 let ctx = RequestContext::new(RequestId::Number(1));
1454
1455 let result = RawArgs::from_tool_request(&ctx, &(), &args);
1456 assert!(result.is_ok());
1457 let RawArgs(extracted) = result.unwrap();
1458 assert_eq!(extracted["foo"], "bar");
1459 assert_eq!(extracted["baz"], 123);
1460 }
1461
1462 #[test]
1463 fn test_extension_extraction() {
1464 use crate::context::Extensions;
1465
1466 #[derive(Clone, Debug, PartialEq)]
1467 struct DatabasePool {
1468 url: String,
1469 }
1470
1471 let args = serde_json::json!({});
1472
1473 let mut extensions = Extensions::new();
1475 extensions.insert(Arc::new(DatabasePool {
1476 url: "postgres://localhost".to_string(),
1477 }));
1478
1479 let ctx = RequestContext::new(RequestId::Number(1)).with_extensions(Arc::new(extensions));
1481
1482 let result = Extension::<Arc<DatabasePool>>::from_tool_request(&ctx, &(), &args);
1484 assert!(result.is_ok());
1485 let Extension(pool) = result.unwrap();
1486 assert_eq!(pool.url, "postgres://localhost");
1487 }
1488
1489 #[test]
1490 fn test_extension_extraction_missing() {
1491 #[derive(Clone, Debug)]
1492 struct NotPresent;
1493
1494 let args = serde_json::json!({});
1495 let ctx = RequestContext::new(RequestId::Number(1));
1496
1497 let result = Extension::<NotPresent>::from_tool_request(&ctx, &(), &args);
1499 assert!(result.is_err());
1500 let rejection = result.unwrap_err();
1501 assert!(rejection.type_name().contains("NotPresent"));
1503 }
1504
1505 #[tokio::test]
1506 async fn test_single_extractor_handler() {
1507 let handler = |Json(input): Json<TestInput>| async move {
1508 Ok(CallToolResult::text(format!(
1509 "{}: {}",
1510 input.name, input.count
1511 )))
1512 };
1513
1514 let ctx = RequestContext::new(RequestId::Number(1));
1515 let args = serde_json::json!({"name": "test", "count": 5});
1516
1517 let result: Result<CallToolResult> =
1519 ExtractorHandler::<(), (Json<TestInput>,)>::call(handler, ctx, (), args).await;
1520 assert!(result.is_ok());
1521 }
1522
1523 #[tokio::test]
1524 async fn test_two_extractor_handler() {
1525 let handler = |State(state): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1526 Ok(CallToolResult::text(format!(
1527 "{}: {} - {}",
1528 state, input.name, input.count
1529 )))
1530 };
1531
1532 let ctx = RequestContext::new(RequestId::Number(1));
1533 let state = Arc::new("prefix".to_string());
1534 let args = serde_json::json!({"name": "test", "count": 5});
1535
1536 let result: Result<CallToolResult> = ExtractorHandler::<
1538 Arc<String>,
1539 (State<Arc<String>>, Json<TestInput>),
1540 >::call(handler, ctx, state, args)
1541 .await;
1542 assert!(result.is_ok());
1543 }
1544
1545 #[tokio::test]
1546 async fn test_three_extractor_handler() {
1547 let handler = |State(state): State<Arc<String>>,
1548 ctx: Context,
1549 Json(input): Json<TestInput>| async move {
1550 assert!(!ctx.is_cancelled());
1552 Ok(CallToolResult::text(format!(
1553 "{}: {} - {}",
1554 state, input.name, input.count
1555 )))
1556 };
1557
1558 let ctx = RequestContext::new(RequestId::Number(1));
1559 let state = Arc::new("prefix".to_string());
1560 let args = serde_json::json!({"name": "test", "count": 5});
1561
1562 let result: Result<CallToolResult> = ExtractorHandler::<
1564 Arc<String>,
1565 (State<Arc<String>>, Context, Json<TestInput>),
1566 >::call(handler, ctx, state, args)
1567 .await;
1568 assert!(result.is_ok());
1569 }
1570
1571 #[test]
1572 fn test_json_schema_generation() {
1573 let schema = Json::<TestInput>::schema();
1574 assert!(schema.is_some());
1575 let schema = schema.unwrap();
1576 assert!(schema.get("properties").is_some());
1577 }
1578
1579 #[test]
1580 fn test_rejection_into_error() {
1581 let rejection = Rejection::new("test error");
1582 let error: Error = rejection.into();
1583 assert!(error.to_string().contains("test error"));
1584 }
1585
1586 #[test]
1587 fn test_json_rejection() {
1588 let rejection = JsonRejection::new("missing field `name`");
1590 assert_eq!(rejection.message(), "missing field `name`");
1591 assert!(rejection.path().is_none());
1592 assert!(rejection.to_string().contains("Invalid input"));
1593
1594 let rejection = JsonRejection::with_path("expected string", "users[0].name");
1596 assert_eq!(rejection.message(), "expected string");
1597 assert_eq!(rejection.path(), Some("users[0].name"));
1598 assert!(rejection.to_string().contains("users[0].name"));
1599
1600 let error: Error = rejection.into();
1602 assert!(error.to_string().contains("users[0].name"));
1603 }
1604
1605 #[test]
1606 fn test_json_rejection_from_serde_error() {
1607 #[derive(Debug, serde::Deserialize)]
1609 struct TestStruct {
1610 #[allow(dead_code)]
1611 name: String,
1612 }
1613
1614 let result: std::result::Result<TestStruct, _> =
1615 serde_json::from_value(serde_json::json!({"count": 42}));
1616 assert!(result.is_err());
1617
1618 let rejection: JsonRejection = result.unwrap_err().into();
1619 assert!(rejection.message().contains("name"));
1620 }
1621
1622 #[test]
1623 fn test_extension_rejection() {
1624 let rejection = ExtensionRejection::not_found::<String>();
1626 assert!(rejection.type_name().contains("String"));
1627 assert!(rejection.to_string().contains("not found"));
1628
1629 let message = rejection.to_string();
1633 assert!(message.contains("with_state"));
1634 assert!(message.contains("with_extension"));
1635 assert!(message.contains(&format!(
1638 "bridge_extension::<{}>()",
1639 std::any::type_name::<String>()
1640 )));
1641
1642 let error: Error = rejection.into();
1644 assert!(error.to_string().contains("not found"));
1645 }
1646
1647 #[tokio::test]
1648 async fn test_tool_builder_extractor_handler() {
1649 use crate::ToolBuilder;
1650
1651 let state = Arc::new("shared-state".to_string());
1652
1653 let tool =
1654 ToolBuilder::new("test_extractor")
1655 .description("Test extractor handler")
1656 .extractor_handler(
1657 state,
1658 |State(state): State<Arc<String>>,
1659 ctx: Context,
1660 Json(input): Json<TestInput>| async move {
1661 assert!(!ctx.is_cancelled());
1662 Ok(CallToolResult::text(format!(
1663 "{}: {} - {}",
1664 state, input.name, input.count
1665 )))
1666 },
1667 )
1668 .build();
1669
1670 assert_eq!(tool.name, "test_extractor");
1671 assert_eq!(tool.description.as_deref(), Some("Test extractor handler"));
1672
1673 let result = tool
1675 .call(serde_json::json!({"name": "test", "count": 42}))
1676 .await;
1677 assert!(!result.is_error);
1678 }
1679
1680 #[tokio::test]
1681 #[allow(deprecated)]
1682 async fn test_tool_builder_extractor_handler_typed() {
1683 use crate::ToolBuilder;
1684
1685 let state = Arc::new("typed-state".to_string());
1686
1687 let tool = ToolBuilder::new("test_typed")
1688 .description("Test typed extractor handler")
1689 .extractor_handler_typed::<_, _, _, TestInput>(
1690 state,
1691 |State(state): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1692 Ok(CallToolResult::text(format!(
1693 "{}: {} - {}",
1694 state, input.name, input.count
1695 )))
1696 },
1697 )
1698 .build();
1699
1700 assert_eq!(tool.name, "test_typed");
1701
1702 let def = tool.definition();
1704 let schema = def.input_schema;
1705 assert!(schema.get("properties").is_some());
1706
1707 let result = tool
1709 .call(serde_json::json!({"name": "world", "count": 99}))
1710 .await;
1711 assert!(!result.is_error);
1712 }
1713
1714 #[tokio::test]
1715 async fn test_extractor_handler_auto_schema() {
1716 use crate::ToolBuilder;
1717
1718 let state = Arc::new("auto-schema".to_string());
1719
1720 let tool = ToolBuilder::new("test_auto_schema")
1722 .description("Test auto schema detection")
1723 .extractor_handler(
1724 state,
1725 |State(state): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1726 Ok(CallToolResult::text(format!(
1727 "{}: {} - {}",
1728 state, input.name, input.count
1729 )))
1730 },
1731 )
1732 .build();
1733
1734 let def = tool.definition();
1736 let schema = def.input_schema;
1737 assert!(
1738 schema.get("properties").is_some(),
1739 "Schema should have properties from TestInput, got: {}",
1740 schema
1741 );
1742 let props = schema.get("properties").unwrap();
1743 assert!(
1744 props.get("name").is_some(),
1745 "Schema should have 'name' property"
1746 );
1747 assert!(
1748 props.get("count").is_some(),
1749 "Schema should have 'count' property"
1750 );
1751
1752 let result = tool
1754 .call(serde_json::json!({"name": "world", "count": 99}))
1755 .await;
1756 assert!(!result.is_error);
1757 }
1758
1759 #[test]
1760 fn test_extractor_handler_no_json_fallback() {
1761 use crate::ToolBuilder;
1762
1763 let tool = ToolBuilder::new("test_no_json")
1765 .description("Test no json fallback")
1766 .extractor_handler((), |RawArgs(args): RawArgs| async move {
1767 Ok(CallToolResult::json(args))
1768 })
1769 .build();
1770
1771 let def = tool.definition();
1772 let schema = def.input_schema;
1773 assert_eq!(
1774 schema.get("type").and_then(|v| v.as_str()),
1775 Some("object"),
1776 "Schema should be generic object"
1777 );
1778 assert_eq!(
1779 schema.get("additionalProperties").and_then(|v| v.as_bool()),
1780 Some(true),
1781 "Schema should allow additional properties"
1782 );
1783 assert!(
1785 schema.get("properties").is_none(),
1786 "Generic schema should not have specific properties"
1787 );
1788 }
1789
1790 #[tokio::test]
1791 async fn test_extractor_handler_with_layer() {
1792 use crate::ToolBuilder;
1793 use std::time::Duration;
1794 use tower::timeout::TimeoutLayer;
1795
1796 let state = Arc::new("layered".to_string());
1797
1798 let tool = ToolBuilder::new("test_extractor_layer")
1799 .description("Test extractor handler with layer")
1800 .extractor_handler(
1801 state,
1802 |State(s): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1803 Ok(CallToolResult::text(format!("{}: {}", s, input.name)))
1804 },
1805 )
1806 .layer(TimeoutLayer::new(Duration::from_secs(5)))
1807 .build();
1808
1809 let result = tool
1811 .call(serde_json::json!({"name": "test", "count": 1}))
1812 .await;
1813 assert!(!result.is_error);
1814 assert_eq!(result.first_text().unwrap(), "layered: test");
1815
1816 let def = tool.definition();
1818 let schema = def.input_schema;
1819 assert!(
1820 schema.get("properties").is_some(),
1821 "Schema should have properties even with layer"
1822 );
1823 }
1824
1825 #[tokio::test]
1826 async fn test_extractor_handler_with_timeout_layer() {
1827 use crate::ToolBuilder;
1828 use std::time::Duration;
1829 use tower::timeout::TimeoutLayer;
1830
1831 let tool = ToolBuilder::new("test_extractor_timeout")
1832 .description("Test extractor handler timeout")
1833 .extractor_handler((), |Json(input): Json<TestInput>| async move {
1834 tokio::time::sleep(Duration::from_millis(200)).await;
1835 Ok(CallToolResult::text(input.name.to_string()))
1836 })
1837 .layer(TimeoutLayer::new(Duration::from_millis(50)))
1838 .build();
1839
1840 let result = tool
1842 .call(serde_json::json!({"name": "slow", "count": 1}))
1843 .await;
1844 assert!(result.is_error);
1845 let msg = result.first_text().unwrap().to_lowercase();
1846 assert!(
1847 msg.contains("timed out") || msg.contains("timeout") || msg.contains("elapsed"),
1848 "Expected timeout error, got: {}",
1849 msg
1850 );
1851 }
1852
1853 #[tokio::test]
1854 async fn test_extractor_handler_with_multiple_layers() {
1855 use crate::ToolBuilder;
1856 use std::time::Duration;
1857 use tower::limit::ConcurrencyLimitLayer;
1858 use tower::timeout::TimeoutLayer;
1859
1860 let state = Arc::new("multi".to_string());
1861
1862 let tool = ToolBuilder::new("test_multi_layer")
1863 .description("Test multiple layers")
1864 .extractor_handler(
1865 state,
1866 |State(s): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1867 Ok(CallToolResult::text(format!("{}: {}", s, input.name)))
1868 },
1869 )
1870 .layer(TimeoutLayer::new(Duration::from_secs(5)))
1871 .layer(ConcurrencyLimitLayer::new(10))
1872 .build();
1873
1874 let result = tool
1875 .call(serde_json::json!({"name": "test", "count": 1}))
1876 .await;
1877 assert!(!result.is_error);
1878 assert_eq!(result.first_text().unwrap(), "multi: test");
1879 }
1880}