claude_code_acp/mcp/tools/
slash_command.rs

1//! SlashCommand tool implementation
2//!
3//! Allows the agent to execute user-invocable slash commands.
4//!
5//! This tool is primarily for permission control. The actual command
6//! execution is handled by the Claude agent SDK through its own mechanisms.
7
8use async_trait::async_trait;
9use serde::Deserialize;
10use serde_json::{Value, json};
11
12use crate::mcp::{Tool, ToolContext, ToolResult};
13
14/// SlashCommand tool
15#[derive(Debug, Default)]
16pub struct SlashCommandTool;
17
18impl SlashCommandTool {
19    pub fn new() -> Self {
20        Self
21    }
22}
23
24/// Input parameters for SlashCommand
25#[derive(Debug, Deserialize)]
26struct SlashCommandInput {
27    /// The slash command to execute (e.g., "commit", "review-pr")
28    command: String,
29    /// Optional arguments for the command
30    #[serde(default)]
31    args: String,
32}
33
34#[async_trait]
35impl Tool for SlashCommandTool {
36    fn name(&self) -> &str {
37        "SlashCommand"
38    }
39
40    fn description(&self) -> &str {
41        "Execute a slash command. Available commands are sent to the client via available_commands_update."
42    }
43
44    fn input_schema(&self) -> Value {
45        json!({
46            "type": "object",
47            "title": "slash_command",
48            "description": "Execute a user-invocable slash command",
49            "properties": {
50                "command": {
51                    "type": "string",
52                    "description": "The slash command to execute (e.g., 'commit', 'review-pr')"
53                },
54                "args": {
55                    "type": "string",
56                    "description": "Optional arguments for the command"
57                }
58            },
59            "required": ["command"]
60        })
61    }
62
63    async fn execute(&self, input: Value, _context: &ToolContext) -> ToolResult {
64        // Parse input
65        let params: SlashCommandInput = match serde_json::from_value(input) {
66            Ok(p) => p,
67            Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
68        };
69
70        // Build the full command string
71        let full_command = if params.args.is_empty() {
72            format!("/{}", params.command)
73        } else {
74            format!("/{} {}", params.command, params.args)
75        };
76
77        // Return success - the actual command execution is handled by the SDK
78        ToolResult::success(format!("Slash command: '{}'", full_command)).with_metadata(json!({
79            "command": params.command,
80            "args": params.args,
81            "full_command": full_command
82        }))
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_slash_command_tool_properties() {
92        let tool = SlashCommandTool;
93        assert_eq!(tool.name(), "SlashCommand");
94        assert!(tool.description().contains("slash command"));
95    }
96
97    #[test]
98    fn test_slash_command_input_schema() {
99        let tool = SlashCommandTool;
100        let schema = tool.input_schema();
101
102        assert_eq!(schema["type"], "object");
103        assert_eq!(schema["title"], "slash_command");
104        assert!(schema["properties"]["command"].is_object());
105        assert!(
106            schema["required"]
107                .as_array()
108                .unwrap()
109                .contains(&json!("command"))
110        );
111    }
112}