@hauptsache.net/clickup-mcp 1.8.0 → 1.9.0
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 +1 -1
- package/dist/clickup-text.js +16 -0
- package/dist/tools/task-write-tools.d.ts.map +1 -1
- package/dist/tools/task-write-tools.js +37 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ The ClickUp MCP supports three operational modes to balance functionality, secur
|
|
|
189
189
|
| `getTaskById` | ✅ | ✅ | ✅ | Get complete task details including comments (with threaded replies), images, and metadata |
|
|
190
190
|
| `addComment` | ❌ | ❌ | ✅ | Add comments to tasks, or reply inside a comment thread via `parent_comment_id` |
|
|
191
191
|
| `editComment` | ❌ | ❌ | ✅ | Correct your own comment within 24h instead of posting a follow-up |
|
|
192
|
-
| `updateTask` | ❌ | ❌ | ✅ | Update tasks (status, priority, assignees, etc.)
|
|
192
|
+
| `updateTask` | ❌ | ❌ | ✅ | Update tasks (status, priority, assignees, etc.); descriptions can be appended to or replaced entirely |
|
|
193
193
|
| `createTask` | ❌ | ❌ | ✅ | Create new tasks with full markdown support |
|
|
194
194
|
| `searchTasks` | ✅ | ✅ | ✅ | Find tasks by content, keywords, assignees, or project context |
|
|
195
195
|
| `searchSpaces` | ❌ | ✅ | ✅ | Browse workspace structure, project organization, and documents |
|
package/dist/clickup-text.js
CHANGED
|
@@ -595,6 +595,14 @@ function walkMdastNodes(nodes, inheritedAttrs, blocks, depth = 0, attachmentsByS
|
|
|
595
595
|
for (let i = 0; i < nodes.length; i++) {
|
|
596
596
|
const node = nodes[i];
|
|
597
597
|
const currentAttrs = { ...inheritedAttrs };
|
|
598
|
+
// ClickUp has no paragraph margins: two paragraphs separated by a single '\n'
|
|
599
|
+
// render as consecutive lines. The UI stores a paragraph break as an extra
|
|
600
|
+
// empty '\n' fragment (what a user gets by pressing Enter twice), so emit one
|
|
601
|
+
// between adjacent flow blocks. Headings, code blocks and blockquotes bring
|
|
602
|
+
// their own spacing - a blank line next to them would double the gap.
|
|
603
|
+
if (i > 0 && needsBlankLineBetween(nodes[i - 1], node)) {
|
|
604
|
+
blocks.push({ text: '\n', attributes: {} });
|
|
605
|
+
}
|
|
598
606
|
switch (node.type) {
|
|
599
607
|
case 'heading':
|
|
600
608
|
// Process heading content with inline formatting
|
|
@@ -689,6 +697,14 @@ function walkMdastNodes(nodes, inheritedAttrs, blocks, depth = 0, attachmentsByS
|
|
|
689
697
|
}
|
|
690
698
|
}
|
|
691
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* Block types that render without their own vertical margin in ClickUp comments.
|
|
702
|
+
* Two of them in a row need an explicit empty line to read as separate blocks.
|
|
703
|
+
*/
|
|
704
|
+
const FLOW_BLOCK_TYPES = new Set(['paragraph', 'list']);
|
|
705
|
+
function needsBlankLineBetween(prev, next) {
|
|
706
|
+
return FLOW_BLOCK_TYPES.has(prev.type) && FLOW_BLOCK_TYPES.has(next.type);
|
|
707
|
+
}
|
|
692
708
|
/**
|
|
693
709
|
* Emit a (possibly multi-line) code block the way ClickUp's Quill-based format
|
|
694
710
|
* expects it: block attributes apply per line, so EVERY line needs its own '\n'
|
|
@@ -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;AAsJpE,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;AAsJpE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QA6pBtE"}
|
|
@@ -298,11 +298,12 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
298
298
|
"Updates various aspects of an existing task including dependencies and relationships.",
|
|
299
299
|
"ALWAYS include the task URL (https://app.clickup.com/t/TASK_ID) when updating or referencing tasks.",
|
|
300
300
|
"Use getListInfo first to see valid status options.",
|
|
301
|
-
"
|
|
301
|
+
"DESCRIPTIONS: `append_description` adds a dated section below the existing text; `description` REPLACES the whole text (use it to restructure, shorten or correct a description). Pass only one of them.",
|
|
302
|
+
"Before replacing, read the current description via getTaskById and carry over everything that is still needed - ClickUp keeps a description history, so a bad replacement can be restored in the UI (edits within the same minute merge into one version), but the MCP cannot undo it.",
|
|
302
303
|
"STATUS UPDATES: Use the `addComment` tool for progress reports, work logs, and status updates rather than the task description.",
|
|
303
304
|
IMAGE_SUPPORT_HINT,
|
|
304
305
|
"Task descriptions should contain requirements, specifications, and core task information.",
|
|
305
|
-
"LINKING IN DESCRIPTIONS:
|
|
306
|
+
"LINKING IN DESCRIPTIONS: Include links to related tasks, lists, or external resources (format: https://app.clickup.com/t/TASK_ID).",
|
|
306
307
|
"IMPORTANT: When updating tasks (especially when booking time or adding progress), ensure the status makes sense for the work being done - tasks in 'backlog' or 'closed' states usually shouldn't have active work.",
|
|
307
308
|
"Suggest appropriate status transitions and always provide the clickable task URL in responses."
|
|
308
309
|
];
|
|
@@ -313,7 +314,8 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
313
314
|
})(), {
|
|
314
315
|
task_id: zod_1.z.string().min(6).max(16).describe("The 6-16 character task ID to update"),
|
|
315
316
|
name: taskNameSchema.optional(),
|
|
316
|
-
|
|
317
|
+
description: zod_1.z.string().optional().describe("Optional markdown that REPLACES the entire task description. Read the current description first (getTaskById) and repeat everything worth keeping - nothing is merged. Cannot be combined with append_description."),
|
|
318
|
+
append_description: zod_1.z.string().optional().describe("Optional markdown content to APPEND below the existing task description as a dated `**Edit (YYYY-MM-DD):**` section (existing content is preserved). Cannot be combined with description."),
|
|
317
319
|
status: zod_1.z.string().optional().describe("Optional new status name - use getListInfo to see valid options"),
|
|
318
320
|
priority: taskPrioritySchema,
|
|
319
321
|
due_date: taskDueDateSchema,
|
|
@@ -330,8 +332,16 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
330
332
|
destructiveHint: true,
|
|
331
333
|
idempotentHint: false,
|
|
332
334
|
openWorldHint: true
|
|
333
|
-
}, async ({ task_id, name, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees, blocking, waiting_on, linked_tasks }) => {
|
|
335
|
+
}, async ({ task_id, name, description, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees, blocking, waiting_on, linked_tasks }) => {
|
|
334
336
|
try {
|
|
337
|
+
if (description !== undefined && append_description !== undefined) {
|
|
338
|
+
return {
|
|
339
|
+
content: [{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: "Error: `description` (replace) and `append_description` (append) are mutually exclusive - pass only one of them. The task was NOT updated."
|
|
342
|
+
}],
|
|
343
|
+
};
|
|
344
|
+
}
|
|
335
345
|
const userData = await (0, utils_1.getCurrentUser)();
|
|
336
346
|
// Get task details including current markdown description
|
|
337
347
|
const taskResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}?include_markdown_description=true`, {
|
|
@@ -344,15 +354,19 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
344
354
|
// Resolve and upload description images FIRST - an image problem must
|
|
345
355
|
// abort before dependencies, tags or the task itself are touched, so the
|
|
346
356
|
// caller can fix the markdown and retry the whole call cleanly.
|
|
347
|
-
|
|
357
|
+
// `description` replaces, `append_description` appends; both go through
|
|
358
|
+
// the same image pipeline. An empty `description` is a valid request to
|
|
359
|
+
// clear the description, so check for undefined rather than truthiness.
|
|
360
|
+
const descriptionInput = description !== undefined ? description : append_description;
|
|
361
|
+
let preparedDescription;
|
|
348
362
|
let uploadedImages = [];
|
|
349
|
-
if (
|
|
363
|
+
if (descriptionInput !== undefined) {
|
|
350
364
|
const abortNotice = "the task was NOT updated";
|
|
351
|
-
const prepared = await resolveImagesOrAbort(
|
|
365
|
+
const prepared = await resolveImagesOrAbort(descriptionInput, abortNotice);
|
|
352
366
|
uploadedImages = await uploadImagesOrAbort(task_id, prepared.images, abortNotice);
|
|
353
367
|
// Descriptions render plain markdown, so no image fragments are involved
|
|
354
368
|
// here - the local paths are simply swapped for the CDN URLs.
|
|
355
|
-
|
|
369
|
+
preparedDescription = (0, clickup_text_1.rewriteMarkdownImageUrls)(prepared.markdown, (0, attachments_1.toAttachmentMap)(uploadedImages));
|
|
356
370
|
}
|
|
357
371
|
// Handle dependencies separately since they need individual API calls
|
|
358
372
|
let dependencyUpdateResults = [];
|
|
@@ -402,13 +416,17 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
402
416
|
}
|
|
403
417
|
}
|
|
404
418
|
}
|
|
405
|
-
//
|
|
419
|
+
// Build the description to write: a full replacement, or the existing
|
|
420
|
+
// text plus a dated append section.
|
|
406
421
|
let finalDescription;
|
|
407
|
-
if (
|
|
422
|
+
if (description !== undefined) {
|
|
423
|
+
finalDescription = preparedDescription;
|
|
424
|
+
}
|
|
425
|
+
else if (preparedDescription !== undefined) {
|
|
408
426
|
const currentDescription = taskData.markdown_description || "";
|
|
409
427
|
const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
|
|
410
428
|
const separator = currentDescription.trim() ? "\n\n---\n" : "";
|
|
411
|
-
finalDescription = currentDescription + separator + `**Edit (${timestamp}):** ${
|
|
429
|
+
finalDescription = currentDescription + separator + `**Edit (${timestamp}):** ${preparedDescription}`;
|
|
412
430
|
}
|
|
413
431
|
// Build update body without tags (they're handled separately)
|
|
414
432
|
const updateBody = buildTaskRequestBody({
|
|
@@ -460,8 +478,15 @@ function registerTaskToolsWrite(server, userData) {
|
|
|
460
478
|
}
|
|
461
479
|
}
|
|
462
480
|
const responseLines = formatTaskResponse(updatedTask, 'updated', {
|
|
463
|
-
name, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees, blocking, waiting_on, linked_tasks
|
|
481
|
+
name, description, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees, blocking, waiting_on, linked_tasks
|
|
464
482
|
}, userData);
|
|
483
|
+
if (description !== undefined) {
|
|
484
|
+
const previousLength = (taskData.markdown_description || "").length;
|
|
485
|
+
responseLines.push(`description: replaced (previous ${previousLength} chars -> ${finalDescription?.length ?? 0} chars; the old version stays restorable via ClickUp's description history)`);
|
|
486
|
+
}
|
|
487
|
+
else if (append_description !== undefined) {
|
|
488
|
+
responseLines.push('description: appended as dated edit section');
|
|
489
|
+
}
|
|
465
490
|
// Add dependency update results if any
|
|
466
491
|
if (dependencyUpdateResults.length > 0) {
|
|
467
492
|
responseLines.push('dependency_warnings: ' + dependencyUpdateResults.join('; '));
|
package/package.json
CHANGED