@hauptsache.net/clickup-mcp 1.5.1 → 1.6.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 CHANGED
@@ -155,21 +155,22 @@ The ClickUp MCP supports three operational modes to balance functionality, secur
155
155
  - **📖 `read`**: Full read-only access for project exploration and workflow understanding
156
156
  - **✏️ `write`** (Default): Complete functionality for task management and productivity workflows
157
157
 
158
- | Tool | read-minimal | read | write | Description |
159
- |-------------------|:------------:|:----:|:-----:|-----------------------------------------------------------------------------------------|
160
- | `getTaskById` | ✅ | ✅ | ✅ | Get complete task details including comments, images, and metadata |
161
- | `addComment` | ❌ | ❌ | ✅ | Add comments to tasks for collaboration |
162
- | `updateTask` | ❌ | ❌ | ✅ | Update tasks (status, priority, assignees, etc.) with **SAFE APPEND-ONLY** descriptions |
163
- | `createTask` | ❌ | ❌ | ✅ | Create new tasks with full markdown support |
164
- | `searchTasks` | ✅ | ✅ | ✅ | Find tasks by content, keywords, assignees, or project context |
165
- | `searchSpaces` | ❌ | ✅ | ✅ | Browse workspace structure, project organization, and documents |
166
- | `getListInfo` | ❌ | ✅ | ✅ | Get list details and available statuses for task creation |
167
- | `updateListInfo` | ❌ | ❌ | ✅ | **SAFE APPEND-ONLY** updates to list descriptions (preserves existing content) |
168
- | `getTimeEntries` | ❌ | ✅ | ✅ | View time entries and analyze time spent across projects |
169
- | `createTimeEntry` | ❌ | ❌ | ✅ | Log time entries for task tracking |
170
- | `readDocument` | ❌ | ✅ | ✅ | Get document details, page structure, and content with navigation |
171
- | `searchDocuments` | ❌ | ✅ | ✅ | Search documents by name and space with fuzzy matching and space filtering |
172
- | `writeDocument` | ❌ | ❌ | ✅ | Universal document and page operations with smart document creation |
158
+ | Tool | read-minimal | read | write | Description |
159
+ |------------------------|:------------:|:----:|:-----:|-----------------------------------------------------------------------------------------|
160
+ | `getTaskById` | ✅ | ✅ | ✅ | Get complete task details including comments, images, and metadata |
161
+ | `addComment` | ❌ | ❌ | ✅ | Add comments to tasks for collaboration |
162
+ | `updateTask` | ❌ | ❌ | ✅ | Update tasks (status, priority, assignees, etc.) with **SAFE APPEND-ONLY** descriptions |
163
+ | `createTask` | ❌ | ❌ | ✅ | Create new tasks with full markdown support |
164
+ | `searchTasks` | ✅ | ✅ | ✅ | Find tasks by content, keywords, assignees, or project context |
165
+ | `searchSpaces` | ❌ | ✅ | ✅ | Browse workspace structure, project organization, and documents |
166
+ | `getListInfo` | ❌ | ✅ | ✅ | Get list details and available statuses for task creation |
167
+ | `updateListInfo` | ❌ | ❌ | ✅ | **SAFE APPEND-ONLY** updates to list descriptions (preserves existing content) |
168
+ | `getTimeEntries` | ❌ | ✅ | ✅ | View time entries and analyze time spent across projects |
169
+ | `createTimeEntry` | ❌ | ❌ | ✅ | Log time entries for task tracking |
170
+ | `readDocument` | ❌ | ✅ | ✅ | Get document details, page structure, and content with navigation |
171
+ | `searchDocuments` | ❌ | ✅ | ✅ | Search documents by name and space with fuzzy matching and space filtering |
172
+ | `updateDocumentPage` | ❌ | ❌ | ✅ | Update existing page content or name with replace/append modes |
173
+ | `createDocumentOrPage` | ❌ | ❌ | ✅ | Create new documents with first page, or add pages/sub-pages to existing documents |
173
174
 
174
175
  ### Setting the Mode
175
176
 
