@@ -137,10 +137,24 @@ use crate::{
137137 tool:: { CallToolHandler , DynCallToolHandler , ToolCallContext } ,
138138 tool_name_validation:: validate_and_warn_tool_name,
139139 } ,
140- model:: { CallToolResult , Tool , ToolAnnotations } ,
140+ model:: { CallToolResult , Content , ErrorCode , Tool , ToolAnnotations } ,
141141 service:: { MaybeBoxFuture , MaybeSend } ,
142142} ;
143143
144+ const TOOL_ARGUMENT_DESERIALIZATION_ERROR_PREFIX : & str = "failed to deserialize parameters:" ;
145+
146+ fn into_tool_argument_error ( error : crate :: ErrorData ) -> Result < CallToolResult , crate :: ErrorData > {
147+ if error. code == ErrorCode :: INVALID_PARAMS
148+ && error
149+ . message
150+ . starts_with ( TOOL_ARGUMENT_DESERIALIZATION_ERROR_PREFIX )
151+ {
152+ return Ok ( CallToolResult :: error ( vec ! [ Content :: text( error. message) ] ) ) ;
153+ }
154+
155+ Err ( error)
156+ }
157+
144158#[ non_exhaustive]
145159pub struct ToolRoute < S > {
146160 #[ allow( clippy:: type_complexity) ]
@@ -555,7 +569,10 @@ where
555569 . get ( name)
556570 . ok_or_else ( || crate :: ErrorData :: invalid_params ( "tool not found" , None ) ) ?;
557571
558- let result = ( item. call ) ( context) . await ?;
572+ let result = match ( item. call ) ( context) . await {
573+ Ok ( result) => result,
574+ Err ( error) => return into_tool_argument_error ( error) ,
575+ } ;
559576
560577 Ok ( result)
561578 }
@@ -611,13 +628,65 @@ mod tests {
611628 use super :: * ;
612629 use crate :: {
613630 RoleServer ,
631+ handler:: server:: wrapper:: Parameters ,
614632 model:: { CallToolRequestParams , ErrorCode , NumberOrString } ,
615633 service:: { AtomicU32RequestIdProvider , Peer , RequestContext } ,
616634 } ;
617635
618636 struct DummyService ;
619637 impl crate :: handler:: server:: ServerHandler for DummyService { }
620638
639+ #[ derive( serde:: Deserialize , schemars:: JsonSchema ) ]
640+ struct RequiredParams {
641+ project : String ,
642+ }
643+
644+ fn requires_params ( Parameters ( params) : Parameters < RequiredParams > ) -> String {
645+ params. project
646+ }
647+
648+ #[ tokio:: test]
649+ async fn test_argument_deserialization_error_returns_tool_error_result ( ) {
650+ let service = DummyService ;
651+ let router = ToolRouter :: new ( ) . with_route ( ToolRoute :: new (
652+ crate :: model:: Tool :: new (
653+ "requires_params" ,
654+ "requires params" ,
655+ Arc :: new ( Default :: default ( ) ) ,
656+ ) ,
657+ requires_params,
658+ ) ) ;
659+
660+ let id_provider: Arc < dyn crate :: service:: RequestIdProvider > =
661+ Arc :: new ( AtomicU32RequestIdProvider :: default ( ) ) ;
662+ let ( peer, _rx) = Peer :: < RoleServer > :: new ( id_provider, None ) ;
663+ let ctx = crate :: handler:: server:: tool:: ToolCallContext :: new (
664+ & service,
665+ CallToolRequestParams {
666+ meta : None ,
667+ name : Cow :: Borrowed ( "requires_params" ) ,
668+ arguments : Some ( Default :: default ( ) ) ,
669+ task : None ,
670+ } ,
671+ RequestContext :: new ( NumberOrString :: Number ( 1 ) , peer) ,
672+ ) ;
673+
674+ let result = router
675+ . call ( ctx)
676+ . await
677+ . expect ( "argument validation should be a tool result" ) ;
678+ assert_eq ! ( result. is_error, Some ( true ) ) ;
679+
680+ let text = result
681+ . content
682+ . first ( )
683+ . and_then ( |content| content. raw . as_text ( ) )
684+ . map ( |text| text. text . as_str ( ) )
685+ . expect ( "tool error result should include text" ) ;
686+ assert ! ( text. contains( "failed to deserialize parameters" ) ) ;
687+ assert ! ( text. contains( "missing field `project`" ) ) ;
688+ }
689+
621690 #[ tokio:: test]
622691 async fn test_call_disabled_tool_returns_error ( ) {
623692 let service = DummyService ;
0 commit comments