@taazkareem/clickup-mcp-server 0.4.73 → 0.4.75
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 -0
- package/build/server.js +1 -1
- package/build/server.log +101 -31
- package/build/services/clickup/task.js +26 -0
- package/build/tools/folder.js +87 -58
- package/build/tools/list.js +123 -86
- package/build/tools/task/bulk-operations.js +69 -5
- package/build/tools/task/handlers.js +7 -6
- package/build/tools/task/single-operations.js +112 -14
- package/build/tools/task/utilities.js +72 -11
- package/build/tools/workspace.js +46 -32
- package/build/utils/sponsor-service.js +24 -5
- package/package.json +1 -1
|
@@ -32,14 +32,14 @@ export function formatTaskData(task, additional = {}) {
|
|
|
32
32
|
//=============================================================================
|
|
33
33
|
/**
|
|
34
34
|
* Validates task identification parameters
|
|
35
|
-
* Ensures either taskId
|
|
35
|
+
* Ensures either taskId, customTaskId, or both taskName and listName are provided
|
|
36
36
|
*/
|
|
37
|
-
export function validateTaskIdentification(taskId, taskName, listName) {
|
|
38
|
-
if (!taskId && !taskName) {
|
|
39
|
-
throw new Error("Either taskId or taskName must be provided");
|
|
37
|
+
export function validateTaskIdentification(taskId, taskName, listName, customTaskId) {
|
|
38
|
+
if (!taskId && !taskName && !customTaskId) {
|
|
39
|
+
throw new Error("Either taskId, customTaskId, or taskName must be provided");
|
|
40
40
|
}
|
|
41
|
-
if (!taskId && taskName && !listName) {
|
|
42
|
-
throw new Error("
|
|
41
|
+
if (!taskId && !customTaskId && taskName && !listName) {
|
|
42
|
+
throw new Error("listName is required when using taskName");
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
@@ -84,18 +84,79 @@ export function parseBulkOptions(rawOptions) {
|
|
|
84
84
|
return rawOptions;
|
|
85
85
|
}
|
|
86
86
|
//=============================================================================
|
|
87
|
+
// ID DETECTION UTILITIES
|
|
88
|
+
//=============================================================================
|
|
89
|
+
/**
|
|
90
|
+
* Determines if an ID is a custom ID based on its format
|
|
91
|
+
* Custom IDs typically have an uppercase prefix followed by a hyphen and number (e.g., DEV-1234)
|
|
92
|
+
* Regular task IDs are always 9 characters long
|
|
93
|
+
*
|
|
94
|
+
* @param id The task ID to check
|
|
95
|
+
* @returns True if the ID appears to be a custom ID
|
|
96
|
+
*/
|
|
97
|
+
export function isCustomTaskId(id) {
|
|
98
|
+
if (!id)
|
|
99
|
+
return false;
|
|
100
|
+
// Regular task IDs are exactly 9 characters
|
|
101
|
+
if (id.length === 9) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
// Custom IDs have an uppercase prefix followed by a hyphen and numbers
|
|
105
|
+
const customIdPattern = /^[A-Z]+-\d+$/;
|
|
106
|
+
return customIdPattern.test(id);
|
|
107
|
+
}
|
|
108
|
+
//=============================================================================
|
|
87
109
|
// ID RESOLUTION UTILITIES
|
|
88
110
|
//=============================================================================
|
|
89
111
|
/**
|
|
90
|
-
* Resolves a task ID from
|
|
112
|
+
* Resolves a task ID from direct ID, custom ID, or name
|
|
91
113
|
* Handles validation and throws appropriate errors
|
|
92
114
|
*/
|
|
93
|
-
export async function resolveTaskIdWithValidation(taskId, taskName, listName) {
|
|
115
|
+
export async function resolveTaskIdWithValidation(taskId, taskName, listName, customTaskId) {
|
|
94
116
|
// Validate parameters
|
|
95
|
-
validateTaskIdentification(taskId, taskName, listName);
|
|
96
|
-
// If
|
|
97
|
-
if (
|
|
117
|
+
validateTaskIdentification(taskId, taskName, listName, customTaskId);
|
|
118
|
+
// If customTaskId is explicitly provided, use it
|
|
119
|
+
if (customTaskId) {
|
|
120
|
+
const { task: taskService } = clickUpServices;
|
|
121
|
+
try {
|
|
122
|
+
// First try to get the task by custom ID
|
|
123
|
+
// If listName is provided, we can also look up in a specific list for better performance
|
|
124
|
+
let listId;
|
|
125
|
+
if (listName) {
|
|
126
|
+
listId = await resolveListIdWithValidation(undefined, listName);
|
|
127
|
+
}
|
|
128
|
+
// Look up by custom ID
|
|
129
|
+
const foundTask = await taskService.getTaskByCustomId(customTaskId, listId);
|
|
130
|
+
return foundTask.id;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
throw new Error(`Task with custom ID "${customTaskId}" not found`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// If taskId is provided, check if it looks like a custom ID
|
|
137
|
+
if (taskId) {
|
|
138
|
+
if (isCustomTaskId(taskId)) {
|
|
139
|
+
console.log(`Detected task ID "${taskId}" as a custom ID, using custom ID lookup`);
|
|
140
|
+
// If it looks like a custom ID, try to get it as a custom ID first
|
|
141
|
+
const { task: taskService } = clickUpServices;
|
|
142
|
+
try {
|
|
143
|
+
// Look up by custom ID
|
|
144
|
+
let listId;
|
|
145
|
+
if (listName) {
|
|
146
|
+
listId = await resolveListIdWithValidation(undefined, listName);
|
|
147
|
+
}
|
|
148
|
+
const foundTask = await taskService.getTaskByCustomId(taskId, listId);
|
|
149
|
+
return foundTask.id;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
// If it fails as a custom ID, try as a regular ID
|
|
153
|
+
console.log(`Failed to find task with custom ID "${taskId}", falling back to regular ID`);
|
|
154
|
+
return taskId;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Regular task ID
|
|
98
158
|
return taskId;
|
|
159
|
+
}
|
|
99
160
|
// At this point we know we have taskName and listName (validation ensures this)
|
|
100
161
|
// Find the list ID from its name
|
|
101
162
|
const listId = await resolveListIdWithValidation(undefined, listName);
|
package/build/tools/workspace.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* It handles the workspace tool definitions and the implementation of their handlers.
|
|
6
6
|
*/
|
|
7
7
|
import { Logger } from '../logger.js';
|
|
8
|
+
import { sponsorService } from '../utils/sponsor-service.js';
|
|
8
9
|
// Create a logger for workspace tools
|
|
9
10
|
const logger = new Logger('WorkspaceTool');
|
|
10
11
|
// Use the workspace service imported from the server
|
|
@@ -15,7 +16,18 @@ let workspaceService;
|
|
|
15
16
|
*/
|
|
16
17
|
export const workspaceHierarchyTool = {
|
|
17
18
|
name: 'get_workspace_hierarchy',
|
|
18
|
-
description:
|
|
19
|
+
description: `Purpose: Retrieve the complete workspace hierarchy including spaces, folders, and lists.
|
|
20
|
+
|
|
21
|
+
Valid Usage:
|
|
22
|
+
1. Call without parameters to get the full hierarchy
|
|
23
|
+
|
|
24
|
+
Requirements:
|
|
25
|
+
- No parameters required
|
|
26
|
+
|
|
27
|
+
Notes:
|
|
28
|
+
- Returns a tree structure showing all spaces, folders, and lists
|
|
29
|
+
- Each item includes its name and ID
|
|
30
|
+
- Use this to navigate the workspace and understand its organization`,
|
|
19
31
|
inputSchema: {
|
|
20
32
|
type: 'object',
|
|
21
33
|
properties: {}
|
|
@@ -42,18 +54,14 @@ export async function handleGetWorkspaceHierarchy() {
|
|
|
42
54
|
try {
|
|
43
55
|
// Get workspace hierarchy from the workspace service
|
|
44
56
|
const hierarchy = await workspaceService.getWorkspaceHierarchy();
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
// Generate tree representation
|
|
58
|
+
const treeOutput = formatTreeOutput(hierarchy);
|
|
59
|
+
return sponsorService.createResponse({
|
|
60
|
+
hierarchy: treeOutput
|
|
61
|
+
}, true);
|
|
47
62
|
}
|
|
48
63
|
catch (error) {
|
|
49
|
-
return {
|
|
50
|
-
content: [
|
|
51
|
-
{
|
|
52
|
-
type: "text",
|
|
53
|
-
text: `Error getting workspace hierarchy: ${error.message}`
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
};
|
|
64
|
+
return sponsorService.createErrorResponse(`Error getting workspace hierarchy: ${error.message}`);
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
/**
|
|
@@ -61,27 +69,7 @@ export async function handleGetWorkspaceHierarchy() {
|
|
|
61
69
|
*/
|
|
62
70
|
function formatHierarchyResponse(hierarchy) {
|
|
63
71
|
try {
|
|
64
|
-
|
|
65
|
-
const formatNodeAsTree = (node, level = 0, isLast = true, parentPrefix = '') => {
|
|
66
|
-
const lines = [];
|
|
67
|
-
// Calculate the current line's prefix
|
|
68
|
-
const currentPrefix = level === 0 ? '' : parentPrefix + (isLast ? '└── ' : '├── ');
|
|
69
|
-
// Format current node with descriptive ID type
|
|
70
|
-
const idType = 'type' in node ? `${node.type.charAt(0).toUpperCase() + node.type.slice(1)} ID` : 'Workspace ID';
|
|
71
|
-
lines.push(`${currentPrefix}${node.name} (${idType}: ${node.id})`);
|
|
72
|
-
// Calculate the prefix for children
|
|
73
|
-
const childPrefix = level === 0 ? '' : parentPrefix + (isLast ? ' ' : '│ ');
|
|
74
|
-
// Process children
|
|
75
|
-
const children = node.children || [];
|
|
76
|
-
children.forEach((child, index) => {
|
|
77
|
-
const childLines = formatNodeAsTree(child, level + 1, index === children.length - 1, childPrefix);
|
|
78
|
-
lines.push(...childLines);
|
|
79
|
-
});
|
|
80
|
-
return lines;
|
|
81
|
-
};
|
|
82
|
-
// Generate tree representation
|
|
83
|
-
const treeLines = formatNodeAsTree(hierarchy.root);
|
|
84
|
-
const treeOutput = treeLines.join('\n');
|
|
72
|
+
const treeOutput = formatTreeOutput(hierarchy);
|
|
85
73
|
return {
|
|
86
74
|
content: [
|
|
87
75
|
{
|
|
@@ -102,3 +90,29 @@ function formatHierarchyResponse(hierarchy) {
|
|
|
102
90
|
};
|
|
103
91
|
}
|
|
104
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Format the hierarchy as a tree string
|
|
95
|
+
*/
|
|
96
|
+
function formatTreeOutput(hierarchy) {
|
|
97
|
+
// Helper function to format a node and its children as a tree
|
|
98
|
+
const formatNodeAsTree = (node, level = 0, isLast = true, parentPrefix = '') => {
|
|
99
|
+
const lines = [];
|
|
100
|
+
// Calculate the current line's prefix
|
|
101
|
+
const currentPrefix = level === 0 ? '' : parentPrefix + (isLast ? '└── ' : '├── ');
|
|
102
|
+
// Format current node with descriptive ID type
|
|
103
|
+
const idType = 'type' in node ? `${node.type.charAt(0).toUpperCase() + node.type.slice(1)} ID` : 'Workspace ID';
|
|
104
|
+
lines.push(`${currentPrefix}${node.name} (${idType}: ${node.id})`);
|
|
105
|
+
// Calculate the prefix for children
|
|
106
|
+
const childPrefix = level === 0 ? '' : parentPrefix + (isLast ? ' ' : '│ ');
|
|
107
|
+
// Process children
|
|
108
|
+
const children = node.children || [];
|
|
109
|
+
children.forEach((child, index) => {
|
|
110
|
+
const childLines = formatNodeAsTree(child, level + 1, index === children.length - 1, childPrefix);
|
|
111
|
+
lines.push(...childLines);
|
|
112
|
+
});
|
|
113
|
+
return lines;
|
|
114
|
+
};
|
|
115
|
+
// Generate tree representation
|
|
116
|
+
const treeLines = formatNodeAsTree(hierarchy.root);
|
|
117
|
+
return treeLines.join('\n');
|
|
118
|
+
}
|
|
@@ -30,16 +30,35 @@ export class SponsorService {
|
|
|
30
30
|
*/
|
|
31
31
|
createResponse(data, includeSponsorMessage = false) {
|
|
32
32
|
const content = [];
|
|
33
|
+
// Special handling for workspace hierarchy which contains a preformatted tree
|
|
34
|
+
if (data && typeof data === 'object' && 'hierarchy' in data && typeof data.hierarchy === 'string') {
|
|
35
|
+
// Handle workspace hierarchy specially - it contains a preformatted tree
|
|
36
|
+
content.push({
|
|
37
|
+
type: "text",
|
|
38
|
+
text: data.hierarchy
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else if (typeof data === 'string') {
|
|
42
|
+
// If it's already a string, use it directly
|
|
43
|
+
content.push({
|
|
44
|
+
type: "text",
|
|
45
|
+
text: data
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Otherwise, stringify the JSON object
|
|
50
|
+
content.push({
|
|
51
|
+
type: "text",
|
|
52
|
+
text: JSON.stringify(data, null, 2)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Then add sponsorship message if enabled
|
|
33
56
|
if (this.isEnabled && includeSponsorMessage) {
|
|
34
57
|
content.push({
|
|
35
58
|
type: "text",
|
|
36
|
-
text:
|
|
59
|
+
text: `\n\n❤️ Support this project by sponsoring the developer at ${this.sponsorUrl}`
|
|
37
60
|
});
|
|
38
61
|
}
|
|
39
|
-
content.push({
|
|
40
|
-
type: "text",
|
|
41
|
-
text: JSON.stringify(data, null, 2)
|
|
42
|
-
});
|
|
43
62
|
return { content };
|
|
44
63
|
}
|
|
45
64
|
/**
|
package/package.json
CHANGED