diff --git a/src/services/clickup/task/task-comments.ts b/src/services/clickup/task/task-comments.ts index 30c1db6..99bcf19 100644 --- a/src/services/clickup/task/task-comments.ts +++ b/src/services/clickup/task/task-comments.ts @@ -13,7 +13,7 @@ */ import { TaskServiceCore } from './task-core.js'; -import { ClickUpComment } from '../types.js'; +import { ClickUpComment, FormattedComment } from '../types.js'; /** * Comments functionality for the TaskService @@ -61,29 +61,38 @@ export class TaskServiceComments { * Create a comment on a task * * @param taskId ID of the task to comment on - * @param commentText Text content of the comment + * @param commentText Text content of the comment (used when formattedComment is not provided) * @param notifyAll Whether to notify all assignees * @param assignee Optional user ID to assign the comment to + * @param formattedComment Optional formatted comment structure (takes precedence over commentText) * @returns The created comment */ async createTaskComment( taskId: string, commentText: string, notifyAll: boolean = false, - assignee?: number | null + assignee?: number | null, + formattedComment?: FormattedComment ): Promise { - (this.core as any).logOperation('createTaskComment', { taskId, commentText, notifyAll, assignee }); + (this.core as any).logOperation('createTaskComment', { taskId, commentText, notifyAll, assignee, formattedComment }); try { const payload: { - comment_text: string; + comment_text?: string; + comment?: FormattedComment; notify_all: boolean; assignee?: number; } = { - comment_text: commentText, notify_all: notifyAll }; - + + // Use formatted comment if provided, otherwise fall back to plain text + if (formattedComment && formattedComment.length > 0) { + payload.comment = formattedComment; + } else { + payload.comment_text = commentText; + } + if (assignee) { payload.assignee = assignee; } diff --git a/src/services/clickup/types.ts b/src/services/clickup/types.ts index e4ada0f..12f2bdd 100644 --- a/src/services/clickup/types.ts +++ b/src/services/clickup/types.ts @@ -346,6 +346,46 @@ export interface BulkCreateTasksData { tasks: CreateTaskData[]; } +/** + * Comment formatting attributes (matches ClickUp API) + */ +export interface CommentAttributes { + bold?: boolean; + italic?: boolean; + underline?: boolean; + strikethrough?: boolean; + code?: boolean; + color?: string; + background_color?: string; + link?: string; // For hyperlinks - URL goes here +} + +/** + * Comment list item for formatted comments + */ +export interface CommentListItem { + text: string; + attributes?: CommentAttributes; + checked?: boolean; // For checklist items +} + +/** + * Comment block types for formatted comments (matches ClickUp API exactly) + */ +export type CommentBlock = + | { text: string; attributes?: CommentAttributes } // Text with optional formatting/links + | { type: 'tag'; user: { id: number } } // User mentions (@mentions) + | { type: 'emoji'; unicode: string } // Emojis + | { type: 'code_block'; language?: string; text: string } // Code blocks + | { type: 'bulleted_list'; items: CommentListItem[] } // Bulleted lists + | { type: 'numbered_list'; items: CommentListItem[] } // Numbered lists + | { type: 'checklist'; items: CommentListItem[] }; // Checklists + +/** + * Formatted comment structure for API requests + */ +export type FormattedComment = CommentBlock[]; + /** * Comment object as returned by the ClickUp API */ diff --git a/src/tools/task/handlers.ts b/src/tools/task/handlers.ts index a6c69ac..5a1d48c 100644 --- a/src/tools/task/handlers.ts +++ b/src/tools/task/handlers.ts @@ -702,9 +702,9 @@ export async function getTaskCommentsHandler(params) { * Handler for creating a task comment */ export async function createTaskCommentHandler(params) { - // Validate required parameters - if (!params.commentText) { - throw new Error('Comment text is required'); + // Validate required parameters - either commentText or formattedComment must be provided + if (!params.commentText && !params.formattedComment) { + throw new Error('Either commentText or formattedComment is required'); } try { @@ -713,13 +713,14 @@ export async function createTaskCommentHandler(params) { // Extract other parameters with defaults const { - commentText, + commentText = '', + formattedComment, notifyAll = false, assignee = null } = params; // Create the comment - return await taskService.createTaskComment(taskId, commentText, notifyAll, assignee); + return await taskService.createTaskComment(taskId, commentText, notifyAll, assignee, formattedComment); } catch (error) { // If this is a task lookup error, provide more helpful message if (error.message?.includes('not found') || error.message?.includes('identify task')) { diff --git a/src/tools/task/single-operations.ts b/src/tools/task/single-operations.ts index 8b82a06..a514908 100644 --- a/src/tools/task/single-operations.ts +++ b/src/tools/task/single-operations.ts @@ -443,7 +443,7 @@ export const getTaskCommentsTool = { */ export const createTaskCommentTool = { name: "create_task_comment", - description: `Creates task comment. Use taskId (preferred) or taskName + listName. Required: commentText. Optional: notifyAll to notify assignees, assignee to assign comment.`, + description: `Creates task comment with optional formatting. Use taskId (preferred) or taskName + listName. Required: commentText OR formattedComment. Optional: notifyAll to notify assignees, assignee to assign comment.`, inputSchema: { type: "object", properties: { @@ -461,7 +461,162 @@ export const createTaskCommentTool = { }, commentText: { type: "string", - description: "REQUIRED: Text content of the comment to create." + description: "Plain text content of the comment. Required if formattedComment is not provided." + }, + formattedComment: { + type: "array", + description: "Formatted comment with rich text support. Array of comment blocks supporting text styling, lists, links, mentions, etc. Takes precedence over commentText when provided.", + items: { + type: "object", + oneOf: [ + { + description: "Text block with optional formatting and links", + properties: { + text: { + type: "string", + description: "Text content" + }, + attributes: { + type: "object", + properties: { + bold: { type: "boolean" }, + italic: { type: "boolean" }, + underline: { type: "boolean" }, + strikethrough: { type: "boolean" }, + code: { type: "boolean" }, + color: { type: "string" }, + background_color: { type: "string" }, + link: { type: "string", description: "URL for hyperlinks" } + }, + description: "Text formatting attributes. Use 'link' for hyperlinks." + } + }, + required: ["text"] + }, + { + description: "User mention (@mention)", + properties: { + type: { type: "string", enum: ["tag"] }, + user: { + type: "object", + properties: { + id: { type: "number", description: "ClickUp user ID" } + }, + required: ["id"] + } + }, + required: ["type", "user"] + }, + { + description: "Emoji", + properties: { + type: { type: "string", enum: ["emoji"] }, + unicode: { type: "string", description: "Unicode hex value" } + }, + required: ["type", "unicode"] + }, + { + description: "Code block", + properties: { + type: { type: "string", enum: ["code_block"] }, + text: { type: "string", description: "Code content" }, + language: { type: "string", description: "Programming language (optional)" } + }, + required: ["type", "text"] + }, + { + description: "Bulleted list", + properties: { + type: { type: "string", enum: ["bulleted_list"] }, + items: { + type: "array", + items: { + type: "object", + properties: { + text: { type: "string" }, + attributes: { + type: "object", + properties: { + bold: { type: "boolean" }, + italic: { type: "boolean" }, + underline: { type: "boolean" }, + strikethrough: { type: "boolean" }, + code: { type: "boolean" }, + color: { type: "string" }, + background_color: { type: "string" }, + link: { type: "string" } + } + } + }, + required: ["text"] + } + } + }, + required: ["type", "items"] + }, + { + description: "Numbered list", + properties: { + type: { type: "string", enum: ["numbered_list"] }, + items: { + type: "array", + items: { + type: "object", + properties: { + text: { type: "string" }, + attributes: { + type: "object", + properties: { + bold: { type: "boolean" }, + italic: { type: "boolean" }, + underline: { type: "boolean" }, + strikethrough: { type: "boolean" }, + code: { type: "boolean" }, + color: { type: "string" }, + background_color: { type: "string" }, + link: { type: "string" } + } + } + }, + required: ["text"] + } + } + }, + required: ["type", "items"] + }, + { + description: "Checklist", + properties: { + type: { type: "string", enum: ["checklist"] }, + items: { + type: "array", + items: { + type: "object", + properties: { + text: { type: "string" }, + checked: { type: "boolean", description: "Whether item is checked" }, + attributes: { + type: "object", + properties: { + bold: { type: "boolean" }, + italic: { type: "boolean" }, + underline: { type: "boolean" }, + strikethrough: { type: "boolean" }, + code: { type: "boolean" }, + color: { type: "string" }, + background_color: { type: "string" }, + link: { type: "string" } + } + } + }, + required: ["text"] + } + } + }, + required: ["type", "items"] + } + ] + } }, notifyAll: { type: "boolean", @@ -472,7 +627,7 @@ export const createTaskCommentTool = { description: "Optional user ID to assign the comment to." } }, - required: ["commentText"] + required: [] } };