Skip to main content

clickup_cli/commands/
mcp_cmd.rs

1use clap::Subcommand;
2use crate::error::CliError;
3use crate::mcp::filter::{Filter, RawFilter};
4
5#[derive(Subcommand)]
6pub enum McpCommands {
7    /// Start the MCP server (reads JSON-RPC from stdin, writes to stdout).
8    Serve {
9        /// Preset tool bundle: `all` (default), `read`, `safe`.
10        #[arg(long, value_name = "NAME")]
11        profile: Option<String>,
12
13        /// Shortcut for `--profile read`.
14        #[arg(long)]
15        read_only: bool,
16
17        /// Include only tools in these resource groups (comma-separated).
18        #[arg(long, value_name = "LIST", value_delimiter = ',')]
19        groups: Option<Vec<String>>,
20
21        /// Drop tools in these resource groups (comma-separated).
22        #[arg(long, value_name = "LIST", value_delimiter = ',')]
23        exclude_groups: Option<Vec<String>>,
24
25        /// Include only these tools by exact name (comma-separated).
26        #[arg(long, value_name = "LIST", value_delimiter = ',')]
27        tools: Option<Vec<String>>,
28
29        /// Drop these tools by exact name (comma-separated).
30        #[arg(long, value_name = "LIST", value_delimiter = ',')]
31        exclude_tools: Option<Vec<String>>,
32    },
33}
34
35fn env_list(var: &str) -> Option<Vec<String>> {
36    std::env::var(var).ok().map(|v| {
37        v.split(',')
38            .map(str::trim)
39            .filter(|s| !s.is_empty())
40            .map(str::to_string)
41            .collect()
42    })
43}
44
45fn env_bool(var: &str) -> bool {
46    matches!(
47        std::env::var(var).ok().as_deref(),
48        Some("1" | "true" | "yes" | "on")
49    )
50}
51
52fn env_string(var: &str) -> Option<String> {
53    std::env::var(var).ok().filter(|s| !s.is_empty())
54}
55
56pub async fn execute(command: McpCommands) -> Result<(), CliError> {
57    match command {
58        McpCommands::Serve {
59            profile,
60            read_only,
61            groups,
62            exclude_groups,
63            tools,
64            exclude_tools,
65        } => {
66            let raw = RawFilter {
67                profile: profile.or_else(|| env_string("CLICKUP_MCP_PROFILE")),
68                read_only: read_only || env_bool("CLICKUP_MCP_READ_ONLY"),
69                groups: groups.or_else(|| env_list("CLICKUP_MCP_GROUPS")),
70                exclude_groups: exclude_groups
71                    .or_else(|| env_list("CLICKUP_MCP_EXCLUDE_GROUPS")),
72                tools: tools.or_else(|| env_list("CLICKUP_MCP_TOOLS")),
73                exclude_tools: exclude_tools
74                    .or_else(|| env_list("CLICKUP_MCP_EXCLUDE_TOOLS")),
75            };
76            let filter = Filter::resolve(raw)
77                .map_err(|e| CliError::ConfigError(e.to_string()))?;
78            crate::mcp::serve(filter)
79                .await
80                .map_err(|e| CliError::ConfigError(e.to_string()))
81        }
82    }
83}