@taazkareem/clickup-mcp-server 0.4.0 → 0.4.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 +4 -13
- package/build/config.js +8 -1
- package/build/handlers/prompts.js +25 -0
- package/build/handlers/tools.js +38 -0
- package/build/index.js +99 -192
- package/build/utils/resolvers.js +48 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -45,16 +45,11 @@ A Model Context Protocol (MCP) server for integrating ClickUp tasks with AI appl
|
|
|
45
45
|
|
|
46
46
|
## Installation
|
|
47
47
|
|
|
48
|
-
### Using npx
|
|
48
|
+
### Using npx
|
|
49
49
|
```bash
|
|
50
50
|
npx @taazkareem/clickup-mcp-server
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
### Global Installation
|
|
54
|
-
```bash
|
|
55
|
-
npm install -g @taazkareem/clickup-mcp-server
|
|
56
|
-
```
|
|
57
|
-
|
|
58
53
|
## Configuration
|
|
59
54
|
|
|
60
55
|
1. Get your ClickUp API key from [ClickUp Settings](https://app.clickup.com/settings/apps)
|
|
@@ -64,7 +59,7 @@ CLICKUP_API_KEY=your_api_key_here
|
|
|
64
59
|
TEAM_ID=your_team_id_here
|
|
65
60
|
```
|
|
66
61
|
|
|
67
|
-
## Using with Cursor AI Composer
|
|
62
|
+
## Using with Cursor AI Composer Agent
|
|
68
63
|
|
|
69
64
|
To add this server to Cursor AI Composer, follow these steps:
|
|
70
65
|
|
|
@@ -72,17 +67,13 @@ To add this server to Cursor AI Composer, follow these steps:
|
|
|
72
67
|
2. Add the following command under MCP Servers:
|
|
73
68
|
|
|
74
69
|
```bash
|
|
75
|
-
npx -y @taazkareem/clickup-mcp-server \
|
|
70
|
+
npx -y @taazkareem/clickup-mcp-server --stdio \
|
|
76
71
|
--env CLICKUP_API_KEY=your_api_key_here \
|
|
77
72
|
--env TEAM_ID=your_team_id_here
|
|
78
73
|
```
|
|
79
74
|
3. Replace `your_api_key_here` and `your_team_id_here` with your actual ClickUp credentials.
|
|
80
75
|
4. Click on 'Save' to add the server.
|
|
81
76
|
|
|
82
|
-
You can get these values from:
|
|
83
|
-
- `CLICKUP_API_KEY`: Get from [ClickUp Settings > Apps](https://app.clickup.com/settings/apps)
|
|
84
|
-
- `TEAM_ID`: Your ClickUp Team ID (found in the URL when viewing your workspace or via API)
|
|
85
|
-
|
|
86
77
|
> **Security Note**: Your API key will be stored securely and will not be exposed to AI models.
|
|
87
78
|
|
|
88
79
|
### Available Tools
|
|
@@ -246,4 +237,4 @@ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md)
|
|
|
246
237
|
|
|
247
238
|
## License
|
|
248
239
|
|
|
249
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
240
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/build/config.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import dotenv from 'dotenv';
|
|
2
2
|
// Load environment variables from .env file
|
|
3
3
|
dotenv.config();
|
|
4
|
+
console.log('Environment variables received:', {
|
|
5
|
+
CLICKUP_API_KEY: process.env.CLICKUP_API_KEY,
|
|
6
|
+
TEAM_ID: process.env.TEAM_ID
|
|
7
|
+
});
|
|
4
8
|
// Parse command line arguments for --env flags
|
|
5
9
|
const args = process.argv.slice(2);
|
|
10
|
+
console.log('Command line arguments:', args);
|
|
6
11
|
const envArgs = {};
|
|
7
12
|
for (let i = 0; i < args.length; i++) {
|
|
8
13
|
if (args[i] === '--env' && i + 1 < args.length) {
|
|
@@ -14,10 +19,12 @@ for (let i = 0; i < args.length; i++) {
|
|
|
14
19
|
i++; // Skip the next argument since we used it
|
|
15
20
|
}
|
|
16
21
|
}
|
|
22
|
+
console.log('Parsed environment arguments:', envArgs);
|
|
17
23
|
const configuration = {
|
|
18
24
|
clickupApiKey: envArgs.clickupApiKey || process.env.CLICKUP_API_KEY || '',
|
|
19
|
-
teamId: envArgs.teamId || process.env.TEAM_ID || ''
|
|
25
|
+
teamId: envArgs.teamId || process.env.TEAM_ID || ''
|
|
20
26
|
};
|
|
27
|
+
console.log('Final configuration:', configuration);
|
|
21
28
|
// Check for missing environment variables
|
|
22
29
|
const missingEnvVars = Object.entries(configuration)
|
|
23
30
|
.filter(([_, value]) => !value)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getAllTasks } from '../utils/resolvers.js';
|
|
2
|
+
export async function handleSummarizeTasks(clickup, teamId) {
|
|
3
|
+
const { tasks } = await getAllTasks(clickup, teamId);
|
|
4
|
+
let output = "Summarized Tasks:\n\n";
|
|
5
|
+
for (const task of tasks) {
|
|
6
|
+
output += `- ${task.name}: ${task.description}\n`;
|
|
7
|
+
}
|
|
8
|
+
return output;
|
|
9
|
+
}
|
|
10
|
+
export async function handleAnalyzeTaskPriorities(clickup, teamId) {
|
|
11
|
+
const { tasks } = await getAllTasks(clickup, teamId);
|
|
12
|
+
const priorities = tasks.map(task => task.priority?.priority);
|
|
13
|
+
const uniquePriorities = [...new Set(priorities.filter(p => p !== undefined))];
|
|
14
|
+
const priorityCounts = uniquePriorities.map(priority => ({
|
|
15
|
+
priority,
|
|
16
|
+
count: priorities.filter(p => p === priority).length
|
|
17
|
+
}));
|
|
18
|
+
let output = "Task Priorities Analysis:\n\n";
|
|
19
|
+
output += "Available Priorities: " + uniquePriorities.join(', ') + "\n\n";
|
|
20
|
+
output += "Priority Counts:\n";
|
|
21
|
+
for (const priority of priorityCounts) {
|
|
22
|
+
output += `- Priority ${priority.priority}: ${priority.count}\n`;
|
|
23
|
+
}
|
|
24
|
+
return output;
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveListId } from '../utils/resolvers.js';
|
|
2
|
+
export async function handleWorkspaceHierarchy(clickup, teamId) {
|
|
3
|
+
const spaces = await clickup.getSpaces(teamId);
|
|
4
|
+
const allLists = await clickup.getAllLists(teamId);
|
|
5
|
+
let output = "ClickUp Workspace Hierarchy:\n\n";
|
|
6
|
+
for (const space of spaces) {
|
|
7
|
+
output += `Space: ${space.name} (ID: ${space.id})\n`;
|
|
8
|
+
const folders = await clickup.getFolders(space.id);
|
|
9
|
+
for (const folder of folders) {
|
|
10
|
+
output += ` ├─ Folder: ${folder.name} (ID: ${folder.id})\n`;
|
|
11
|
+
const folderLists = folder.lists || [];
|
|
12
|
+
for (const list of folderLists) {
|
|
13
|
+
const { statuses } = await clickup.getTasks(list.id);
|
|
14
|
+
output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
15
|
+
output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const spaceLists = allLists.filter(list => list.space &&
|
|
19
|
+
list.space.id === space.id &&
|
|
20
|
+
!folders.some(folder => folder.lists?.some(fl => fl.id === list.id)));
|
|
21
|
+
if (spaceLists.length > 0) {
|
|
22
|
+
output += " ├─ Lists (not in folders):\n";
|
|
23
|
+
for (const list of spaceLists) {
|
|
24
|
+
const { statuses } = await clickup.getTasks(list.id);
|
|
25
|
+
output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
26
|
+
output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
output += "\n";
|
|
30
|
+
}
|
|
31
|
+
return output;
|
|
32
|
+
}
|
|
33
|
+
export async function handleCreateTask(clickup, teamId, args) {
|
|
34
|
+
const listId = await resolveListId(clickup, teamId, args.listId, args.listName);
|
|
35
|
+
const { listId: _, listName: __, ...taskData } = args;
|
|
36
|
+
return await clickup.createTask(listId, taskData);
|
|
37
|
+
}
|
|
38
|
+
// Add other handler functions for each tool...
|
package/build/index.js
CHANGED
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* This is a template MCP server that implements a simple ClickUp task management system.
|
|
4
|
-
* It demonstrates core MCP concepts like resources, tools, and prompts by allowing:
|
|
5
|
-
* - Listing ClickUp tasks as resources
|
|
6
|
-
* - Reading individual ClickUp tasks
|
|
7
|
-
* - Creating new ClickUp tasks via a tool
|
|
8
|
-
* - Updating existing ClickUp tasks via a tool
|
|
9
|
-
* - Summarizing all ClickUp tasks via a prompt
|
|
10
|
-
* - Analyzing task priorities via a prompt
|
|
11
|
-
* - Generating detailed descriptions for tasks via a prompt
|
|
12
|
-
*/
|
|
13
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
4
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
5
|
import { ClickUpService } from "./services/clickup.js";
|
|
17
6
|
import config from "./config.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
7
|
+
import { handleWorkspaceHierarchy, handleCreateTask } from "./handlers/tools.js";
|
|
8
|
+
import { handleSummarizeTasks, handleAnalyzeTaskPriorities } from "./handlers/prompts.js";
|
|
9
|
+
import { getAllTasks } from "./utils/resolvers.js";
|
|
10
|
+
console.log('Server starting up...');
|
|
11
|
+
console.log('Config loaded:', {
|
|
12
|
+
clickupApiKey: config.clickupApiKey ? '***' : 'missing',
|
|
13
|
+
teamId: config.teamId || 'missing'
|
|
14
|
+
});
|
|
26
15
|
// Initialize ClickUp service
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
let clickup;
|
|
17
|
+
try {
|
|
18
|
+
console.log('Initializing ClickUp service...');
|
|
19
|
+
clickup = ClickUpService.initialize(config.clickupApiKey);
|
|
20
|
+
console.log('ClickUp service initialized successfully');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error("Failed to initialize ClickUp service:", error);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
console.log('Creating MCP server...');
|
|
32
27
|
const server = new Server({
|
|
33
|
-
name: "clickup
|
|
28
|
+
name: "clickup",
|
|
34
29
|
version: "0.1.0",
|
|
35
30
|
}, {
|
|
36
31
|
capabilities: {
|
|
@@ -39,40 +34,31 @@ const server = new Server({
|
|
|
39
34
|
prompts: {},
|
|
40
35
|
},
|
|
41
36
|
});
|
|
37
|
+
console.log('MCP server created');
|
|
42
38
|
/**
|
|
43
39
|
* Handler for listing available ClickUp tasks as resources.
|
|
44
|
-
* Each task is exposed as a resource with:
|
|
45
|
-
* - A clickup:// URI scheme
|
|
46
|
-
* - JSON MIME type
|
|
47
|
-
* - Human readable name and description (including the task name and description)
|
|
48
40
|
*/
|
|
49
41
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
42
|
+
console.log('Handling ListResources request');
|
|
50
43
|
try {
|
|
51
|
-
const spaces = await clickup
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
description: task.description || `Task in ${list.name} (${space.name})`,
|
|
62
|
-
tags: []
|
|
63
|
-
})));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return { resources };
|
|
44
|
+
const { tasks, spaces } = await getAllTasks(clickup, config.teamId);
|
|
45
|
+
return {
|
|
46
|
+
resources: tasks.map(task => ({
|
|
47
|
+
uri: `clickup://task/${task.id}`,
|
|
48
|
+
mimeType: "application/json",
|
|
49
|
+
name: task.name,
|
|
50
|
+
description: task.description || `Task in ${task.list.name} (${task.space.name})`,
|
|
51
|
+
tags: []
|
|
52
|
+
}))
|
|
53
|
+
};
|
|
67
54
|
}
|
|
68
55
|
catch (error) {
|
|
69
|
-
console.error('Error
|
|
56
|
+
console.error('Error in ListResources:', error);
|
|
70
57
|
throw error;
|
|
71
58
|
}
|
|
72
59
|
});
|
|
73
60
|
/**
|
|
74
61
|
* Handler for reading the contents of a specific ClickUp task.
|
|
75
|
-
* Takes a clickup:// URI and returns the task content as JSON.
|
|
76
62
|
*/
|
|
77
63
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
78
64
|
try {
|
|
@@ -94,15 +80,15 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
94
80
|
}
|
|
95
81
|
});
|
|
96
82
|
/**
|
|
97
|
-
* Handler
|
|
98
|
-
* Exposes tools for listing spaces, creating tasks, and updating tasks.
|
|
83
|
+
* Handler for listing available tools.
|
|
99
84
|
*/
|
|
100
85
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
86
|
+
console.log('Handling ListTools request');
|
|
101
87
|
return {
|
|
102
88
|
tools: [
|
|
103
89
|
{
|
|
104
90
|
name: "workspace_hierarchy",
|
|
105
|
-
description: "List complete hierarchy of the ClickUp workspace
|
|
91
|
+
description: "List complete hierarchy of the ClickUp workspace",
|
|
106
92
|
inputSchema: {
|
|
107
93
|
type: "object",
|
|
108
94
|
properties: {},
|
|
@@ -390,54 +376,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
390
376
|
};
|
|
391
377
|
});
|
|
392
378
|
/**
|
|
393
|
-
* Handler for
|
|
394
|
-
* Handles the execution of tools like listing spaces, creating tasks, and updating tasks.
|
|
379
|
+
* Handler for executing tools.
|
|
395
380
|
*/
|
|
396
381
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
397
382
|
try {
|
|
398
383
|
switch (request.params.name) {
|
|
399
384
|
case "workspace_hierarchy": {
|
|
400
|
-
const
|
|
401
|
-
const allLists = await clickup.getAllLists(config.teamId);
|
|
402
|
-
let output = "ClickUp Workspace Hierarchy:\n\n";
|
|
403
|
-
for (const space of spaces) {
|
|
404
|
-
output += `Space: ${space.name} (ID: ${space.id})\n`;
|
|
405
|
-
// Get folders in this space
|
|
406
|
-
const folders = await clickup.getFolders(space.id);
|
|
407
|
-
for (const folder of folders) {
|
|
408
|
-
output += ` ├─ Folder: ${folder.name} (ID: ${folder.id})\n`;
|
|
409
|
-
// Get lists in this folder
|
|
410
|
-
const folderLists = folder.lists || [];
|
|
411
|
-
for (const list of folderLists) {
|
|
412
|
-
const { statuses } = await clickup.getTasks(list.id);
|
|
413
|
-
output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
414
|
-
output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
// Get lists directly in space (not in folders)
|
|
418
|
-
const spaceLists = allLists.filter(list => list.space &&
|
|
419
|
-
list.space.id === space.id &&
|
|
420
|
-
!folders.some(folder => folder.lists?.some(fl => fl.id === list.id)));
|
|
421
|
-
if (spaceLists.length > 0) {
|
|
422
|
-
output += " ├─ Lists (not in folders):\n";
|
|
423
|
-
for (const list of spaceLists) {
|
|
424
|
-
const { statuses } = await clickup.getTasks(list.id);
|
|
425
|
-
output += ` │ └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
426
|
-
output += ` │ Available Statuses: ${statuses.join(', ')}\n`;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
output += "\n";
|
|
430
|
-
}
|
|
431
|
-
// Add lists without spaces at the end
|
|
432
|
-
const listsWithoutSpace = allLists.filter(list => !list.space);
|
|
433
|
-
if (listsWithoutSpace.length > 0) {
|
|
434
|
-
output += "Lists without assigned spaces:\n";
|
|
435
|
-
for (const list of listsWithoutSpace) {
|
|
436
|
-
const { statuses } = await clickup.getTasks(list.id);
|
|
437
|
-
output += ` └─ List: ${list.name} (ID: ${list.id})\n`;
|
|
438
|
-
output += ` Available Statuses: ${statuses.join(', ')}\n`;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
385
|
+
const output = await handleWorkspaceHierarchy(clickup, config.teamId);
|
|
441
386
|
return {
|
|
442
387
|
content: [{
|
|
443
388
|
type: "text",
|
|
@@ -447,19 +392,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
447
392
|
}
|
|
448
393
|
case "create_task": {
|
|
449
394
|
const args = request.params.arguments;
|
|
450
|
-
|
|
451
|
-
if (!listId && args.listName) {
|
|
452
|
-
const result = await clickup.findListByNameGlobally(config.teamId, args.listName);
|
|
453
|
-
if (!result) {
|
|
454
|
-
throw new Error(`List with name "${args.listName}" not found`);
|
|
455
|
-
}
|
|
456
|
-
listId = result.list.id;
|
|
457
|
-
}
|
|
458
|
-
if (!listId) {
|
|
459
|
-
throw new Error("Either listId or listName is required");
|
|
460
|
-
}
|
|
461
|
-
const { listId: _, listName: __, ...taskData } = args;
|
|
462
|
-
const task = await clickup.createTask(listId, taskData);
|
|
395
|
+
const task = await handleCreateTask(clickup, config.teamId, args);
|
|
463
396
|
return {
|
|
464
397
|
content: [{
|
|
465
398
|
type: "text",
|
|
@@ -572,19 +505,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
572
505
|
}
|
|
573
506
|
let folderId = args.folderId;
|
|
574
507
|
if (!folderId && args.folderName) {
|
|
575
|
-
|
|
576
|
-
if (!
|
|
577
|
-
|
|
578
|
-
if (!space) {
|
|
579
|
-
throw new Error(`Space with name "${args.spaceName}" not found`);
|
|
580
|
-
}
|
|
581
|
-
spaceId = space.id;
|
|
582
|
-
}
|
|
583
|
-
if (!spaceId) {
|
|
584
|
-
throw new Error("Either spaceId or spaceName must be provided");
|
|
508
|
+
const result = await clickup.findFolderByNameGlobally(config.teamId, args.folderName);
|
|
509
|
+
if (!result) {
|
|
510
|
+
throw new Error(`Folder with name "${args.folderName}" not found`);
|
|
585
511
|
}
|
|
586
|
-
|
|
587
|
-
|
|
512
|
+
folderId = result.folder.id;
|
|
513
|
+
}
|
|
514
|
+
if (!folderId) {
|
|
515
|
+
throw new Error("Either folderId or folderName is required");
|
|
516
|
+
}
|
|
517
|
+
const listData = {
|
|
518
|
+
name: args.name,
|
|
519
|
+
content: args.content,
|
|
520
|
+
status: args.status
|
|
521
|
+
};
|
|
522
|
+
try {
|
|
523
|
+
const list = await clickup.createListInFolder(folderId, listData);
|
|
588
524
|
return {
|
|
589
525
|
content: [{
|
|
590
526
|
type: "text",
|
|
@@ -592,17 +528,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
592
528
|
}]
|
|
593
529
|
};
|
|
594
530
|
}
|
|
595
|
-
|
|
596
|
-
throw new Error(
|
|
531
|
+
catch (error) {
|
|
532
|
+
throw new Error(`Failed to create list: ${error.message}`);
|
|
597
533
|
}
|
|
598
|
-
const { spaceId: _, spaceName: ___, ...listData } = args;
|
|
599
|
-
const list = await clickup.createListInFolder(folderId, listData);
|
|
600
|
-
return {
|
|
601
|
-
content: [{
|
|
602
|
-
type: "text",
|
|
603
|
-
text: `Created list ${list.id}: ${list.name} in folder`
|
|
604
|
-
}]
|
|
605
|
-
};
|
|
606
534
|
}
|
|
607
535
|
case "move_task": {
|
|
608
536
|
const args = request.params.arguments;
|
|
@@ -654,15 +582,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
654
582
|
}
|
|
655
583
|
case "update_task": {
|
|
656
584
|
const args = request.params.arguments;
|
|
657
|
-
if (!args.taskId
|
|
658
|
-
throw new Error("
|
|
585
|
+
if (!args.taskId) {
|
|
586
|
+
throw new Error("taskId is required");
|
|
659
587
|
}
|
|
588
|
+
const dueDate = args.due_date ? new Date(args.due_date).getTime() : undefined;
|
|
660
589
|
const task = await clickup.updateTask(args.taskId, {
|
|
661
590
|
name: args.name,
|
|
662
591
|
description: args.description,
|
|
663
592
|
status: args.status,
|
|
664
593
|
priority: args.priority,
|
|
665
|
-
due_date:
|
|
594
|
+
due_date: dueDate
|
|
666
595
|
});
|
|
667
596
|
return {
|
|
668
597
|
content: [{
|
|
@@ -671,8 +600,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
671
600
|
}]
|
|
672
601
|
};
|
|
673
602
|
}
|
|
603
|
+
default:
|
|
604
|
+
throw new Error("Unknown tool");
|
|
674
605
|
}
|
|
675
|
-
throw new Error("Unknown tool");
|
|
676
606
|
}
|
|
677
607
|
catch (error) {
|
|
678
608
|
console.error('Error executing tool:', error);
|
|
@@ -681,7 +611,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
681
611
|
});
|
|
682
612
|
/**
|
|
683
613
|
* Handler for listing available prompts.
|
|
684
|
-
* Exposes prompts for summarizing/analyzing ClickUp tasks.
|
|
685
614
|
*/
|
|
686
615
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
687
616
|
return {
|
|
@@ -693,33 +622,18 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
693
622
|
{
|
|
694
623
|
name: "analyze_task_priorities",
|
|
695
624
|
description: "Analyze task priorities"
|
|
696
|
-
},
|
|
697
|
-
{
|
|
698
|
-
name: "generate_task_descriptions",
|
|
699
|
-
description: "Generate detailed descriptions for tasks"
|
|
700
625
|
}
|
|
701
626
|
]
|
|
702
627
|
};
|
|
703
628
|
});
|
|
704
629
|
/**
|
|
705
630
|
* Handler for getting a specific prompt.
|
|
706
|
-
* Takes a prompt name and returns the prompt content.
|
|
707
631
|
*/
|
|
708
632
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
709
633
|
try {
|
|
710
634
|
switch (request.params.name) {
|
|
711
635
|
case "summarize_tasks": {
|
|
712
|
-
const
|
|
713
|
-
let output = "Summarized Tasks:\n\n";
|
|
714
|
-
for (const space of spaces) {
|
|
715
|
-
const lists = await clickup.getLists(space.id);
|
|
716
|
-
for (const list of lists) {
|
|
717
|
-
const { tasks } = await clickup.getTasks(list.id);
|
|
718
|
-
for (const task of tasks) {
|
|
719
|
-
output += `- ${task.name}: ${task.description}\n`;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
636
|
+
const output = await handleSummarizeTasks(clickup, config.teamId);
|
|
723
637
|
return {
|
|
724
638
|
content: [{
|
|
725
639
|
type: "text",
|
|
@@ -728,46 +642,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
728
642
|
};
|
|
729
643
|
}
|
|
730
644
|
case "analyze_task_priorities": {
|
|
731
|
-
const
|
|
732
|
-
const allTasks = [];
|
|
733
|
-
for (const space of spaces) {
|
|
734
|
-
const lists = await clickup.getLists(space.id);
|
|
735
|
-
for (const list of lists) {
|
|
736
|
-
const { tasks } = await clickup.getTasks(list.id);
|
|
737
|
-
allTasks.push(...tasks);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
const priorities = allTasks.map((task) => task.priority?.priority);
|
|
741
|
-
const uniquePriorities = [...new Set(priorities.filter(p => p !== undefined))];
|
|
742
|
-
const priorityCounts = uniquePriorities.map(priority => ({
|
|
743
|
-
priority: priority,
|
|
744
|
-
count: priorities.filter((p) => p === priority).length
|
|
745
|
-
}));
|
|
746
|
-
let output = "Task Priorities Analysis:\n\n";
|
|
747
|
-
output += "Available Priorities: " + uniquePriorities.join(', ') + "\n\n";
|
|
748
|
-
output += "Priority Counts:\n";
|
|
749
|
-
for (const priority of priorityCounts) {
|
|
750
|
-
output += `- Priority ${priority.priority}: ${priority.count}\n`;
|
|
751
|
-
}
|
|
752
|
-
return {
|
|
753
|
-
content: [{
|
|
754
|
-
type: "text",
|
|
755
|
-
text: output
|
|
756
|
-
}]
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
case "generate_task_descriptions": {
|
|
760
|
-
const spaces = await clickup.getSpaces(config.teamId);
|
|
761
|
-
let output = "Generated Task Descriptions:\n\n";
|
|
762
|
-
for (const space of spaces) {
|
|
763
|
-
const lists = await clickup.getLists(space.id);
|
|
764
|
-
for (const list of lists) {
|
|
765
|
-
const { tasks } = await clickup.getTasks(list.id);
|
|
766
|
-
for (const task of tasks) {
|
|
767
|
-
output += `- ${task.name}: ${task.description}\n`;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
645
|
+
const output = await handleAnalyzeTaskPriorities(clickup, config.teamId);
|
|
771
646
|
return {
|
|
772
647
|
content: [{
|
|
773
648
|
type: "text",
|
|
@@ -784,6 +659,38 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
784
659
|
throw error;
|
|
785
660
|
}
|
|
786
661
|
});
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
transport
|
|
662
|
+
if (process.argv.includes('--stdio')) {
|
|
663
|
+
console.log('Starting server in stdio mode...');
|
|
664
|
+
// Set up stdio transport
|
|
665
|
+
const transport = new StdioServerTransport();
|
|
666
|
+
// Connect server with better error handling
|
|
667
|
+
server.connect(transport)
|
|
668
|
+
.then(() => {
|
|
669
|
+
console.log('Server connected successfully to stdio transport');
|
|
670
|
+
// Keep the process alive
|
|
671
|
+
process.stdin.resume();
|
|
672
|
+
// Handle process termination
|
|
673
|
+
process.on('SIGINT', () => {
|
|
674
|
+
console.log('Received SIGINT. Shutting down...');
|
|
675
|
+
transport.close();
|
|
676
|
+
process.exit(0);
|
|
677
|
+
});
|
|
678
|
+
process.on('SIGTERM', () => {
|
|
679
|
+
console.log('Received SIGTERM. Shutting down...');
|
|
680
|
+
transport.close();
|
|
681
|
+
process.exit(0);
|
|
682
|
+
});
|
|
683
|
+
})
|
|
684
|
+
.catch(error => {
|
|
685
|
+
console.error('Failed to connect server to transport:', error);
|
|
686
|
+
process.exit(1);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
console.log('Starting server in standard mode...');
|
|
691
|
+
// Add your non-stdio server initialization here if needed
|
|
692
|
+
}
|
|
693
|
+
// Prevent unhandled promise rejections from crashing the server
|
|
694
|
+
process.on('unhandledRejection', (error) => {
|
|
695
|
+
console.error('Unhandled promise rejection:', error);
|
|
696
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export async function resolveListId(clickup, teamId, listId, listName) {
|
|
2
|
+
if (listId)
|
|
3
|
+
return listId;
|
|
4
|
+
if (!listName) {
|
|
5
|
+
throw new Error("Either listId or listName is required");
|
|
6
|
+
}
|
|
7
|
+
const result = await clickup.findListByNameGlobally(teamId, listName);
|
|
8
|
+
if (!result) {
|
|
9
|
+
throw new Error(`List with name "${listName}" not found`);
|
|
10
|
+
}
|
|
11
|
+
return result.list.id;
|
|
12
|
+
}
|
|
13
|
+
export async function resolveSpaceId(clickup, teamId, spaceId, spaceName) {
|
|
14
|
+
if (spaceId)
|
|
15
|
+
return spaceId;
|
|
16
|
+
if (!spaceName) {
|
|
17
|
+
throw new Error("Either spaceId or spaceName is required");
|
|
18
|
+
}
|
|
19
|
+
const space = await clickup.findSpaceByName(teamId, spaceName);
|
|
20
|
+
if (!space) {
|
|
21
|
+
throw new Error(`Space with name "${spaceName}" not found`);
|
|
22
|
+
}
|
|
23
|
+
return space.id;
|
|
24
|
+
}
|
|
25
|
+
export async function resolveFolderId(clickup, teamId, folderId, folderName) {
|
|
26
|
+
if (folderId)
|
|
27
|
+
return folderId;
|
|
28
|
+
if (!folderName) {
|
|
29
|
+
throw new Error("Either folderId or folderName is required");
|
|
30
|
+
}
|
|
31
|
+
const result = await clickup.findFolderByNameGlobally(teamId, folderName);
|
|
32
|
+
if (!result) {
|
|
33
|
+
throw new Error(`Folder with name "${folderName}" not found`);
|
|
34
|
+
}
|
|
35
|
+
return result.folder.id;
|
|
36
|
+
}
|
|
37
|
+
export async function getAllTasks(clickup, teamId) {
|
|
38
|
+
const spaces = await clickup.getSpaces(teamId);
|
|
39
|
+
const spacePromises = spaces.map(async (space) => {
|
|
40
|
+
const lists = await clickup.getLists(space.id);
|
|
41
|
+
const listPromises = lists.map(list => clickup.getTasks(list.id));
|
|
42
|
+
const listResults = await Promise.all(listPromises);
|
|
43
|
+
return listResults.flatMap(result => result.tasks);
|
|
44
|
+
});
|
|
45
|
+
const tasksPerSpace = await Promise.all(spacePromises);
|
|
46
|
+
const allTasks = tasksPerSpace.flat();
|
|
47
|
+
return { tasks: allTasks, spaces };
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taazkareem/clickup-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
17
17
|
"start": "node build/index.js",
|
|
18
|
+
"serve": "node build/index.js --stdio",
|
|
18
19
|
"dev": "tsc -w",
|
|
19
20
|
"prepare": "npm run build",
|
|
20
21
|
"prepublishOnly": "npm test",
|