UNPKG

@taazkareem/clickup-mcp-server

Version:

ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol

283 lines (282 loc) 13 kB
/** * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com> * SPDX-License-Identifier: MIT * * MCP Server for ClickUp integration */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import config from "./config.js"; import { workspaceHierarchyTool, handleGetWorkspaceHierarchy } from "./tools/workspace.js"; import { createTaskTool, updateTaskTool, moveTaskTool, duplicateTaskTool, getTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool, createBulkTasksTool, updateBulkTasksTool, moveBulkTasksTool, deleteBulkTasksTool, attachTaskFileTool, getWorkspaceTasksTool, getTaskTimeEntriesTool, startTimeTrackingTool, stopTimeTrackingTool, addTimeEntryTool, deleteTimeEntryTool, getCurrentTimeEntryTool, handleCreateTask, handleUpdateTask, handleMoveTask, handleDuplicateTask, handleDeleteTask, handleGetTaskComments, handleCreateTaskComment, handleCreateBulkTasks, handleUpdateBulkTasks, handleMoveBulkTasks, handleDeleteBulkTasks, handleGetTask, handleAttachTaskFile, handleGetWorkspaceTasks, handleGetTaskTimeEntries, handleStartTimeTracking, handleStopTimeTracking, handleAddTimeEntry, handleDeleteTimeEntry, handleGetCurrentTimeEntry } from "./tools/task/index.js"; import { createListTool, handleCreateList, createListInFolderTool, handleCreateListInFolder, getListTool, handleGetList, updateListTool, handleUpdateList, deleteListTool, handleDeleteList } from "./tools/list.js"; import { createFolderTool, handleCreateFolder, getFolderTool, handleGetFolder, updateFolderTool, handleUpdateFolder, deleteFolderTool, handleDeleteFolder } from "./tools/folder.js"; import { getSpaceTagsTool, handleGetSpaceTags, addTagToTaskTool, handleAddTagToTask, removeTagFromTaskTool, handleRemoveTagFromTask } from "./tools/tag.js"; import { createDocumentTool, handleCreateDocument, getDocumentTool, handleGetDocument, listDocumentsTool, handleListDocuments, listDocumentPagesTool, handleListDocumentPages, getDocumentPagesTool, handleGetDocumentPages, createDocumentPageTool, handleCreateDocumentPage, updateDocumentPageTool, handleUpdateDocumentPage } from "./tools/documents.js"; import { getWorkspaceMembersTool, handleGetWorkspaceMembers, findMemberByNameTool, handleFindMemberByName, resolveAssigneesTool, handleResolveAssignees } from "./tools/member.js"; import { Logger } from "./logger.js"; import { clickUpServices } from "./services/shared.js"; // Create a logger instance for server const logger = new Logger('Server'); // Use existing services from shared module instead of creating new ones const { workspace } = clickUpServices; /** * Determines if a tool should be enabled based on ENABLED_TOOLS and DISABLED_TOOLS configuration. * * Logic: * 1. If ENABLED_TOOLS is specified, only tools in that list are enabled (ENABLED_TOOLS takes precedence) * 2. If ENABLED_TOOLS is not specified but DISABLED_TOOLS is, all tools except those in DISABLED_TOOLS are enabled * 3. If neither is specified, all tools are enabled * * @param toolName - The name of the tool to check * @returns true if the tool should be enabled, false otherwise */ const isToolEnabled = (toolName) => { // If ENABLED_TOOLS is specified, it takes precedence if (config.enabledTools.length > 0) { return config.enabledTools.includes(toolName); } // If only DISABLED_TOOLS is specified, enable all tools except those disabled if (config.disabledTools.length > 0) { return !config.disabledTools.includes(toolName); } // If neither is specified, enable all tools return true; }; export const server = new Server({ name: "clickup-mcp-server", version: "0.8.5", }, { capabilities: { tools: {}, prompts: {}, resources: {}, }, }); const documentModule = () => { if (config.documentSupport === 'true') { return [ createDocumentTool, getDocumentTool, listDocumentsTool, listDocumentPagesTool, getDocumentPagesTool, createDocumentPageTool, updateDocumentPageTool, ]; } else { return []; } }; /** * Configure the server routes and handlers */ export function configureServer() { logger.info("Registering server request handlers"); // Register ListTools handler server.setRequestHandler(ListToolsRequestSchema, async () => { logger.debug("Received ListTools request"); return { tools: [ workspaceHierarchyTool, createTaskTool, getTaskTool, updateTaskTool, moveTaskTool, duplicateTaskTool, deleteTaskTool, getTaskCommentsTool, createTaskCommentTool, attachTaskFileTool, createBulkTasksTool, updateBulkTasksTool, moveBulkTasksTool, deleteBulkTasksTool, getWorkspaceTasksTool, getTaskTimeEntriesTool, startTimeTrackingTool, stopTimeTrackingTool, addTimeEntryTool, deleteTimeEntryTool, getCurrentTimeEntryTool, createListTool, createListInFolderTool, getListTool, updateListTool, deleteListTool, createFolderTool, getFolderTool, updateFolderTool, deleteFolderTool, getSpaceTagsTool, addTagToTaskTool, removeTagFromTaskTool, getWorkspaceMembersTool, findMemberByNameTool, resolveAssigneesTool, ...documentModule() ].filter(tool => isToolEnabled(tool.name)) }; }); // Add handler for resources/list server.setRequestHandler(ListResourcesRequestSchema, async (req) => { logger.debug("Received ListResources request"); return { resources: [] }; }); // Register CallTool handler with proper logging logger.info("Registering tool handlers", { toolCount: 36, categories: ["workspace", "task", "time-tracking", "list", "folder", "tag", "member", "document"] }); server.setRequestHandler(CallToolRequestSchema, async (req) => { const { name, arguments: params } = req.params; // Improved logging with more context logger.info(`Received CallTool request for tool: ${name}`, { params }); // Check if the tool is enabled if (!isToolEnabled(name)) { const reason = config.enabledTools.length > 0 ? `Tool '${name}' is not in the enabled tools list.` : `Tool '${name}' is disabled.`; logger.warn(`Tool execution blocked: ${reason}`); throw { code: -32601, message: reason }; } try { // Handle tool calls by routing to the appropriate handler switch (name) { case "get_workspace_hierarchy": return handleGetWorkspaceHierarchy(); case "create_task": return handleCreateTask(params); case "update_task": return handleUpdateTask(params); case "move_task": return handleMoveTask(params); case "duplicate_task": return handleDuplicateTask(params); case "get_task": return handleGetTask(params); case "delete_task": return handleDeleteTask(params); case "get_task_comments": return handleGetTaskComments(params); case "create_task_comment": return handleCreateTaskComment(params); case "attach_task_file": return handleAttachTaskFile(params); case "create_bulk_tasks": return handleCreateBulkTasks(params); case "update_bulk_tasks": return handleUpdateBulkTasks(params); case "move_bulk_tasks": return handleMoveBulkTasks(params); case "delete_bulk_tasks": return handleDeleteBulkTasks(params); case "get_workspace_tasks": return handleGetWorkspaceTasks(params); case "create_list": return handleCreateList(params); case "create_list_in_folder": return handleCreateListInFolder(params); case "get_list": return handleGetList(params); case "update_list": return handleUpdateList(params); case "delete_list": return handleDeleteList(params); case "create_folder": return handleCreateFolder(params); case "get_folder": return handleGetFolder(params); case "update_folder": return handleUpdateFolder(params); case "delete_folder": return handleDeleteFolder(params); case "get_space_tags": return handleGetSpaceTags(params); case "add_tag_to_task": return handleAddTagToTask(params); case "remove_tag_from_task": return handleRemoveTagFromTask(params); case "get_task_time_entries": return handleGetTaskTimeEntries(params); case "start_time_tracking": return handleStartTimeTracking(params); case "stop_time_tracking": return handleStopTimeTracking(params); case "add_time_entry": return handleAddTimeEntry(params); case "delete_time_entry": return handleDeleteTimeEntry(params); case "get_current_time_entry": return handleGetCurrentTimeEntry(params); case "create_document": return handleCreateDocument(params); case "get_document": return handleGetDocument(params); case "list_documents": return handleListDocuments(params); case "list_document_pages": return handleListDocumentPages(params); case "get_document_pages": return handleGetDocumentPages(params); case "create_document_page": return handleCreateDocumentPage(params); case "update_document_page": return handleUpdateDocumentPage(params); case "get_workspace_members": return handleGetWorkspaceMembers(); case "find_member_by_name": return handleFindMemberByName(params); case "resolve_assignees": return handleResolveAssignees(params); default: logger.error(`Unknown tool requested: ${name}`); const error = new Error(`Unknown tool: ${name}`); error.name = "UnknownToolError"; throw error; } } catch (err) { logger.error(`Error executing tool: ${name}`, err); // Transform error to a more descriptive JSON-RPC error if (err.name === "UnknownToolError") { throw { code: -32601, message: `Method not found: ${name}` }; } else if (err.name === "ValidationError") { throw { code: -32602, message: `Invalid params for tool ${name}: ${err.message}` }; } else { // Generic server error throw { code: -32000, message: `Error executing tool ${name}: ${err.message}` }; } } }); server.setRequestHandler(ListPromptsRequestSchema, async () => { logger.info("Received ListPrompts request"); return { prompts: [] }; }); server.setRequestHandler(GetPromptRequestSchema, async () => { logger.error("Received GetPrompt request, but prompts are not supported"); throw new Error("Prompt not found"); }); return server; } /** * Export the clickup service for use in tool handlers */ export { workspace };