claude_code_acp/mcp/tools/
ask_user_question.rs

1//! Ask User Question tool implementation
2//!
3//! Allows the agent to ask the user a question during execution.
4//!
5//! This tool is primarily for permission control. The actual question
6//! asking is handled by the Claude agent SDK through its own mechanisms.
7
8use async_trait::async_trait;
9use serde_json::{Value, json};
10
11use crate::mcp::{Tool, ToolContext, ToolResult};
12
13/// Ask User Question tool
14#[derive(Debug, Clone, Default)]
15pub struct AskUserQuestionTool;
16
17impl AskUserQuestionTool {
18    pub fn new() -> Self {
19        Self
20    }
21}
22
23#[async_trait]
24impl Tool for AskUserQuestionTool {
25    fn name(&self) -> &str {
26        "AskUserQuestion"
27    }
28
29    fn description(&self) -> &str {
30        "Ask the user a question during execution"
31    }
32
33    fn input_schema(&self) -> Value {
34        json!({
35            "type": "object",
36            "title": "ask_user_question",
37            "description": "Ask the user a question during execution",
38            "properties": {
39                "question": {
40                    "type": "string",
41                    "description": "The question to ask the user"
42                },
43                "options": {
44                    "type": "array",
45                    "items": {"type": "string"},
46                    "description": "Optional predefined options for the user to choose from"
47                },
48                "allow_freeform": {
49                    "type": "boolean",
50                    "description": "Whether to allow free-form text input",
51                    "default": false
52                }
53            },
54            "required": ["question"]
55        })
56    }
57
58    async fn execute(&self, input: Value, _context: &ToolContext) -> ToolResult {
59        // Extract parameters
60        let question = input.get("question").and_then(|v| v.as_str()).unwrap_or("");
61
62        let options = input.get("options").and_then(|v| v.as_array());
63        let allow_freeform = input
64            .get("allow_freeform")
65            .and_then(|v| v.as_bool())
66            .unwrap_or(false);
67
68        // Return success - the actual question asking is handled by the SDK
69        ToolResult::success(format!("Question: '{}'", question)).with_metadata(json!({
70            "question": question,
71            "has_options": options.is_some(),
72            "allow_freeform": allow_freeform
73        }))
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_ask_user_question_name() {
83        let tool = AskUserQuestionTool;
84        assert_eq!(tool.name(), "AskUserQuestion");
85    }
86
87    #[test]
88    fn test_ask_user_question_input_schema() {
89        let tool = AskUserQuestionTool;
90        let schema = tool.input_schema();
91
92        assert_eq!(schema["type"], "object");
93        assert_eq!(schema["title"], "ask_user_question");
94        assert!(schema["properties"]["question"].is_object());
95    }
96
97    #[tokio::test]
98    async fn test_ask_user_question_execute() {
99        let tool = AskUserQuestionTool;
100        let context = ToolContext::new("test-session", std::path::Path::new("/tmp"));
101
102        let input = json!({
103            "question": "What is your favorite color?",
104            "options": ["Red", "Green", "Blue"],
105            "allow_freeform": true
106        });
107
108        let result = tool.execute(input, &context).await;
109
110        // Should succeed
111        assert!(!result.is_error);
112    }
113}