Skip to main content
Glama
comment-tools.ts15.3 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { createClickUpClient } from '../clickup-client/index.js'; import { CommentsEnhancedClient, // CreateTaskCommentParams, CreateChatViewCommentParams, CreateListCommentParams, UpdateCommentParams, CreateThreadedCommentParams } from '../clickup-client/comments-enhanced.js'; import { /* applyMarkdownStyling, */ createMarkdownPreview } from '../utils/markdown-styling.js'; import { processCommentBlocks } from '../utils/clickup-comment-formatter.js'; // Create clients const clickUpClient = createClickUpClient(); const commentsClient = new CommentsEnhancedClient(clickUpClient); /** * Format comment response with enhanced markdown styling */ function formatCommentResponse(result: any, title?: string): any { try { // Create a styled preview if we have markdown content if (result.comment_markdown) { const styledPreview = createMarkdownPreview( result.comment_markdown, title || 'Comment Preview', { useColors: true, useEmojis: true } ); // Add the styled preview to the response result.styled_preview = styledPreview; } // If we have multiple comments, style each one if (result.comments && Array.isArray(result.comments)) { result.comments = result.comments.map((comment: any, index: number) => { if (comment.comment_markdown) { comment.styled_preview = createMarkdownPreview( comment.comment_markdown, `Comment ${index + 1}`, { useColors: true, useEmojis: true } ); } return comment; }); } return result; } catch (error) { console.warn('Failed to apply markdown styling:', error); return result; } } export function setupCommentTools(server: McpServer): void { // Register raw API test tool for debugging server.tool( 'clickup_create_task_comment_raw_test', 'RAW API TEST: Create a comment bypassing ALL MCP processing to isolate duplication issue. Returns raw ClickUp API response.', { task_id: z.string().describe('The ID of the task to comment on'), comment_text: z.string().describe('The text content of the comment') }, async ({ task_id, comment_text }) => { try { const result = await commentsClient.createTaskCommentRaw(task_id, comment_text); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error in raw API test:', error); return { content: [{ type: 'text', text: `Error in raw API test: ${error.message}` }], isError: true }; } } ); // Register get_task_comments tool server.tool( 'clickup_get_task_comments', 'Get comments for a ClickUp task. Returns comment details including text, author, and timestamps with enhanced markdown styling.', { task_id: z.string().describe('The ID of the task to get comments for'), start: z.number().optional().describe('Pagination start (timestamp)'), start_id: z.string().optional().describe('Pagination start ID') }, async ({ task_id, ...params }) => { try { const result = await commentsClient.getTaskComments(task_id, params); const styledResult = formatCommentResponse(result, 'Task Comments'); return { content: [{ type: 'text', text: JSON.stringify(styledResult, null, 2) }] }; } catch (error: any) { console.error('Error getting task comments:', error); return { content: [{ type: 'text', text: `Error getting task comments: ${error.message}` }], isError: true }; } } ); // Register create_task_comment tool server.tool( 'clickup_create_task_comment', 'Create a new comment on a ClickUp task using structured array format. Supports optional assignee and notification settings.', { task_id: z.string().describe('The ID of the task to comment on'), comment: z.array(z.object({ text: z.string().describe('The text content of this block'), attributes: z.object({ bold: z.boolean().optional().describe('Whether text is bold'), italic: z.boolean().optional().describe('Whether text is italic'), underline: z.boolean().optional().describe('Whether text is underlined'), strikethrough: z.boolean().optional().describe('Whether text is strikethrough'), code: z.boolean().optional().describe('Whether text is code'), color: z.string().optional().describe('Text color'), background_color: z.string().optional().describe('Background color'), link: z.object({ url: z.string().describe('Link URL') }).optional().describe('Link attributes'), 'code-block': z.object({ 'code-block': z.string().describe('Programming language for syntax highlighting (e.g., "javascript", "python", "bash", "plain")') }).optional().describe('Code block attributes for multi-line code with syntax highlighting') }).optional().describe('Text formatting attributes') })).describe('Array of comment blocks with text and formatting'), assignee: z.number().optional().describe('The ID of the user to assign to the comment'), notify_all: z.boolean().optional().describe('Whether to notify all assignees') }, async ({ task_id, comment, ...commentParams }) => { try { // Process comment blocks to ensure proper code block separation const processedComment = processCommentBlocks(comment); // Create payload with processed structured comment array const payload = { notify_all: commentParams.notify_all || false, assignee: commentParams.assignee, comment: processedComment }; // DEBUG: Log exactly what we're sending to ClickUp API console.log('=== DEBUG: Sending to ClickUp API ==='); console.log('URL:', `/task/${task_id}/comment`); console.log('Original comment blocks:', JSON.stringify(comment, null, 2)); console.log('Processed comment blocks:', JSON.stringify(processedComment, null, 2)); console.log('Full payload:', JSON.stringify(payload, null, 2)); console.log('====================================='); const result = await clickUpClient.post(`/task/${task_id}/comment`, payload); // DEBUG: Log what ClickUp returns console.log('=== DEBUG: ClickUp API Response ==='); console.log('Raw Response:', JSON.stringify(result, null, 2)); console.log('==================================='); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error creating task comment:', error); return { content: [{ type: 'text', text: `Error creating task comment: ${error.message}` }], isError: true }; } } ); // Register get_chat_view_comments tool server.tool( 'clickup_get_chat_view_comments', 'Get comments for a ClickUp chat view. Returns comment details with pagination support.', { view_id: z.string().describe('The ID of the chat view to get comments for'), start: z.number().optional().describe('Pagination start (timestamp)'), start_id: z.string().optional().describe('Pagination start ID') }, async ({ view_id, ...params }) => { try { const result = await commentsClient.getChatViewComments(view_id, params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error getting chat view comments:', error); return { content: [{ type: 'text', text: `Error getting chat view comments: ${error.message}` }], isError: true }; } } ); // Register create_chat_view_comment tool server.tool( 'clickup_create_chat_view_comment', 'Create a new comment in a ClickUp chat view. Supports notification settings. Supports GitHub Flavored Markdown in comment text.', { view_id: z.string().describe('The ID of the chat view to comment on'), comment_text: z.string().describe('The text content of the comment (supports GitHub Flavored Markdown including headers, bold, italic, code blocks, links, lists, etc.)'), notify_all: z.boolean().optional().describe('Whether to notify all assignees') }, async ({ view_id, ...commentParams }) => { try { const result = await commentsClient.createChatViewComment(view_id, commentParams as CreateChatViewCommentParams); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error creating chat view comment:', error); return { content: [{ type: 'text', text: `Error creating chat view comment: ${error.message}` }], isError: true }; } } ); // Register get_list_comments tool server.tool( 'clickup_get_list_comments', 'Get comments for a ClickUp list. Returns comment details with pagination support.', { list_id: z.string().describe('The ID of the list to get comments for'), start: z.number().optional().describe('Pagination start (timestamp)'), start_id: z.string().optional().describe('Pagination start ID') }, async ({ list_id, ...params }) => { try { const result = await commentsClient.getListComments(list_id, params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error getting list comments:', error); return { content: [{ type: 'text', text: `Error getting list comments: ${error.message}` }], isError: true }; } } ); // Register create_list_comment tool server.tool( 'clickup_create_list_comment', 'Create a new comment on a ClickUp list. Supports optional assignee and notification settings. Supports GitHub Flavored Markdown in comment text.', { list_id: z.string().describe('The ID of the list to comment on'), comment_text: z.string().describe('The text content of the comment (supports GitHub Flavored Markdown including headers, bold, italic, code blocks, links, lists, etc.)'), assignee: z.number().optional().describe('The ID of the user to assign to the comment'), notify_all: z.boolean().optional().describe('Whether to notify all assignees') }, async ({ list_id, ...commentParams }) => { try { const result = await commentsClient.createListComment(list_id, commentParams as CreateListCommentParams); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error creating list comment:', error); return { content: [{ type: 'text', text: `Error creating list comment: ${error.message}` }], isError: true }; } } ); // Register update_comment tool server.tool( 'clickup_update_comment', 'Update an existing ClickUp comment\'s properties including text, assignee, and resolved status. Supports GitHub Flavored Markdown in comment text.', { comment_id: z.string().describe('The ID of the comment to update'), comment_text: z.string().describe('The new text content of the comment (supports GitHub Flavored Markdown including headers, bold, italic, code blocks, links, lists, etc.)'), assignee: z.number().optional().describe('The ID of the user to assign to the comment'), resolved: z.boolean().optional().describe('Whether the comment is resolved') }, async ({ comment_id, ...commentParams }) => { try { const result = await commentsClient.updateComment(comment_id, commentParams as UpdateCommentParams); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error updating comment:', error); return { content: [{ type: 'text', text: `Error updating comment: ${error.message}` }], isError: true }; } } ); // Register delete_comment tool server.tool( 'clickup_delete_comment', 'Delete a comment from ClickUp.', { comment_id: z.string().describe('The ID of the comment to delete') }, async ({ comment_id }) => { try { const result = await commentsClient.deleteComment(comment_id); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error deleting comment:', error); return { content: [{ type: 'text', text: `Error deleting comment: ${error.message}` }], isError: true }; } } ); // Register get_threaded_comments tool server.tool( 'clickup_get_threaded_comments', 'Get threaded comments (replies) for a parent comment. Returns comment details with pagination support.', { comment_id: z.string().describe('The ID of the parent comment'), start: z.number().optional().describe('Pagination start (timestamp)'), start_id: z.string().optional().describe('Pagination start ID') }, async ({ comment_id, ...params }) => { try { const result = await commentsClient.getThreadedComments(comment_id, params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error getting threaded comments:', error); return { content: [{ type: 'text', text: `Error getting threaded comments: ${error.message}` }], isError: true }; } } ); // Register create_threaded_comment tool server.tool( 'clickup_create_threaded_comment', 'Create a new threaded comment (reply) to a parent comment. Supports notification settings. Supports GitHub Flavored Markdown in comment text.', { comment_id: z.string().describe('The ID of the parent comment'), comment_text: z.string().describe('The text content of the comment (supports GitHub Flavored Markdown including headers, bold, italic, code blocks, links, lists, etc.)'), notify_all: z.boolean().optional().describe('Whether to notify all assignees') }, async ({ comment_id, ...commentParams }) => { try { const result = await commentsClient.createThreadedComment(comment_id, commentParams as CreateThreadedCommentParams); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { console.error('Error creating threaded comment:', error); return { content: [{ type: 'text', text: `Error creating threaded comment: ${error.message}` }], isError: true }; } } ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Chykalophia/ClickUp-MCP-Server---Enhanced'

If you have feedback or need assistance with the MCP directory API, please join our Discord server