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 `{}` not found. Did you call `router.with_state()` or `router.with_extension()`?",
259 self.type_name
260 )
261 }
262}
263
264impl std::error::Error for ExtensionRejection {}
265
266impl From<ExtensionRejection> for Error {
267 fn from(rejection: ExtensionRejection) -> Self {
268 Error::tool(rejection.to_string())
269 }
270}
271
272pub trait FromToolRequest<S = ()>: Sized {
303 type Rejection: Into<Error>;
305
306 fn from_tool_request(
314 ctx: &RequestContext,
315 state: &S,
316 args: &Value,
317 ) -> std::result::Result<Self, Self::Rejection>;
318}
319
320#[derive(Debug, Clone, Copy)]
351pub struct Json<T>(pub T);
352
353impl<T> Deref for Json<T> {
354 type Target = T;
355
356 fn deref(&self) -> &Self::Target {
357 &self.0
358 }
359}
360
361impl<S, T> FromToolRequest<S> for Json<T>
362where
363 T: DeserializeOwned,
364{
365 type Rejection = JsonRejection;
366
367 fn from_tool_request(
368 _ctx: &RequestContext,
369 _state: &S,
370 args: &Value,
371 ) -> std::result::Result<Self, Self::Rejection> {
372 serde_json::from_value(args.clone())
373 .map(Json)
374 .map_err(JsonRejection::from)
375 }
376}
377
378#[derive(Debug, Clone, Copy)]
403pub struct State<T>(pub T);
404
405impl<T> Deref for State<T> {
406 type Target = T;
407
408 fn deref(&self) -> &Self::Target {
409 &self.0
410 }
411}
412
413impl<S: Clone> FromToolRequest<S> for State<S> {
414 type Rejection = Rejection;
415
416 fn from_tool_request(
417 _ctx: &RequestContext,
418 state: &S,
419 _args: &Value,
420 ) -> std::result::Result<Self, Self::Rejection> {
421 Ok(State(state.clone()))
422 }
423}
424
425#[derive(Debug, Clone)]
446pub struct Context(RequestContext);
447
448impl Context {
449 pub fn into_inner(self) -> RequestContext {
451 self.0
452 }
453}
454
455impl Deref for Context {
456 type Target = RequestContext;
457
458 fn deref(&self) -> &Self::Target {
459 &self.0
460 }
461}
462
463impl<S> FromToolRequest<S> for Context {
464 type Rejection = Rejection;
465
466 fn from_tool_request(
467 ctx: &RequestContext,
468 _state: &S,
469 _args: &Value,
470 ) -> std::result::Result<Self, Self::Rejection> {
471 Ok(Context(ctx.clone()))
472 }
473}
474
475#[derive(Debug, Clone)]
493pub struct RawArgs(pub Value);
494
495impl Deref for RawArgs {
496 type Target = Value;
497
498 fn deref(&self) -> &Self::Target {
499 &self.0
500 }
501}
502
503impl<S> FromToolRequest<S> for RawArgs {
504 type Rejection = Rejection;
505
506 fn from_tool_request(
507 _ctx: &RequestContext,
508 _state: &S,
509 args: &Value,
510 ) -> std::result::Result<Self, Self::Rejection> {
511 Ok(RawArgs(args.clone()))
512 }
513}
514
515#[derive(Debug, Clone)]
562pub struct Extension<T>(pub T);
563
564impl<T> Deref for Extension<T> {
565 type Target = T;
566
567 fn deref(&self) -> &Self::Target {
568 &self.0
569 }
570}
571
572impl<S, T> FromToolRequest<S> for Extension<T>
573where
574 T: Clone + Send + Sync + 'static,
575{
576 type Rejection = ExtensionRejection;
577
578 fn from_tool_request(
579 ctx: &RequestContext,
580 _state: &S,
581 _args: &Value,
582 ) -> std::result::Result<Self, Self::Rejection> {
583 ctx.extension::<T>()
584 .cloned()
585 .map(Extension)
586 .ok_or_else(ExtensionRejection::not_found::<T>)
587 }
588}
589
590#[diagnostic::on_unimplemented(
600 message = "`{Self}` is not a valid extractor handler",
601 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>`",
602 note = "for a `Json<T>` argument, `T` must implement `serde::Deserialize` and `schemars::JsonSchema`",
603 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"
604)]
605pub trait ExtractorHandler<S, T>: Clone + Send + Sync + 'static {
606 type Future: Future<Output = Result<CallToolResult>> + Send;
608
609 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future;
611
612 fn input_schema() -> Value;
616}
617
618impl<S, F, Fut, T1> ExtractorHandler<S, (T1,)> for F
620where
621 S: Clone + Send + Sync + 'static,
622 F: Fn(T1) -> Fut + Clone + Send + Sync + 'static,
623 Fut: Future<Output = Result<CallToolResult>> + Send,
624 T1: FromToolRequest<S> + HasSchema + Send,
625{
626 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
627
628 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
629 Box::pin(async move {
630 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
631 self(t1).await
632 })
633 }
634
635 fn input_schema() -> Value {
636 if let Some(schema) = T1::schema() {
637 return schema;
638 }
639 serde_json::json!({
640 "type": "object",
641 "additionalProperties": true
642 })
643 }
644}
645
646impl<S, F, Fut, T1, T2> ExtractorHandler<S, (T1, T2)> for F
648where
649 S: Clone + Send + Sync + 'static,
650 F: Fn(T1, T2) -> Fut + Clone + Send + Sync + 'static,
651 Fut: Future<Output = Result<CallToolResult>> + Send,
652 T1: FromToolRequest<S> + HasSchema + Send,
653 T2: FromToolRequest<S> + HasSchema + Send,
654{
655 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
656
657 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
658 Box::pin(async move {
659 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
660 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
661 self(t1, t2).await
662 })
663 }
664
665 fn input_schema() -> Value {
666 if let Some(schema) = T2::schema() {
667 return schema;
668 }
669 if let Some(schema) = T1::schema() {
670 return schema;
671 }
672 serde_json::json!({
673 "type": "object",
674 "additionalProperties": true
675 })
676 }
677}
678
679impl<S, F, Fut, T1, T2, T3> ExtractorHandler<S, (T1, T2, T3)> for F
681where
682 S: Clone + Send + Sync + 'static,
683 F: Fn(T1, T2, T3) -> Fut + Clone + Send + Sync + 'static,
684 Fut: Future<Output = Result<CallToolResult>> + Send,
685 T1: FromToolRequest<S> + HasSchema + Send,
686 T2: FromToolRequest<S> + HasSchema + Send,
687 T3: FromToolRequest<S> + HasSchema + Send,
688{
689 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
690
691 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
692 Box::pin(async move {
693 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
694 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
695 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
696 self(t1, t2, t3).await
697 })
698 }
699
700 fn input_schema() -> Value {
701 if let Some(schema) = T3::schema() {
702 return schema;
703 }
704 if let Some(schema) = T2::schema() {
705 return schema;
706 }
707 if let Some(schema) = T1::schema() {
708 return schema;
709 }
710 serde_json::json!({
711 "type": "object",
712 "additionalProperties": true
713 })
714 }
715}
716
717impl<S, F, Fut, T1, T2, T3, T4> ExtractorHandler<S, (T1, T2, T3, T4)> for F
719where
720 S: Clone + Send + Sync + 'static,
721 F: Fn(T1, T2, T3, T4) -> Fut + Clone + Send + Sync + 'static,
722 Fut: Future<Output = Result<CallToolResult>> + Send,
723 T1: FromToolRequest<S> + HasSchema + Send,
724 T2: FromToolRequest<S> + HasSchema + Send,
725 T3: FromToolRequest<S> + HasSchema + Send,
726 T4: FromToolRequest<S> + HasSchema + Send,
727{
728 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
729
730 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
731 Box::pin(async move {
732 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
733 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
734 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
735 let t4 = T4::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
736 self(t1, t2, t3, t4).await
737 })
738 }
739
740 fn input_schema() -> Value {
741 if let Some(schema) = T4::schema() {
742 return schema;
743 }
744 if let Some(schema) = T3::schema() {
745 return schema;
746 }
747 if let Some(schema) = T2::schema() {
748 return schema;
749 }
750 if let Some(schema) = T1::schema() {
751 return schema;
752 }
753 serde_json::json!({
754 "type": "object",
755 "additionalProperties": true
756 })
757 }
758}
759
760impl<S, F, Fut, T1, T2, T3, T4, T5> ExtractorHandler<S, (T1, T2, T3, T4, T5)> for F
762where
763 S: Clone + Send + Sync + 'static,
764 F: Fn(T1, T2, T3, T4, T5) -> Fut + Clone + Send + Sync + 'static,
765 Fut: Future<Output = Result<CallToolResult>> + Send,
766 T1: FromToolRequest<S> + HasSchema + Send,
767 T2: FromToolRequest<S> + HasSchema + Send,
768 T3: FromToolRequest<S> + HasSchema + Send,
769 T4: FromToolRequest<S> + HasSchema + Send,
770 T5: FromToolRequest<S> + HasSchema + Send,
771{
772 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
773
774 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
775 Box::pin(async move {
776 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
777 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
778 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
779 let t4 = T4::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
780 let t5 = T5::from_tool_request(&ctx, &state, &args).map_err(Into::into)?;
781 self(t1, t2, t3, t4, t5).await
782 })
783 }
784
785 fn input_schema() -> Value {
786 if let Some(schema) = T5::schema() {
787 return schema;
788 }
789 if let Some(schema) = T4::schema() {
790 return schema;
791 }
792 if let Some(schema) = T3::schema() {
793 return schema;
794 }
795 if let Some(schema) = T2::schema() {
796 return schema;
797 }
798 if let Some(schema) = T1::schema() {
799 return schema;
800 }
801 serde_json::json!({
802 "type": "object",
803 "additionalProperties": true
804 })
805 }
806}
807
808#[diagnostic::on_unimplemented(
814 message = "`{Self}` does not implement `HasSchema`",
815 note = "for `Json<T>` this means `T: schemars::JsonSchema` is not satisfied",
816 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)",
817 note = "depend on `schemars` via the `tower_mcp::schemars` re-export to keep the versions aligned"
818)]
819pub trait HasSchema {
820 fn schema() -> Option<Value>;
822}
823
824impl<T: JsonSchema> HasSchema for Json<T> {
825 fn schema() -> Option<Value> {
826 let schema = schemars::schema_for!(T);
827 serde_json::to_value(schema)
828 .ok()
829 .map(crate::tool::ensure_object_schema)
830 }
831}
832
833impl HasSchema for Context {
835 fn schema() -> Option<Value> {
836 None
837 }
838}
839
840impl HasSchema for RawArgs {
841 fn schema() -> Option<Value> {
842 None
843 }
844}
845
846impl<T> HasSchema for State<T> {
847 fn schema() -> Option<Value> {
848 None
849 }
850}
851
852impl<T> HasSchema for Extension<T> {
853 fn schema() -> Option<Value> {
854 None
855 }
856}
857
858#[deprecated(
867 since = "0.8.0",
868 note = "Use `ExtractorHandler` instead -- `extractor_handler` auto-detects JSON schema from `Json<T>` extractors"
869)]
870pub trait TypedExtractorHandler<S, T, I>: Clone + Send + Sync + 'static
871where
872 I: JsonSchema,
873{
874 type Future: Future<Output = Result<CallToolResult>> + Send;
876
877 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future;
879}
880
881#[allow(deprecated)]
883impl<S, F, Fut, T> TypedExtractorHandler<S, (Json<T>,), T> for F
884where
885 S: Clone + Send + Sync + 'static,
886 F: Fn(Json<T>) -> Fut + Clone + Send + Sync + 'static,
887 Fut: Future<Output = Result<CallToolResult>> + Send,
888 T: DeserializeOwned + JsonSchema + Send,
889{
890 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
891
892 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
893 Box::pin(async move {
894 let t1 =
895 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
896 self(t1).await
897 })
898 }
899}
900
901#[allow(deprecated)]
903impl<S, F, Fut, T1, T> TypedExtractorHandler<S, (T1, Json<T>), T> for F
904where
905 S: Clone + Send + Sync + 'static,
906 F: Fn(T1, Json<T>) -> Fut + Clone + Send + Sync + 'static,
907 Fut: Future<Output = Result<CallToolResult>> + Send,
908 T1: FromToolRequest<S> + Send,
909 T: DeserializeOwned + JsonSchema + Send,
910{
911 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
912
913 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
914 Box::pin(async move {
915 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
916 let t2 =
917 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
918 self(t1, t2).await
919 })
920 }
921}
922
923#[allow(deprecated)]
925impl<S, F, Fut, T1, T2, T> TypedExtractorHandler<S, (T1, T2, Json<T>), T> for F
926where
927 S: Clone + Send + Sync + 'static,
928 F: Fn(T1, T2, Json<T>) -> Fut + Clone + Send + Sync + 'static,
929 Fut: Future<Output = Result<CallToolResult>> + Send,
930 T1: FromToolRequest<S> + Send,
931 T2: FromToolRequest<S> + Send,
932 T: DeserializeOwned + JsonSchema + Send,
933{
934 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
935
936 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
937 Box::pin(async move {
938 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
939 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
940 let t3 =
941 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
942 self(t1, t2, t3).await
943 })
944 }
945}
946
947#[allow(deprecated)]
949impl<S, F, Fut, T1, T2, T3, T> TypedExtractorHandler<S, (T1, T2, T3, Json<T>), T> for F
950where
951 S: Clone + Send + Sync + 'static,
952 F: Fn(T1, T2, T3, Json<T>) -> Fut + Clone + Send + Sync + 'static,
953 Fut: Future<Output = Result<CallToolResult>> + Send,
954 T1: FromToolRequest<S> + Send,
955 T2: FromToolRequest<S> + Send,
956 T3: FromToolRequest<S> + Send,
957 T: DeserializeOwned + JsonSchema + Send,
958{
959 type Future = Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>>;
960
961 fn call(self, ctx: RequestContext, state: S, args: Value) -> Self::Future {
962 Box::pin(async move {
963 let t1 = T1::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
964 let t2 = T2::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
965 let t3 = T3::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
966 let t4 =
967 Json::<T>::from_tool_request(&ctx, &state, &args).map_err(Into::<Error>::into)?;
968 self(t1, t2, t3, t4).await
969 })
970 }
971}
972
973use crate::tool::{
978 BoxFuture, GuardLayer, Tool, ToolCatchError, ToolHandler, ToolHandlerService, ToolRequest,
979};
980use tower::util::BoxCloneService;
981use tower_service::Service;
982
983pub(crate) struct ExtractorToolHandler<S, F, T> {
985 state: S,
986 handler: F,
987 input_schema: Value,
988 _phantom: PhantomData<T>,
989}
990
991impl<S, F, T> ToolHandler for ExtractorToolHandler<S, F, T>
992where
993 S: Clone + Send + Sync + 'static,
994 F: ExtractorHandler<S, T> + Clone,
995 T: Send + Sync + 'static,
996{
997 fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
998 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
999 self.call_with_context(ctx, args)
1000 }
1001
1002 fn call_with_context(
1003 &self,
1004 ctx: RequestContext,
1005 args: Value,
1006 ) -> BoxFuture<'_, Result<CallToolResult>> {
1007 let state = self.state.clone();
1008 let handler = self.handler.clone();
1009 Box::pin(async move { handler.call(ctx, state, args).await })
1010 }
1011
1012 fn uses_context(&self) -> bool {
1013 true
1014 }
1015
1016 fn input_schema(&self) -> Value {
1017 self.input_schema.clone()
1018 }
1019}
1020
1021#[doc(hidden)]
1023pub struct ToolBuilderWithExtractor<S, F, T> {
1024 pub(crate) name: String,
1025 pub(crate) title: Option<String>,
1026 pub(crate) description: Option<String>,
1027 pub(crate) output_schema: Option<Value>,
1028 pub(crate) icons: Option<Vec<crate::protocol::ToolIcon>>,
1029 pub(crate) annotations: Option<crate::protocol::ToolAnnotations>,
1030 pub(crate) task_support: crate::protocol::TaskSupportMode,
1031 pub(crate) state: S,
1032 pub(crate) handler: F,
1033 pub(crate) input_schema: Value,
1034 pub(crate) _phantom: PhantomData<T>,
1035}
1036
1037impl<S, F, T> ToolBuilderWithExtractor<S, F, T>
1038where
1039 S: Clone + Send + Sync + 'static,
1040 F: ExtractorHandler<S, T> + Clone,
1041 T: Send + Sync + 'static,
1042{
1043 pub fn build(self) -> Tool {
1045 let handler = ExtractorToolHandler {
1046 state: self.state,
1047 handler: self.handler,
1048 input_schema: self.input_schema.clone(),
1049 _phantom: PhantomData,
1050 };
1051
1052 let handler_service = ToolHandlerService::new(handler);
1053 let catch_error = ToolCatchError::new(handler_service);
1054 let service = BoxCloneService::new(catch_error);
1055
1056 Tool {
1057 name: self.name,
1058 title: self.title,
1059 description: self.description,
1060 output_schema: self.output_schema,
1061 icons: self.icons,
1062 annotations: self.annotations,
1063 meta: None,
1064 task_support: self.task_support,
1065 required_client_capabilities: None,
1066 task_preparer: None,
1067 service: Some(service),
1068 #[cfg(feature = "stateless")]
1069 mrtr_handler: None,
1070 input_schema: self.input_schema,
1071 }
1072 }
1073
1074 pub fn layer<L>(self, layer: L) -> ToolBuilderWithExtractorLayer<S, F, T, L> {
1110 ToolBuilderWithExtractorLayer {
1111 name: self.name,
1112 title: self.title,
1113 description: self.description,
1114 output_schema: self.output_schema,
1115 icons: self.icons,
1116 annotations: self.annotations,
1117 task_support: self.task_support,
1118 state: self.state,
1119 handler: self.handler,
1120 input_schema: self.input_schema,
1121 layer,
1122 _phantom: PhantomData,
1123 }
1124 }
1125
1126 pub fn guard<G>(self, guard: G) -> ToolBuilderWithExtractorLayer<S, F, T, GuardLayer<G>>
1130 where
1131 G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1132 {
1133 self.layer(GuardLayer::new(guard))
1134 }
1135}
1136
1137#[doc(hidden)]
1141pub struct ToolBuilderWithExtractorLayer<S, F, T, L> {
1142 name: String,
1143 title: Option<String>,
1144 description: Option<String>,
1145 output_schema: Option<Value>,
1146 icons: Option<Vec<crate::protocol::ToolIcon>>,
1147 annotations: Option<crate::protocol::ToolAnnotations>,
1148 task_support: crate::protocol::TaskSupportMode,
1149 state: S,
1150 handler: F,
1151 input_schema: Value,
1152 layer: L,
1153 _phantom: PhantomData<T>,
1154}
1155
1156#[allow(private_bounds)]
1157impl<S, F, T, L> ToolBuilderWithExtractorLayer<S, F, T, L>
1158where
1159 S: Clone + Send + Sync + 'static,
1160 F: ExtractorHandler<S, T> + Clone,
1161 T: Send + Sync + 'static,
1162 L: tower::Layer<ToolHandlerService<ExtractorToolHandler<S, F, T>>>
1163 + Clone
1164 + Send
1165 + Sync
1166 + 'static,
1167 L::Service: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
1168 <L::Service as Service<ToolRequest>>::Error: std::fmt::Display + Send,
1169 <L::Service as Service<ToolRequest>>::Future: Send,
1170{
1171 pub fn build(self) -> Tool {
1173 let handler = ExtractorToolHandler {
1174 state: self.state,
1175 handler: self.handler,
1176 input_schema: self.input_schema.clone(),
1177 _phantom: PhantomData,
1178 };
1179
1180 let handler_service = ToolHandlerService::new(handler);
1181 let layered = self.layer.layer(handler_service);
1182 let catch_error = ToolCatchError::new(layered);
1183 let service = BoxCloneService::new(catch_error);
1184
1185 Tool {
1186 name: self.name,
1187 title: self.title,
1188 description: self.description,
1189 output_schema: self.output_schema,
1190 icons: self.icons,
1191 annotations: self.annotations,
1192 meta: None,
1193 task_support: self.task_support,
1194 required_client_capabilities: None,
1195 task_preparer: None,
1196 service: Some(service),
1197 #[cfg(feature = "stateless")]
1198 mrtr_handler: None,
1199 input_schema: self.input_schema,
1200 }
1201 }
1202
1203 pub fn layer<L2>(
1208 self,
1209 layer: L2,
1210 ) -> ToolBuilderWithExtractorLayer<S, F, T, tower::layer::util::Stack<L2, L>> {
1211 ToolBuilderWithExtractorLayer {
1212 name: self.name,
1213 title: self.title,
1214 description: self.description,
1215 output_schema: self.output_schema,
1216 icons: self.icons,
1217 annotations: self.annotations,
1218 task_support: self.task_support,
1219 state: self.state,
1220 handler: self.handler,
1221 input_schema: self.input_schema,
1222 layer: tower::layer::util::Stack::new(layer, self.layer),
1223 _phantom: PhantomData,
1224 }
1225 }
1226
1227 pub fn guard<G>(
1231 self,
1232 guard: G,
1233 ) -> ToolBuilderWithExtractorLayer<S, F, T, tower::layer::util::Stack<GuardLayer<G>, L>>
1234 where
1235 G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
1236 {
1237 self.layer(GuardLayer::new(guard))
1238 }
1239}
1240
1241#[doc(hidden)]
1243#[deprecated(
1244 since = "0.8.0",
1245 note = "Use `ToolBuilderWithExtractor` via `extractor_handler` instead"
1246)]
1247pub struct ToolBuilderWithTypedExtractor<S, F, T, I> {
1248 pub(crate) name: String,
1249 pub(crate) title: Option<String>,
1250 pub(crate) description: Option<String>,
1251 pub(crate) output_schema: Option<Value>,
1252 pub(crate) input_schema_override: Option<Value>,
1253 pub(crate) icons: Option<Vec<crate::protocol::ToolIcon>>,
1254 pub(crate) annotations: Option<crate::protocol::ToolAnnotations>,
1255 pub(crate) task_support: crate::protocol::TaskSupportMode,
1256 pub(crate) state: S,
1257 pub(crate) handler: F,
1258 pub(crate) _phantom: PhantomData<(T, I)>,
1259}
1260
1261#[allow(deprecated)]
1262impl<S, F, T, I> ToolBuilderWithTypedExtractor<S, F, T, I>
1263where
1264 S: Clone + Send + Sync + 'static,
1265 F: TypedExtractorHandler<S, T, I> + Clone,
1266 T: Send + Sync + 'static,
1267 I: JsonSchema + Send + Sync + 'static,
1268{
1269 pub fn build(self) -> Tool {
1271 let input_schema = {
1272 let schema = self.input_schema_override.unwrap_or_else(|| {
1273 let schema = schemars::schema_for!(I);
1274 serde_json::to_value(schema).unwrap_or_else(|_| {
1275 serde_json::json!({
1276 "type": "object"
1277 })
1278 })
1279 });
1280 crate::tool::ensure_object_schema(schema)
1281 };
1282
1283 let handler = TypedExtractorToolHandler {
1284 state: self.state,
1285 handler: self.handler,
1286 input_schema: input_schema.clone(),
1287 _phantom: PhantomData,
1288 };
1289
1290 let handler_service = crate::tool::ToolHandlerService::new(handler);
1291 let catch_error = ToolCatchError::new(handler_service);
1292 let service = BoxCloneService::new(catch_error);
1293
1294 Tool {
1295 name: self.name,
1296 title: self.title,
1297 description: self.description,
1298 output_schema: self.output_schema,
1299 icons: self.icons,
1300 annotations: self.annotations,
1301 meta: None,
1302 task_support: self.task_support,
1303 required_client_capabilities: None,
1304 task_preparer: None,
1305 service: Some(service),
1306 #[cfg(feature = "stateless")]
1307 mrtr_handler: None,
1308 input_schema,
1309 }
1310 }
1311}
1312
1313struct TypedExtractorToolHandler<S, F, T, I> {
1315 state: S,
1316 handler: F,
1317 input_schema: Value,
1318 _phantom: PhantomData<(T, I)>,
1319}
1320
1321#[allow(deprecated)]
1322impl<S, F, T, I> ToolHandler for TypedExtractorToolHandler<S, F, T, I>
1323where
1324 S: Clone + Send + Sync + 'static,
1325 F: TypedExtractorHandler<S, T, I> + Clone,
1326 T: Send + Sync + 'static,
1327 I: JsonSchema + Send + Sync + 'static,
1328{
1329 fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
1330 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
1331 self.call_with_context(ctx, args)
1332 }
1333
1334 fn call_with_context(
1335 &self,
1336 ctx: RequestContext,
1337 args: Value,
1338 ) -> BoxFuture<'_, Result<CallToolResult>> {
1339 let state = self.state.clone();
1340 let handler = self.handler.clone();
1341 Box::pin(async move { handler.call(ctx, state, args).await })
1342 }
1343
1344 fn uses_context(&self) -> bool {
1345 true
1346 }
1347
1348 fn input_schema(&self) -> Value {
1349 self.input_schema.clone()
1350 }
1351}
1352
1353#[cfg(test)]
1354mod tests {
1355 use super::*;
1356 use crate::protocol::RequestId;
1357 use schemars::JsonSchema;
1358 use serde::Deserialize;
1359 use std::sync::Arc;
1360
1361 #[derive(Debug, Deserialize, JsonSchema)]
1362 struct TestInput {
1363 name: String,
1364 count: i32,
1365 }
1366
1367 #[derive(Debug, Deserialize, JsonSchema)]
1372 #[schemars(crate = "crate::schemars")]
1373 struct ReexportInput {
1374 field: String,
1375 }
1376
1377 #[test]
1378 fn reexported_schemars_derive_produces_schema() {
1379 let schema = <Json<ReexportInput> as HasSchema>::schema()
1380 .expect("re-exported schemars derive should yield a schema");
1381 assert_eq!(schema["type"], "object");
1382 assert!(schema["properties"].get("field").is_some());
1383
1384 let ctx = RequestContext::new(RequestId::Number(1));
1386 let args = serde_json::json!({"field": "value"});
1387 let Json(input) = Json::<ReexportInput>::from_tool_request(&ctx, &(), &args)
1388 .expect("deserialization should succeed");
1389 assert_eq!(input.field, "value");
1390 }
1391
1392 #[test]
1393 fn test_json_extraction() {
1394 let args = serde_json::json!({"name": "test", "count": 42});
1395 let ctx = RequestContext::new(RequestId::Number(1));
1396
1397 let result = Json::<TestInput>::from_tool_request(&ctx, &(), &args);
1398 assert!(result.is_ok());
1399 let Json(input) = result.unwrap();
1400 assert_eq!(input.name, "test");
1401 assert_eq!(input.count, 42);
1402 }
1403
1404 #[test]
1405 fn test_json_extraction_error() {
1406 let args = serde_json::json!({"name": "test"}); let ctx = RequestContext::new(RequestId::Number(1));
1408
1409 let result = Json::<TestInput>::from_tool_request(&ctx, &(), &args);
1410 assert!(result.is_err());
1411 let rejection = result.unwrap_err();
1412 assert!(rejection.message().contains("count"));
1414 }
1415
1416 #[test]
1417 fn test_state_extraction() {
1418 let args = serde_json::json!({});
1419 let ctx = RequestContext::new(RequestId::Number(1));
1420 let state = Arc::new("my-state".to_string());
1421
1422 let result = State::<Arc<String>>::from_tool_request(&ctx, &state, &args);
1423 assert!(result.is_ok());
1424 let State(extracted) = result.unwrap();
1425 assert_eq!(*extracted, "my-state");
1426 }
1427
1428 #[test]
1429 fn test_context_extraction() {
1430 let args = serde_json::json!({});
1431 let ctx = RequestContext::new(RequestId::Number(42));
1432
1433 let result = Context::from_tool_request(&ctx, &(), &args);
1434 assert!(result.is_ok());
1435 let extracted = result.unwrap();
1436 assert_eq!(*extracted.request_id(), RequestId::Number(42));
1437 }
1438
1439 #[test]
1440 fn test_raw_args_extraction() {
1441 let args = serde_json::json!({"foo": "bar", "baz": 123});
1442 let ctx = RequestContext::new(RequestId::Number(1));
1443
1444 let result = RawArgs::from_tool_request(&ctx, &(), &args);
1445 assert!(result.is_ok());
1446 let RawArgs(extracted) = result.unwrap();
1447 assert_eq!(extracted["foo"], "bar");
1448 assert_eq!(extracted["baz"], 123);
1449 }
1450
1451 #[test]
1452 fn test_extension_extraction() {
1453 use crate::context::Extensions;
1454
1455 #[derive(Clone, Debug, PartialEq)]
1456 struct DatabasePool {
1457 url: String,
1458 }
1459
1460 let args = serde_json::json!({});
1461
1462 let mut extensions = Extensions::new();
1464 extensions.insert(Arc::new(DatabasePool {
1465 url: "postgres://localhost".to_string(),
1466 }));
1467
1468 let ctx = RequestContext::new(RequestId::Number(1)).with_extensions(Arc::new(extensions));
1470
1471 let result = Extension::<Arc<DatabasePool>>::from_tool_request(&ctx, &(), &args);
1473 assert!(result.is_ok());
1474 let Extension(pool) = result.unwrap();
1475 assert_eq!(pool.url, "postgres://localhost");
1476 }
1477
1478 #[test]
1479 fn test_extension_extraction_missing() {
1480 #[derive(Clone, Debug)]
1481 struct NotPresent;
1482
1483 let args = serde_json::json!({});
1484 let ctx = RequestContext::new(RequestId::Number(1));
1485
1486 let result = Extension::<NotPresent>::from_tool_request(&ctx, &(), &args);
1488 assert!(result.is_err());
1489 let rejection = result.unwrap_err();
1490 assert!(rejection.type_name().contains("NotPresent"));
1492 }
1493
1494 #[tokio::test]
1495 async fn test_single_extractor_handler() {
1496 let handler = |Json(input): Json<TestInput>| async move {
1497 Ok(CallToolResult::text(format!(
1498 "{}: {}",
1499 input.name, input.count
1500 )))
1501 };
1502
1503 let ctx = RequestContext::new(RequestId::Number(1));
1504 let args = serde_json::json!({"name": "test", "count": 5});
1505
1506 let result: Result<CallToolResult> =
1508 ExtractorHandler::<(), (Json<TestInput>,)>::call(handler, ctx, (), args).await;
1509 assert!(result.is_ok());
1510 }
1511
1512 #[tokio::test]
1513 async fn test_two_extractor_handler() {
1514 let handler = |State(state): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1515 Ok(CallToolResult::text(format!(
1516 "{}: {} - {}",
1517 state, input.name, input.count
1518 )))
1519 };
1520
1521 let ctx = RequestContext::new(RequestId::Number(1));
1522 let state = Arc::new("prefix".to_string());
1523 let args = serde_json::json!({"name": "test", "count": 5});
1524
1525 let result: Result<CallToolResult> = ExtractorHandler::<
1527 Arc<String>,
1528 (State<Arc<String>>, Json<TestInput>),
1529 >::call(handler, ctx, state, args)
1530 .await;
1531 assert!(result.is_ok());
1532 }
1533
1534 #[tokio::test]
1535 async fn test_three_extractor_handler() {
1536 let handler = |State(state): State<Arc<String>>,
1537 ctx: Context,
1538 Json(input): Json<TestInput>| async move {
1539 assert!(!ctx.is_cancelled());
1541 Ok(CallToolResult::text(format!(
1542 "{}: {} - {}",
1543 state, input.name, input.count
1544 )))
1545 };
1546
1547 let ctx = RequestContext::new(RequestId::Number(1));
1548 let state = Arc::new("prefix".to_string());
1549 let args = serde_json::json!({"name": "test", "count": 5});
1550
1551 let result: Result<CallToolResult> = ExtractorHandler::<
1553 Arc<String>,
1554 (State<Arc<String>>, Context, Json<TestInput>),
1555 >::call(handler, ctx, state, args)
1556 .await;
1557 assert!(result.is_ok());
1558 }
1559
1560 #[test]
1561 fn test_json_schema_generation() {
1562 let schema = Json::<TestInput>::schema();
1563 assert!(schema.is_some());
1564 let schema = schema.unwrap();
1565 assert!(schema.get("properties").is_some());
1566 }
1567
1568 #[test]
1569 fn test_rejection_into_error() {
1570 let rejection = Rejection::new("test error");
1571 let error: Error = rejection.into();
1572 assert!(error.to_string().contains("test error"));
1573 }
1574
1575 #[test]
1576 fn test_json_rejection() {
1577 let rejection = JsonRejection::new("missing field `name`");
1579 assert_eq!(rejection.message(), "missing field `name`");
1580 assert!(rejection.path().is_none());
1581 assert!(rejection.to_string().contains("Invalid input"));
1582
1583 let rejection = JsonRejection::with_path("expected string", "users[0].name");
1585 assert_eq!(rejection.message(), "expected string");
1586 assert_eq!(rejection.path(), Some("users[0].name"));
1587 assert!(rejection.to_string().contains("users[0].name"));
1588
1589 let error: Error = rejection.into();
1591 assert!(error.to_string().contains("users[0].name"));
1592 }
1593
1594 #[test]
1595 fn test_json_rejection_from_serde_error() {
1596 #[derive(Debug, serde::Deserialize)]
1598 struct TestStruct {
1599 #[allow(dead_code)]
1600 name: String,
1601 }
1602
1603 let result: std::result::Result<TestStruct, _> =
1604 serde_json::from_value(serde_json::json!({"count": 42}));
1605 assert!(result.is_err());
1606
1607 let rejection: JsonRejection = result.unwrap_err().into();
1608 assert!(rejection.message().contains("name"));
1609 }
1610
1611 #[test]
1612 fn test_extension_rejection() {
1613 let rejection = ExtensionRejection::not_found::<String>();
1615 assert!(rejection.type_name().contains("String"));
1616 assert!(rejection.to_string().contains("not found"));
1617 assert!(rejection.to_string().contains("with_state"));
1618
1619 let error: Error = rejection.into();
1621 assert!(error.to_string().contains("not found"));
1622 }
1623
1624 #[tokio::test]
1625 async fn test_tool_builder_extractor_handler() {
1626 use crate::ToolBuilder;
1627
1628 let state = Arc::new("shared-state".to_string());
1629
1630 let tool =
1631 ToolBuilder::new("test_extractor")
1632 .description("Test extractor handler")
1633 .extractor_handler(
1634 state,
1635 |State(state): State<Arc<String>>,
1636 ctx: Context,
1637 Json(input): Json<TestInput>| async move {
1638 assert!(!ctx.is_cancelled());
1639 Ok(CallToolResult::text(format!(
1640 "{}: {} - {}",
1641 state, input.name, input.count
1642 )))
1643 },
1644 )
1645 .build();
1646
1647 assert_eq!(tool.name, "test_extractor");
1648 assert_eq!(tool.description.as_deref(), Some("Test extractor handler"));
1649
1650 let result = tool
1652 .call(serde_json::json!({"name": "test", "count": 42}))
1653 .await;
1654 assert!(!result.is_error);
1655 }
1656
1657 #[tokio::test]
1658 #[allow(deprecated)]
1659 async fn test_tool_builder_extractor_handler_typed() {
1660 use crate::ToolBuilder;
1661
1662 let state = Arc::new("typed-state".to_string());
1663
1664 let tool = ToolBuilder::new("test_typed")
1665 .description("Test typed extractor handler")
1666 .extractor_handler_typed::<_, _, _, TestInput>(
1667 state,
1668 |State(state): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1669 Ok(CallToolResult::text(format!(
1670 "{}: {} - {}",
1671 state, input.name, input.count
1672 )))
1673 },
1674 )
1675 .build();
1676
1677 assert_eq!(tool.name, "test_typed");
1678
1679 let def = tool.definition();
1681 let schema = def.input_schema;
1682 assert!(schema.get("properties").is_some());
1683
1684 let result = tool
1686 .call(serde_json::json!({"name": "world", "count": 99}))
1687 .await;
1688 assert!(!result.is_error);
1689 }
1690
1691 #[tokio::test]
1692 async fn test_extractor_handler_auto_schema() {
1693 use crate::ToolBuilder;
1694
1695 let state = Arc::new("auto-schema".to_string());
1696
1697 let tool = ToolBuilder::new("test_auto_schema")
1699 .description("Test auto schema detection")
1700 .extractor_handler(
1701 state,
1702 |State(state): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1703 Ok(CallToolResult::text(format!(
1704 "{}: {} - {}",
1705 state, input.name, input.count
1706 )))
1707 },
1708 )
1709 .build();
1710
1711 let def = tool.definition();
1713 let schema = def.input_schema;
1714 assert!(
1715 schema.get("properties").is_some(),
1716 "Schema should have properties from TestInput, got: {}",
1717 schema
1718 );
1719 let props = schema.get("properties").unwrap();
1720 assert!(
1721 props.get("name").is_some(),
1722 "Schema should have 'name' property"
1723 );
1724 assert!(
1725 props.get("count").is_some(),
1726 "Schema should have 'count' property"
1727 );
1728
1729 let result = tool
1731 .call(serde_json::json!({"name": "world", "count": 99}))
1732 .await;
1733 assert!(!result.is_error);
1734 }
1735
1736 #[test]
1737 fn test_extractor_handler_no_json_fallback() {
1738 use crate::ToolBuilder;
1739
1740 let tool = ToolBuilder::new("test_no_json")
1742 .description("Test no json fallback")
1743 .extractor_handler((), |RawArgs(args): RawArgs| async move {
1744 Ok(CallToolResult::json(args))
1745 })
1746 .build();
1747
1748 let def = tool.definition();
1749 let schema = def.input_schema;
1750 assert_eq!(
1751 schema.get("type").and_then(|v| v.as_str()),
1752 Some("object"),
1753 "Schema should be generic object"
1754 );
1755 assert_eq!(
1756 schema.get("additionalProperties").and_then(|v| v.as_bool()),
1757 Some(true),
1758 "Schema should allow additional properties"
1759 );
1760 assert!(
1762 schema.get("properties").is_none(),
1763 "Generic schema should not have specific properties"
1764 );
1765 }
1766
1767 #[tokio::test]
1768 async fn test_extractor_handler_with_layer() {
1769 use crate::ToolBuilder;
1770 use std::time::Duration;
1771 use tower::timeout::TimeoutLayer;
1772
1773 let state = Arc::new("layered".to_string());
1774
1775 let tool = ToolBuilder::new("test_extractor_layer")
1776 .description("Test extractor handler with layer")
1777 .extractor_handler(
1778 state,
1779 |State(s): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1780 Ok(CallToolResult::text(format!("{}: {}", s, input.name)))
1781 },
1782 )
1783 .layer(TimeoutLayer::new(Duration::from_secs(5)))
1784 .build();
1785
1786 let result = tool
1788 .call(serde_json::json!({"name": "test", "count": 1}))
1789 .await;
1790 assert!(!result.is_error);
1791 assert_eq!(result.first_text().unwrap(), "layered: test");
1792
1793 let def = tool.definition();
1795 let schema = def.input_schema;
1796 assert!(
1797 schema.get("properties").is_some(),
1798 "Schema should have properties even with layer"
1799 );
1800 }
1801
1802 #[tokio::test]
1803 async fn test_extractor_handler_with_timeout_layer() {
1804 use crate::ToolBuilder;
1805 use std::time::Duration;
1806 use tower::timeout::TimeoutLayer;
1807
1808 let tool = ToolBuilder::new("test_extractor_timeout")
1809 .description("Test extractor handler timeout")
1810 .extractor_handler((), |Json(input): Json<TestInput>| async move {
1811 tokio::time::sleep(Duration::from_millis(200)).await;
1812 Ok(CallToolResult::text(input.name.to_string()))
1813 })
1814 .layer(TimeoutLayer::new(Duration::from_millis(50)))
1815 .build();
1816
1817 let result = tool
1819 .call(serde_json::json!({"name": "slow", "count": 1}))
1820 .await;
1821 assert!(result.is_error);
1822 let msg = result.first_text().unwrap().to_lowercase();
1823 assert!(
1824 msg.contains("timed out") || msg.contains("timeout") || msg.contains("elapsed"),
1825 "Expected timeout error, got: {}",
1826 msg
1827 );
1828 }
1829
1830 #[tokio::test]
1831 async fn test_extractor_handler_with_multiple_layers() {
1832 use crate::ToolBuilder;
1833 use std::time::Duration;
1834 use tower::limit::ConcurrencyLimitLayer;
1835 use tower::timeout::TimeoutLayer;
1836
1837 let state = Arc::new("multi".to_string());
1838
1839 let tool = ToolBuilder::new("test_multi_layer")
1840 .description("Test multiple layers")
1841 .extractor_handler(
1842 state,
1843 |State(s): State<Arc<String>>, Json(input): Json<TestInput>| async move {
1844 Ok(CallToolResult::text(format!("{}: {}", s, input.name)))
1845 },
1846 )
1847 .layer(TimeoutLayer::new(Duration::from_secs(5)))
1848 .layer(ConcurrencyLimitLayer::new(10))
1849 .build();
1850
1851 let result = tool
1852 .call(serde_json::json!({"name": "test", "count": 1}))
1853 .await;
1854 assert!(!result.is_error);
1855 assert_eq!(result.first_text().unwrap(), "multi: test");
1856 }
1857}