clickup_cli/commands/
mcp_cmd.rs1use crate::error::CliError;
2use crate::mcp::filter::{Filter, RawFilter};
3use clap::Subcommand;
4
5#[derive(Subcommand)]
6pub enum McpCommands {
7 Serve {
9 #[arg(long, value_name = "NAME")]
11 profile: Option<String>,
12
13 #[arg(long)]
15 read_only: bool,
16
17 #[arg(long, value_name = "LIST", value_delimiter = ',')]
19 groups: Option<Vec<String>>,
20
21 #[arg(long, value_name = "LIST", value_delimiter = ',')]
23 exclude_groups: Option<Vec<String>>,
24
25 #[arg(long, value_name = "LIST", value_delimiter = ',')]
27 tools: Option<Vec<String>>,
28
29 #[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.or_else(|| env_list("CLICKUP_MCP_EXCLUDE_GROUPS")),
71 tools: tools.or_else(|| env_list("CLICKUP_MCP_TOOLS")),
72 exclude_tools: exclude_tools.or_else(|| env_list("CLICKUP_MCP_EXCLUDE_TOOLS")),
73 };
74 let filter = Filter::resolve(raw).map_err(|e| CliError::ConfigError(e.to_string()))?;
75 crate::mcp::serve(filter)
76 .await
77 .map_err(|e| CliError::ConfigError(e.to_string()))
78 }
79 }
80}