@alanse/clickup-multi-mcp-server 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -31
- package/build/config.js +3 -0
- package/build/server.js +21 -3
- package/build/server.log +1 -0
- package/build/services/clickup/task/task-core.js +99 -0
- package/build/services/shared.js +3 -0
- package/build/tools/task/handlers.js +86 -0
- package/build/tools/task/index.js +2 -2
- package/build/tools/task/single-operations.js +139 -0
- package/build/tools/workspace-helper.js +4 -1
- package/build/tools/workspace.js +46 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,17 +178,77 @@ await getTasks({ workspace: "personal", list_id: "987654321" });
|
|
|
178
178
|
await getWorkspaceHierarchy({ workspace: "work" });
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
## Local Development Setup
|
|
182
|
+
|
|
183
|
+
For local development or when running the server directly from source code:
|
|
184
|
+
|
|
185
|
+
### 1. Clone and Install
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
git clone https://github.com/da-okazaki/clickup-multi-mcp-server.git
|
|
189
|
+
cd clickup-multi-mcp-server
|
|
190
|
+
npm install
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 2. Configure Environment Variables
|
|
194
|
+
|
|
195
|
+
Copy `.env.example` to `.env` and configure your ClickUp credentials:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
cp .env.example .env
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Edit `.env` file:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Multi-workspace configuration
|
|
205
|
+
CLICKUP_WORKSPACES={"default":"alanse","workspaces":{"alanse":{"token":"pk_YOUR_TOKEN_1","teamId":"YOUR_TEAM_ID_1","description":"Alanse workspace"},"potz":{"token":"pk_YOUR_TOKEN_2","teamId":"YOUR_TEAM_ID_2","description":"Potz workspace"}}}
|
|
206
|
+
|
|
207
|
+
# Or use single workspace (legacy)
|
|
208
|
+
# CLICKUP_API_KEY=your_api_key_here
|
|
209
|
+
# CLICKUP_TEAM_ID=your_team_id_here
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Note**: The `.env` file is automatically loaded when the server starts. Environment variables in `.env` are automatically picked up without needing to pass them via command line.
|
|
213
|
+
|
|
214
|
+
### 3. Build and Run
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Build the project
|
|
218
|
+
npm run build
|
|
219
|
+
|
|
220
|
+
# Run locally
|
|
221
|
+
node build/index.js
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 4. Configure Claude Code for Local Development
|
|
225
|
+
|
|
226
|
+
If you want to use your local build with Claude Code, update `~/.claude.json`:
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"mcpServers": {
|
|
231
|
+
"clickup": {
|
|
232
|
+
"type": "stdio",
|
|
233
|
+
"command": "node",
|
|
234
|
+
"args": ["/absolute/path/to/clickup-multi-mcp-server/build/index.js"]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Important**: When using local build with Claude Code:
|
|
241
|
+
- Environment variables are loaded from `.env` file in the project root
|
|
242
|
+
- No need to specify `env` in `~/.claude.json` (unless you want to override `.env` values)
|
|
243
|
+
- Rebuild after making changes: `npm run build`
|
|
244
|
+
|
|
181
245
|
## Smithery Installation (Quick Start)
|
|
182
246
|
|
|
183
247
|
[](https://smithery.ai/server/@TaazKareem/clickup-mcp-server)
|
|
184
248
|
|
|
185
249
|
The server is hosted on [Smithery](https://smithery.ai/server/@taazkareem/clickup-mcp-server). There, you can preview the available tools or copy the commands to run on your specific client app.
|
|
186
250
|
|
|
187
|
-
## NPX Installation
|
|
188
251
|
|
|
189
|
-
[](https://www.npmjs.com/package/@taazkareem/clickup-mcp-server)
|
|
190
|
-
[](https://github.com/TaazKareem/clickup-mcp-server/blob/main/package.json)
|
|
191
|
-
[](https://npmcharts.com/compare/@taazkareem/clickup-mcp-server?minimal=true)
|
|
192
252
|
|
|
193
253
|
Add this entry to your client's MCP settings JSON file:
|
|
194
254
|
|
|
@@ -368,11 +428,12 @@ npm run sse-client
|
|
|
368
428
|
| ⚡ **Integration Features** | 🏗️ **Architecture & Performance** |
|
|
369
429
|
| • Global name or ID-based lookups<br>• Case-insensitive matching<br>• Markdown formatting support<br>• Built-in rate limiting<br>• Error handling and validation<br>• Comprehensive API coverage | • **70% codebase reduction** for improved performance<br>• **Unified architecture** across all transport types<br>• **Zero code duplication**<br>• **HTTP Streamable transport** (MCP Inspector compatible)<br>• **Legacy SSE support** for backwards compatibility |
|
|
370
430
|
|
|
371
|
-
## Available Tools (
|
|
431
|
+
## Available Tools (42 Total)
|
|
372
432
|
|
|
373
433
|
| Tool | Description | Required Parameters |
|
|
374
434
|
| ------------------------------------------------------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
375
435
|
| [get_workspace_hierarchy](docs/user-guide.md#workspace-navigation) | Get workspace structure | None |
|
|
436
|
+
| [get_available_workspaces](docs/user-guide.md#workspace-navigation) | Get all available workspaces | None |
|
|
376
437
|
| [create_task](docs/user-guide.md#task-management) | Create a task | `name`, (`listId`/`listName`) |
|
|
377
438
|
| [create_bulk_tasks](docs/user-guide.md#task-management) | Create multiple tasks | `tasks[]` |
|
|
378
439
|
| [update_task](docs/user-guide.md#task-management) | Modify task | `taskId`/`taskName` |
|
|
@@ -388,6 +449,11 @@ npm run sse-client
|
|
|
388
449
|
| [move_task](docs/user-guide.md#task-management) | Move task | `taskId`/`taskName`, `listId`/`listName` |
|
|
389
450
|
| [move_bulk_tasks](docs/user-guide.md#task-management) | Move multiple tasks | `tasks[]` with IDs or names, target list |
|
|
390
451
|
| [duplicate_task](docs/user-guide.md#task-management) | Copy task | `taskId`/`taskName`, `listId`/`listName` |
|
|
452
|
+
| [merge_task](docs/user-guide.md#task-management) | Merge two tasks | `taskId`, `mergeFromId` |
|
|
453
|
+
| [get_task_time_in_status](docs/user-guide.md#task-management) | Get time in status for a task | `taskId`/`taskName` |
|
|
454
|
+
| [get_bulk_tasks_time_in_status](docs/user-guide.md#task-management)| Get bulk time in status | `taskIds[]` |
|
|
455
|
+
| [add_task_to_list](docs/user-guide.md#list-management) | Add task to additional list | `listId`, `taskId` |
|
|
456
|
+
| [remove_task_from_list](docs/user-guide.md#list-management) | Remove task from list | `listId`, `taskId` |
|
|
391
457
|
| [create_list](docs/user-guide.md#list-management) | Create list in space | `name`, `spaceId`/`spaceName` |
|
|
392
458
|
| [create_folder](docs/user-guide.md#folder-management) | Create folder | `name`, `spaceId`/`spaceName` |
|
|
393
459
|
| [create_list_in_folder](docs/user-guide.md#list-management) | Create list in folder | `name`, `folderId`/`folderName` |
|
|
@@ -469,35 +535,9 @@ The server provides clear error messages for:
|
|
|
469
535
|
The `LOG_LEVEL` environment variable can be specified to control the verbosity of server logs. Valid values are `trace`, `debug`, `info`, `warn`, and `error` (default).
|
|
470
536
|
This can be also be specified on the command line as, e.g. `--env LOG_LEVEL=info`.
|
|
471
537
|
|
|
472
|
-
## Support the Developer
|
|
473
|
-
|
|
474
|
-
When using this server, you may occasionally see a small sponsor message with a link to this repository included in tool responses. I hope you can support the project!
|
|
475
|
-
If you find this project useful, please consider supporting:
|
|
476
|
-
|
|
477
|
-
[](https://github.com/sponsors/TaazKareem)
|
|
478
|
-
|
|
479
|
-
<a href="https://buymeacoffee.com/taazkareem">
|
|
480
|
-
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" width="200" alt="Buy Me A Coffee">
|
|
481
|
-
</a>
|
|
482
|
-
|
|
483
|
-
## Acknowledgements
|
|
484
|
-
|
|
485
|
-
Special thanks to [ClickUp](https://clickup.com) for their excellent API and services that make this integration possible.
|
|
486
|
-
|
|
487
|
-
## Contributing
|
|
488
|
-
|
|
489
|
-
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
490
538
|
|
|
491
539
|
## License
|
|
492
540
|
|
|
493
541
|
[](https://opensource.org/licenses/MIT)
|
|
494
542
|
|
|
495
543
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
496
|
-
|
|
497
|
-
## Disclaimer
|
|
498
|
-
|
|
499
|
-
This software makes use of third-party APIs and may reference trademarks
|
|
500
|
-
or brands owned by third parties. The use of such APIs or references does not imply
|
|
501
|
-
any affiliation with or endorsement by the respective companies. All trademarks and
|
|
502
|
-
brand names are the property of their respective owners. This project is an independent
|
|
503
|
-
work and is not officially associated with or sponsored by any third-party company mentioned.
|
package/build/config.js
CHANGED
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* - SSE_PORT: Port for SSE server (default: 3000)
|
|
22
22
|
* - ENABLE_STDIO: Enable STDIO transport (default: true)
|
|
23
23
|
*/
|
|
24
|
+
// Load environment variables from .env file
|
|
25
|
+
import dotenv from 'dotenv';
|
|
26
|
+
dotenv.config();
|
|
24
27
|
// Parse any command line environment arguments
|
|
25
28
|
const args = process.argv.slice(2);
|
|
26
29
|
const envArgs = {};
|
package/build/server.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import config from "./config.js";
|
|
10
|
-
import { workspaceHierarchyTool, handleGetWorkspaceHierarchy } from "./tools/workspace.js";
|
|
11
|
-
import { createTaskTool, updateTaskTool, moveTaskTool, duplicateTaskTool, getTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool, createBulkTasksTool, updateBulkTasksTool, moveBulkTasksTool, deleteBulkTasksTool, attachTaskFileTool, getWorkspaceTasksTool, getTaskTimeEntriesTool, startTimeTrackingTool, stopTimeTrackingTool, addTimeEntryTool, deleteTimeEntryTool, getCurrentTimeEntryTool, handleCreateTask, handleUpdateTask, handleMoveTask, handleDuplicateTask, handleDeleteTask, handleGetTaskComments, handleCreateTaskComment, handleCreateBulkTasks, handleUpdateBulkTasks, handleMoveBulkTasks, handleDeleteBulkTasks, handleGetTask, handleAttachTaskFile, handleGetWorkspaceTasks, handleGetTaskTimeEntries, handleStartTimeTracking, handleStopTimeTracking, handleAddTimeEntry, handleDeleteTimeEntry, handleGetCurrentTimeEntry } from "./tools/task/index.js";
|
|
10
|
+
import { workspaceHierarchyTool, handleGetWorkspaceHierarchy, availableWorkspacesTool, handleGetAvailableWorkspaces } from "./tools/workspace.js";
|
|
11
|
+
import { createTaskTool, updateTaskTool, moveTaskTool, duplicateTaskTool, getTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool, createBulkTasksTool, updateBulkTasksTool, moveBulkTasksTool, deleteBulkTasksTool, attachTaskFileTool, getWorkspaceTasksTool, getTaskTimeEntriesTool, startTimeTrackingTool, stopTimeTrackingTool, addTimeEntryTool, deleteTimeEntryTool, getCurrentTimeEntryTool, mergeTaskTool, getTimeInStatusTool, getBulkTimeInStatusTool, addTaskToListTool, removeTaskFromListTool, handleCreateTask, handleUpdateTask, handleMoveTask, handleDuplicateTask, handleDeleteTask, handleGetTaskComments, handleCreateTaskComment, handleCreateBulkTasks, handleUpdateBulkTasks, handleMoveBulkTasks, handleDeleteBulkTasks, handleGetTask, handleAttachTaskFile, handleGetWorkspaceTasks, handleGetTaskTimeEntries, handleStartTimeTracking, handleStopTimeTracking, handleAddTimeEntry, handleDeleteTimeEntry, handleGetCurrentTimeEntry, mergeTaskHandler, getTimeInStatusHandler, getBulkTimeInStatusHandler, addTaskToListHandler, removeTaskFromListHandler } from "./tools/task/index.js";
|
|
12
12
|
import { createListTool, handleCreateList, createListInFolderTool, handleCreateListInFolder, getListTool, handleGetList, updateListTool, handleUpdateList, deleteListTool, handleDeleteList } from "./tools/list.js";
|
|
13
13
|
import { createFolderTool, handleCreateFolder, getFolderTool, handleGetFolder, updateFolderTool, handleUpdateFolder, deleteFolderTool, handleDeleteFolder } from "./tools/folder.js";
|
|
14
14
|
import { getSpaceTagsTool, handleGetSpaceTags, addTagToTaskTool, handleAddTagToTask, removeTagFromTaskTool, handleRemoveTagFromTask } from "./tools/tag.js";
|
|
@@ -81,6 +81,7 @@ export function configureServer() {
|
|
|
81
81
|
// Collect all tool definitions
|
|
82
82
|
const allTools = [
|
|
83
83
|
workspaceHierarchyTool,
|
|
84
|
+
availableWorkspacesTool,
|
|
84
85
|
createTaskTool,
|
|
85
86
|
getTaskTool,
|
|
86
87
|
updateTaskTool,
|
|
@@ -101,6 +102,11 @@ export function configureServer() {
|
|
|
101
102
|
addTimeEntryTool,
|
|
102
103
|
deleteTimeEntryTool,
|
|
103
104
|
getCurrentTimeEntryTool,
|
|
105
|
+
mergeTaskTool,
|
|
106
|
+
getTimeInStatusTool,
|
|
107
|
+
getBulkTimeInStatusTool,
|
|
108
|
+
addTaskToListTool,
|
|
109
|
+
removeTaskFromListTool,
|
|
104
110
|
createListTool,
|
|
105
111
|
createListInFolderTool,
|
|
106
112
|
getListTool,
|
|
@@ -156,7 +162,9 @@ export function configureServer() {
|
|
|
156
162
|
// Handle tool calls by routing to the appropriate handler
|
|
157
163
|
switch (name) {
|
|
158
164
|
case "get_workspace_hierarchy":
|
|
159
|
-
return handleGetWorkspaceHierarchy();
|
|
165
|
+
return handleGetWorkspaceHierarchy(params);
|
|
166
|
+
case "get_available_workspaces":
|
|
167
|
+
return handleGetAvailableWorkspaces();
|
|
160
168
|
case "create_task":
|
|
161
169
|
return handleCreateTask(params);
|
|
162
170
|
case "update_task":
|
|
@@ -221,6 +229,16 @@ export function configureServer() {
|
|
|
221
229
|
return handleDeleteTimeEntry(params);
|
|
222
230
|
case "get_current_time_entry":
|
|
223
231
|
return handleGetCurrentTimeEntry(params);
|
|
232
|
+
case "merge_task":
|
|
233
|
+
return mergeTaskHandler(params);
|
|
234
|
+
case "get_task_time_in_status":
|
|
235
|
+
return getTimeInStatusHandler(params);
|
|
236
|
+
case "get_bulk_tasks_time_in_status":
|
|
237
|
+
return getBulkTimeInStatusHandler(params);
|
|
238
|
+
case "add_task_to_list":
|
|
239
|
+
return addTaskToListHandler(params);
|
|
240
|
+
case "remove_task_from_list":
|
|
241
|
+
return removeTaskFromListHandler(params);
|
|
224
242
|
case "create_document":
|
|
225
243
|
return handleCreateDocument(params);
|
|
226
244
|
case "get_document":
|
package/build/server.log
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Logging initialized to /Users/daichiokazaki/Documents/project/alanse/clickup-multi-mcp-server/build/server.log
|
|
@@ -601,4 +601,103 @@ export class TaskServiceCore extends BaseClickUpService {
|
|
|
601
601
|
});
|
|
602
602
|
this.logger.debug('Cached task name to ID mapping', { taskName, taskId, listId });
|
|
603
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* Merge two tasks together
|
|
606
|
+
* @param taskId ID of the task to merge into (destination)
|
|
607
|
+
* @param data Merge configuration including merge_from_id
|
|
608
|
+
* @returns The merged task
|
|
609
|
+
*/
|
|
610
|
+
async mergeTask(taskId, data) {
|
|
611
|
+
try {
|
|
612
|
+
this.logOperation('mergeTask', { taskId, ...data });
|
|
613
|
+
const path = `/task/${taskId}/merge`;
|
|
614
|
+
this.traceRequest('POST', path, data);
|
|
615
|
+
return await this.makeRequest(async () => {
|
|
616
|
+
const response = await this.client.post(path, data);
|
|
617
|
+
return response.data;
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
throw this.handleError(error, `Failed to merge task ${taskId}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Get time in status for a task
|
|
626
|
+
* @param taskId ID of the task
|
|
627
|
+
* @returns Time in status data
|
|
628
|
+
*/
|
|
629
|
+
async getTimeInStatus(taskId) {
|
|
630
|
+
try {
|
|
631
|
+
this.logOperation('getTimeInStatus', { taskId });
|
|
632
|
+
const path = `/task/${taskId}/time_in_status`;
|
|
633
|
+
this.traceRequest('GET', path);
|
|
634
|
+
return await this.makeRequest(async () => {
|
|
635
|
+
const response = await this.client.get(path);
|
|
636
|
+
return response.data;
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
throw this.handleError(error, `Failed to get time in status for task ${taskId}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Get time in status for multiple tasks
|
|
645
|
+
* @param taskIds Array of task IDs
|
|
646
|
+
* @returns Bulk time in status data
|
|
647
|
+
*/
|
|
648
|
+
async getBulkTimeInStatus(taskIds) {
|
|
649
|
+
try {
|
|
650
|
+
this.logOperation('getBulkTimeInStatus', { taskIds, count: taskIds.length });
|
|
651
|
+
// Convert array to query string: ?task_ids=id1&task_ids=id2
|
|
652
|
+
const queryParams = taskIds.map(id => `task_ids=${id}`).join('&');
|
|
653
|
+
const path = `/task/bulk_time_in_status/task_ids?${queryParams}`;
|
|
654
|
+
this.traceRequest('GET', path);
|
|
655
|
+
return await this.makeRequest(async () => {
|
|
656
|
+
const response = await this.client.get(path);
|
|
657
|
+
return response.data;
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
throw this.handleError(error, `Failed to get bulk time in status`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Add a task to an additional list (multiple list membership)
|
|
666
|
+
* @param listId ID of the list to add the task to
|
|
667
|
+
* @param taskId ID of the task to add
|
|
668
|
+
* @returns The updated task
|
|
669
|
+
*/
|
|
670
|
+
async addTaskToList(listId, taskId) {
|
|
671
|
+
try {
|
|
672
|
+
this.logOperation('addTaskToList', { listId, taskId });
|
|
673
|
+
const path = `/list/${listId}/task/${taskId}`;
|
|
674
|
+
this.traceRequest('POST', path);
|
|
675
|
+
return await this.makeRequest(async () => {
|
|
676
|
+
const response = await this.client.post(path);
|
|
677
|
+
return response.data;
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
throw this.handleError(error, `Failed to add task ${taskId} to list ${listId}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Remove a task from a list
|
|
686
|
+
* @param listId ID of the list to remove the task from
|
|
687
|
+
* @param taskId ID of the task to remove
|
|
688
|
+
* @returns Success indicator
|
|
689
|
+
*/
|
|
690
|
+
async removeTaskFromList(listId, taskId) {
|
|
691
|
+
try {
|
|
692
|
+
this.logOperation('removeTaskFromList', { listId, taskId });
|
|
693
|
+
const path = `/list/${listId}/task/${taskId}`;
|
|
694
|
+
this.traceRequest('DELETE', path);
|
|
695
|
+
await this.makeRequest(async () => {
|
|
696
|
+
await this.client.delete(path);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
throw this.handleError(error, `Failed to remove task ${taskId} from list ${listId}`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
604
703
|
}
|
package/build/services/shared.js
CHANGED
|
@@ -21,12 +21,15 @@ const workspaceServicesMap = new Map();
|
|
|
21
21
|
*/
|
|
22
22
|
export function getClickUpServices(workspaceId) {
|
|
23
23
|
const wsId = workspaceId || getDefaultWorkspace();
|
|
24
|
+
console.error('[DEBUG] getClickUpServices called with workspaceId:', workspaceId);
|
|
25
|
+
console.error('[DEBUG] Resolved workspace ID:', wsId);
|
|
24
26
|
// Check if services already exist for this workspace
|
|
25
27
|
let services = workspaceServicesMap.get(wsId);
|
|
26
28
|
if (!services) {
|
|
27
29
|
logger.info(`Creating ClickUp services for workspace: ${wsId}`);
|
|
28
30
|
// Get workspace configuration
|
|
29
31
|
const workspaceConfig = getWorkspaceConfig(wsId);
|
|
32
|
+
console.error('[DEBUG] Workspace config:', JSON.stringify(workspaceConfig));
|
|
30
33
|
// Create the services instance
|
|
31
34
|
services = createClickUpServices({
|
|
32
35
|
apiKey: workspaceConfig.token,
|
|
@@ -15,6 +15,7 @@ import { validateTaskIdentification, validateListIdentification, validateTaskUpd
|
|
|
15
15
|
import { handleResolveAssignees } from '../member.js';
|
|
16
16
|
import { isNameMatch } from '../../utils/resolver-utils.js';
|
|
17
17
|
import { Logger } from '../../logger.js';
|
|
18
|
+
import { sponsorService } from '../../utils/sponsor-service.js';
|
|
18
19
|
// Use default workspace services for backwards compatibility
|
|
19
20
|
const defaultServices = getClickUpServices();
|
|
20
21
|
const { task: taskService, list: listService, workspace: workspaceService } = defaultServices;
|
|
@@ -917,3 +918,88 @@ export async function deleteTaskHandler(params) {
|
|
|
917
918
|
await taskService.deleteTask(taskId);
|
|
918
919
|
return true;
|
|
919
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* Handler for merging two tasks
|
|
923
|
+
*/
|
|
924
|
+
export async function mergeTaskHandler(params) {
|
|
925
|
+
try {
|
|
926
|
+
const { taskId, mergeFromId } = params;
|
|
927
|
+
if (!taskId || !mergeFromId) {
|
|
928
|
+
throw new Error('taskId and mergeFromId are required');
|
|
929
|
+
}
|
|
930
|
+
const result = await taskService.mergeTask(taskId, {
|
|
931
|
+
merge_from_id: mergeFromId
|
|
932
|
+
});
|
|
933
|
+
return sponsorService.createResponse(result, true);
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
return sponsorService.createErrorResponse(error, params);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Handler for getting time in status for a task
|
|
941
|
+
*/
|
|
942
|
+
export async function getTimeInStatusHandler(params) {
|
|
943
|
+
try {
|
|
944
|
+
const { taskId } = params;
|
|
945
|
+
if (!taskId) {
|
|
946
|
+
throw new Error('taskId is required');
|
|
947
|
+
}
|
|
948
|
+
const result = await taskService.getTimeInStatus(taskId);
|
|
949
|
+
return sponsorService.createResponse(result, true);
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
return sponsorService.createErrorResponse(error, params);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Handler for getting time in status for multiple tasks
|
|
957
|
+
*/
|
|
958
|
+
export async function getBulkTimeInStatusHandler(params) {
|
|
959
|
+
try {
|
|
960
|
+
const { taskIds } = params;
|
|
961
|
+
if (!taskIds || !Array.isArray(taskIds) || taskIds.length === 0) {
|
|
962
|
+
throw new Error('taskIds array is required and must not be empty');
|
|
963
|
+
}
|
|
964
|
+
const result = await taskService.getBulkTimeInStatus(taskIds);
|
|
965
|
+
return sponsorService.createResponse(result, true);
|
|
966
|
+
}
|
|
967
|
+
catch (error) {
|
|
968
|
+
return sponsorService.createErrorResponse(error, params);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Handler for adding a task to an additional list
|
|
973
|
+
*/
|
|
974
|
+
export async function addTaskToListHandler(params) {
|
|
975
|
+
try {
|
|
976
|
+
const { listId, taskId } = params;
|
|
977
|
+
if (!listId || !taskId) {
|
|
978
|
+
throw new Error('listId and taskId are required');
|
|
979
|
+
}
|
|
980
|
+
const result = await taskService.addTaskToList(listId, taskId);
|
|
981
|
+
return sponsorService.createResponse(result, true);
|
|
982
|
+
}
|
|
983
|
+
catch (error) {
|
|
984
|
+
return sponsorService.createErrorResponse(error, params);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Handler for removing a task from a list
|
|
989
|
+
*/
|
|
990
|
+
export async function removeTaskFromListHandler(params) {
|
|
991
|
+
try {
|
|
992
|
+
const { listId, taskId } = params;
|
|
993
|
+
if (!listId || !taskId) {
|
|
994
|
+
throw new Error('listId and taskId are required');
|
|
995
|
+
}
|
|
996
|
+
await taskService.removeTaskFromList(listId, taskId);
|
|
997
|
+
return sponsorService.createResponse({
|
|
998
|
+
success: true,
|
|
999
|
+
message: `Task ${taskId} removed from list ${listId}`
|
|
1000
|
+
}, true);
|
|
1001
|
+
}
|
|
1002
|
+
catch (error) {
|
|
1003
|
+
return sponsorService.createErrorResponse(error, params);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Re-export from main module
|
|
10
10
|
export * from './main.js';
|
|
11
11
|
// Re-export single task operation tools
|
|
12
|
-
export { createTaskTool, getTaskTool, getTasksTool, updateTaskTool, moveTaskTool, duplicateTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool } from './single-operations.js';
|
|
12
|
+
export { createTaskTool, getTaskTool, getTasksTool, updateTaskTool, moveTaskTool, duplicateTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool, mergeTaskTool, getTimeInStatusTool, getBulkTimeInStatusTool, addTaskToListTool, removeTaskFromListTool } from './single-operations.js';
|
|
13
13
|
// Re-export bulk task operation tools
|
|
14
14
|
export { createBulkTasksTool, updateBulkTasksTool, moveBulkTasksTool, deleteBulkTasksTool } from './bulk-operations.js';
|
|
15
15
|
// Re-export workspace task operation tools
|
|
@@ -21,7 +21,7 @@ export { attachTaskFileTool, handleAttachTaskFile } from './attachments.js';
|
|
|
21
21
|
// Re-export handlers
|
|
22
22
|
export {
|
|
23
23
|
// Single task operation handlers
|
|
24
|
-
createTaskHandler, getTaskHandler, getTasksHandler, updateTaskHandler, moveTaskHandler, duplicateTaskHandler, deleteTaskHandler, getTaskCommentsHandler, createTaskCommentHandler,
|
|
24
|
+
createTaskHandler, getTaskHandler, getTasksHandler, updateTaskHandler, moveTaskHandler, duplicateTaskHandler, deleteTaskHandler, getTaskCommentsHandler, createTaskCommentHandler, mergeTaskHandler, getTimeInStatusHandler, getBulkTimeInStatusHandler, addTaskToListHandler, removeTaskFromListHandler,
|
|
25
25
|
// Bulk task operation handlers
|
|
26
26
|
createBulkTasksHandler, updateBulkTasksHandler, moveBulkTasksHandler, deleteBulkTasksHandler,
|
|
27
27
|
// Team task operation handlers
|
|
@@ -467,3 +467,142 @@ export const deleteTaskTool = {
|
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
469
|
};
|
|
470
|
+
/**
|
|
471
|
+
* Tool definition for merging two tasks
|
|
472
|
+
*/
|
|
473
|
+
export const mergeTaskTool = {
|
|
474
|
+
name: "merge_task",
|
|
475
|
+
description: `Merges two ClickUp tasks together. The merge_from task will be merged into the destination task.
|
|
476
|
+
|
|
477
|
+
Example usage:
|
|
478
|
+
{
|
|
479
|
+
"taskId": "abc123", // Destination task ID (merge into this)
|
|
480
|
+
"mergeFromId": "xyz789" // Source task ID (merge from this)
|
|
481
|
+
}`,
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: {
|
|
485
|
+
taskId: {
|
|
486
|
+
type: "string",
|
|
487
|
+
description: "REQUIRED: ID of the destination task (merge into this task)"
|
|
488
|
+
},
|
|
489
|
+
mergeFromId: {
|
|
490
|
+
type: "string",
|
|
491
|
+
description: "REQUIRED: ID of the source task (merge from this task)"
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
required: ["taskId", "mergeFromId"]
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* Tool definition for getting time in status for a task
|
|
499
|
+
*/
|
|
500
|
+
export const getTimeInStatusTool = {
|
|
501
|
+
name: "get_task_time_in_status",
|
|
502
|
+
description: `Gets the time tracking information for how long a task has been in each status.
|
|
503
|
+
|
|
504
|
+
Example usage:
|
|
505
|
+
{
|
|
506
|
+
"taskId": "abc123"
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
- current_status: Current status with time spent
|
|
511
|
+
- status_history: Array of all status changes with timestamps`,
|
|
512
|
+
inputSchema: {
|
|
513
|
+
type: "object",
|
|
514
|
+
properties: {
|
|
515
|
+
taskId: {
|
|
516
|
+
type: "string",
|
|
517
|
+
description: "REQUIRED: ID of the task to get time in status for"
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
required: ["taskId"]
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* Tool definition for getting time in status for multiple tasks
|
|
525
|
+
*/
|
|
526
|
+
export const getBulkTimeInStatusTool = {
|
|
527
|
+
name: "get_bulk_tasks_time_in_status",
|
|
528
|
+
description: `Gets the time in status information for multiple tasks at once. More efficient than calling get_task_time_in_status multiple times.
|
|
529
|
+
|
|
530
|
+
Example usage:
|
|
531
|
+
{
|
|
532
|
+
"taskIds": ["abc123", "xyz789", "def456"]
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
Returns an object with task IDs as keys and their time in status data as values.`,
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
taskIds: {
|
|
540
|
+
type: "array",
|
|
541
|
+
description: "REQUIRED: Array of task IDs to get time in status for",
|
|
542
|
+
items: {
|
|
543
|
+
type: "string"
|
|
544
|
+
},
|
|
545
|
+
minItems: 1
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
required: ["taskIds"]
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
/**
|
|
552
|
+
* Tool definition for adding a task to an additional list
|
|
553
|
+
*/
|
|
554
|
+
export const addTaskToListTool = {
|
|
555
|
+
name: "add_task_to_list",
|
|
556
|
+
description: `Adds a task to an additional list, enabling multiple list membership for the task.
|
|
557
|
+
|
|
558
|
+
Example usage:
|
|
559
|
+
{
|
|
560
|
+
"listId": "12345",
|
|
561
|
+
"taskId": "abc123"
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
Note: This does NOT move the task. The task will belong to multiple lists after this operation.`,
|
|
565
|
+
inputSchema: {
|
|
566
|
+
type: "object",
|
|
567
|
+
properties: {
|
|
568
|
+
listId: {
|
|
569
|
+
type: "string",
|
|
570
|
+
description: "REQUIRED: ID of the list to add the task to"
|
|
571
|
+
},
|
|
572
|
+
taskId: {
|
|
573
|
+
type: "string",
|
|
574
|
+
description: "REQUIRED: ID of the task to add to the list"
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
required: ["listId", "taskId"]
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
/**
|
|
581
|
+
* Tool definition for removing a task from a list
|
|
582
|
+
*/
|
|
583
|
+
export const removeTaskFromListTool = {
|
|
584
|
+
name: "remove_task_from_list",
|
|
585
|
+
description: `Removes a task from a specific list. If the task belongs to multiple lists, it will only be removed from the specified list.
|
|
586
|
+
|
|
587
|
+
Example usage:
|
|
588
|
+
{
|
|
589
|
+
"listId": "12345",
|
|
590
|
+
"taskId": "abc123"
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
Warning: If this is the task's only list, the task may be deleted or moved to an archive depending on ClickUp settings.`,
|
|
594
|
+
inputSchema: {
|
|
595
|
+
type: "object",
|
|
596
|
+
properties: {
|
|
597
|
+
listId: {
|
|
598
|
+
type: "string",
|
|
599
|
+
description: "REQUIRED: ID of the list to remove the task from"
|
|
600
|
+
},
|
|
601
|
+
taskId: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "REQUIRED: ID of the task to remove from the list"
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
required: ["listId", "taskId"]
|
|
607
|
+
}
|
|
608
|
+
};
|
|
@@ -25,7 +25,10 @@ export const workspaceParameter = {
|
|
|
25
25
|
*/
|
|
26
26
|
export function getServicesForWorkspace(params) {
|
|
27
27
|
try {
|
|
28
|
-
|
|
28
|
+
console.error('[DEBUG] getServicesForWorkspace called with workspace:', params.workspace);
|
|
29
|
+
const services = getClickUpServices(params.workspace);
|
|
30
|
+
console.error('[DEBUG] Retrieved services for workspace:', params.workspace || 'default');
|
|
31
|
+
return services;
|
|
29
32
|
}
|
|
30
33
|
catch (error) {
|
|
31
34
|
// Enhance error message with available workspaces
|
package/build/tools/workspace.js
CHANGED
|
@@ -30,6 +30,9 @@ export const workspaceHierarchyTool = {
|
|
|
30
30
|
*/
|
|
31
31
|
export async function handleGetWorkspaceHierarchy(params = {}) {
|
|
32
32
|
try {
|
|
33
|
+
// DEBUG: Log received parameters
|
|
34
|
+
console.error('[DEBUG] handleGetWorkspaceHierarchy called with params:', JSON.stringify(params));
|
|
35
|
+
console.error('[DEBUG] params.workspace =', params.workspace);
|
|
33
36
|
// Get services for the specified workspace
|
|
34
37
|
const services = getServicesForWorkspace(params);
|
|
35
38
|
const { workspace: workspaceService } = services;
|
|
@@ -44,6 +47,49 @@ export async function handleGetWorkspaceHierarchy(params = {}) {
|
|
|
44
47
|
return sponsorService.createErrorResponse(`Error getting workspace hierarchy: ${error.message}`);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Tool definition for retrieving available workspaces
|
|
52
|
+
*/
|
|
53
|
+
export const availableWorkspacesTool = {
|
|
54
|
+
name: 'get_available_workspaces',
|
|
55
|
+
description: `Gets list of all available workspaces configured in the ClickUp MCP server. Returns workspace identifiers, default workspace, and descriptions if available.`,
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Handler for the get_available_workspaces tool
|
|
63
|
+
*/
|
|
64
|
+
export async function handleGetAvailableWorkspaces() {
|
|
65
|
+
try {
|
|
66
|
+
// Import config functions
|
|
67
|
+
const { getAvailableWorkspaces, getDefaultWorkspace, getWorkspaceConfig } = await import('../config.js');
|
|
68
|
+
// Get list of available workspace identifiers
|
|
69
|
+
const workspaceIds = getAvailableWorkspaces();
|
|
70
|
+
const defaultWorkspace = getDefaultWorkspace();
|
|
71
|
+
// Get detailed information for each workspace
|
|
72
|
+
const workspaces = workspaceIds.map(id => {
|
|
73
|
+
const config = getWorkspaceConfig(id);
|
|
74
|
+
return {
|
|
75
|
+
id,
|
|
76
|
+
teamId: config.teamId,
|
|
77
|
+
description: config.description || (id === 'default' ? 'Default workspace' : undefined),
|
|
78
|
+
isDefault: id === defaultWorkspace
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
const result = {
|
|
82
|
+
default: defaultWorkspace,
|
|
83
|
+
workspaces,
|
|
84
|
+
count: workspaces.length
|
|
85
|
+
};
|
|
86
|
+
// Use sponsor service to create the response
|
|
87
|
+
return sponsorService.createResponse(result, true);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return sponsorService.createErrorResponse(`Error getting available workspaces: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
47
93
|
/**
|
|
48
94
|
* Format the hierarchy as a tree string
|
|
49
95
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alanse/clickup-multi-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "ClickUp MCP Server with Multi-Workspace Support - Integrate multiple ClickUp workspaces with AI through Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|