1use crate::client::ClickUpClient;
2use crate::config::Config;
3use crate::output::compact_items;
4use serde_json::{json, Value};
5use tokio::io::{AsyncBufReadExt, BufReader};
6
7fn ok_response(id: &Value, result: Value) -> Value {
10 json!({"jsonrpc":"2.0","id":id,"result":result})
11}
12
13fn error_response(id: &Value, code: i64, message: &str) -> Value {
14 json!({"jsonrpc":"2.0","id":id,"error":{"code":code,"message":message}})
15}
16
17fn tool_result(text: String) -> Value {
18 json!({"content":[{"type":"text","text":text}]})
19}
20
21fn tool_error(msg: String) -> Value {
22 json!({"content":[{"type":"text","text":msg}],"isError":true})
23}
24
25fn tool_list() -> Value {
28 json!([
29 {
30 "name": "clickup_whoami",
31 "description": "Get the currently authenticated ClickUp user",
32 "inputSchema": {
33 "type": "object",
34 "properties": {},
35 "required": []
36 }
37 },
38 {
39 "name": "clickup_workspace_list",
40 "description": "List all ClickUp workspaces (teams) accessible to the current user",
41 "inputSchema": {
42 "type": "object",
43 "properties": {},
44 "required": []
45 }
46 },
47 {
48 "name": "clickup_space_list",
49 "description": "List spaces in a workspace",
50 "inputSchema": {
51 "type": "object",
52 "properties": {
53 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
54 "archived": {"type": "boolean", "description": "Include archived spaces"}
55 },
56 "required": []
57 }
58 },
59 {
60 "name": "clickup_folder_list",
61 "description": "List folders in a space",
62 "inputSchema": {
63 "type": "object",
64 "properties": {
65 "space_id": {"type": "string", "description": "Space ID"},
66 "archived": {"type": "boolean", "description": "Include archived folders"}
67 },
68 "required": ["space_id"]
69 }
70 },
71 {
72 "name": "clickup_list_list",
73 "description": "List ClickUp lists in a folder or space (folderless lists)",
74 "inputSchema": {
75 "type": "object",
76 "properties": {
77 "folder_id": {"type": "string", "description": "Folder ID (mutually exclusive with space_id)"},
78 "space_id": {"type": "string", "description": "Space ID for folderless lists (mutually exclusive with folder_id)"},
79 "archived": {"type": "boolean", "description": "Include archived lists"}
80 },
81 "required": []
82 }
83 },
84 {
85 "name": "clickup_task_list",
86 "description": "List tasks in a ClickUp list",
87 "inputSchema": {
88 "type": "object",
89 "properties": {
90 "list_id": {"type": "string", "description": "List ID"},
91 "statuses": {
92 "type": "array",
93 "items": {"type": "string"},
94 "description": "Filter by status names"
95 },
96 "assignees": {
97 "type": "array",
98 "items": {"type": "string"},
99 "description": "Filter by assignee user IDs"
100 },
101 "include_closed": {"type": "boolean", "description": "Include closed tasks"}
102 },
103 "required": ["list_id"]
104 }
105 },
106 {
107 "name": "clickup_task_get",
108 "description": "Get details of a specific ClickUp task",
109 "inputSchema": {
110 "type": "object",
111 "properties": {
112 "task_id": {"type": "string", "description": "Task ID"},
113 "include_subtasks": {"type": "boolean", "description": "Include subtasks in the response"}
114 },
115 "required": ["task_id"]
116 }
117 },
118 {
119 "name": "clickup_task_create",
120 "description": "Create a new task in a ClickUp list",
121 "inputSchema": {
122 "type": "object",
123 "properties": {
124 "list_id": {"type": "string", "description": "List ID to create the task in"},
125 "name": {"type": "string", "description": "Task name"},
126 "description": {"type": "string", "description": "Task description (markdown supported)"},
127 "status": {"type": "string", "description": "Task status"},
128 "priority": {"type": "integer", "description": "Priority (1=urgent, 2=high, 3=normal, 4=low)"},
129 "assignees": {
130 "type": "array",
131 "items": {"type": "integer"},
132 "description": "List of assignee user IDs"
133 },
134 "tags": {
135 "type": "array",
136 "items": {"type": "string"},
137 "description": "List of tag names"
138 },
139 "due_date": {"type": "integer", "description": "Due date as Unix timestamp (milliseconds)"}
140 },
141 "required": ["list_id", "name"]
142 }
143 },
144 {
145 "name": "clickup_task_update",
146 "description": "Update an existing ClickUp task",
147 "inputSchema": {
148 "type": "object",
149 "properties": {
150 "task_id": {"type": "string", "description": "Task ID"},
151 "name": {"type": "string", "description": "New task name"},
152 "status": {"type": "string", "description": "New status"},
153 "priority": {"type": "integer", "description": "New priority (1=urgent, 2=high, 3=normal, 4=low)"},
154 "description": {"type": "string", "description": "New description"},
155 "add_assignees": {
156 "type": "array",
157 "items": {"type": "integer"},
158 "description": "User IDs to add as assignees"
159 },
160 "rem_assignees": {
161 "type": "array",
162 "items": {"type": "integer"},
163 "description": "User IDs to remove from assignees"
164 }
165 },
166 "required": ["task_id"]
167 }
168 },
169 {
170 "name": "clickup_task_delete",
171 "description": "Delete a ClickUp task",
172 "inputSchema": {
173 "type": "object",
174 "properties": {
175 "task_id": {"type": "string", "description": "Task ID"}
176 },
177 "required": ["task_id"]
178 }
179 },
180 {
181 "name": "clickup_task_search",
182 "description": "Search tasks across a workspace with optional filters",
183 "inputSchema": {
184 "type": "object",
185 "properties": {
186 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
187 "space_ids": {
188 "type": "array",
189 "items": {"type": "string"},
190 "description": "Filter by space IDs"
191 },
192 "list_ids": {
193 "type": "array",
194 "items": {"type": "string"},
195 "description": "Filter by list IDs"
196 },
197 "statuses": {
198 "type": "array",
199 "items": {"type": "string"},
200 "description": "Filter by status names"
201 },
202 "assignees": {
203 "type": "array",
204 "items": {"type": "string"},
205 "description": "Filter by assignee user IDs"
206 }
207 },
208 "required": []
209 }
210 },
211 {
212 "name": "clickup_comment_list",
213 "description": "List comments on a ClickUp task",
214 "inputSchema": {
215 "type": "object",
216 "properties": {
217 "task_id": {"type": "string", "description": "Task ID"}
218 },
219 "required": ["task_id"]
220 }
221 },
222 {
223 "name": "clickup_comment_create",
224 "description": "Create a comment on a ClickUp task",
225 "inputSchema": {
226 "type": "object",
227 "properties": {
228 "task_id": {"type": "string", "description": "Task ID"},
229 "text": {"type": "string", "description": "Comment text"},
230 "assignee": {"type": "integer", "description": "Assign the comment to a user ID"},
231 "notify_all": {"type": "boolean", "description": "Notify all assignees"}
232 },
233 "required": ["task_id", "text"]
234 }
235 },
236 {
237 "name": "clickup_field_list",
238 "description": "List custom fields for a ClickUp list",
239 "inputSchema": {
240 "type": "object",
241 "properties": {
242 "list_id": {"type": "string", "description": "List ID"}
243 },
244 "required": ["list_id"]
245 }
246 },
247 {
248 "name": "clickup_field_set",
249 "description": "Set a custom field value on a ClickUp task",
250 "inputSchema": {
251 "type": "object",
252 "properties": {
253 "task_id": {"type": "string", "description": "Task ID"},
254 "field_id": {"type": "string", "description": "Custom field ID"},
255 "value": {"description": "Value to set (type depends on the custom field type)"}
256 },
257 "required": ["task_id", "field_id", "value"]
258 }
259 },
260 {
261 "name": "clickup_time_start",
262 "description": "Start a time tracking entry for a task",
263 "inputSchema": {
264 "type": "object",
265 "properties": {
266 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
267 "task_id": {"type": "string", "description": "Task ID to track time against"},
268 "description": {"type": "string", "description": "Description of the time entry"},
269 "billable": {"type": "boolean", "description": "Whether this entry is billable"}
270 },
271 "required": []
272 }
273 },
274 {
275 "name": "clickup_time_stop",
276 "description": "Stop the currently running time tracking entry",
277 "inputSchema": {
278 "type": "object",
279 "properties": {
280 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
281 },
282 "required": []
283 }
284 },
285 {
286 "name": "clickup_time_list",
287 "description": "List time tracking entries for a workspace",
288 "inputSchema": {
289 "type": "object",
290 "properties": {
291 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
292 "start_date": {"type": "integer", "description": "Start date as Unix timestamp (milliseconds)"},
293 "end_date": {"type": "integer", "description": "End date as Unix timestamp (milliseconds)"},
294 "task_id": {"type": "string", "description": "Filter by task ID"}
295 },
296 "required": []
297 }
298 },
299 {
300 "name": "clickup_checklist_create",
301 "description": "Create a checklist on a ClickUp task",
302 "inputSchema": {
303 "type": "object",
304 "properties": {
305 "task_id": {"type": "string", "description": "Task ID"},
306 "name": {"type": "string", "description": "Checklist name"}
307 },
308 "required": ["task_id", "name"]
309 }
310 },
311 {
312 "name": "clickup_checklist_delete",
313 "description": "Delete a checklist from a ClickUp task",
314 "inputSchema": {
315 "type": "object",
316 "properties": {
317 "checklist_id": {"type": "string", "description": "Checklist ID"}
318 },
319 "required": ["checklist_id"]
320 }
321 },
322 {
323 "name": "clickup_goal_list",
324 "description": "List goals in a workspace",
325 "inputSchema": {
326 "type": "object",
327 "properties": {
328 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
329 },
330 "required": []
331 }
332 },
333 {
334 "name": "clickup_goal_get",
335 "description": "Get details of a specific goal",
336 "inputSchema": {
337 "type": "object",
338 "properties": {
339 "goal_id": {"type": "string", "description": "Goal ID"}
340 },
341 "required": ["goal_id"]
342 }
343 },
344 {
345 "name": "clickup_goal_create",
346 "description": "Create a new goal in a workspace",
347 "inputSchema": {
348 "type": "object",
349 "properties": {
350 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
351 "name": {"type": "string", "description": "Goal name"},
352 "due_date": {"type": "integer", "description": "Due date as Unix timestamp (milliseconds)"},
353 "description": {"type": "string", "description": "Goal description"},
354 "owner_ids": {
355 "type": "array",
356 "items": {"type": "integer"},
357 "description": "List of owner user IDs"
358 }
359 },
360 "required": ["name"]
361 }
362 },
363 {
364 "name": "clickup_goal_update",
365 "description": "Update an existing goal",
366 "inputSchema": {
367 "type": "object",
368 "properties": {
369 "goal_id": {"type": "string", "description": "Goal ID"},
370 "name": {"type": "string", "description": "New goal name"},
371 "due_date": {"type": "integer", "description": "New due date as Unix timestamp (milliseconds)"},
372 "description": {"type": "string", "description": "New goal description"}
373 },
374 "required": ["goal_id"]
375 }
376 },
377 {
378 "name": "clickup_view_list",
379 "description": "List views for a space, folder, list, or workspace",
380 "inputSchema": {
381 "type": "object",
382 "properties": {
383 "space_id": {"type": "string", "description": "Space ID (mutually exclusive with other IDs)"},
384 "folder_id": {"type": "string", "description": "Folder ID (mutually exclusive with other IDs)"},
385 "list_id": {"type": "string", "description": "List ID (mutually exclusive with other IDs)"},
386 "team_id": {"type": "string", "description": "Workspace (team) ID for workspace-level views. Omit to use the default workspace from config."}
387 },
388 "required": []
389 }
390 },
391 {
392 "name": "clickup_view_tasks",
393 "description": "Get tasks in a specific view",
394 "inputSchema": {
395 "type": "object",
396 "properties": {
397 "view_id": {"type": "string", "description": "View ID"},
398 "page": {"type": "integer", "description": "Page number (0-indexed, default 0)"}
399 },
400 "required": ["view_id"]
401 }
402 },
403 {
404 "name": "clickup_doc_list",
405 "description": "List docs in a workspace",
406 "inputSchema": {
407 "type": "object",
408 "properties": {
409 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
410 },
411 "required": []
412 }
413 },
414 {
415 "name": "clickup_doc_get",
416 "description": "Get a specific doc from a workspace",
417 "inputSchema": {
418 "type": "object",
419 "properties": {
420 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
421 "doc_id": {"type": "string", "description": "Doc ID"}
422 },
423 "required": ["doc_id"]
424 }
425 },
426 {
427 "name": "clickup_doc_pages",
428 "description": "List pages in a doc",
429 "inputSchema": {
430 "type": "object",
431 "properties": {
432 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
433 "doc_id": {"type": "string", "description": "Doc ID"},
434 "content": {"type": "boolean", "description": "Include page content in the response"}
435 },
436 "required": ["doc_id"]
437 }
438 },
439 {
440 "name": "clickup_tag_list",
441 "description": "List tags for a space",
442 "inputSchema": {
443 "type": "object",
444 "properties": {
445 "space_id": {"type": "string", "description": "Space ID"}
446 },
447 "required": ["space_id"]
448 }
449 },
450 {
451 "name": "clickup_task_add_tag",
452 "description": "Add a tag to a ClickUp task",
453 "inputSchema": {
454 "type": "object",
455 "properties": {
456 "task_id": {"type": "string", "description": "Task ID"},
457 "tag_name": {"type": "string", "description": "Tag name to add"}
458 },
459 "required": ["task_id", "tag_name"]
460 }
461 },
462 {
463 "name": "clickup_task_remove_tag",
464 "description": "Remove a tag from a ClickUp task",
465 "inputSchema": {
466 "type": "object",
467 "properties": {
468 "task_id": {"type": "string", "description": "Task ID"},
469 "tag_name": {"type": "string", "description": "Tag name to remove"}
470 },
471 "required": ["task_id", "tag_name"]
472 }
473 },
474 {
475 "name": "clickup_webhook_list",
476 "description": "List webhooks for a workspace",
477 "inputSchema": {
478 "type": "object",
479 "properties": {
480 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
481 },
482 "required": []
483 }
484 },
485 {
486 "name": "clickup_member_list",
487 "description": "List members of a task or list",
488 "inputSchema": {
489 "type": "object",
490 "properties": {
491 "task_id": {"type": "string", "description": "Task ID (mutually exclusive with list_id)"},
492 "list_id": {"type": "string", "description": "List ID (mutually exclusive with task_id)"}
493 },
494 "required": []
495 }
496 },
497 {
498 "name": "clickup_template_list",
499 "description": "List task templates for a workspace",
500 "inputSchema": {
501 "type": "object",
502 "properties": {
503 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
504 "page": {"type": "integer", "description": "Page number (0-indexed, default 0)"}
505 },
506 "required": []
507 }
508 },
509 {
510 "name": "clickup_space_get",
511 "description": "Get details of a specific space",
512 "inputSchema": {
513 "type": "object",
514 "properties": {
515 "space_id": {"type": "string", "description": "Space ID"}
516 },
517 "required": ["space_id"]
518 }
519 },
520 {
521 "name": "clickup_space_create",
522 "description": "Create a new space in a workspace",
523 "inputSchema": {
524 "type": "object",
525 "properties": {
526 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
527 "name": {"type": "string", "description": "Space name"},
528 "private": {"type": "boolean", "description": "Whether the space is private"}
529 },
530 "required": ["name"]
531 }
532 },
533 {
534 "name": "clickup_space_update",
535 "description": "Update an existing space",
536 "inputSchema": {
537 "type": "object",
538 "properties": {
539 "space_id": {"type": "string", "description": "Space ID"},
540 "name": {"type": "string", "description": "New space name"},
541 "private": {"type": "boolean", "description": "Whether the space is private"},
542 "archived": {"type": "boolean", "description": "Archive/unarchive the space"}
543 },
544 "required": ["space_id"]
545 }
546 },
547 {
548 "name": "clickup_space_delete",
549 "description": "Delete a space",
550 "inputSchema": {
551 "type": "object",
552 "properties": {
553 "space_id": {"type": "string", "description": "Space ID"}
554 },
555 "required": ["space_id"]
556 }
557 },
558 {
559 "name": "clickup_folder_get",
560 "description": "Get details of a specific folder",
561 "inputSchema": {
562 "type": "object",
563 "properties": {
564 "folder_id": {"type": "string", "description": "Folder ID"}
565 },
566 "required": ["folder_id"]
567 }
568 },
569 {
570 "name": "clickup_folder_create",
571 "description": "Create a new folder in a space",
572 "inputSchema": {
573 "type": "object",
574 "properties": {
575 "space_id": {"type": "string", "description": "Space ID"},
576 "name": {"type": "string", "description": "Folder name"}
577 },
578 "required": ["space_id", "name"]
579 }
580 },
581 {
582 "name": "clickup_folder_update",
583 "description": "Update an existing folder",
584 "inputSchema": {
585 "type": "object",
586 "properties": {
587 "folder_id": {"type": "string", "description": "Folder ID"},
588 "name": {"type": "string", "description": "New folder name"}
589 },
590 "required": ["folder_id", "name"]
591 }
592 },
593 {
594 "name": "clickup_folder_delete",
595 "description": "Delete a folder",
596 "inputSchema": {
597 "type": "object",
598 "properties": {
599 "folder_id": {"type": "string", "description": "Folder ID"}
600 },
601 "required": ["folder_id"]
602 }
603 },
604 {
605 "name": "clickup_list_get",
606 "description": "Get details of a specific list",
607 "inputSchema": {
608 "type": "object",
609 "properties": {
610 "list_id": {"type": "string", "description": "List ID"}
611 },
612 "required": ["list_id"]
613 }
614 },
615 {
616 "name": "clickup_list_create",
617 "description": "Create a new list in a folder or space (folderless)",
618 "inputSchema": {
619 "type": "object",
620 "properties": {
621 "folder_id": {"type": "string", "description": "Folder ID (mutually exclusive with space_id)"},
622 "space_id": {"type": "string", "description": "Space ID for a folderless list (mutually exclusive with folder_id)"},
623 "name": {"type": "string", "description": "List name"},
624 "content": {"type": "string", "description": "List description"},
625 "due_date": {"type": "integer", "description": "Due date as Unix timestamp (milliseconds)"},
626 "status": {"type": "string", "description": "List status"}
627 },
628 "required": ["name"]
629 }
630 },
631 {
632 "name": "clickup_list_update",
633 "description": "Update an existing list",
634 "inputSchema": {
635 "type": "object",
636 "properties": {
637 "list_id": {"type": "string", "description": "List ID"},
638 "name": {"type": "string", "description": "New list name"},
639 "content": {"type": "string", "description": "New description"},
640 "due_date": {"type": "integer", "description": "Due date as Unix timestamp (milliseconds)"},
641 "status": {"type": "string", "description": "List status"}
642 },
643 "required": ["list_id"]
644 }
645 },
646 {
647 "name": "clickup_list_delete",
648 "description": "Delete a list",
649 "inputSchema": {
650 "type": "object",
651 "properties": {
652 "list_id": {"type": "string", "description": "List ID"}
653 },
654 "required": ["list_id"]
655 }
656 },
657 {
658 "name": "clickup_list_add_task",
659 "description": "Add a task to an additional list",
660 "inputSchema": {
661 "type": "object",
662 "properties": {
663 "list_id": {"type": "string", "description": "List ID"},
664 "task_id": {"type": "string", "description": "Task ID"}
665 },
666 "required": ["list_id", "task_id"]
667 }
668 },
669 {
670 "name": "clickup_list_remove_task",
671 "description": "Remove a task from an additional list",
672 "inputSchema": {
673 "type": "object",
674 "properties": {
675 "list_id": {"type": "string", "description": "List ID"},
676 "task_id": {"type": "string", "description": "Task ID"}
677 },
678 "required": ["list_id", "task_id"]
679 }
680 },
681 {
682 "name": "clickup_comment_update",
683 "description": "Update a comment",
684 "inputSchema": {
685 "type": "object",
686 "properties": {
687 "comment_id": {"type": "string", "description": "Comment ID"},
688 "text": {"type": "string", "description": "New comment text"},
689 "assignee": {"type": "integer", "description": "Reassign comment to user ID"},
690 "resolved": {"type": "boolean", "description": "Mark comment as resolved"}
691 },
692 "required": ["comment_id", "text"]
693 }
694 },
695 {
696 "name": "clickup_comment_delete",
697 "description": "Delete a comment",
698 "inputSchema": {
699 "type": "object",
700 "properties": {
701 "comment_id": {"type": "string", "description": "Comment ID"}
702 },
703 "required": ["comment_id"]
704 }
705 },
706 {
707 "name": "clickup_task_add_dep",
708 "description": "Add a dependency to a task",
709 "inputSchema": {
710 "type": "object",
711 "properties": {
712 "task_id": {"type": "string", "description": "Task ID"},
713 "depends_on": {"type": "string", "description": "Task ID that this task depends on"},
714 "dependency_of": {"type": "string", "description": "Task ID that depends on this task"}
715 },
716 "required": ["task_id"]
717 }
718 },
719 {
720 "name": "clickup_task_remove_dep",
721 "description": "Remove a dependency from a task",
722 "inputSchema": {
723 "type": "object",
724 "properties": {
725 "task_id": {"type": "string", "description": "Task ID"},
726 "depends_on": {"type": "string", "description": "Task ID to remove as a depends_on dependency"},
727 "dependency_of": {"type": "string", "description": "Task ID to remove as a dependency_of dependency"}
728 },
729 "required": ["task_id"]
730 }
731 },
732 {
733 "name": "clickup_task_link",
734 "description": "Link two tasks together",
735 "inputSchema": {
736 "type": "object",
737 "properties": {
738 "task_id": {"type": "string", "description": "Task ID"},
739 "links_to": {"type": "string", "description": "Task ID to link to"}
740 },
741 "required": ["task_id", "links_to"]
742 }
743 },
744 {
745 "name": "clickup_task_unlink",
746 "description": "Remove a link between two tasks",
747 "inputSchema": {
748 "type": "object",
749 "properties": {
750 "task_id": {"type": "string", "description": "Task ID"},
751 "links_to": {"type": "string", "description": "Task ID to unlink"}
752 },
753 "required": ["task_id", "links_to"]
754 }
755 },
756 {
757 "name": "clickup_goal_delete",
758 "description": "Delete a goal",
759 "inputSchema": {
760 "type": "object",
761 "properties": {
762 "goal_id": {"type": "string", "description": "Goal ID"}
763 },
764 "required": ["goal_id"]
765 }
766 },
767 {
768 "name": "clickup_goal_add_kr",
769 "description": "Add a key result to a goal",
770 "inputSchema": {
771 "type": "object",
772 "properties": {
773 "goal_id": {"type": "string", "description": "Goal ID"},
774 "name": {"type": "string", "description": "Key result name"},
775 "type": {"type": "string", "description": "Key result type (number, currency, boolean, percentage, automatic)"},
776 "steps_start": {"type": "number", "description": "Starting value"},
777 "steps_end": {"type": "number", "description": "Target value"},
778 "unit": {"type": "string", "description": "Unit label"},
779 "owner_ids": {"type": "array", "items": {"type": "integer"}, "description": "Owner user IDs"},
780 "task_ids": {"type": "array", "items": {"type": "string"}, "description": "Task IDs to link (for automatic type)"},
781 "list_ids": {"type": "array", "items": {"type": "string"}, "description": "List IDs to link (for automatic type)"}
782 },
783 "required": ["goal_id", "name", "type", "steps_start", "steps_end"]
784 }
785 },
786 {
787 "name": "clickup_goal_update_kr",
788 "description": "Update a key result",
789 "inputSchema": {
790 "type": "object",
791 "properties": {
792 "kr_id": {"type": "string", "description": "Key result ID"},
793 "steps_current": {"type": "number", "description": "Current progress value"},
794 "name": {"type": "string", "description": "New key result name"},
795 "unit": {"type": "string", "description": "Unit label"}
796 },
797 "required": ["kr_id"]
798 }
799 },
800 {
801 "name": "clickup_goal_delete_kr",
802 "description": "Delete a key result",
803 "inputSchema": {
804 "type": "object",
805 "properties": {
806 "kr_id": {"type": "string", "description": "Key result ID"}
807 },
808 "required": ["kr_id"]
809 }
810 },
811 {
812 "name": "clickup_time_get",
813 "description": "Get a specific time tracking entry",
814 "inputSchema": {
815 "type": "object",
816 "properties": {
817 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
818 "timer_id": {"type": "string", "description": "Time entry ID"}
819 },
820 "required": ["timer_id"]
821 }
822 },
823 {
824 "name": "clickup_time_create",
825 "description": "Create a time tracking entry",
826 "inputSchema": {
827 "type": "object",
828 "properties": {
829 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
830 "task_id": {"type": "string", "description": "Task ID to log time against"},
831 "start": {"type": "integer", "description": "Start time as Unix timestamp (milliseconds)"},
832 "duration": {"type": "integer", "description": "Duration in milliseconds"},
833 "description": {"type": "string", "description": "Time entry description"},
834 "billable": {"type": "boolean", "description": "Whether this entry is billable"}
835 },
836 "required": ["start", "duration"]
837 }
838 },
839 {
840 "name": "clickup_time_update",
841 "description": "Update a time tracking entry",
842 "inputSchema": {
843 "type": "object",
844 "properties": {
845 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
846 "timer_id": {"type": "string", "description": "Time entry ID"},
847 "start": {"type": "integer", "description": "New start time as Unix timestamp (milliseconds)"},
848 "duration": {"type": "integer", "description": "New duration in milliseconds"},
849 "description": {"type": "string", "description": "New description"},
850 "billable": {"type": "boolean", "description": "Whether this entry is billable"}
851 },
852 "required": ["timer_id"]
853 }
854 },
855 {
856 "name": "clickup_time_delete",
857 "description": "Delete a time tracking entry",
858 "inputSchema": {
859 "type": "object",
860 "properties": {
861 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
862 "timer_id": {"type": "string", "description": "Time entry ID"}
863 },
864 "required": ["timer_id"]
865 }
866 },
867 {
868 "name": "clickup_view_get",
869 "description": "Get details of a specific view",
870 "inputSchema": {
871 "type": "object",
872 "properties": {
873 "view_id": {"type": "string", "description": "View ID"}
874 },
875 "required": ["view_id"]
876 }
877 },
878 {
879 "name": "clickup_view_create",
880 "description": "Create a view for a space, folder, list, or workspace",
881 "inputSchema": {
882 "type": "object",
883 "properties": {
884 "scope": {"type": "string", "description": "Scope type: space, folder, list, or team"},
885 "scope_id": {"type": "string", "description": "ID of the scope object"},
886 "name": {"type": "string", "description": "View name"},
887 "type": {"type": "string", "description": "View type (list, board, calendar, table, timeline, etc.)"}
888 },
889 "required": ["scope", "scope_id", "name", "type"]
890 }
891 },
892 {
893 "name": "clickup_view_update",
894 "description": "Update an existing view",
895 "inputSchema": {
896 "type": "object",
897 "properties": {
898 "view_id": {"type": "string", "description": "View ID"},
899 "name": {"type": "string", "description": "New view name"},
900 "type": {"type": "string", "description": "New view type"}
901 },
902 "required": ["view_id", "name", "type"]
903 }
904 },
905 {
906 "name": "clickup_view_delete",
907 "description": "Delete a view",
908 "inputSchema": {
909 "type": "object",
910 "properties": {
911 "view_id": {"type": "string", "description": "View ID"}
912 },
913 "required": ["view_id"]
914 }
915 },
916 {
917 "name": "clickup_doc_create",
918 "description": "Create a new doc in a workspace",
919 "inputSchema": {
920 "type": "object",
921 "properties": {
922 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
923 "name": {"type": "string", "description": "Doc name"},
924 "parent": {"type": "object", "description": "Parent object with id and type fields"}
925 },
926 "required": ["name"]
927 }
928 },
929 {
930 "name": "clickup_doc_add_page",
931 "description": "Add a page to a doc",
932 "inputSchema": {
933 "type": "object",
934 "properties": {
935 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
936 "doc_id": {"type": "string", "description": "Doc ID"},
937 "name": {"type": "string", "description": "Page name"},
938 "content": {"type": "string", "description": "Page content (markdown)"},
939 "sub_title": {"type": "string", "description": "Page subtitle"},
940 "parent_page_id": {"type": "string", "description": "Parent page ID for nested pages"}
941 },
942 "required": ["doc_id", "name"]
943 }
944 },
945 {
946 "name": "clickup_doc_edit_page",
947 "description": "Edit an existing page in a doc",
948 "inputSchema": {
949 "type": "object",
950 "properties": {
951 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
952 "doc_id": {"type": "string", "description": "Doc ID"},
953 "page_id": {"type": "string", "description": "Page ID"},
954 "name": {"type": "string", "description": "New page name"},
955 "content": {"type": "string", "description": "New page content (markdown)"}
956 },
957 "required": ["doc_id", "page_id"]
958 }
959 },
960 {
961 "name": "clickup_chat_channel_create",
962 "description": "Create a chat channel in a workspace",
963 "inputSchema": {
964 "type": "object",
965 "properties": {
966 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
967 "name": {"type": "string", "description": "Channel name"},
968 "description": {"type": "string", "description": "Channel description"},
969 "visibility": {"type": "string", "description": "Channel visibility (public or private)"}
970 },
971 "required": ["name"]
972 }
973 },
974 {
975 "name": "clickup_chat_channel_get",
976 "description": "Get a chat channel",
977 "inputSchema": {
978 "type": "object",
979 "properties": {
980 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
981 "channel_id": {"type": "string", "description": "Channel ID"}
982 },
983 "required": ["channel_id"]
984 }
985 },
986 {
987 "name": "clickup_chat_channel_update",
988 "description": "Update a chat channel",
989 "inputSchema": {
990 "type": "object",
991 "properties": {
992 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
993 "channel_id": {"type": "string", "description": "Channel ID"},
994 "name": {"type": "string", "description": "New channel name"},
995 "description": {"type": "string", "description": "New channel description"}
996 },
997 "required": ["channel_id"]
998 }
999 },
1000 {
1001 "name": "clickup_chat_channel_delete",
1002 "description": "Delete a chat channel",
1003 "inputSchema": {
1004 "type": "object",
1005 "properties": {
1006 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1007 "channel_id": {"type": "string", "description": "Channel ID"}
1008 },
1009 "required": ["channel_id"]
1010 }
1011 },
1012 {
1013 "name": "clickup_chat_message_list",
1014 "description": "List messages in a chat channel",
1015 "inputSchema": {
1016 "type": "object",
1017 "properties": {
1018 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1019 "channel_id": {"type": "string", "description": "Channel ID"},
1020 "cursor": {"type": "string", "description": "Pagination cursor"}
1021 },
1022 "required": ["channel_id"]
1023 }
1024 },
1025 {
1026 "name": "clickup_chat_message_send",
1027 "description": "Send a message to a chat channel",
1028 "inputSchema": {
1029 "type": "object",
1030 "properties": {
1031 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1032 "channel_id": {"type": "string", "description": "Channel ID"},
1033 "content": {"type": "string", "description": "Message content"}
1034 },
1035 "required": ["channel_id", "content"]
1036 }
1037 },
1038 {
1039 "name": "clickup_chat_message_delete",
1040 "description": "Delete a chat message",
1041 "inputSchema": {
1042 "type": "object",
1043 "properties": {
1044 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1045 "message_id": {"type": "string", "description": "Message ID"}
1046 },
1047 "required": ["message_id"]
1048 }
1049 },
1050 {
1051 "name": "clickup_chat_dm",
1052 "description": "Send a direct message to a user",
1053 "inputSchema": {
1054 "type": "object",
1055 "properties": {
1056 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1057 "user_id": {"type": "integer", "description": "User ID to DM"},
1058 "content": {"type": "string", "description": "Message content"}
1059 },
1060 "required": ["user_id", "content"]
1061 }
1062 },
1063 {
1064 "name": "clickup_webhook_create",
1065 "description": "Create a webhook for a workspace",
1066 "inputSchema": {
1067 "type": "object",
1068 "properties": {
1069 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1070 "endpoint": {"type": "string", "description": "Webhook URL endpoint"},
1071 "events": {
1072 "type": "array",
1073 "items": {"type": "string"},
1074 "description": "List of events to subscribe to (use ['*'] for all events)"
1075 },
1076 "space_id": {"type": "string", "description": "Filter events to a specific space"},
1077 "folder_id": {"type": "string", "description": "Filter events to a specific folder"},
1078 "list_id": {"type": "string", "description": "Filter events to a specific list"},
1079 "task_id": {"type": "string", "description": "Filter events to a specific task"}
1080 },
1081 "required": ["endpoint", "events"]
1082 }
1083 },
1084 {
1085 "name": "clickup_webhook_update",
1086 "description": "Update a webhook",
1087 "inputSchema": {
1088 "type": "object",
1089 "properties": {
1090 "webhook_id": {"type": "string", "description": "Webhook ID"},
1091 "endpoint": {"type": "string", "description": "New webhook URL endpoint"},
1092 "events": {
1093 "type": "array",
1094 "items": {"type": "string"},
1095 "description": "New list of events to subscribe to"
1096 },
1097 "status": {"type": "string", "description": "Webhook status (active or suspended)"}
1098 },
1099 "required": ["webhook_id"]
1100 }
1101 },
1102 {
1103 "name": "clickup_webhook_delete",
1104 "description": "Delete a webhook",
1105 "inputSchema": {
1106 "type": "object",
1107 "properties": {
1108 "webhook_id": {"type": "string", "description": "Webhook ID"}
1109 },
1110 "required": ["webhook_id"]
1111 }
1112 },
1113 {
1114 "name": "clickup_checklist_add_item",
1115 "description": "Add an item to a checklist",
1116 "inputSchema": {
1117 "type": "object",
1118 "properties": {
1119 "checklist_id": {"type": "string", "description": "Checklist ID"},
1120 "name": {"type": "string", "description": "Item name"},
1121 "assignee": {"type": "integer", "description": "Assign item to user ID"}
1122 },
1123 "required": ["checklist_id", "name"]
1124 }
1125 },
1126 {
1127 "name": "clickup_checklist_update_item",
1128 "description": "Update a checklist item",
1129 "inputSchema": {
1130 "type": "object",
1131 "properties": {
1132 "checklist_id": {"type": "string", "description": "Checklist ID"},
1133 "item_id": {"type": "string", "description": "Checklist item ID"},
1134 "name": {"type": "string", "description": "New item name"},
1135 "resolved": {"type": "boolean", "description": "Mark item as resolved"},
1136 "assignee": {"type": "integer", "description": "Reassign item to user ID"}
1137 },
1138 "required": ["checklist_id", "item_id"]
1139 }
1140 },
1141 {
1142 "name": "clickup_checklist_delete_item",
1143 "description": "Delete a checklist item",
1144 "inputSchema": {
1145 "type": "object",
1146 "properties": {
1147 "checklist_id": {"type": "string", "description": "Checklist ID"},
1148 "item_id": {"type": "string", "description": "Checklist item ID"}
1149 },
1150 "required": ["checklist_id", "item_id"]
1151 }
1152 },
1153 {
1154 "name": "clickup_user_get",
1155 "description": "Get a user in a workspace",
1156 "inputSchema": {
1157 "type": "object",
1158 "properties": {
1159 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1160 "user_id": {"type": "integer", "description": "User ID"}
1161 },
1162 "required": ["user_id"]
1163 }
1164 },
1165 {
1166 "name": "clickup_workspace_seats",
1167 "description": "Get seat usage for a workspace",
1168 "inputSchema": {
1169 "type": "object",
1170 "properties": {
1171 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
1172 },
1173 "required": []
1174 }
1175 },
1176 {
1177 "name": "clickup_workspace_plan",
1178 "description": "Get the plan for a workspace",
1179 "inputSchema": {
1180 "type": "object",
1181 "properties": {
1182 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
1183 },
1184 "required": []
1185 }
1186 },
1187 {
1188 "name": "clickup_tag_create",
1189 "description": "Create a tag in a space",
1190 "inputSchema": {
1191 "type": "object",
1192 "properties": {
1193 "space_id": {"type": "string", "description": "Space ID"},
1194 "name": {"type": "string", "description": "Tag name"},
1195 "tag_fg": {"type": "string", "description": "Foreground color (hex)"},
1196 "tag_bg": {"type": "string", "description": "Background color (hex)"}
1197 },
1198 "required": ["space_id", "name"]
1199 }
1200 },
1201 {
1202 "name": "clickup_tag_update",
1203 "description": "Update a tag in a space",
1204 "inputSchema": {
1205 "type": "object",
1206 "properties": {
1207 "space_id": {"type": "string", "description": "Space ID"},
1208 "tag_name": {"type": "string", "description": "Current tag name"},
1209 "name": {"type": "string", "description": "New tag name"},
1210 "tag_fg": {"type": "string", "description": "New foreground color (hex)"},
1211 "tag_bg": {"type": "string", "description": "New background color (hex)"}
1212 },
1213 "required": ["space_id", "tag_name"]
1214 }
1215 },
1216 {
1217 "name": "clickup_tag_delete",
1218 "description": "Delete a tag from a space",
1219 "inputSchema": {
1220 "type": "object",
1221 "properties": {
1222 "space_id": {"type": "string", "description": "Space ID"},
1223 "tag_name": {"type": "string", "description": "Tag name"}
1224 },
1225 "required": ["space_id", "tag_name"]
1226 }
1227 },
1228 {
1229 "name": "clickup_field_unset",
1230 "description": "Remove a custom field value from a task",
1231 "inputSchema": {
1232 "type": "object",
1233 "properties": {
1234 "task_id": {"type": "string", "description": "Task ID"},
1235 "field_id": {"type": "string", "description": "Custom field ID"}
1236 },
1237 "required": ["task_id", "field_id"]
1238 }
1239 },
1240 {
1241 "name": "clickup_attachment_list",
1242 "description": "List attachments on a task",
1243 "inputSchema": {
1244 "type": "object",
1245 "properties": {
1246 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1247 "task_id": {"type": "string", "description": "Task ID"}
1248 },
1249 "required": ["task_id"]
1250 }
1251 },
1252 {
1253 "name": "clickup_shared_list",
1254 "description": "Get shared hierarchy (tasks, lists, folders) for a workspace",
1255 "inputSchema": {
1256 "type": "object",
1257 "properties": {
1258 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
1259 },
1260 "required": []
1261 }
1262 },
1263 {
1264 "name": "clickup_group_list",
1265 "description": "List user groups (teams) in a workspace",
1266 "inputSchema": {
1267 "type": "object",
1268 "properties": {
1269 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1270 "group_ids": {
1271 "type": "array",
1272 "items": {"type": "string"},
1273 "description": "Filter by specific group IDs"
1274 }
1275 },
1276 "required": []
1277 }
1278 },
1279 {
1280 "name": "clickup_group_create",
1281 "description": "Create a user group in a workspace",
1282 "inputSchema": {
1283 "type": "object",
1284 "properties": {
1285 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1286 "name": {"type": "string", "description": "Group name"},
1287 "member_ids": {
1288 "type": "array",
1289 "items": {"type": "integer"},
1290 "description": "User IDs to add as members"
1291 }
1292 },
1293 "required": ["name"]
1294 }
1295 },
1296 {
1297 "name": "clickup_group_update",
1298 "description": "Update a user group",
1299 "inputSchema": {
1300 "type": "object",
1301 "properties": {
1302 "group_id": {"type": "string", "description": "Group ID"},
1303 "name": {"type": "string", "description": "New group name"},
1304 "add_members": {
1305 "type": "array",
1306 "items": {"type": "integer"},
1307 "description": "User IDs to add"
1308 },
1309 "rem_members": {
1310 "type": "array",
1311 "items": {"type": "integer"},
1312 "description": "User IDs to remove"
1313 }
1314 },
1315 "required": ["group_id"]
1316 }
1317 },
1318 {
1319 "name": "clickup_group_delete",
1320 "description": "Delete a user group",
1321 "inputSchema": {
1322 "type": "object",
1323 "properties": {
1324 "group_id": {"type": "string", "description": "Group ID"}
1325 },
1326 "required": ["group_id"]
1327 }
1328 },
1329 {
1330 "name": "clickup_role_list",
1331 "description": "List custom roles in a workspace",
1332 "inputSchema": {
1333 "type": "object",
1334 "properties": {
1335 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."}
1336 },
1337 "required": []
1338 }
1339 },
1340 {
1341 "name": "clickup_guest_get",
1342 "description": "Get a guest in a workspace",
1343 "inputSchema": {
1344 "type": "object",
1345 "properties": {
1346 "team_id": {"type": "string", "description": "Workspace (team) ID. Omit to use the default workspace from config."},
1347 "guest_id": {"type": "integer", "description": "Guest user ID"}
1348 },
1349 "required": ["guest_id"]
1350 }
1351 }
1352 ])
1353}
1354
1355async fn call_tool(
1358 name: &str,
1359 args: &Value,
1360 client: &ClickUpClient,
1361 workspace_id: &Option<String>,
1362) -> Value {
1363 let result = dispatch_tool(name, args, client, workspace_id).await;
1364 match result {
1365 Ok(v) => tool_result(v.to_string()),
1366 Err(e) => tool_error(format!("Error: {}", e)),
1367 }
1368}
1369
1370async fn dispatch_tool(
1371 name: &str,
1372 args: &Value,
1373 client: &ClickUpClient,
1374 workspace_id: &Option<String>,
1375) -> Result<Value, String> {
1376 let empty = json!({});
1377 let args = if args.is_null() { &empty } else { args };
1378
1379 let resolve_workspace = |args: &Value| -> Result<String, String> {
1381 if let Some(id) = args.get("team_id").and_then(|v| v.as_str()) {
1382 return Ok(id.to_string());
1383 }
1384 workspace_id
1385 .clone()
1386 .ok_or_else(|| "No workspace_id found in config. Please run `clickup setup` or provide team_id in the tool arguments.".to_string())
1387 };
1388
1389 match name {
1390 "clickup_whoami" => {
1391 let resp = client.get("/v2/user").await.map_err(|e| e.to_string())?;
1392 let user = resp.get("user").cloned().unwrap_or(resp);
1393 Ok(compact_items(&[user], &["id", "username", "email"]))
1394 }
1395
1396 "clickup_workspace_list" => {
1397 let resp = client.get("/v2/team").await.map_err(|e| e.to_string())?;
1398 let teams = resp.get("teams").and_then(|t| t.as_array()).cloned().unwrap_or_default();
1399 let items: Vec<Value> = teams.iter().map(|ws| {
1400 json!({
1401 "id": ws.get("id"),
1402 "name": ws.get("name"),
1403 "members": ws.get("members").and_then(|m| m.as_array()).map(|a| a.len()).unwrap_or(0),
1404 })
1405 }).collect();
1406 Ok(compact_items(&items, &["id", "name", "members"]))
1407 }
1408
1409 "clickup_space_list" => {
1410 let team_id = resolve_workspace(args)?;
1411 let archived = args.get("archived").and_then(|v| v.as_bool()).unwrap_or(false);
1412 let path = format!("/v2/team/{}/space?archived={}", team_id, archived);
1413 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1414 let spaces = resp.get("spaces").and_then(|s| s.as_array()).cloned().unwrap_or_default();
1415 Ok(compact_items(&spaces, &["id", "name", "private", "archived"]))
1416 }
1417
1418 "clickup_folder_list" => {
1419 let space_id = args
1420 .get("space_id")
1421 .and_then(|v| v.as_str())
1422 .ok_or("Missing required parameter: space_id")?;
1423 let archived = args.get("archived").and_then(|v| v.as_bool()).unwrap_or(false);
1424 let path = format!("/v2/space/{}/folder?archived={}", space_id, archived);
1425 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1426 let folders = resp.get("folders").and_then(|f| f.as_array()).cloned().unwrap_or_default();
1427 let items: Vec<Value> = folders.iter().map(|f| {
1428 let list_count = f.get("lists").and_then(|l| l.as_array()).map(|a| a.len()).unwrap_or(0);
1429 json!({
1430 "id": f.get("id"),
1431 "name": f.get("name"),
1432 "task_count": f.get("task_count"),
1433 "list_count": list_count,
1434 })
1435 }).collect();
1436 Ok(compact_items(&items, &["id", "name", "task_count", "list_count"]))
1437 }
1438
1439 "clickup_list_list" => {
1440 let archived = args.get("archived").and_then(|v| v.as_bool()).unwrap_or(false);
1441 let path = if let Some(folder_id) = args.get("folder_id").and_then(|v| v.as_str()) {
1442 format!("/v2/folder/{}/list?archived={}", folder_id, archived)
1443 } else if let Some(space_id) = args.get("space_id").and_then(|v| v.as_str()) {
1444 format!("/v2/space/{}/list?archived={}", space_id, archived)
1445 } else {
1446 return Err("Provide either folder_id or space_id".to_string());
1447 };
1448 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1449 let lists = resp.get("lists").and_then(|l| l.as_array()).cloned().unwrap_or_default();
1450 Ok(compact_items(&lists, &["id", "name", "task_count", "status", "due_date"]))
1451 }
1452
1453 "clickup_task_list" => {
1454 let list_id = args
1455 .get("list_id")
1456 .and_then(|v| v.as_str())
1457 .ok_or("Missing required parameter: list_id")?;
1458 let mut qs = String::new();
1459 if let Some(include_closed) = args.get("include_closed").and_then(|v| v.as_bool()) {
1460 qs.push_str(&format!("&include_closed={}", include_closed));
1461 }
1462 if let Some(statuses) = args.get("statuses").and_then(|v| v.as_array()) {
1463 for s in statuses {
1464 if let Some(s) = s.as_str() {
1465 qs.push_str(&format!("&statuses[]={}", s));
1466 }
1467 }
1468 }
1469 if let Some(assignees) = args.get("assignees").and_then(|v| v.as_array()) {
1470 for a in assignees {
1471 if let Some(a) = a.as_str() {
1472 qs.push_str(&format!("&assignees[]={}", a));
1473 }
1474 }
1475 }
1476 let path = format!("/v2/list/{}/task?{}", list_id, qs.trim_start_matches('&'));
1477 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1478 let tasks = resp.get("tasks").and_then(|t| t.as_array()).cloned().unwrap_or_default();
1479 Ok(compact_items(&tasks, &["id", "name", "status", "priority", "assignees", "due_date"]))
1480 }
1481
1482 "clickup_task_get" => {
1483 let task_id = args
1484 .get("task_id")
1485 .and_then(|v| v.as_str())
1486 .ok_or("Missing required parameter: task_id")?;
1487 let include_subtasks = args
1488 .get("include_subtasks")
1489 .and_then(|v| v.as_bool())
1490 .unwrap_or(false);
1491 let path = format!(
1492 "/v2/task/{}?include_subtasks={}",
1493 task_id, include_subtasks
1494 );
1495 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1496 Ok(compact_items(&[resp], &["id", "name", "status", "priority", "assignees", "due_date", "description"]))
1497 }
1498
1499 "clickup_task_create" => {
1500 let list_id = args
1501 .get("list_id")
1502 .and_then(|v| v.as_str())
1503 .ok_or("Missing required parameter: list_id")?;
1504 let name = args
1505 .get("name")
1506 .and_then(|v| v.as_str())
1507 .ok_or("Missing required parameter: name")?;
1508 let mut body = json!({"name": name});
1509 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) {
1510 body["description"] = json!(desc);
1511 }
1512 if let Some(status) = args.get("status").and_then(|v| v.as_str()) {
1513 body["status"] = json!(status);
1514 }
1515 if let Some(priority) = args.get("priority").and_then(|v| v.as_i64()) {
1516 body["priority"] = json!(priority);
1517 }
1518 if let Some(assignees) = args.get("assignees") {
1519 body["assignees"] = assignees.clone();
1520 }
1521 if let Some(tags) = args.get("tags") {
1522 body["tags"] = tags.clone();
1523 }
1524 if let Some(due_date) = args.get("due_date").and_then(|v| v.as_i64()) {
1525 body["due_date"] = json!(due_date);
1526 }
1527 let path = format!("/v2/list/{}/task", list_id);
1528 let resp = client.post(&path, &body).await.map_err(|e| e.to_string())?;
1529 Ok(compact_items(&[resp], &["id", "name", "status", "priority", "assignees", "due_date"]))
1530 }
1531
1532 "clickup_task_update" => {
1533 let task_id = args
1534 .get("task_id")
1535 .and_then(|v| v.as_str())
1536 .ok_or("Missing required parameter: task_id")?;
1537 let mut body = json!({});
1538 if let Some(name) = args.get("name").and_then(|v| v.as_str()) {
1539 body["name"] = json!(name);
1540 }
1541 if let Some(status) = args.get("status").and_then(|v| v.as_str()) {
1542 body["status"] = json!(status);
1543 }
1544 if let Some(priority) = args.get("priority").and_then(|v| v.as_i64()) {
1545 body["priority"] = json!(priority);
1546 }
1547 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) {
1548 body["description"] = json!(desc);
1549 }
1550 if let Some(add) = args.get("add_assignees") {
1551 body["assignees"] = json!({"add": add, "rem": args.get("rem_assignees").cloned().unwrap_or(json!([]))});
1552 } else if let Some(rem) = args.get("rem_assignees") {
1553 body["assignees"] = json!({"add": [], "rem": rem});
1554 }
1555 let path = format!("/v2/task/{}", task_id);
1556 let resp = client.put(&path, &body).await.map_err(|e| e.to_string())?;
1557 Ok(compact_items(&[resp], &["id", "name", "status", "priority", "assignees", "due_date"]))
1558 }
1559
1560 "clickup_task_delete" => {
1561 let task_id = args
1562 .get("task_id")
1563 .and_then(|v| v.as_str())
1564 .ok_or("Missing required parameter: task_id")?;
1565 let path = format!("/v2/task/{}", task_id);
1566 client.delete(&path).await.map_err(|e| e.to_string())?;
1567 Ok(json!({"message": format!("Task {} deleted", task_id)}))
1568 }
1569
1570 "clickup_task_search" => {
1571 let team_id = resolve_workspace(args)?;
1572 let mut qs = String::new();
1573 if let Some(space_ids) = args.get("space_ids").and_then(|v| v.as_array()) {
1574 for id in space_ids {
1575 if let Some(id) = id.as_str() {
1576 qs.push_str(&format!("&space_ids[]={}", id));
1577 }
1578 }
1579 }
1580 if let Some(list_ids) = args.get("list_ids").and_then(|v| v.as_array()) {
1581 for id in list_ids {
1582 if let Some(id) = id.as_str() {
1583 qs.push_str(&format!("&list_ids[]={}", id));
1584 }
1585 }
1586 }
1587 if let Some(statuses) = args.get("statuses").and_then(|v| v.as_array()) {
1588 for s in statuses {
1589 if let Some(s) = s.as_str() {
1590 qs.push_str(&format!("&statuses[]={}", s));
1591 }
1592 }
1593 }
1594 if let Some(assignees) = args.get("assignees").and_then(|v| v.as_array()) {
1595 for a in assignees {
1596 if let Some(a) = a.as_str() {
1597 qs.push_str(&format!("&assignees[]={}", a));
1598 }
1599 }
1600 }
1601 let path = format!(
1602 "/v2/team/{}/task?{}",
1603 team_id,
1604 qs.trim_start_matches('&')
1605 );
1606 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1607 let tasks = resp.get("tasks").and_then(|t| t.as_array()).cloned().unwrap_or_default();
1608 Ok(compact_items(&tasks, &["id", "name", "status", "priority", "assignees", "due_date"]))
1609 }
1610
1611 "clickup_comment_list" => {
1612 let task_id = args
1613 .get("task_id")
1614 .and_then(|v| v.as_str())
1615 .ok_or("Missing required parameter: task_id")?;
1616 let path = format!("/v2/task/{}/comment", task_id);
1617 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1618 let comments = resp.get("comments").and_then(|c| c.as_array()).cloned().unwrap_or_default();
1619 Ok(compact_items(&comments, &["id", "user", "date", "comment_text"]))
1620 }
1621
1622 "clickup_comment_create" => {
1623 let task_id = args
1624 .get("task_id")
1625 .and_then(|v| v.as_str())
1626 .ok_or("Missing required parameter: task_id")?;
1627 let text = args
1628 .get("text")
1629 .and_then(|v| v.as_str())
1630 .ok_or("Missing required parameter: text")?;
1631 let mut body = json!({"comment_text": text});
1632 if let Some(assignee) = args.get("assignee").and_then(|v| v.as_i64()) {
1633 body["assignee"] = json!(assignee);
1634 }
1635 if let Some(notify_all) = args.get("notify_all").and_then(|v| v.as_bool()) {
1636 body["notify_all"] = json!(notify_all);
1637 }
1638 let path = format!("/v2/task/{}/comment", task_id);
1639 let resp = client.post(&path, &body).await.map_err(|e| e.to_string())?;
1640 Ok(json!({"message": "Comment created", "id": resp.get("id")}))
1641 }
1642
1643 "clickup_field_list" => {
1644 let list_id = args
1645 .get("list_id")
1646 .and_then(|v| v.as_str())
1647 .ok_or("Missing required parameter: list_id")?;
1648 let path = format!("/v2/list/{}/field", list_id);
1649 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1650 let fields = resp.get("fields").and_then(|f| f.as_array()).cloned().unwrap_or_default();
1651 Ok(compact_items(&fields, &["id", "name", "type", "required"]))
1652 }
1653
1654 "clickup_field_set" => {
1655 let task_id = args
1656 .get("task_id")
1657 .and_then(|v| v.as_str())
1658 .ok_or("Missing required parameter: task_id")?;
1659 let field_id = args
1660 .get("field_id")
1661 .and_then(|v| v.as_str())
1662 .ok_or("Missing required parameter: field_id")?;
1663 let value = args.get("value").ok_or("Missing required parameter: value")?;
1664 let body = json!({"value": value});
1665 let path = format!("/v2/task/{}/field/{}", task_id, field_id);
1666 client.post(&path, &body).await.map_err(|e| e.to_string())?;
1667 Ok(json!({"message": format!("Field {} set on task {}", field_id, task_id)}))
1668 }
1669
1670 "clickup_time_start" => {
1671 let team_id = resolve_workspace(args)?;
1672 let mut body = json!({});
1673 if let Some(task_id) = args.get("task_id").and_then(|v| v.as_str()) {
1674 body["tid"] = json!(task_id);
1675 }
1676 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) {
1677 body["description"] = json!(desc);
1678 }
1679 if let Some(billable) = args.get("billable").and_then(|v| v.as_bool()) {
1680 body["billable"] = json!(billable);
1681 }
1682 let path = format!("/v2/team/{}/time_entries/start", team_id);
1683 let resp = client.post(&path, &body).await.map_err(|e| e.to_string())?;
1684 let data = resp.get("data").cloned().unwrap_or(resp);
1685 Ok(compact_items(&[data], &["id", "task", "duration", "start", "billable"]))
1686 }
1687
1688 "clickup_time_stop" => {
1689 let team_id = resolve_workspace(args)?;
1690 let path = format!("/v2/team/{}/time_entries/stop", team_id);
1691 let resp = client.post(&path, &json!({})).await.map_err(|e| e.to_string())?;
1692 let data = resp.get("data").cloned().unwrap_or(resp);
1693 Ok(compact_items(&[data], &["id", "task", "duration", "start", "end", "billable"]))
1694 }
1695
1696 "clickup_time_list" => {
1697 let team_id = resolve_workspace(args)?;
1698 let mut qs = String::new();
1699 if let Some(start_date) = args.get("start_date").and_then(|v| v.as_i64()) {
1700 qs.push_str(&format!("&start_date={}", start_date));
1701 }
1702 if let Some(end_date) = args.get("end_date").and_then(|v| v.as_i64()) {
1703 qs.push_str(&format!("&end_date={}", end_date));
1704 }
1705 if let Some(task_id) = args.get("task_id").and_then(|v| v.as_str()) {
1706 qs.push_str(&format!("&task_id={}", task_id));
1707 }
1708 let path = format!(
1709 "/v2/team/{}/time_entries?{}",
1710 team_id,
1711 qs.trim_start_matches('&')
1712 );
1713 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1714 let entries = resp.get("data").and_then(|d| d.as_array()).cloned().unwrap_or_default();
1715 Ok(compact_items(&entries, &["id", "task", "duration", "start", "billable"]))
1716 }
1717
1718 "clickup_checklist_create" => {
1719 let task_id = args
1720 .get("task_id")
1721 .and_then(|v| v.as_str())
1722 .ok_or("Missing required parameter: task_id")?;
1723 let name = args
1724 .get("name")
1725 .and_then(|v| v.as_str())
1726 .ok_or("Missing required parameter: name")?;
1727 let path = format!("/v2/task/{}/checklist", task_id);
1728 let body = json!({"name": name});
1729 let resp = client.post(&path, &body).await.map_err(|e| e.to_string())?;
1730 let checklist = resp.get("checklist").cloned().unwrap_or(resp);
1731 Ok(compact_items(&[checklist], &["id", "name"]))
1732 }
1733
1734 "clickup_checklist_delete" => {
1735 let checklist_id = args
1736 .get("checklist_id")
1737 .and_then(|v| v.as_str())
1738 .ok_or("Missing required parameter: checklist_id")?;
1739 let path = format!("/v2/checklist/{}", checklist_id);
1740 client.delete(&path).await.map_err(|e| e.to_string())?;
1741 Ok(json!({"message": format!("Checklist {} deleted", checklist_id)}))
1742 }
1743
1744 "clickup_goal_list" => {
1745 let team_id = resolve_workspace(args)?;
1746 let path = format!("/v2/team/{}/goal", team_id);
1747 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1748 let goals = resp.get("goals").and_then(|g| g.as_array()).cloned().unwrap_or_default();
1749 Ok(compact_items(&goals, &["id", "name", "percent_completed", "due_date"]))
1750 }
1751
1752 "clickup_goal_get" => {
1753 let goal_id = args
1754 .get("goal_id")
1755 .and_then(|v| v.as_str())
1756 .ok_or("Missing required parameter: goal_id")?;
1757 let path = format!("/v2/goal/{}", goal_id);
1758 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1759 let goal = resp.get("goal").cloned().unwrap_or(resp);
1760 Ok(compact_items(&[goal], &["id", "name", "percent_completed", "due_date", "description"]))
1761 }
1762
1763 "clickup_goal_create" => {
1764 let team_id = resolve_workspace(args)?;
1765 let name = args
1766 .get("name")
1767 .and_then(|v| v.as_str())
1768 .ok_or("Missing required parameter: name")?;
1769 let mut body = json!({"name": name});
1770 if let Some(due_date) = args.get("due_date").and_then(|v| v.as_i64()) {
1771 body["due_date"] = json!(due_date);
1772 }
1773 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) {
1774 body["description"] = json!(desc);
1775 }
1776 if let Some(owner_ids) = args.get("owner_ids") {
1777 body["owners"] = owner_ids.clone();
1778 }
1779 let path = format!("/v2/team/{}/goal", team_id);
1780 let resp = client.post(&path, &body).await.map_err(|e| e.to_string())?;
1781 let goal = resp.get("goal").cloned().unwrap_or(resp);
1782 Ok(compact_items(&[goal], &["id", "name"]))
1783 }
1784
1785 "clickup_goal_update" => {
1786 let goal_id = args
1787 .get("goal_id")
1788 .and_then(|v| v.as_str())
1789 .ok_or("Missing required parameter: goal_id")?;
1790 let mut body = json!({});
1791 if let Some(name) = args.get("name").and_then(|v| v.as_str()) {
1792 body["name"] = json!(name);
1793 }
1794 if let Some(due_date) = args.get("due_date").and_then(|v| v.as_i64()) {
1795 body["due_date"] = json!(due_date);
1796 }
1797 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) {
1798 body["description"] = json!(desc);
1799 }
1800 let path = format!("/v2/goal/{}", goal_id);
1801 let resp = client.put(&path, &body).await.map_err(|e| e.to_string())?;
1802 let goal = resp.get("goal").cloned().unwrap_or(resp);
1803 Ok(compact_items(&[goal], &["id", "name"]))
1804 }
1805
1806 "clickup_view_list" => {
1807 let path = if let Some(space_id) = args.get("space_id").and_then(|v| v.as_str()) {
1808 format!("/v2/space/{}/view", space_id)
1809 } else if let Some(folder_id) = args.get("folder_id").and_then(|v| v.as_str()) {
1810 format!("/v2/folder/{}/view", folder_id)
1811 } else if let Some(list_id) = args.get("list_id").and_then(|v| v.as_str()) {
1812 format!("/v2/list/{}/view", list_id)
1813 } else {
1814 let team_id = resolve_workspace(args)?;
1815 format!("/v2/team/{}/view", team_id)
1816 };
1817 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1818 let views = resp.get("views").and_then(|v| v.as_array()).cloned().unwrap_or_default();
1819 Ok(compact_items(&views, &["id", "name", "type"]))
1820 }
1821
1822 "clickup_view_tasks" => {
1823 let view_id = args
1824 .get("view_id")
1825 .and_then(|v| v.as_str())
1826 .ok_or("Missing required parameter: view_id")?;
1827 let page = args.get("page").and_then(|v| v.as_i64()).unwrap_or(0);
1828 let path = format!("/v2/view/{}/task?page={}", view_id, page);
1829 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1830 let tasks = resp.get("tasks").and_then(|t| t.as_array()).cloned().unwrap_or_default();
1831 Ok(compact_items(&tasks, &["id", "name", "status", "priority", "assignees", "due_date"]))
1832 }
1833
1834 "clickup_doc_list" => {
1835 let team_id = resolve_workspace(args)?;
1836 let path = format!("/v3/workspaces/{}/docs", team_id);
1837 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1838 let docs = resp.get("docs").and_then(|d| d.as_array()).cloned().unwrap_or_default();
1839 Ok(compact_items(&docs, &["id", "name", "date_created"]))
1840 }
1841
1842 "clickup_doc_get" => {
1843 let team_id = resolve_workspace(args)?;
1844 let doc_id = args
1845 .get("doc_id")
1846 .and_then(|v| v.as_str())
1847 .ok_or("Missing required parameter: doc_id")?;
1848 let path = format!("/v3/workspaces/{}/docs/{}", team_id, doc_id);
1849 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1850 Ok(compact_items(&[resp], &["id", "name", "date_created"]))
1851 }
1852
1853 "clickup_doc_pages" => {
1854 let team_id = resolve_workspace(args)?;
1855 let doc_id = args
1856 .get("doc_id")
1857 .and_then(|v| v.as_str())
1858 .ok_or("Missing required parameter: doc_id")?;
1859 let content = args.get("content").and_then(|v| v.as_bool()).unwrap_or(false);
1860 let path = format!("/v3/workspaces/{}/docs/{}/pages?content_format=text/md&max_page_depth=-1&include_content={}", team_id, doc_id, content);
1861 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1862 let pages = resp.get("pages").and_then(|p| p.as_array()).cloned().unwrap_or_default();
1863 Ok(compact_items(&pages, &["id", "name"]))
1864 }
1865
1866 "clickup_tag_list" => {
1867 let space_id = args
1868 .get("space_id")
1869 .and_then(|v| v.as_str())
1870 .ok_or("Missing required parameter: space_id")?;
1871 let path = format!("/v2/space/{}/tag", space_id);
1872 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1873 let tags = resp.get("tags").and_then(|t| t.as_array()).cloned().unwrap_or_default();
1874 Ok(compact_items(&tags, &["name", "tag_fg", "tag_bg"]))
1875 }
1876
1877 "clickup_task_add_tag" => {
1878 let task_id = args
1879 .get("task_id")
1880 .and_then(|v| v.as_str())
1881 .ok_or("Missing required parameter: task_id")?;
1882 let tag_name = args
1883 .get("tag_name")
1884 .and_then(|v| v.as_str())
1885 .ok_or("Missing required parameter: tag_name")?;
1886 let path = format!("/v2/task/{}/tag/{}", task_id, tag_name);
1887 client.post(&path, &json!({})).await.map_err(|e| e.to_string())?;
1888 Ok(json!({"message": format!("Tag '{}' added to task {}", tag_name, task_id)}))
1889 }
1890
1891 "clickup_task_remove_tag" => {
1892 let task_id = args
1893 .get("task_id")
1894 .and_then(|v| v.as_str())
1895 .ok_or("Missing required parameter: task_id")?;
1896 let tag_name = args
1897 .get("tag_name")
1898 .and_then(|v| v.as_str())
1899 .ok_or("Missing required parameter: tag_name")?;
1900 let path = format!("/v2/task/{}/tag/{}", task_id, tag_name);
1901 client.delete(&path).await.map_err(|e| e.to_string())?;
1902 Ok(json!({"message": format!("Tag '{}' removed from task {}", tag_name, task_id)}))
1903 }
1904
1905 "clickup_webhook_list" => {
1906 let team_id = resolve_workspace(args)?;
1907 let path = format!("/v2/team/{}/webhook", team_id);
1908 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1909 let webhooks = resp.get("webhooks").and_then(|w| w.as_array()).cloned().unwrap_or_default();
1910 Ok(compact_items(&webhooks, &["id", "endpoint", "events", "status"]))
1911 }
1912
1913 "clickup_member_list" => {
1914 let path = if let Some(task_id) = args.get("task_id").and_then(|v| v.as_str()) {
1915 format!("/v2/task/{}/member", task_id)
1916 } else if let Some(list_id) = args.get("list_id").and_then(|v| v.as_str()) {
1917 format!("/v2/list/{}/member", list_id)
1918 } else {
1919 return Err("Provide either task_id or list_id".to_string());
1920 };
1921 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1922 let members = resp.get("members").and_then(|m| m.as_array()).cloned().unwrap_or_default();
1923 Ok(compact_items(&members, &["id", "username", "email"]))
1924 }
1925
1926 "clickup_template_list" => {
1927 let team_id = resolve_workspace(args)?;
1928 let page = args.get("page").and_then(|v| v.as_i64()).unwrap_or(0);
1929 let path = format!("/v2/team/{}/taskTemplate?page={}", team_id, page);
1930 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
1931 let templates = resp.get("templates").and_then(|t| t.as_array()).cloned().unwrap_or_default();
1932 Ok(compact_items(&templates, &["id", "name"]))
1933 }
1934
1935 "clickup_space_get" => {
1936 let space_id = args.get("space_id").and_then(|v| v.as_str())
1937 .ok_or("Missing required parameter: space_id")?;
1938 let resp = client.get(&format!("/v2/space/{}", space_id)).await.map_err(|e| e.to_string())?;
1939 Ok(compact_items(&[resp], &["id", "name", "private", "archived"]))
1940 }
1941
1942 "clickup_space_create" => {
1943 let team_id = resolve_workspace(args)?;
1944 let name = args.get("name").and_then(|v| v.as_str())
1945 .ok_or("Missing required parameter: name")?;
1946 let mut body = json!({"name": name});
1947 if let Some(private) = args.get("private").and_then(|v| v.as_bool()) {
1948 body["private"] = json!(private);
1949 }
1950 let resp = client.post(&format!("/v2/team/{}/space", team_id), &body).await.map_err(|e| e.to_string())?;
1951 Ok(compact_items(&[resp], &["id", "name", "private"]))
1952 }
1953
1954 "clickup_space_update" => {
1955 let space_id = args.get("space_id").and_then(|v| v.as_str())
1956 .ok_or("Missing required parameter: space_id")?;
1957 let mut body = json!({});
1958 if let Some(name) = args.get("name").and_then(|v| v.as_str()) { body["name"] = json!(name); }
1959 if let Some(private) = args.get("private").and_then(|v| v.as_bool()) { body["private"] = json!(private); }
1960 if let Some(archived) = args.get("archived").and_then(|v| v.as_bool()) { body["archived"] = json!(archived); }
1961 let resp = client.put(&format!("/v2/space/{}", space_id), &body).await.map_err(|e| e.to_string())?;
1962 Ok(compact_items(&[resp], &["id", "name", "private", "archived"]))
1963 }
1964
1965 "clickup_space_delete" => {
1966 let space_id = args.get("space_id").and_then(|v| v.as_str())
1967 .ok_or("Missing required parameter: space_id")?;
1968 client.delete(&format!("/v2/space/{}", space_id)).await.map_err(|e| e.to_string())?;
1969 Ok(json!({"message": format!("Space {} deleted", space_id)}))
1970 }
1971
1972 "clickup_folder_get" => {
1973 let folder_id = args.get("folder_id").and_then(|v| v.as_str())
1974 .ok_or("Missing required parameter: folder_id")?;
1975 let resp = client.get(&format!("/v2/folder/{}", folder_id)).await.map_err(|e| e.to_string())?;
1976 Ok(compact_items(&[resp], &["id", "name", "task_count"]))
1977 }
1978
1979 "clickup_folder_create" => {
1980 let space_id = args.get("space_id").and_then(|v| v.as_str())
1981 .ok_or("Missing required parameter: space_id")?;
1982 let name = args.get("name").and_then(|v| v.as_str())
1983 .ok_or("Missing required parameter: name")?;
1984 let body = json!({"name": name});
1985 let resp = client.post(&format!("/v2/space/{}/folder", space_id), &body).await.map_err(|e| e.to_string())?;
1986 Ok(compact_items(&[resp], &["id", "name"]))
1987 }
1988
1989 "clickup_folder_update" => {
1990 let folder_id = args.get("folder_id").and_then(|v| v.as_str())
1991 .ok_or("Missing required parameter: folder_id")?;
1992 let name = args.get("name").and_then(|v| v.as_str())
1993 .ok_or("Missing required parameter: name")?;
1994 let body = json!({"name": name});
1995 let resp = client.put(&format!("/v2/folder/{}", folder_id), &body).await.map_err(|e| e.to_string())?;
1996 Ok(compact_items(&[resp], &["id", "name"]))
1997 }
1998
1999 "clickup_folder_delete" => {
2000 let folder_id = args.get("folder_id").and_then(|v| v.as_str())
2001 .ok_or("Missing required parameter: folder_id")?;
2002 client.delete(&format!("/v2/folder/{}", folder_id)).await.map_err(|e| e.to_string())?;
2003 Ok(json!({"message": format!("Folder {} deleted", folder_id)}))
2004 }
2005
2006 "clickup_list_get" => {
2007 let list_id = args.get("list_id").and_then(|v| v.as_str())
2008 .ok_or("Missing required parameter: list_id")?;
2009 let resp = client.get(&format!("/v2/list/{}", list_id)).await.map_err(|e| e.to_string())?;
2010 Ok(compact_items(&[resp], &["id", "name", "task_count", "status", "due_date"]))
2011 }
2012
2013 "clickup_list_create" => {
2014 let name = args.get("name").and_then(|v| v.as_str())
2015 .ok_or("Missing required parameter: name")?;
2016 let mut body = json!({"name": name});
2017 if let Some(content) = args.get("content").and_then(|v| v.as_str()) { body["content"] = json!(content); }
2018 if let Some(due_date) = args.get("due_date").and_then(|v| v.as_i64()) { body["due_date"] = json!(due_date); }
2019 if let Some(status) = args.get("status").and_then(|v| v.as_str()) { body["status"] = json!(status); }
2020 let path = if let Some(folder_id) = args.get("folder_id").and_then(|v| v.as_str()) {
2021 format!("/v2/folder/{}/list", folder_id)
2022 } else if let Some(space_id) = args.get("space_id").and_then(|v| v.as_str()) {
2023 format!("/v2/space/{}/list", space_id)
2024 } else {
2025 return Err("Provide either folder_id or space_id".to_string());
2026 };
2027 let resp = client.post(&path, &body).await.map_err(|e| e.to_string())?;
2028 Ok(compact_items(&[resp], &["id", "name"]))
2029 }
2030
2031 "clickup_list_update" => {
2032 let list_id = args.get("list_id").and_then(|v| v.as_str())
2033 .ok_or("Missing required parameter: list_id")?;
2034 let mut body = json!({});
2035 if let Some(name) = args.get("name").and_then(|v| v.as_str()) { body["name"] = json!(name); }
2036 if let Some(content) = args.get("content").and_then(|v| v.as_str()) { body["content"] = json!(content); }
2037 if let Some(due_date) = args.get("due_date").and_then(|v| v.as_i64()) { body["due_date"] = json!(due_date); }
2038 if let Some(status) = args.get("status").and_then(|v| v.as_str()) { body["status"] = json!(status); }
2039 let resp = client.put(&format!("/v2/list/{}", list_id), &body).await.map_err(|e| e.to_string())?;
2040 Ok(compact_items(&[resp], &["id", "name", "task_count", "status"]))
2041 }
2042
2043 "clickup_list_delete" => {
2044 let list_id = args.get("list_id").and_then(|v| v.as_str())
2045 .ok_or("Missing required parameter: list_id")?;
2046 client.delete(&format!("/v2/list/{}", list_id)).await.map_err(|e| e.to_string())?;
2047 Ok(json!({"message": format!("List {} deleted", list_id)}))
2048 }
2049
2050 "clickup_list_add_task" => {
2051 let list_id = args.get("list_id").and_then(|v| v.as_str())
2052 .ok_or("Missing required parameter: list_id")?;
2053 let task_id = args.get("task_id").and_then(|v| v.as_str())
2054 .ok_or("Missing required parameter: task_id")?;
2055 client.post(&format!("/v2/list/{}/task/{}", list_id, task_id), &json!({})).await.map_err(|e| e.to_string())?;
2056 Ok(json!({"message": format!("Task {} added to list {}", task_id, list_id)}))
2057 }
2058
2059 "clickup_list_remove_task" => {
2060 let list_id = args.get("list_id").and_then(|v| v.as_str())
2061 .ok_or("Missing required parameter: list_id")?;
2062 let task_id = args.get("task_id").and_then(|v| v.as_str())
2063 .ok_or("Missing required parameter: task_id")?;
2064 client.delete(&format!("/v2/list/{}/task/{}", list_id, task_id)).await.map_err(|e| e.to_string())?;
2065 Ok(json!({"message": format!("Task {} removed from list {}", task_id, list_id)}))
2066 }
2067
2068 "clickup_comment_update" => {
2069 let comment_id = args.get("comment_id").and_then(|v| v.as_str())
2070 .ok_or("Missing required parameter: comment_id")?;
2071 let text = args.get("text").and_then(|v| v.as_str())
2072 .ok_or("Missing required parameter: text")?;
2073 let mut body = json!({"comment_text": text});
2074 if let Some(assignee) = args.get("assignee").and_then(|v| v.as_i64()) { body["assignee"] = json!(assignee); }
2075 if let Some(resolved) = args.get("resolved").and_then(|v| v.as_bool()) { body["resolved"] = json!(resolved); }
2076 client.put(&format!("/v2/comment/{}", comment_id), &body).await.map_err(|e| e.to_string())?;
2077 Ok(json!({"message": format!("Comment {} updated", comment_id)}))
2078 }
2079
2080 "clickup_comment_delete" => {
2081 let comment_id = args.get("comment_id").and_then(|v| v.as_str())
2082 .ok_or("Missing required parameter: comment_id")?;
2083 client.delete(&format!("/v2/comment/{}", comment_id)).await.map_err(|e| e.to_string())?;
2084 Ok(json!({"message": format!("Comment {} deleted", comment_id)}))
2085 }
2086
2087 "clickup_task_add_dep" => {
2088 let task_id = args.get("task_id").and_then(|v| v.as_str())
2089 .ok_or("Missing required parameter: task_id")?;
2090 let mut body = json!({});
2091 if let Some(dep) = args.get("depends_on").and_then(|v| v.as_str()) { body["depends_on"] = json!(dep); }
2092 if let Some(dep) = args.get("dependency_of").and_then(|v| v.as_str()) { body["dependency_of"] = json!(dep); }
2093 client.post(&format!("/v2/task/{}/dependency", task_id), &body).await.map_err(|e| e.to_string())?;
2094 Ok(json!({"message": format!("Dependency added to task {}", task_id)}))
2095 }
2096
2097 "clickup_task_remove_dep" => {
2098 let task_id = args.get("task_id").and_then(|v| v.as_str())
2099 .ok_or("Missing required parameter: task_id")?;
2100 let mut body = json!({});
2101 if let Some(dep) = args.get("depends_on").and_then(|v| v.as_str()) { body["depends_on"] = json!(dep); }
2102 if let Some(dep) = args.get("dependency_of").and_then(|v| v.as_str()) { body["dependency_of"] = json!(dep); }
2103 client.delete_with_body(&format!("/v2/task/{}/dependency", task_id), &body).await.map_err(|e| e.to_string())?;
2104 Ok(json!({"message": format!("Dependency removed from task {}", task_id)}))
2105 }
2106
2107 "clickup_task_link" => {
2108 let task_id = args.get("task_id").and_then(|v| v.as_str())
2109 .ok_or("Missing required parameter: task_id")?;
2110 let links_to = args.get("links_to").and_then(|v| v.as_str())
2111 .ok_or("Missing required parameter: links_to")?;
2112 let resp = client.post(&format!("/v2/task/{}/link/{}", task_id, links_to), &json!({})).await.map_err(|e| e.to_string())?;
2113 Ok(json!({"message": format!("Task {} linked to {}", task_id, links_to), "data": resp}))
2114 }
2115
2116 "clickup_task_unlink" => {
2117 let task_id = args.get("task_id").and_then(|v| v.as_str())
2118 .ok_or("Missing required parameter: task_id")?;
2119 let links_to = args.get("links_to").and_then(|v| v.as_str())
2120 .ok_or("Missing required parameter: links_to")?;
2121 client.delete(&format!("/v2/task/{}/link/{}", task_id, links_to)).await.map_err(|e| e.to_string())?;
2122 Ok(json!({"message": format!("Task {} unlinked from {}", task_id, links_to)}))
2123 }
2124
2125 "clickup_goal_delete" => {
2126 let goal_id = args.get("goal_id").and_then(|v| v.as_str())
2127 .ok_or("Missing required parameter: goal_id")?;
2128 client.delete(&format!("/v2/goal/{}", goal_id)).await.map_err(|e| e.to_string())?;
2129 Ok(json!({"message": format!("Goal {} deleted", goal_id)}))
2130 }
2131
2132 "clickup_goal_add_kr" => {
2133 let goal_id = args.get("goal_id").and_then(|v| v.as_str())
2134 .ok_or("Missing required parameter: goal_id")?;
2135 let name = args.get("name").and_then(|v| v.as_str())
2136 .ok_or("Missing required parameter: name")?;
2137 let kr_type = args.get("type").and_then(|v| v.as_str())
2138 .ok_or("Missing required parameter: type")?;
2139 let steps_start = args.get("steps_start").and_then(|v| v.as_f64())
2140 .ok_or("Missing required parameter: steps_start")?;
2141 let steps_end = args.get("steps_end").and_then(|v| v.as_f64())
2142 .ok_or("Missing required parameter: steps_end")?;
2143 let mut body = json!({"name": name, "type": kr_type, "steps_start": steps_start, "steps_end": steps_end});
2144 if let Some(unit) = args.get("unit").and_then(|v| v.as_str()) { body["unit"] = json!(unit); }
2145 if let Some(owners) = args.get("owner_ids") { body["owners"] = owners.clone(); }
2146 if let Some(task_ids) = args.get("task_ids") { body["task_ids"] = task_ids.clone(); }
2147 if let Some(list_ids) = args.get("list_ids") { body["list_ids"] = list_ids.clone(); }
2148 let resp = client.post(&format!("/v2/goal/{}/key_result", goal_id), &body).await.map_err(|e| e.to_string())?;
2149 let kr = resp.get("key_result").cloned().unwrap_or(resp);
2150 Ok(compact_items(&[kr], &["id", "name", "type", "steps_start", "steps_end", "steps_current"]))
2151 }
2152
2153 "clickup_goal_update_kr" => {
2154 let kr_id = args.get("kr_id").and_then(|v| v.as_str())
2155 .ok_or("Missing required parameter: kr_id")?;
2156 let mut body = json!({});
2157 if let Some(v) = args.get("steps_current").and_then(|v| v.as_f64()) { body["steps_current"] = json!(v); }
2158 if let Some(v) = args.get("name").and_then(|v| v.as_str()) { body["name"] = json!(v); }
2159 if let Some(v) = args.get("unit").and_then(|v| v.as_str()) { body["unit"] = json!(v); }
2160 let resp = client.put(&format!("/v2/key_result/{}", kr_id), &body).await.map_err(|e| e.to_string())?;
2161 let kr = resp.get("key_result").cloned().unwrap_or(resp);
2162 Ok(compact_items(&[kr], &["id", "name", "steps_current", "steps_end"]))
2163 }
2164
2165 "clickup_goal_delete_kr" => {
2166 let kr_id = args.get("kr_id").and_then(|v| v.as_str())
2167 .ok_or("Missing required parameter: kr_id")?;
2168 client.delete(&format!("/v2/key_result/{}", kr_id)).await.map_err(|e| e.to_string())?;
2169 Ok(json!({"message": format!("Key result {} deleted", kr_id)}))
2170 }
2171
2172 "clickup_time_get" => {
2173 let team_id = resolve_workspace(args)?;
2174 let timer_id = args.get("timer_id").and_then(|v| v.as_str())
2175 .ok_or("Missing required parameter: timer_id")?;
2176 let resp = client.get(&format!("/v2/team/{}/time_entries/{}", team_id, timer_id)).await.map_err(|e| e.to_string())?;
2177 let data = resp.get("data").cloned().unwrap_or(resp);
2178 Ok(compact_items(&[data], &["id", "task", "duration", "start", "end", "billable"]))
2179 }
2180
2181 "clickup_time_create" => {
2182 let team_id = resolve_workspace(args)?;
2183 let start = args.get("start").and_then(|v| v.as_i64())
2184 .ok_or("Missing required parameter: start")?;
2185 let duration = args.get("duration").and_then(|v| v.as_i64())
2186 .ok_or("Missing required parameter: duration")?;
2187 let mut body = json!({"start": start, "duration": duration});
2188 if let Some(task_id) = args.get("task_id").and_then(|v| v.as_str()) { body["tid"] = json!(task_id); }
2189 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) { body["description"] = json!(desc); }
2190 if let Some(billable) = args.get("billable").and_then(|v| v.as_bool()) { body["billable"] = json!(billable); }
2191 let resp = client.post(&format!("/v2/team/{}/time_entries", team_id), &body).await.map_err(|e| e.to_string())?;
2192 let data = resp.get("data").cloned().unwrap_or(resp);
2193 Ok(compact_items(&[data], &["id", "task", "duration", "start", "billable"]))
2194 }
2195
2196 "clickup_time_update" => {
2197 let team_id = resolve_workspace(args)?;
2198 let timer_id = args.get("timer_id").and_then(|v| v.as_str())
2199 .ok_or("Missing required parameter: timer_id")?;
2200 let mut body = json!({});
2201 if let Some(start) = args.get("start").and_then(|v| v.as_i64()) { body["start"] = json!(start); }
2202 if let Some(duration) = args.get("duration").and_then(|v| v.as_i64()) { body["duration"] = json!(duration); }
2203 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) { body["description"] = json!(desc); }
2204 if let Some(billable) = args.get("billable").and_then(|v| v.as_bool()) { body["billable"] = json!(billable); }
2205 let resp = client.put(&format!("/v2/team/{}/time_entries/{}", team_id, timer_id), &body).await.map_err(|e| e.to_string())?;
2206 let data = resp.get("data").cloned().unwrap_or(resp);
2207 Ok(compact_items(&[data], &["id", "task", "duration", "start", "billable"]))
2208 }
2209
2210 "clickup_time_delete" => {
2211 let team_id = resolve_workspace(args)?;
2212 let timer_id = args.get("timer_id").and_then(|v| v.as_str())
2213 .ok_or("Missing required parameter: timer_id")?;
2214 client.delete(&format!("/v2/team/{}/time_entries/{}", team_id, timer_id)).await.map_err(|e| e.to_string())?;
2215 Ok(json!({"message": format!("Time entry {} deleted", timer_id)}))
2216 }
2217
2218 "clickup_view_get" => {
2219 let view_id = args.get("view_id").and_then(|v| v.as_str())
2220 .ok_or("Missing required parameter: view_id")?;
2221 let resp = client.get(&format!("/v2/view/{}", view_id)).await.map_err(|e| e.to_string())?;
2222 let view = resp.get("view").cloned().unwrap_or(resp);
2223 Ok(compact_items(&[view], &["id", "name", "type"]))
2224 }
2225
2226 "clickup_view_create" => {
2227 let scope = args.get("scope").and_then(|v| v.as_str())
2228 .ok_or("Missing required parameter: scope")?;
2229 let scope_id = args.get("scope_id").and_then(|v| v.as_str())
2230 .ok_or("Missing required parameter: scope_id")?;
2231 let name = args.get("name").and_then(|v| v.as_str())
2232 .ok_or("Missing required parameter: name")?;
2233 let view_type = args.get("type").and_then(|v| v.as_str())
2234 .ok_or("Missing required parameter: type")?;
2235 let body = json!({"name": name, "type": view_type});
2236 let resp = client.post(&format!("/v2/{}/{}/view", scope, scope_id), &body).await.map_err(|e| e.to_string())?;
2237 let view = resp.get("view").cloned().unwrap_or(resp);
2238 Ok(compact_items(&[view], &["id", "name", "type"]))
2239 }
2240
2241 "clickup_view_update" => {
2242 let view_id = args.get("view_id").and_then(|v| v.as_str())
2243 .ok_or("Missing required parameter: view_id")?;
2244 let name = args.get("name").and_then(|v| v.as_str())
2245 .ok_or("Missing required parameter: name")?;
2246 let view_type = args.get("type").and_then(|v| v.as_str())
2247 .ok_or("Missing required parameter: type")?;
2248 let body = json!({"name": name, "type": view_type});
2249 let resp = client.put(&format!("/v2/view/{}", view_id), &body).await.map_err(|e| e.to_string())?;
2250 let view = resp.get("view").cloned().unwrap_or(resp);
2251 Ok(compact_items(&[view], &["id", "name", "type"]))
2252 }
2253
2254 "clickup_view_delete" => {
2255 let view_id = args.get("view_id").and_then(|v| v.as_str())
2256 .ok_or("Missing required parameter: view_id")?;
2257 client.delete(&format!("/v2/view/{}", view_id)).await.map_err(|e| e.to_string())?;
2258 Ok(json!({"message": format!("View {} deleted", view_id)}))
2259 }
2260
2261 "clickup_doc_create" => {
2262 let team_id = resolve_workspace(args)?;
2263 let name = args.get("name").and_then(|v| v.as_str())
2264 .ok_or("Missing required parameter: name")?;
2265 let mut body = json!({"name": name});
2266 if let Some(parent) = args.get("parent") { body["parent"] = parent.clone(); }
2267 let resp = client.post(&format!("/v3/workspaces/{}/docs", team_id), &body).await.map_err(|e| e.to_string())?;
2268 Ok(compact_items(&[resp], &["id", "name"]))
2269 }
2270
2271 "clickup_doc_add_page" => {
2272 let team_id = resolve_workspace(args)?;
2273 let doc_id = args.get("doc_id").and_then(|v| v.as_str())
2274 .ok_or("Missing required parameter: doc_id")?;
2275 let name = args.get("name").and_then(|v| v.as_str())
2276 .ok_or("Missing required parameter: name")?;
2277 let mut body = json!({"name": name});
2278 if let Some(content) = args.get("content").and_then(|v| v.as_str()) { body["content"] = json!(content); }
2279 if let Some(subtitle) = args.get("sub_title").and_then(|v| v.as_str()) { body["sub_title"] = json!(subtitle); }
2280 if let Some(parent_page_id) = args.get("parent_page_id").and_then(|v| v.as_str()) { body["parent_page_id"] = json!(parent_page_id); }
2281 let resp = client.post(&format!("/v3/workspaces/{}/docs/{}/pages", team_id, doc_id), &body).await.map_err(|e| e.to_string())?;
2282 Ok(compact_items(&[resp], &["id", "name"]))
2283 }
2284
2285 "clickup_doc_edit_page" => {
2286 let team_id = resolve_workspace(args)?;
2287 let doc_id = args.get("doc_id").and_then(|v| v.as_str())
2288 .ok_or("Missing required parameter: doc_id")?;
2289 let page_id = args.get("page_id").and_then(|v| v.as_str())
2290 .ok_or("Missing required parameter: page_id")?;
2291 let mut body = json!({});
2292 if let Some(name) = args.get("name").and_then(|v| v.as_str()) { body["name"] = json!(name); }
2293 if let Some(content) = args.get("content").and_then(|v| v.as_str()) { body["content"] = json!(content); }
2294 let resp = client.put(&format!("/v3/workspaces/{}/docs/{}/pages/{}", team_id, doc_id, page_id), &body).await.map_err(|e| e.to_string())?;
2295 Ok(compact_items(&[resp], &["id", "name"]))
2296 }
2297
2298 "clickup_chat_channel_create" => {
2299 let team_id = resolve_workspace(args)?;
2300 let name = args.get("name").and_then(|v| v.as_str())
2301 .ok_or("Missing required parameter: name")?;
2302 let mut body = json!({"name": name});
2303 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) { body["description"] = json!(desc); }
2304 if let Some(vis) = args.get("visibility").and_then(|v| v.as_str()) { body["visibility"] = json!(vis); }
2305 let resp = client.post(&format!("/v3/workspaces/{}/chat/channels", team_id), &body).await.map_err(|e| e.to_string())?;
2306 Ok(compact_items(&[resp], &["id", "name", "visibility"]))
2307 }
2308
2309 "clickup_chat_channel_get" => {
2310 let team_id = resolve_workspace(args)?;
2311 let channel_id = args.get("channel_id").and_then(|v| v.as_str())
2312 .ok_or("Missing required parameter: channel_id")?;
2313 let resp = client.get(&format!("/v3/workspaces/{}/chat/channels/{}", team_id, channel_id)).await.map_err(|e| e.to_string())?;
2314 Ok(compact_items(&[resp], &["id", "name", "visibility"]))
2315 }
2316
2317 "clickup_chat_channel_update" => {
2318 let team_id = resolve_workspace(args)?;
2319 let channel_id = args.get("channel_id").and_then(|v| v.as_str())
2320 .ok_or("Missing required parameter: channel_id")?;
2321 let mut body = json!({});
2322 if let Some(name) = args.get("name").and_then(|v| v.as_str()) { body["name"] = json!(name); }
2323 if let Some(desc) = args.get("description").and_then(|v| v.as_str()) { body["description"] = json!(desc); }
2324 let resp = client.patch(&format!("/v3/workspaces/{}/chat/channels/{}", team_id, channel_id), &body).await.map_err(|e| e.to_string())?;
2325 Ok(compact_items(&[resp], &["id", "name"]))
2326 }
2327
2328 "clickup_chat_channel_delete" => {
2329 let team_id = resolve_workspace(args)?;
2330 let channel_id = args.get("channel_id").and_then(|v| v.as_str())
2331 .ok_or("Missing required parameter: channel_id")?;
2332 client.delete(&format!("/v3/workspaces/{}/chat/channels/{}", team_id, channel_id)).await.map_err(|e| e.to_string())?;
2333 Ok(json!({"message": format!("Channel {} deleted", channel_id)}))
2334 }
2335
2336 "clickup_chat_message_list" => {
2337 let team_id = resolve_workspace(args)?;
2338 let channel_id = args.get("channel_id").and_then(|v| v.as_str())
2339 .ok_or("Missing required parameter: channel_id")?;
2340 let mut path = format!("/v3/workspaces/{}/chat/channels/{}/messages", team_id, channel_id);
2341 if let Some(cursor) = args.get("cursor").and_then(|v| v.as_str()) {
2342 path.push_str(&format!("?cursor={}", cursor));
2343 }
2344 let resp = client.get(&path).await.map_err(|e| e.to_string())?;
2345 let messages = resp.get("messages").and_then(|m| m.as_array()).cloned().unwrap_or_default();
2346 Ok(compact_items(&messages, &["id", "content", "date"]))
2347 }
2348
2349 "clickup_chat_message_send" => {
2350 let team_id = resolve_workspace(args)?;
2351 let channel_id = args.get("channel_id").and_then(|v| v.as_str())
2352 .ok_or("Missing required parameter: channel_id")?;
2353 let content = args.get("content").and_then(|v| v.as_str())
2354 .ok_or("Missing required parameter: content")?;
2355 let body = json!({"content": content});
2356 let resp = client.post(&format!("/v3/workspaces/{}/chat/channels/{}/messages", team_id, channel_id), &body).await.map_err(|e| e.to_string())?;
2357 Ok(json!({"message": "Message sent", "id": resp.get("id")}))
2358 }
2359
2360 "clickup_chat_message_delete" => {
2361 let team_id = resolve_workspace(args)?;
2362 let message_id = args.get("message_id").and_then(|v| v.as_str())
2363 .ok_or("Missing required parameter: message_id")?;
2364 client.delete(&format!("/v3/workspaces/{}/chat/messages/{}", team_id, message_id)).await.map_err(|e| e.to_string())?;
2365 Ok(json!({"message": format!("Message {} deleted", message_id)}))
2366 }
2367
2368 "clickup_chat_dm" => {
2369 let team_id = resolve_workspace(args)?;
2370 let user_id = args.get("user_id").and_then(|v| v.as_i64())
2371 .ok_or("Missing required parameter: user_id")?;
2372 let content = args.get("content").and_then(|v| v.as_str())
2373 .ok_or("Missing required parameter: content")?;
2374 let body = json!({"user_id": user_id, "content": content});
2375 let resp = client.post(&format!("/v3/workspaces/{}/chat/channels/direct_message", team_id), &body).await.map_err(|e| e.to_string())?;
2376 Ok(json!({"message": "DM sent", "id": resp.get("id")}))
2377 }
2378
2379 "clickup_webhook_create" => {
2380 let team_id = resolve_workspace(args)?;
2381 let endpoint = args.get("endpoint").and_then(|v| v.as_str())
2382 .ok_or("Missing required parameter: endpoint")?;
2383 let events = args.get("events").ok_or("Missing required parameter: events")?;
2384 let mut body = json!({"endpoint": endpoint, "events": events});
2385 if let Some(space_id) = args.get("space_id").and_then(|v| v.as_str()) { body["space_id"] = json!(space_id); }
2386 if let Some(folder_id) = args.get("folder_id").and_then(|v| v.as_str()) { body["folder_id"] = json!(folder_id); }
2387 if let Some(list_id) = args.get("list_id").and_then(|v| v.as_str()) { body["list_id"] = json!(list_id); }
2388 if let Some(task_id) = args.get("task_id").and_then(|v| v.as_str()) { body["task_id"] = json!(task_id); }
2389 let resp = client.post(&format!("/v2/team/{}/webhook", team_id), &body).await.map_err(|e| e.to_string())?;
2390 let webhook = resp.get("webhook").cloned().unwrap_or(resp);
2391 Ok(compact_items(&[webhook], &["id", "endpoint", "events", "status"]))
2392 }
2393
2394 "clickup_webhook_update" => {
2395 let webhook_id = args.get("webhook_id").and_then(|v| v.as_str())
2396 .ok_or("Missing required parameter: webhook_id")?;
2397 let mut body = json!({});
2398 if let Some(endpoint) = args.get("endpoint").and_then(|v| v.as_str()) { body["endpoint"] = json!(endpoint); }
2399 if let Some(events) = args.get("events") { body["events"] = events.clone(); }
2400 if let Some(status) = args.get("status").and_then(|v| v.as_str()) { body["status"] = json!(status); }
2401 let resp = client.put(&format!("/v2/webhook/{}", webhook_id), &body).await.map_err(|e| e.to_string())?;
2402 let webhook = resp.get("webhook").cloned().unwrap_or(resp);
2403 Ok(compact_items(&[webhook], &["id", "endpoint", "events", "status"]))
2404 }
2405
2406 "clickup_webhook_delete" => {
2407 let webhook_id = args.get("webhook_id").and_then(|v| v.as_str())
2408 .ok_or("Missing required parameter: webhook_id")?;
2409 client.delete(&format!("/v2/webhook/{}", webhook_id)).await.map_err(|e| e.to_string())?;
2410 Ok(json!({"message": format!("Webhook {} deleted", webhook_id)}))
2411 }
2412
2413 "clickup_checklist_add_item" => {
2414 let checklist_id = args.get("checklist_id").and_then(|v| v.as_str())
2415 .ok_or("Missing required parameter: checklist_id")?;
2416 let name = args.get("name").and_then(|v| v.as_str())
2417 .ok_or("Missing required parameter: name")?;
2418 let mut body = json!({"name": name});
2419 if let Some(assignee) = args.get("assignee").and_then(|v| v.as_i64()) { body["assignee"] = json!(assignee); }
2420 let resp = client.post(&format!("/v2/checklist/{}/checklist_item", checklist_id), &body).await.map_err(|e| e.to_string())?;
2421 let item = resp.get("checklist").cloned().unwrap_or(resp);
2422 Ok(compact_items(&[item], &["id", "name"]))
2423 }
2424
2425 "clickup_checklist_update_item" => {
2426 let checklist_id = args.get("checklist_id").and_then(|v| v.as_str())
2427 .ok_or("Missing required parameter: checklist_id")?;
2428 let item_id = args.get("item_id").and_then(|v| v.as_str())
2429 .ok_or("Missing required parameter: item_id")?;
2430 let mut body = json!({});
2431 if let Some(name) = args.get("name").and_then(|v| v.as_str()) { body["name"] = json!(name); }
2432 if let Some(resolved) = args.get("resolved").and_then(|v| v.as_bool()) { body["resolved"] = json!(resolved); }
2433 if let Some(assignee) = args.get("assignee").and_then(|v| v.as_i64()) { body["assignee"] = json!(assignee); }
2434 client.put(&format!("/v2/checklist/{}/checklist_item/{}", checklist_id, item_id), &body).await.map_err(|e| e.to_string())?;
2435 Ok(json!({"message": format!("Checklist item {} updated", item_id)}))
2436 }
2437
2438 "clickup_checklist_delete_item" => {
2439 let checklist_id = args.get("checklist_id").and_then(|v| v.as_str())
2440 .ok_or("Missing required parameter: checklist_id")?;
2441 let item_id = args.get("item_id").and_then(|v| v.as_str())
2442 .ok_or("Missing required parameter: item_id")?;
2443 client.delete(&format!("/v2/checklist/{}/checklist_item/{}", checklist_id, item_id)).await.map_err(|e| e.to_string())?;
2444 Ok(json!({"message": format!("Checklist item {} deleted", item_id)}))
2445 }
2446
2447 "clickup_user_get" => {
2448 let team_id = resolve_workspace(args)?;
2449 let user_id = args.get("user_id").and_then(|v| v.as_i64())
2450 .ok_or("Missing required parameter: user_id")?;
2451 let resp = client.get(&format!("/v2/team/{}/user/{}", team_id, user_id)).await.map_err(|e| e.to_string())?;
2452 let member = resp.get("member").cloned().unwrap_or(resp);
2453 Ok(compact_items(&[member], &["user", "role"]))
2454 }
2455
2456 "clickup_workspace_seats" => {
2457 let team_id = resolve_workspace(args)?;
2458 let resp = client.get(&format!("/v2/team/{}/seats", team_id)).await.map_err(|e| e.to_string())?;
2459 Ok(json!(resp))
2460 }
2461
2462 "clickup_workspace_plan" => {
2463 let team_id = resolve_workspace(args)?;
2464 let resp = client.get(&format!("/v2/team/{}/plan", team_id)).await.map_err(|e| e.to_string())?;
2465 Ok(json!(resp))
2466 }
2467
2468 "clickup_tag_create" => {
2469 let space_id = args.get("space_id").and_then(|v| v.as_str())
2470 .ok_or("Missing required parameter: space_id")?;
2471 let name = args.get("name").and_then(|v| v.as_str())
2472 .ok_or("Missing required parameter: name")?;
2473 let mut tag = json!({"name": name});
2474 if let Some(fg) = args.get("tag_fg").and_then(|v| v.as_str()) { tag["tag_fg"] = json!(fg); }
2475 if let Some(bg) = args.get("tag_bg").and_then(|v| v.as_str()) { tag["tag_bg"] = json!(bg); }
2476 let body = json!({"tag": tag});
2477 client.post(&format!("/v2/space/{}/tag", space_id), &body).await.map_err(|e| e.to_string())?;
2478 Ok(json!({"message": format!("Tag '{}' created in space {}", name, space_id)}))
2479 }
2480
2481 "clickup_tag_update" => {
2482 let space_id = args.get("space_id").and_then(|v| v.as_str())
2483 .ok_or("Missing required parameter: space_id")?;
2484 let tag_name = args.get("tag_name").and_then(|v| v.as_str())
2485 .ok_or("Missing required parameter: tag_name")?;
2486 let mut tag = json!({});
2487 if let Some(name) = args.get("name").and_then(|v| v.as_str()) { tag["name"] = json!(name); }
2488 if let Some(fg) = args.get("tag_fg").and_then(|v| v.as_str()) { tag["tag_fg"] = json!(fg); }
2489 if let Some(bg) = args.get("tag_bg").and_then(|v| v.as_str()) { tag["tag_bg"] = json!(bg); }
2490 let body = json!({"tag": tag});
2491 client.put(&format!("/v2/space/{}/tag/{}", space_id, tag_name), &body).await.map_err(|e| e.to_string())?;
2492 Ok(json!({"message": format!("Tag '{}' updated", tag_name)}))
2493 }
2494
2495 "clickup_tag_delete" => {
2496 let space_id = args.get("space_id").and_then(|v| v.as_str())
2497 .ok_or("Missing required parameter: space_id")?;
2498 let tag_name = args.get("tag_name").and_then(|v| v.as_str())
2499 .ok_or("Missing required parameter: tag_name")?;
2500 client.delete(&format!("/v2/space/{}/tag/{}", space_id, tag_name)).await.map_err(|e| e.to_string())?;
2501 Ok(json!({"message": format!("Tag '{}' deleted from space {}", tag_name, space_id)}))
2502 }
2503
2504 "clickup_field_unset" => {
2505 let task_id = args.get("task_id").and_then(|v| v.as_str())
2506 .ok_or("Missing required parameter: task_id")?;
2507 let field_id = args.get("field_id").and_then(|v| v.as_str())
2508 .ok_or("Missing required parameter: field_id")?;
2509 client.delete(&format!("/v2/task/{}/field/{}", task_id, field_id)).await.map_err(|e| e.to_string())?;
2510 Ok(json!({"message": format!("Field {} unset on task {}", field_id, task_id)}))
2511 }
2512
2513 "clickup_attachment_list" => {
2514 let team_id = resolve_workspace(args)?;
2515 let task_id = args.get("task_id").and_then(|v| v.as_str())
2516 .ok_or("Missing required parameter: task_id")?;
2517 let resp = client.get(&format!("/v3/workspaces/{}/task/{}/attachments", team_id, task_id)).await.map_err(|e| e.to_string())?;
2518 let attachments = resp.get("attachments").and_then(|a| a.as_array()).cloned().unwrap_or_default();
2519 Ok(compact_items(&attachments, &["id", "title", "url", "date"]))
2520 }
2521
2522 "clickup_shared_list" => {
2523 let team_id = resolve_workspace(args)?;
2524 let resp = client.get(&format!("/v2/team/{}/shared", team_id)).await.map_err(|e| e.to_string())?;
2525 Ok(json!(resp))
2526 }
2527
2528 "clickup_group_list" => {
2529 let team_id = resolve_workspace(args)?;
2530 let mut qs = format!("team_id={}", team_id);
2531 if let Some(group_ids) = args.get("group_ids").and_then(|v| v.as_array()) {
2532 for id in group_ids {
2533 if let Some(id) = id.as_str() {
2534 qs.push_str(&format!("&group_ids[]={}", id));
2535 }
2536 }
2537 }
2538 let resp = client.get(&format!("/v2/group?{}", qs)).await.map_err(|e| e.to_string())?;
2539 let groups = resp.get("groups").and_then(|g| g.as_array()).cloned().unwrap_or_default();
2540 Ok(compact_items(&groups, &["id", "name", "members"]))
2541 }
2542
2543 "clickup_group_create" => {
2544 let team_id = resolve_workspace(args)?;
2545 let name = args.get("name").and_then(|v| v.as_str())
2546 .ok_or("Missing required parameter: name")?;
2547 let mut body = json!({"name": name});
2548 if let Some(members) = args.get("member_ids") { body["members"] = members.clone(); }
2549 let resp = client.post(&format!("/v2/team/{}/group", team_id), &body).await.map_err(|e| e.to_string())?;
2550 Ok(compact_items(&[resp], &["id", "name"]))
2551 }
2552
2553 "clickup_group_update" => {
2554 let group_id = args.get("group_id").and_then(|v| v.as_str())
2555 .ok_or("Missing required parameter: group_id")?;
2556 let mut body = json!({});
2557 if let Some(name) = args.get("name").and_then(|v| v.as_str()) { body["name"] = json!(name); }
2558 if let Some(add) = args.get("add_members") {
2559 body["members"] = json!({"add": add, "rem": args.get("rem_members").cloned().unwrap_or(json!([]))});
2560 } else if let Some(rem) = args.get("rem_members") {
2561 body["members"] = json!({"add": [], "rem": rem});
2562 }
2563 let resp = client.put(&format!("/v2/group/{}", group_id), &body).await.map_err(|e| e.to_string())?;
2564 Ok(compact_items(&[resp], &["id", "name"]))
2565 }
2566
2567 "clickup_group_delete" => {
2568 let group_id = args.get("group_id").and_then(|v| v.as_str())
2569 .ok_or("Missing required parameter: group_id")?;
2570 client.delete(&format!("/v2/group/{}", group_id)).await.map_err(|e| e.to_string())?;
2571 Ok(json!({"message": format!("Group {} deleted", group_id)}))
2572 }
2573
2574 "clickup_role_list" => {
2575 let team_id = resolve_workspace(args)?;
2576 let resp = client.get(&format!("/v2/team/{}/customroles", team_id)).await.map_err(|e| e.to_string())?;
2577 let roles = resp.get("roles").and_then(|r| r.as_array()).cloned().unwrap_or_default();
2578 Ok(compact_items(&roles, &["id", "name"]))
2579 }
2580
2581 "clickup_guest_get" => {
2582 let team_id = resolve_workspace(args)?;
2583 let guest_id = args.get("guest_id").and_then(|v| v.as_i64())
2584 .ok_or("Missing required parameter: guest_id")?;
2585 let resp = client.get(&format!("/v2/team/{}/guest/{}", team_id, guest_id)).await.map_err(|e| e.to_string())?;
2586 let guest = resp.get("guest").cloned().unwrap_or(resp);
2587 Ok(compact_items(&[guest], &["user", "role"]))
2588 }
2589
2590 unknown => Err(format!("Unknown tool: {}", unknown)),
2591 }
2592}
2593
2594pub async fn serve() -> Result<(), Box<dyn std::error::Error>> {
2597 let config = Config::load().map_err(|e| format!("Failed to load config: {}", e))?;
2599 let token = config.auth.token.clone();
2600 if token.is_empty() {
2601 return Err("No API token configured. Run `clickup setup` first.".into());
2602 }
2603 let workspace_id = config.defaults.workspace_id.clone();
2604
2605 let client = ClickUpClient::new(&token, 30)
2606 .map_err(|e| format!("Failed to create HTTP client: {}", e))?;
2607
2608 let stdin = tokio::io::stdin();
2609 let reader = BufReader::new(stdin);
2610 let mut lines = reader.lines();
2611
2612 while let Some(line) = lines.next_line().await? {
2613 let line = line.trim().to_string();
2614 if line.is_empty() {
2615 continue;
2616 }
2617
2618 let msg: Value = match serde_json::from_str(&line) {
2619 Ok(v) => v,
2620 Err(e) => {
2621 let resp = error_response(&Value::Null, -32700, &format!("Parse error: {}", e));
2623 println!("{}", resp);
2624 continue;
2625 }
2626 };
2627
2628 let id = msg.get("id").cloned().unwrap_or(Value::Null);
2630 let method = msg.get("method").and_then(|v| v.as_str()).unwrap_or("");
2631
2632 if id.is_null() && method.starts_with("notifications/") {
2633 continue;
2635 }
2636
2637 let resp = match method {
2638 "initialize" => {
2639 let version = msg
2640 .get("params")
2641 .and_then(|p| p.get("protocolVersion"))
2642 .and_then(|v| v.as_str())
2643 .unwrap_or("2024-11-05");
2644 ok_response(
2645 &id,
2646 json!({
2647 "protocolVersion": version,
2648 "capabilities": {"tools": {}},
2649 "serverInfo": {
2650 "name": "clickup-cli",
2651 "version": env!("CARGO_PKG_VERSION")
2652 }
2653 }),
2654 )
2655 }
2656
2657 "tools/list" => ok_response(&id, json!({"tools": tool_list()})),
2658
2659 "tools/call" => {
2660 let params = msg.get("params").cloned().unwrap_or(json!({}));
2661 let tool_name = params
2662 .get("name")
2663 .and_then(|v| v.as_str())
2664 .unwrap_or("");
2665 let arguments = params.get("arguments").cloned().unwrap_or(json!({}));
2666
2667 if tool_name.is_empty() {
2668 let result = tool_error("Missing tool name".to_string());
2669 ok_response(&id, result)
2670 } else {
2671 let result = call_tool(tool_name, &arguments, &client, &workspace_id).await;
2672 ok_response(&id, result)
2673 }
2674 }
2675
2676 other => {
2677 eprintln!("Unknown method: {}", other);
2679 error_response(&id, -32601, &format!("Method not found: {}", other))
2680 }
2681 };
2682
2683 println!("{}", resp);
2684 }
2685
2686 Ok(())
2687}