@hauptsache.net/clickup-mcp 1.2.0 → 1.3.1
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/LICENSE +21 -0
- package/README.md +30 -36
- package/dist/cli.js +7 -0
- package/dist/clickup-text.d.ts +1 -1
- package/dist/clickup-text.d.ts.map +1 -1
- package/dist/clickup-text.js +17 -13
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -9
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +34 -5
- package/dist/shared/utils.d.ts +16 -26
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +247 -61
- package/dist/tools/doc-tools.d.ts +4 -0
- package/dist/tools/doc-tools.d.ts.map +1 -0
- package/dist/tools/doc-tools.js +491 -0
- package/dist/tools/list-tools.d.ts.map +1 -1
- package/dist/tools/list-tools.js +4 -0
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +9 -29
- package/dist/tools/space-tools.d.ts.map +1 -1
- package/dist/tools/space-tools.js +96 -98
- package/dist/tools/task-tools.d.ts.map +1 -1
- package/dist/tools/task-tools.js +3 -1
- package/dist/tools/task-write-tools.d.ts.map +1 -1
- package/dist/tools/task-write-tools.js +6 -0
- package/dist/tools/time-tools.d.ts.map +1 -1
- package/dist/tools/time-tools.js +4 -0
- package/package.json +4 -4
|
@@ -16,6 +16,8 @@ function registerSpaceTools(server) {
|
|
|
16
16
|
.optional()
|
|
17
17
|
.describe("Array of search terms to match against space names or IDs. If not provided, returns all spaces."),
|
|
18
18
|
archived: zod_1.z.boolean().optional().describe("Include archived spaces (default: false)")
|
|
19
|
+
}, {
|
|
20
|
+
readOnlyHint: true
|
|
19
21
|
}, async ({ terms, archived = false }) => {
|
|
20
22
|
try {
|
|
21
23
|
const searchIndex = await (0, utils_1.getSpaceSearchIndex)();
|
|
@@ -30,36 +32,10 @@ function registerSpaceTools(server) {
|
|
|
30
32
|
matchingSpaces = searchIndex._docs || [];
|
|
31
33
|
}
|
|
32
34
|
else {
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (trimmedTerm.length === 0)
|
|
38
|
-
return;
|
|
39
|
-
// Check if it's an exact space ID first
|
|
40
|
-
const exactMatch = searchIndex._docs.find((space) => space.id === trimmedTerm);
|
|
41
|
-
if (exactMatch) {
|
|
42
|
-
uniqueResults.set(exactMatch.id, { item: exactMatch, score: 0 });
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
// Fuzzy search
|
|
46
|
-
const results = searchIndex.search(trimmedTerm);
|
|
47
|
-
results.forEach(result => {
|
|
48
|
-
if (result.item && typeof result.item.id === 'string') {
|
|
49
|
-
const currentScore = result.score ?? 1;
|
|
50
|
-
const existing = uniqueResults.get(result.item.id);
|
|
51
|
-
if (!existing || currentScore < existing.score) {
|
|
52
|
-
uniqueResults.set(result.item.id, {
|
|
53
|
-
item: result.item,
|
|
54
|
-
score: currentScore
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
matchingSpaces = Array.from(uniqueResults.values())
|
|
61
|
-
.sort((a, b) => a.score - b.score)
|
|
62
|
-
.map(entry => entry.item);
|
|
35
|
+
// Perform multi-term search with aggressive boosting
|
|
36
|
+
matchingSpaces = await (0, utils_1.performMultiTermSearch)(searchIndex, terms
|
|
37
|
+
// No ID matcher or direct fetcher for spaces - they don't have direct API endpoints
|
|
38
|
+
);
|
|
63
39
|
}
|
|
64
40
|
// Filter by archived status
|
|
65
41
|
if (!archived) {
|
|
@@ -75,87 +51,105 @@ function registerSpaceTools(server) {
|
|
|
75
51
|
try {
|
|
76
52
|
if (matchingSpaces.length <= 5) {
|
|
77
53
|
// Detailed mode: fetch lists and folders for this space
|
|
78
|
-
const { lists, folders } = await (0, utils_1.getSpaceContent)(space.id);
|
|
79
|
-
return { space, lists, folders };
|
|
54
|
+
const { lists, folders, documents } = await (0, utils_1.getSpaceContent)(space.id);
|
|
55
|
+
return { space, lists, folders, documents };
|
|
80
56
|
}
|
|
81
57
|
else {
|
|
82
58
|
// Summary mode: just return space without content
|
|
83
|
-
return { space, lists: [], folders: [] };
|
|
59
|
+
return { space, lists: [], folders: [], documents: [] };
|
|
84
60
|
}
|
|
85
61
|
}
|
|
86
62
|
catch (error) {
|
|
87
63
|
console.error(`Error fetching content for space ${space.id}:`, error);
|
|
88
|
-
return { space, lists: [], folders: [] };
|
|
64
|
+
return { space, lists: [], folders: [], documents: [] };
|
|
89
65
|
}
|
|
90
66
|
});
|
|
91
67
|
const spacesWithContent = await Promise.all(spaceContentPromises);
|
|
92
68
|
const contentBlocks = [];
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
69
|
+
const isDetailedMode = matchingSpaces.length <= 5;
|
|
70
|
+
if (isDetailedMode) {
|
|
71
|
+
// Detailed mode: create separate blocks for each space
|
|
72
|
+
spacesWithContent.forEach(({ space, lists, folders, documents }) => {
|
|
73
|
+
const spaceLines = [];
|
|
74
|
+
const totalLists = lists.length + folders.reduce((sum, f) => sum + (f.lists?.length || 0), 0);
|
|
75
|
+
// Space header
|
|
76
|
+
spaceLines.push(`🏢 SPACE: ${space.name} (space_id: ${space.id}${space.private ? ', private' : ''}${space.archived ? ', archived' : ''}) ${(0, utils_1.generateSpaceUrl)(space.id)}`, ` ${totalLists} lists, ${folders.length} folders, ${documents.length} documents`);
|
|
77
|
+
// Create a tree structure
|
|
78
|
+
const hasDirectLists = lists.length > 0;
|
|
79
|
+
const hasFolders = folders.length > 0;
|
|
80
|
+
const hasDocuments = documents.length > 0;
|
|
81
|
+
// Direct lists (not in folders)
|
|
82
|
+
if (hasDirectLists) {
|
|
83
|
+
lists.forEach((list, listIndex) => {
|
|
84
|
+
const isLastDirectList = listIndex === lists.length - 1;
|
|
85
|
+
const isLastOverall = !hasFolders && !hasDocuments && isLastDirectList;
|
|
86
|
+
const treeChar = isLastOverall ? '└──' : '├──';
|
|
87
|
+
const extraInfo = [
|
|
88
|
+
...(list.task_count ? [`${list.task_count} tasks`] : []),
|
|
89
|
+
...(list.private ? ['private'] : []),
|
|
90
|
+
...(list.archived ? ['archived'] : [])
|
|
91
|
+
].join(', ');
|
|
92
|
+
const listLine = `${treeChar} 📝 ${list.name} (list_id: ${list.id}${extraInfo ? `, ${extraInfo}` : ''}) ${(0, utils_1.generateListUrl)(list.id)}`;
|
|
93
|
+
spaceLines.push(listLine);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// Folders and their lists
|
|
97
|
+
if (hasFolders) {
|
|
98
|
+
folders.forEach((folder, folderIndex) => {
|
|
99
|
+
const isLastFolder = folderIndex === folders.length - 1;
|
|
100
|
+
const isLastOverall = !hasDocuments && isLastFolder;
|
|
101
|
+
const folderTreeChar = isLastOverall ? '└──' : '├──';
|
|
102
|
+
const folderContinuation = isLastOverall ? ' ' : '│ ';
|
|
103
|
+
const folderExtraInfo = [
|
|
104
|
+
...(folder.lists?.length ? [`${folder.lists.length} lists`] : []),
|
|
105
|
+
...(folder.private ? ['private'] : []),
|
|
106
|
+
...(folder.archived ? ['archived'] : [])
|
|
107
|
+
].join(', ');
|
|
108
|
+
const folderLine = `${folderTreeChar} 📂 ${folder.name} (folder_id: ${folder.id}${folderExtraInfo ? `, ${folderExtraInfo}` : ''}) ${(0, utils_1.generateFolderUrl)(folder.id)}`;
|
|
109
|
+
spaceLines.push(folderLine);
|
|
110
|
+
// Lists within this folder
|
|
111
|
+
if (folder.lists && folder.lists.length > 0) {
|
|
112
|
+
folder.lists.forEach((list, listIndex) => {
|
|
113
|
+
const isLastListInFolder = listIndex === folder.lists.length - 1;
|
|
114
|
+
const listTreeChar = isLastListInFolder ? '└──' : '├──';
|
|
115
|
+
const listExtraInfo = [
|
|
116
|
+
...(list.task_count ? [`${list.task_count} tasks`] : []),
|
|
117
|
+
...(list.private ? ['private'] : []),
|
|
118
|
+
...(list.archived ? ['archived'] : [])
|
|
119
|
+
].join(', ');
|
|
120
|
+
const listLine = `${folderContinuation}${listTreeChar} 📝 ${list.name} (list_id: ${list.id}${listExtraInfo ? `, ${listExtraInfo}` : ''}) ${(0, utils_1.generateListUrl)(list.id)}`;
|
|
121
|
+
spaceLines.push(listLine);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Documents attached to this space
|
|
127
|
+
if (hasDocuments) {
|
|
128
|
+
documents.forEach((document, docIndex) => {
|
|
129
|
+
const isLastDocument = docIndex === documents.length - 1;
|
|
130
|
+
const docTreeChar = isLastDocument ? '└──' : '├──';
|
|
131
|
+
const docLine = `${docTreeChar} 📄 ${document.name} (doc_id: ${document.id}) ${(0, utils_1.generateDocumentUrl)(document.id)}`;
|
|
132
|
+
spaceLines.push(docLine);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Add the complete space as a single content block
|
|
136
|
+
contentBlocks.push({
|
|
137
|
+
type: "text",
|
|
138
|
+
text: spaceLines.join('\n')
|
|
143
139
|
});
|
|
144
|
-
}
|
|
145
|
-
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Summary mode: create a single combined block with all spaces
|
|
144
|
+
const allSpaceLines = [];
|
|
145
|
+
spacesWithContent.forEach(({ space }) => {
|
|
146
|
+
allSpaceLines.push(`🏢 SPACE: ${space.name} (space_id: ${space.id}${space.private ? ', private' : ''}${space.archived ? ', archived' : ''})`);
|
|
147
|
+
});
|
|
146
148
|
contentBlocks.push({
|
|
147
149
|
type: "text",
|
|
148
|
-
text:
|
|
150
|
+
text: allSpaceLines.join('\n')
|
|
149
151
|
});
|
|
150
|
-
|
|
151
|
-
if (index < spacesWithContent.length - 1) {
|
|
152
|
-
contentBlocks.push({
|
|
153
|
-
type: "text",
|
|
154
|
-
text: '─'.repeat(50)
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
const totalLists = spacesWithContent.reduce((sum, { lists, folders }) => sum + lists.length + folders.reduce((folderSum, f) => folderSum + (f.lists?.length || 0), 0), 0);
|
|
152
|
+
}
|
|
159
153
|
// Add tip message for summary mode (when there are too many spaces)
|
|
160
154
|
if (matchingSpaces.length > 5) {
|
|
161
155
|
contentBlocks.push({
|
|
@@ -168,8 +162,12 @@ function registerSpaceTools(server) {
|
|
|
168
162
|
{
|
|
169
163
|
type: "text",
|
|
170
164
|
text: matchingSpaces.length <= 5
|
|
171
|
-
?
|
|
172
|
-
|
|
165
|
+
? (() => {
|
|
166
|
+
const totalLists = spacesWithContent.reduce((sum, { lists, folders }) => sum + lists.length + folders.reduce((folderSum, f) => folderSum + (f.lists?.length || 0), 0), 0);
|
|
167
|
+
const totalDocuments = spacesWithContent.reduce((sum, { documents }) => sum + documents.length, 0);
|
|
168
|
+
return `Found ${matchingSpaces.length} space(s) with complete tree structure (${totalLists} total lists, ${totalDocuments} total documents):`;
|
|
169
|
+
})()
|
|
170
|
+
: `Found ${matchingSpaces.length} space(s) - showing names and IDs only. Use more specific search terms to get detailed information:`
|
|
173
171
|
},
|
|
174
172
|
...contentBlocks
|
|
175
173
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAyC,MAAM,iBAAiB,CAAC;AAOtF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAyC,MAAM,iBAAiB,CAAC;AAOtF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QA2DrE;AA+MD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,EAAE,YAAY,GAAE,OAAe,GAAG,OAAO,CAAC,YAAY,CAAC,CA+G/H"}
|
package/dist/tools/task-tools.js
CHANGED
|
@@ -22,6 +22,8 @@ function registerTaskToolsRead(server, userData) {
|
|
|
22
22
|
message: "Task ID must be 6-9 alphanumeric characters only"
|
|
23
23
|
})
|
|
24
24
|
.describe(`The 6-9 character ID of the task to get without a prefix like "#", "CU-" or "https://app.clickup.com/t/"`),
|
|
25
|
+
}, {
|
|
26
|
+
readOnlyHint: true
|
|
25
27
|
}, async ({ id }) => {
|
|
26
28
|
// 1. Load base task content, comment events, and status change events in parallel
|
|
27
29
|
const [taskDetailContentBlocks, commentEvents, statusChangeEvents] = await Promise.all([
|
|
@@ -91,7 +93,7 @@ async function loadTaskContent(taskId) {
|
|
|
91
93
|
return await generateTaskMetadata(task, timeEntries, true);
|
|
92
94
|
})(),
|
|
93
95
|
// process markdown and download images
|
|
94
|
-
(0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments),
|
|
96
|
+
(0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments || []),
|
|
95
97
|
]);
|
|
96
98
|
return [taskMetadata, ...content];
|
|
97
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-write-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-write-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"task-write-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-write-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QAqTtE"}
|
|
@@ -30,6 +30,8 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
30
30
|
})(), {
|
|
31
31
|
task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to comment on"),
|
|
32
32
|
comment: zod_1.z.string().min(1).describe("The comment text to add to the task"),
|
|
33
|
+
}, {
|
|
34
|
+
readOnlyHint: false
|
|
33
35
|
}, async ({ task_id, comment }) => {
|
|
34
36
|
try {
|
|
35
37
|
const requestBody = {
|
|
@@ -105,6 +107,8 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
105
107
|
tags: taskTagsSchema.describe("Optional array of tag names (will replace existing tags)"),
|
|
106
108
|
parent_task_id: zod_1.z.string().optional().describe("Optional parent task ID to change parent/child relationships"),
|
|
107
109
|
assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
|
|
110
|
+
}, {
|
|
111
|
+
readOnlyHint: false
|
|
108
112
|
}, async ({ task_id, name, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees }) => {
|
|
109
113
|
try {
|
|
110
114
|
const userData = await (0, utils_1.getCurrentUser)();
|
|
@@ -213,6 +217,8 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
213
217
|
tags: taskTagsSchema,
|
|
214
218
|
parent_task_id: zod_1.z.string().optional().describe("Optional parent task ID to create this as a subtask"),
|
|
215
219
|
assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
|
|
220
|
+
}, {
|
|
221
|
+
readOnlyHint: false
|
|
216
222
|
}, async ({ list_id, name, description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees }) => {
|
|
217
223
|
try {
|
|
218
224
|
const userData = await (0, utils_1.getCurrentUser)();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-tools.d.ts","sourceRoot":"","sources":["../../src/tools/time-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA0DpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"time-tools.d.ts","sourceRoot":"","sources":["../../src/tools/time-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA0DpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,QAgFtD;AAwLD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,QAsFvD"}
|
package/dist/tools/time-tools.js
CHANGED
|
@@ -58,6 +58,8 @@ function registerTimeToolsRead(server) {
|
|
|
58
58
|
list_id: zod_1.z.string().optional().describe("Optional single list ID to filter time entries by a specific list"),
|
|
59
59
|
space_id: zod_1.z.string().optional().describe("Optional single space ID to filter time entries by a specific space"),
|
|
60
60
|
include_all_users: zod_1.z.boolean().optional().describe("Optional flag to include time entries from all team members (default: false, only current user)")
|
|
61
|
+
}, {
|
|
62
|
+
readOnlyHint: true
|
|
61
63
|
}, async ({ task_id, start_date, end_date, list_id, space_id, include_all_users }) => {
|
|
62
64
|
try {
|
|
63
65
|
// Build query parameters
|
|
@@ -268,6 +270,8 @@ function registerTimeToolsWrite(server) {
|
|
|
268
270
|
hours: zod_1.z.number().min(0.01).max(24).describe("Hours to book (decimal format, e.g., 0.25 = 15min, 1.5 = 1h 30min)"),
|
|
269
271
|
description: zod_1.z.string().optional().describe("Optional description for the time entry"),
|
|
270
272
|
start_time: zod_1.z.string().optional().describe("Optional start time as ISO date string (e.g., '2024-10-06T09:00:00+02:00', defaults to current time)")
|
|
273
|
+
}, {
|
|
274
|
+
readOnlyHint: false
|
|
271
275
|
}, async ({ task_id, hours, description, start_time }) => {
|
|
272
276
|
try {
|
|
273
277
|
// Convert hours to milliseconds (ClickUp API uses milliseconds)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hauptsache.net/clickup-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "Search, create, and retrieve tasks, add comments, and track time through natural language commands.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": "dist/index.js",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"name": "Marco Pfeiffer",
|
|
37
37
|
"email": "marco@hauptsache.net"
|
|
38
38
|
},
|
|
39
|
-
"license": "
|
|
39
|
+
"license": "MIT",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
42
42
|
"fuse.js": "^7.1.0",
|
|
43
43
|
"zod": "^3.24.2"
|
|
44
44
|
},
|