@taazkareem/clickup-mcp-server 0.4.60 → 0.4.63
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 +10 -2
- package/build/config.js +10 -0
- package/build/index.js +9 -1356
- package/build/server.js +120 -0
- package/build/services/clickup/base.js +253 -0
- package/build/services/clickup/bulk.js +116 -0
- package/build/services/clickup/folder.js +133 -0
- package/build/services/clickup/index.js +43 -0
- package/build/services/clickup/initialization.js +28 -0
- package/build/services/clickup/list.js +188 -0
- package/build/services/clickup/task.js +492 -0
- package/build/services/clickup/types.js +4 -0
- package/build/services/clickup/workspace.js +314 -0
- package/build/services/shared.js +15 -0
- package/build/tools/folder.js +356 -0
- package/build/tools/index.js +11 -0
- package/build/tools/list.js +452 -0
- package/build/tools/task.js +1519 -0
- package/build/tools/utils.js +150 -0
- package/build/tools/workspace.js +132 -0
- package/package.json +3 -1
- package/build/services/clickup.js +0 -765
- package/build/types/clickup.js +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for ClickUp MCP tools
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get a timestamp for a relative time
|
|
6
|
+
*
|
|
7
|
+
* @param hours Hours from now
|
|
8
|
+
* @param days Days from now
|
|
9
|
+
* @param weeks Weeks from now
|
|
10
|
+
* @param months Months from now
|
|
11
|
+
* @returns Timestamp in milliseconds
|
|
12
|
+
*/
|
|
13
|
+
export function getRelativeTimestamp(hours = 0, days = 0, weeks = 0, months = 0) {
|
|
14
|
+
const now = new Date();
|
|
15
|
+
if (hours)
|
|
16
|
+
now.setHours(now.getHours() + hours);
|
|
17
|
+
if (days)
|
|
18
|
+
now.setDate(now.getDate() + days);
|
|
19
|
+
if (weeks)
|
|
20
|
+
now.setDate(now.getDate() + (weeks * 7));
|
|
21
|
+
if (months)
|
|
22
|
+
now.setMonth(now.getMonth() + months);
|
|
23
|
+
return now.getTime();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse a due date string into a timestamp
|
|
27
|
+
* Supports ISO 8601 format or natural language like "tomorrow"
|
|
28
|
+
*
|
|
29
|
+
* @param dateString Date string to parse
|
|
30
|
+
* @returns Timestamp in milliseconds or undefined if parsing fails
|
|
31
|
+
*/
|
|
32
|
+
export function parseDueDate(dateString) {
|
|
33
|
+
if (!dateString)
|
|
34
|
+
return undefined;
|
|
35
|
+
try {
|
|
36
|
+
// Handle natural language dates
|
|
37
|
+
const lowerDate = dateString.toLowerCase();
|
|
38
|
+
const now = new Date();
|
|
39
|
+
if (lowerDate === 'today') {
|
|
40
|
+
const today = new Date();
|
|
41
|
+
today.setHours(23, 59, 59, 999);
|
|
42
|
+
return today.getTime();
|
|
43
|
+
}
|
|
44
|
+
// Handle relative dates with specific times
|
|
45
|
+
const relativeTimeRegex = /(?:(\d+)\s*(days?|weeks?|months?)\s*from\s*now|tomorrow|next\s+(?:week|month))\s*(?:at\s+(\d+)(?::(\d+))?\s*(am|pm)?)?/i;
|
|
46
|
+
const match = lowerDate.match(relativeTimeRegex);
|
|
47
|
+
if (match) {
|
|
48
|
+
const date = new Date();
|
|
49
|
+
const [_, amount, unit, hours, minutes, meridian] = match;
|
|
50
|
+
// Calculate the future date
|
|
51
|
+
if (amount && unit) {
|
|
52
|
+
const value = parseInt(amount);
|
|
53
|
+
if (unit.startsWith('day')) {
|
|
54
|
+
date.setDate(date.getDate() + value);
|
|
55
|
+
}
|
|
56
|
+
else if (unit.startsWith('week')) {
|
|
57
|
+
date.setDate(date.getDate() + (value * 7));
|
|
58
|
+
}
|
|
59
|
+
else if (unit.startsWith('month')) {
|
|
60
|
+
date.setMonth(date.getMonth() + value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (lowerDate.startsWith('tomorrow')) {
|
|
64
|
+
date.setDate(date.getDate() + 1);
|
|
65
|
+
}
|
|
66
|
+
else if (lowerDate.includes('next week')) {
|
|
67
|
+
date.setDate(date.getDate() + 7);
|
|
68
|
+
}
|
|
69
|
+
else if (lowerDate.includes('next month')) {
|
|
70
|
+
date.setMonth(date.getMonth() + 1);
|
|
71
|
+
}
|
|
72
|
+
// Set the time if specified
|
|
73
|
+
if (hours) {
|
|
74
|
+
let parsedHours = parseInt(hours);
|
|
75
|
+
const parsedMinutes = minutes ? parseInt(minutes) : 0;
|
|
76
|
+
// Convert to 24-hour format if meridian is specified
|
|
77
|
+
if (meridian?.toLowerCase() === 'pm' && parsedHours < 12)
|
|
78
|
+
parsedHours += 12;
|
|
79
|
+
if (meridian?.toLowerCase() === 'am' && parsedHours === 12)
|
|
80
|
+
parsedHours = 0;
|
|
81
|
+
date.setHours(parsedHours, parsedMinutes, 0, 0);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Default to end of day if no time specified
|
|
85
|
+
date.setHours(23, 59, 59, 999);
|
|
86
|
+
}
|
|
87
|
+
return date.getTime();
|
|
88
|
+
}
|
|
89
|
+
// Handle hours from now
|
|
90
|
+
const hoursRegex = /(\d+)\s*hours?\s*from\s*now/i;
|
|
91
|
+
const daysRegex = /(\d+)\s*days?\s*from\s*now/i;
|
|
92
|
+
const weeksRegex = /(\d+)\s*weeks?\s*from\s*now/i;
|
|
93
|
+
const monthsRegex = /(\d+)\s*months?\s*from\s*now/i;
|
|
94
|
+
if (hoursRegex.test(lowerDate)) {
|
|
95
|
+
const hours = parseInt(lowerDate.match(hoursRegex)[1]);
|
|
96
|
+
return getRelativeTimestamp(hours);
|
|
97
|
+
}
|
|
98
|
+
if (daysRegex.test(lowerDate)) {
|
|
99
|
+
const days = parseInt(lowerDate.match(daysRegex)[1]);
|
|
100
|
+
return getRelativeTimestamp(0, days);
|
|
101
|
+
}
|
|
102
|
+
if (weeksRegex.test(lowerDate)) {
|
|
103
|
+
const weeks = parseInt(lowerDate.match(weeksRegex)[1]);
|
|
104
|
+
return getRelativeTimestamp(0, 0, weeks);
|
|
105
|
+
}
|
|
106
|
+
if (monthsRegex.test(lowerDate)) {
|
|
107
|
+
const months = parseInt(lowerDate.match(monthsRegex)[1]);
|
|
108
|
+
return getRelativeTimestamp(0, 0, 0, months);
|
|
109
|
+
}
|
|
110
|
+
// Try to parse as a date string
|
|
111
|
+
const date = new Date(dateString);
|
|
112
|
+
if (!isNaN(date.getTime())) {
|
|
113
|
+
return date.getTime();
|
|
114
|
+
}
|
|
115
|
+
// If all parsing fails, return undefined
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.warn(`Failed to parse due date: ${dateString}`, error);
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Format a due date timestamp into a human-readable string
|
|
125
|
+
*
|
|
126
|
+
* @param timestamp Unix timestamp in milliseconds
|
|
127
|
+
* @returns Formatted date string or undefined if timestamp is invalid
|
|
128
|
+
*/
|
|
129
|
+
export function formatDueDate(timestamp) {
|
|
130
|
+
if (!timestamp)
|
|
131
|
+
return undefined;
|
|
132
|
+
try {
|
|
133
|
+
const date = new Date(timestamp);
|
|
134
|
+
if (isNaN(date.getTime()))
|
|
135
|
+
return undefined;
|
|
136
|
+
// Format: "March 10, 2025 at 10:56 PM"
|
|
137
|
+
return date.toLocaleString('en-US', {
|
|
138
|
+
year: 'numeric',
|
|
139
|
+
month: 'long',
|
|
140
|
+
day: 'numeric',
|
|
141
|
+
hour: 'numeric',
|
|
142
|
+
minute: '2-digit',
|
|
143
|
+
hour12: true
|
|
144
|
+
}).replace(' at', ',');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.warn(`Failed to format due date: ${timestamp}`, error);
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickUp MCP Workspace Tools
|
|
3
|
+
*
|
|
4
|
+
* This module defines workspace-related tools like retrieving workspace hierarchy.
|
|
5
|
+
* It handles the workspace tool definitions and the implementation of their handlers.
|
|
6
|
+
*/
|
|
7
|
+
// Use the workspace service imported from the server
|
|
8
|
+
// This is defined when server.ts imports this module
|
|
9
|
+
let workspaceService;
|
|
10
|
+
/**
|
|
11
|
+
* Tool definition for retrieving the complete workspace hierarchy
|
|
12
|
+
*/
|
|
13
|
+
export const workspaceHierarchyTool = {
|
|
14
|
+
name: 'get_workspace_hierarchy',
|
|
15
|
+
description: 'Get the complete workspace hierarchy including spaces, folders, and lists.',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Initialize the tool with services
|
|
23
|
+
*/
|
|
24
|
+
export function initializeWorkspaceTool(services) {
|
|
25
|
+
workspaceService = services.workspace;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Handler for the get_workspace_hierarchy tool
|
|
29
|
+
*/
|
|
30
|
+
export async function handleGetWorkspaceHierarchy() {
|
|
31
|
+
try {
|
|
32
|
+
// Get workspace hierarchy from the workspace service
|
|
33
|
+
const hierarchy = await workspaceService.getWorkspaceHierarchy();
|
|
34
|
+
const response = formatHierarchyResponse(hierarchy);
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `Error getting workspace hierarchy: ${error.message}`
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Format the hierarchy for the response
|
|
50
|
+
*/
|
|
51
|
+
function formatHierarchyResponse(hierarchy) {
|
|
52
|
+
try {
|
|
53
|
+
// Helper function to format nodes by type
|
|
54
|
+
const formatNodesByType = (nodes = []) => {
|
|
55
|
+
const result = {
|
|
56
|
+
spaces: [],
|
|
57
|
+
folders: [],
|
|
58
|
+
lists: []
|
|
59
|
+
};
|
|
60
|
+
for (const node of nodes) {
|
|
61
|
+
if (node.type === 'space') {
|
|
62
|
+
const spaceResult = {
|
|
63
|
+
id: node.id,
|
|
64
|
+
name: node.name,
|
|
65
|
+
type: node.type,
|
|
66
|
+
lists: [],
|
|
67
|
+
folders: []
|
|
68
|
+
};
|
|
69
|
+
// Process children of space
|
|
70
|
+
if (node.children && node.children.length > 0) {
|
|
71
|
+
const childrenByType = formatNodesByType(node.children);
|
|
72
|
+
spaceResult.lists = childrenByType.lists;
|
|
73
|
+
spaceResult.folders = childrenByType.folders;
|
|
74
|
+
}
|
|
75
|
+
result.spaces.push(spaceResult);
|
|
76
|
+
}
|
|
77
|
+
else if (node.type === 'folder') {
|
|
78
|
+
const folderResult = {
|
|
79
|
+
id: node.id,
|
|
80
|
+
name: node.name,
|
|
81
|
+
type: node.type,
|
|
82
|
+
lists: []
|
|
83
|
+
};
|
|
84
|
+
// Process children of folder (only lists)
|
|
85
|
+
if (node.children && node.children.length > 0) {
|
|
86
|
+
folderResult.lists = node.children
|
|
87
|
+
.filter(child => child.type === 'list')
|
|
88
|
+
.map(list => ({
|
|
89
|
+
id: list.id,
|
|
90
|
+
name: list.name,
|
|
91
|
+
type: list.type
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
result.folders.push(folderResult);
|
|
95
|
+
}
|
|
96
|
+
else if (node.type === 'list') {
|
|
97
|
+
result.lists.push({
|
|
98
|
+
id: node.id,
|
|
99
|
+
name: node.name,
|
|
100
|
+
type: node.type
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
// Convert the workspace hierarchy to a simplified format
|
|
107
|
+
const rootChildren = formatNodesByType(hierarchy.root.children);
|
|
108
|
+
const formattedHierarchy = {
|
|
109
|
+
workspaceId: hierarchy.root.id,
|
|
110
|
+
workspaceName: hierarchy.root.name,
|
|
111
|
+
spaces: rootChildren.spaces
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: JSON.stringify(formattedHierarchy, null, 2)
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: `Error formatting workspace hierarchy: ${error.message}`
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
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.63",
|
|
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",
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
"homepage": "https://github.com/taazkareem/clickup-mcp-server#readme",
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@modelcontextprotocol/sdk": "0.6.0",
|
|
47
|
+
"@modelcontextprotocol/server-github": "^2025.1.23",
|
|
48
|
+
"@taazkareem/clickup-mcp-server": "^0.4.60",
|
|
47
49
|
"axios": "^1.6.7",
|
|
48
50
|
"dotenv": "^16.4.1"
|
|
49
51
|
},
|