@@ -37,12 +37,47 @@ export interface ClickUpAttachment {
37
37
  * @param textItems Array of text items from ClickUp API
38
38
  * @returns Promise resolving to an array of content blocks (text and images)
39
39
  */
40
- export declare function processClickUpText(textItems: ClickUpTextItem[]): Promise<(CallToolResult["content"][number] | ImageMetadataBlock)[]>;
40
+ export declare function convertClickUpTextItemsToToolCallResult(textItems: ClickUpTextItem[]): Promise<(CallToolResult["content"][number] | ImageMetadataBlock)[]>;
41
41
  /**
42
42
  * Splits markdown text at image references and converts them to image blocks
43
43
  * @param markdownText The markdown text to process
44
44
  * @param attachments Array of attachments from the Clickup API
45
45
  * @returns Array of content blocks (text and images)
46
46
  */
47
- export declare function processClickUpMarkdown(markdownText: string, attachments: ClickUpAttachment[] | null | undefined): (CallToolResult["content"][number] | ImageMetadataBlock)[];
47
+ export declare function convertMarkdownToToolCallResult(markdownText: string, attachments: ClickUpAttachment[] | null | undefined): (CallToolResult["content"][number] | ImageMetadataBlock)[];
48
+ /**
49
+ * Represents a ClickUp comment block with formatting
50
+ */
51
+ export interface ClickUpCommentBlock {
52
+ text?: string;
53
+ type?: string;
54
+ attributes?: {
55
+ bold?: boolean;
56
+ italic?: boolean;
57
+ code?: boolean;
58
+ link?: string;
59
+ 'code-block'?: {
60
+ 'code-block': string;
61
+ };
62
+ header?: number;
63
+ blockquote?: {};
64
+ 'blockquote-size'?: 'large';
65
+ list?: {
66
+ list: 'bullet' | 'ordered' | 'unchecked' | 'checked';
67
+ };
68
+ indent?: number;
69
+ 'block-id'?: string;
70
+ };
71
+ list?: {
72
+ list: 'bullet' | 'ordered' | 'unchecked' | 'checked';
73
+ };
74
+ }
75
+ /**
76
+ * Convert markdown text to ClickUp comment blocks format using remark
77
+ * Supports: headers, bold, italic, code, links, lists, blockquotes, code blocks
78
+ *
79
+ * @param markdown The markdown text to convert
80
+ * @returns Array of ClickUp comment blocks
81
+ */
82
+ export declare function convertMarkdownToClickUpBlocks(markdown: string): ClickUpCommentBlock[];
48
83
  //# sourceMappingURL=clickup-text.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"clickup-text.d.ts","sourceRoot":"","sources":["../src/clickup-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGpD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AA4BD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,eAAe,EAAE,GAC3B,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAkGrE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAClD,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CA0I5D"}
1
+ {"version":3,"file":"clickup-text.d.ts","sourceRoot":"","sources":["../src/clickup-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAOpD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AA4BD;;;;;;GAMG;AACH,wBAAsB,uCAAuC,CAC3D,SAAS,EAAE,eAAe,EAAE,GAC3B,OAAO,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAsNrE;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI,GAAG,SAAS,GAClD,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,CA0I5D;AA8BD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE;YACb,YAAY,EAAE,MAAM,CAAC;SACtB,CAAC;QACF,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,EAAE,CAAC;QAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,IAAI,CAAC,EAAE;YACL,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;SACtD,CAAC;QACF,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;KACtD,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAoBtF"}
@@ -1,8 +1,15 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.processClickUpText = processClickUpText;
4
- exports.processClickUpMarkdown = processClickUpMarkdown;
6
+ exports.convertClickUpTextItemsToToolCallResult = convertClickUpTextItemsToToolCallResult;
7
+ exports.convertMarkdownToToolCallResult = convertMarkdownToToolCallResult;
8
+ exports.convertMarkdownToClickUpBlocks = convertMarkdownToClickUpBlocks;
5
9
  const data_uri_1 = require("./shared/data-uri");
10
+ const unified_1 = require("unified");
11
+ const remark_parse_1 = __importDefault(require("remark-parse"));
12
+ const remark_gfm_1 = __importDefault(require("remark-gfm"));
6
13
  /**
7
14
  * Extract thumbnail URLs from data-attachment attribute JSON
8
15
  * ClickUp API sometimes has broken thumbnail URLs, but data-attachment contains working ones
@@ -31,9 +38,14 @@ function extractThumbnailsFromDataAttachment(attributes) {
31
38
  * @param textItems Array of text items from ClickUp API
32
39
  * @returns Promise resolving to an array of content blocks (text and images)
33
40
  */
34
- async function processClickUpText(textItems) {
41
+ async function convertClickUpTextItemsToToolCallResult(textItems) {
35
42
  const contentBlocks = [];
36
43
  let currentTextBlock = "";
44
+ let currentLine = ""; // Track current line separately for block formatting
45
+ // Track current formatting state to avoid unnecessary close/reopen
46
+ let activeBold = false;
47
+ let activeItalic = false;
48
+ let activeCode = false;
37
49
  for (let i = 0; i < textItems.length; i++) {
38
50
  const item = textItems[i];
39
51
  // Handle image items
@@ -100,7 +112,108 @@ async function processClickUpText(textItems) {
100
112
  }
101
113
  // Handle text items
102
114
  else if (typeof item.text === "string") {
103
- currentTextBlock += item.text;
115
+ // Check if this is a newline with block formatting (header, blockquote, list)
116
+ if (item.text === '\n' && item.attributes) {
117
+ // Header formatting
118
+ if (item.attributes.header) {
119
+ const level = item.attributes.header;
120
+ currentLine = '#'.repeat(level) + ' ' + currentLine;
121
+ }
122
+ // Blockquote formatting
123
+ else if (item.attributes.blockquote) {
124
+ currentLine = '> ' + currentLine;
125
+ }
126
+ // List formatting
127
+ else if (item.attributes.list) {
128
+ const listType = item.attributes.list.list;
129
+ const indent = item.attributes.indent || 0;
130
+ // Add indentation (2 spaces per level) for nested lists
131
+ const indentStr = ' '.repeat(indent);
132
+ switch (listType) {
133
+ case 'bullet':
134
+ currentLine = indentStr + '- ' + currentLine;
135
+ break;
136
+ case 'ordered':
137
+ currentLine = indentStr + '1. ' + currentLine;
138
+ break;
139
+ case 'checked':
140
+ currentLine = indentStr + '- [x] ' + currentLine;
141
+ break;
142
+ case 'unchecked':
143
+ currentLine = indentStr + '- [ ] ' + currentLine;
144
+ break;
145
+ }
146
+ }
147
+ // Code block formatting
148
+ else if (item.attributes['code-block']) {
149
+ // Wrap the current line in code block markers
150
+ currentLine = '```\n' + currentLine + '\n```';
151
+ }
152
+ // Add formatted line to text block
153
+ currentTextBlock += currentLine;
154
+ // Add newline unless it's code block (already has newlines)
155
+ if (!item.attributes['code-block']) {
156
+ currentTextBlock += '\n';
157
+ }
158
+ currentLine = ""; // Reset for next line
159
+ continue;
160
+ }
161
+ // Regular text with inline formatting
162
+ let formattedText = item.text;
163
+ // Determine current and next formatting state
164
+ const hasBold = item.attributes?.bold === true;
165
+ const hasItalic = item.attributes?.italic === true;
166
+ const hasLink = item.attributes?.link;
167
+ // Look ahead to next non-newline block
168
+ let nextHasBold = false;
169
+ let nextHasItalic = false;
170
+ for (let j = i + 1; j < textItems.length; j++) {
171
+ const nextItem = textItems[j];
172
+ if (nextItem.text !== '\n' || !nextItem.attributes) {
173
+ nextHasBold = nextItem.attributes?.bold === true;
174
+ nextHasItalic = nextItem.attributes?.italic === true;
175
+ break;
176
+ }
177
+ }
178
+ // Build prefix (open new formatting)
179
+ let prefix = "";
180
+ if (hasBold && !activeBold)
181
+ prefix += "**";
182
+ if (hasItalic && !activeItalic)
183
+ prefix += "*";
184
+ // Build suffix (close formatting that won't continue)
185
+ let suffix = "";
186
+ if (hasItalic && !nextHasItalic)
187
+ suffix += "*";
188
+ if (hasBold && !nextHasBold)
189
+ suffix += "**";
190
+ // Close formatting that's active but not in this block
191
+ let closingPrefix = "";
192
+ if (activeBold && !hasBold)
193
+ closingPrefix += "**";
194
+ if (activeItalic && !hasItalic)
195
+ closingPrefix += "*";
196
+ formattedText = closingPrefix + prefix + formattedText + suffix;
197
+ // Update state
198
+ activeBold = hasBold && nextHasBold;
199
+ activeItalic = hasItalic && nextHasItalic;
200
+ // Link formatting (wraps everything)
201
+ if (hasLink) {
202
+ formattedText = `[${formattedText}](${hasLink})`;
203
+ }
204
+ // Code formatting
205
+ if (item.attributes?.code) {
206
+ formattedText = `\`${formattedText}\``;
207
+ }
208
+ // Add to current line (not text block yet)
209
+ if (item.text === '\n') {
210
+ // Plain newline without formatting
211
+ currentTextBlock += currentLine + '\n';
212
+ currentLine = "";
213
+ }
214
+ else {
215
+ currentLine += formattedText;
216
+ }
104
217
  }
105
218
  // Handle other types of items like bookmarks or whatever clickup can think of
106
219
  else {
@@ -108,6 +221,9 @@ async function processClickUpText(textItems) {
108
221
  }
109
222
  }
110
223
  // Add any remaining text
224
+ if (currentLine) {
225
+ currentTextBlock += currentLine;
226
+ }
111
227
  if (currentTextBlock.trim()) {
112
228
  contentBlocks.push({
113
229
  type: "text",
@@ -122,7 +238,7 @@ async function processClickUpText(textItems) {
122
238
  * @param attachments Array of attachments from the Clickup API
123
239
  * @returns Array of content blocks (text and images)
124
240
  */
125
- function processClickUpMarkdown(markdownText, attachments) {
241
+ function convertMarkdownToToolCallResult(markdownText, attachments) {
126
242
  const contentBlocks = [];
127
243
  let currentTextBlock = "";
128
244
  // Create a map of attachment URLs to their full info for easy lookup
@@ -264,3 +380,184 @@ function extractFileTypeFromUrl(url) {
264
380
  return null;
265
381
  return filename.substring(lastDot + 1);
266
382
  }
383
+ /**
384
+ * Convert markdown text to ClickUp comment blocks format using remark
385
+ * Supports: headers, bold, italic, code, links, lists, blockquotes, code blocks
386
+ *
387
+ * @param markdown The markdown text to convert
388
+ * @returns Array of ClickUp comment blocks
389
+ */
390
+ function convertMarkdownToClickUpBlocks(markdown) {
391
+ const blocks = [];
392
+ try {
393
+ // Parse the entire markdown document using remark with GFM support (for task lists)
394
+ const tree = (0, unified_1.unified)()
395
+ .use(remark_parse_1.default)
396
+ .use(remark_gfm_1.default)
397
+ .parse(markdown);
398
+ // Walk the tree recursively
399
+ walkMdastNodes(tree.children, {}, blocks);
400
+ }
401
+ catch (error) {
402
+ console.error('Failed to parse markdown:', error);
403
+ // Fallback to plain text
404
+ return [{ text: markdown, attributes: {} }];
405
+ }
406
+ return blocks;
407
+ }
408
+ /**
409
+ * Recursively walk mdast nodes and convert to ClickUp blocks
410
+ * @param nodes Array of mdast nodes to process
411
+ * @param inheritedAttrs Formatting attributes inherited from parent nodes
412
+ * @param blocks Output array to append ClickUp blocks to
413
+ * @param depth Nesting depth for lists (0 = top level, 1 = first nest, etc.)
414
+ */
415
+ function walkMdastNodes(nodes, inheritedAttrs, blocks, depth = 0) {
416
+ for (let i = 0; i < nodes.length; i++) {
417
+ const node = nodes[i];
418
+ const currentAttrs = { ...inheritedAttrs };
419
+ switch (node.type) {
420
+ case 'heading':
421
+ // Process heading content with inline formatting
422
+ walkPhrasingContent(node.children, currentAttrs, blocks);
423
+ // Add newline with header attribute
424
+ blocks.push({ text: '\n', attributes: { header: node.depth } });
425
+ break;
426
+ case 'paragraph':
427
+ // Process paragraph content with inline formatting
428
+ walkPhrasingContent(node.children, currentAttrs, blocks);
429
+ // Add newline unless it's the last node
430
+ if (i < nodes.length - 1) {
431
+ blocks.push({ text: '\n', attributes: {} });
432
+ }
433
+ break;
434
+ case 'blockquote':
435
+ // ClickUp limitation: Blockquotes only support paragraph content, not headers or lists
436
+ // For complex blockquote content (headers, lists), only paragraph text is preserved
437
+ const blockquoteChildren = node.children;
438
+ for (const child of blockquoteChildren) {
439
+ if (child.type === 'paragraph') {
440
+ walkPhrasingContent(child.children, currentAttrs, blocks);
441
+ blocks.push({ text: '\n', attributes: { blockquote: {} } });
442
+ }
443
+ // Note: Other child types (heading, list) are not supported by ClickUp blockquotes
444
+ // and will be silently skipped, preserving only inline paragraph content
445
+ }
446
+ break;
447
+ case 'list':
448
+ const listNode = node;
449
+ const listType = listNode.ordered ? 'ordered' : 'bullet';
450
+ for (const item of listNode.children) {
451
+ const listItem = item;
452
+ // Check if it's a checkbox item
453
+ const isChecked = listItem.checked === true;
454
+ const isUnchecked = listItem.checked === false;
455
+ const finalListType = isChecked ? 'checked' : isUnchecked ? 'unchecked' : listType;
456
+ // Process list item content
457
+ for (const itemChild of listItem.children) {
458
+ if (itemChild.type === 'paragraph') {
459
+ // Process paragraph content with inline formatting
460
+ walkPhrasingContent(itemChild.children, currentAttrs, blocks);
461
+ // Add newline with list formatting and optional indent
462
+ const listAttrs = {
463
+ list: { list: finalListType }
464
+ };
465
+ // Add indent for nested lists (depth 0 = no indent, depth 1+ = indented)
466
+ if (depth > 0) {
467
+ listAttrs.indent = depth;
468
+ }
469
+ blocks.push({ text: '\n', attributes: listAttrs });
470
+ }
471
+ else if (itemChild.type === 'list') {
472
+ // Nested list - recursively process with increased depth
473
+ walkMdastNodes([itemChild], currentAttrs, blocks, depth + 1);
474
+ }
475
+ }
476
+ }
477
+ break;
478
+ case 'code':
479
+ // Code block
480
+ const codeNode = node;
481
+ if (codeNode.value) {
482
+ blocks.push({ text: codeNode.value, attributes: {} });
483
+ blocks.push({
484
+ text: '\n',
485
+ attributes: { 'code-block': { 'code-block': codeNode.lang || 'plain' } }
486
+ });
487
+ }
488
+ break;
489
+ case 'thematicBreak':
490
+ // Horizontal rule - just add a line break
491
+ blocks.push({ text: '\n', attributes: {} });
492
+ break;
493
+ default:
494
+ // For any other block-level nodes, try to process children
495
+ if ('children' in node && Array.isArray(node.children)) {
496
+ walkMdastNodes(node.children, currentAttrs, blocks, depth);
497
+ }
498
+ break;
499
+ }
500
+ }
501
+ }
502
+ /**
503
+ * Recursively walk phrasing content (inline nodes) and build ClickUp blocks
504
+ * Accumulates formatting attributes from parent nodes
505
+ */
506
+ function walkPhrasingContent(nodes, inheritedAttrs, blocks) {
507
+ for (const node of nodes) {
508
+ const currentAttrs = { ...inheritedAttrs };
509
+ switch (node.type) {
510
+ case 'text':
511
+ // Plain text node
512
+ if (node.value) {
513
+ blocks.push({
514
+ text: node.value,
515
+ attributes: Object.keys(currentAttrs).length > 0 ? currentAttrs : {}
516
+ });
517
+ }
518
+ break;
519
+ case 'strong':
520
+ // Bold text - recurse with bold attribute
521
+ currentAttrs.bold = true;
522
+ walkPhrasingContent(node.children, currentAttrs, blocks);
523
+ break;
524
+ case 'emphasis':
525
+ // Italic text - recurse with italic attribute
526
+ currentAttrs.italic = true;
527
+ walkPhrasingContent(node.children, currentAttrs, blocks);
528
+ break;
529
+ case 'inlineCode':
530
+ // Inline code
531
+ if (node.value) {
532
+ currentAttrs.code = true;
533
+ blocks.push({
534
+ text: node.value,
535
+ attributes: currentAttrs
536
+ });
537
+ }
538
+ break;
539
+ case 'link':
540
+ // Link - recurse with link attribute
541
+ currentAttrs.link = node.url;
542
+ walkPhrasingContent(node.children, currentAttrs, blocks);
543
+ break;
544
+ case 'break':
545
+ // Line break - add as plain text
546
+ blocks.push({ text: '\n', attributes: {} });
547
+ break;
548
+ default:
549
+ // For any other node types, try to extract text if available
550
+ if ('value' in node && typeof node.value === 'string') {
551
+ blocks.push({
552
+ text: node.value,
553
+ attributes: Object.keys(currentAttrs).length > 0 ? currentAttrs : {}
554
+ });
555
+ }
556
+ else if ('children' in node && Array.isArray(node.children)) {
557
+ // Recurse into children for other container nodes
558
+ walkPhrasingContent(node.children, currentAttrs, blocks);
559
+ }
560
+ break;
561
+ }
562
+ }
563
+ }
@@ -93,7 +93,7 @@ async function loadTaskContent(taskId) {
93
93
  return await generateTaskMetadata(task, timeEntries, true);
94
94
  })(),
95
95
  // process markdown and download images
96
- (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments || []),
96
+ (0, clickup_text_1.convertMarkdownToToolCallResult)(task.markdown_description || "", task.attachments || []),
97
97
  ]);
98
98
  return [taskMetadata, ...content];
99
99
  }
@@ -114,7 +114,7 @@ async function loadTaskComments(id) {
114
114
  type: "text",
115
115
  text: `Comment by ${comment.user.username} on ${timestampToIso(comment.date)}:`,
116
116
  };
117
- const commentBodyBlocks = await (0, clickup_text_1.processClickUpText)(comment.comment);
117
+ const commentBodyBlocks = await (0, clickup_text_1.convertClickUpTextItemsToToolCallResult)(comment.comment);
118
118
  return {
119
119
  date: comment.date, // String timestamp from ClickUp for sorting
120
120
  contentBlocks: [headerBlock, ...commentBodyBlocks],
@@ -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,QAmZtE"}
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;AAcpE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QAsZtE"}
@@ -4,6 +4,7 @@ exports.registerTaskToolsWrite = registerTaskToolsWrite;
4
4
  const zod_1 = require("zod");
5
5
  const config_1 = require("../shared/config");
6
6
  const utils_1 = require("../shared/utils");
7
+ const clickup_text_1 = require("../clickup-text");
7
8
  // Shared schemas for task parameters
8
9
  const taskNameSchema = zod_1.z.string().min(1).describe("The name/title of the task");
9
10
  const taskPrioritySchema = zod_1.z.enum(["urgent", "high", "normal", "low"]).optional().describe("Optional priority level");
@@ -36,8 +37,10 @@ function registerTaskToolsWrite(server, userData) {
36
37
  idempotentHint: false,
37
38
  }, async ({ task_id, comment }) => {
38
39
  try {
40
+ // Convert markdown to ClickUp formatted blocks
41
+ const commentBlocks = (0, clickup_text_1.convertMarkdownToClickUpBlocks)(comment);
39
42
  const requestBody = {
40
- comment_text: comment,
43
+ comment: commentBlocks,
41
44
  notify_all: true
42
45
  };
43
46
  const response = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/comment`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hauptsache.net/clickup-mcp",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
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",
@@ -42,10 +42,14 @@
42
42
  "dependencies": {
43
43
  "@modelcontextprotocol/sdk": "^1.15.1",
44
44
  "fuse.js": "^7.1.0",
45
+ "remark-gfm": "^4.0.1",
46
+ "remark-parse": "^11.0.0",
47
+ "unified": "^11.0.5",
45
48
  "zod": "^3.24.2"
46
49
  },
47
50
  "devDependencies": {
48
51
  "@anthropic-ai/mcpb": "^1.1.1",
52
+ "@types/mdast": "^4.0.4",
49
53
  "@types/node": "^22.14.1",
50
54
  "dotenv": "^16.5.0",
51
55
  "nodemon": "^3.1.9",