@@ -7,7 +7,7 @@ use tokio::sync::Mutex as AMutex;
77
88use crate :: at_commands:: at_commands:: AtCommandsContext ;
99use crate :: call_validation:: ContextEnum ;
10- use crate :: tools:: tools_description:: { MatchConfirmDenyResult , Tool , ToolConfig , ToolDesc , ToolGroupCategory , ToolSource , ToolSourceType } ;
10+ use crate :: tools:: tools_description:: { MatchConfirmDeny , MatchConfirmDenyResult , Tool , ToolConfig , ToolDesc , ToolGroupCategory , ToolSource , ToolSourceType } ;
1111use crate :: tools:: tools_list:: get_integration_tools;
1212
1313pub struct ToolMcpCall { }
@@ -18,7 +18,7 @@ impl Tool for ToolMcpCall {
1818 ToolDesc {
1919 name : "mcp_call" . to_string ( ) ,
2020 experimental : false ,
21- allow_parallel : true ,
21+ allow_parallel : false ,
2222 description : "Execute any MCP tool by name with the given arguments. \
2323 Use `mcp_tool_search` first to discover the tool name and its input schema, \
2424 then call this with the exact arguments the schema requires."
@@ -51,6 +51,53 @@ impl Tool for ToolMcpCall {
5151 Ok ( ToolConfig { enabled : true , allow_parallel : None } )
5252 }
5353
54+ /// Proxy confirmation/deny checks to the underlying MCP tool so that
55+ /// `check_tools_confirmation()` can trigger the normal pause/deny flow
56+ /// before `tool_execute` is ever called.
57+ async fn match_against_confirm_deny (
58+ & self ,
59+ ccx : Arc < AMutex < AtCommandsContext > > ,
60+ args : & HashMap < String , Value > ,
61+ ) -> Result < crate :: tools:: tools_description:: MatchConfirmDeny , String > {
62+ let tool_name = match args. get ( "tool_name" ) . and_then ( |v| v. as_str ( ) ) {
63+ Some ( n) => n. to_string ( ) ,
64+ None => return Ok ( MatchConfirmDeny {
65+ result : MatchConfirmDenyResult :: PASS ,
66+ command : String :: new ( ) ,
67+ rule : String :: new ( ) ,
68+ } ) ,
69+ } ;
70+
71+ let tool_args: HashMap < String , Value > = args. get ( "args" )
72+ . and_then ( |v| v. as_object ( ) )
73+ . map ( |obj| obj. iter ( ) . map ( |( k, v) | ( k. clone ( ) , v. clone ( ) ) ) . collect ( ) )
74+ . unwrap_or_default ( ) ;
75+
76+ let gcx = ccx. lock ( ) . await . global_context . clone ( ) ;
77+ let mut integration_groups = get_integration_tools ( gcx) . await ;
78+
79+ // Move the tool out of the groups so it can be awaited safely.
80+ let mut found_tool: Option < Box < dyn Tool + Send > > = None ;
81+ ' outer: for group in & mut integration_groups {
82+ if !matches ! ( group. category, ToolGroupCategory :: MCP ) {
83+ continue ;
84+ }
85+ if let Some ( pos) = group. tools . iter ( ) . position ( |t| t. tool_description ( ) . name == tool_name) {
86+ found_tool = Some ( group. tools . remove ( pos) ) ;
87+ break ' outer;
88+ }
89+ }
90+
91+ match found_tool {
92+ Some ( tool) => tool. match_against_confirm_deny ( ccx, & tool_args) . await ,
93+ None => Ok ( MatchConfirmDeny {
94+ result : MatchConfirmDenyResult :: PASS ,
95+ command : String :: new ( ) ,
96+ rule : String :: new ( ) ,
97+ } ) ,
98+ }
99+ }
100+
54101 async fn tool_execute (
55102 & mut self ,
56103 ccx : Arc < AMutex < AtCommandsContext > > ,
@@ -62,10 +109,13 @@ impl Tool for ToolMcpCall {
62109 . ok_or_else ( || "mcp_call: missing required argument 'tool_name'" . to_string ( ) ) ?
63110 . to_string ( ) ;
64111
65- let tool_args: HashMap < String , Value > = args. get ( "args" )
66- . and_then ( |v| v. as_object ( ) )
67- . map ( |obj| obj. iter ( ) . map ( |( k, v) | ( k. clone ( ) , v. clone ( ) ) ) . collect ( ) )
68- . unwrap_or_default ( ) ;
112+ let tool_args: HashMap < String , Value > = match args. get ( "args" ) {
113+ None => return Err ( "mcp_call: missing required argument 'args'" . to_string ( ) ) ,
114+ Some ( v) => match v. as_object ( ) {
115+ None => return Err ( "mcp_call: argument 'args' must be an object" . to_string ( ) ) ,
116+ Some ( obj) => obj. iter ( ) . map ( |( k, v) | ( k. clone ( ) , v. clone ( ) ) ) . collect ( ) ,
117+ } ,
118+ } ;
69119
70120 let gcx = ccx. lock ( ) . await . global_context . clone ( ) ;
71121 let mut integration_groups = get_integration_tools ( gcx) . await ;
@@ -89,19 +139,8 @@ impl Tool for ToolMcpCall {
89139 )
90140 } ) ?;
91141
92- let confirm_result = tool. match_against_confirm_deny ( ccx. clone ( ) , & tool_args) . await
93- . map_err ( |e| format ! ( "mcp_call confirmation check failed: {}" , e) ) ?;
94- if confirm_result. result == MatchConfirmDenyResult :: DENY {
95- return Err ( format ! (
96- "MCP tool '{}' was denied by rule '{}'" ,
97- tool_name, confirm_result. rule
98- ) ) ;
99- }
100- if confirm_result. result == MatchConfirmDenyResult :: CONFIRMATION {
101- return Err ( format ! (
102- "MCP tool '{}' requires user confirmation (rule '{}'). Use the tool directly instead of via mcp_call to enable the confirmation flow." ,
103- tool_name, confirm_result. rule
104- ) ) ;
142+ if !tool. config ( ) . unwrap_or_default ( ) . enabled {
143+ return Err ( format ! ( "MCP tool '{}' is disabled." , tool_name) ) ;
105144 }
106145
107146 tool. tool_execute ( ccx, tool_call_id, & tool_args) . await
0 commit comments