Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions src/services/clickup/task/task-comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ClickUpComment> {
(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;
}
Expand Down
40 changes: 40 additions & 0 deletions src/services/clickup/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
11 changes: 6 additions & 5 deletions src/tools/task/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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')) {
Expand Down
161 changes: 158 additions & 3 deletions src/tools/task/single-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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",
Expand All @@ -472,7 +627,7 @@ export const createTaskCommentTool = {
description: "Optional user ID to assign the comment to."
}
},
required: ["commentText"]
required: []
}
};

Expand Down