UNPKG

@s.pawel940/clickup-mcp-server

Version:

ClickUp MCP Server (Read-Only Fork) - Integrate ClickUp tasks with AI through Model Context Protocol

196 lines (195 loc) 7.98 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 { getTaskTool, getTaskCommentsTool, getWorkspaceTasksTool, getTaskTimeEntriesTool, getCurrentTimeEntryTool, handleGetTaskComments, handleGetTask, handleGetWorkspaceTasks, handleGetTaskTimeEntries, handleGetCurrentTimeEntry } from "./tools/task/index.js"; import { getListTool, handleGetList, } from "./tools/list.js"; import { getFolderTool, handleGetFolder, } from "./tools/folder.js"; import { getSpaceTagsTool, handleGetSpaceTags, } from "./tools/tag.js"; import { getDocumentTool, listDocumentsTool, listDocumentPagesTool, getDocumentPagesTool, } 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; export const server = new Server({ name: "clickup-mcp-server", version: "0.7.2", }, { capabilities: { tools: {}, prompts: {}, resources: {}, }, }); const documentModule = () => { if (config.documentSupport === 'true') { return [ getDocumentTool, listDocumentsTool, listDocumentPagesTool, getDocumentPagesTool, ]; } else { return []; } }; /** * Configure the server routes and handlers */ export function configureServer() { logger.info("Registering server request handlers"); logger.info(`Server readOnlyMode is set to: ${config.readOnlyMode}`); // Register ListTools handler server.setRequestHandler(ListToolsRequestSchema, async () => { logger.debug("Received ListTools request"); // Define read-only tools const tools = [ workspaceHierarchyTool, getTaskTool, getTaskCommentsTool, getWorkspaceTasksTool, getTaskTimeEntriesTool, getCurrentTimeEntryTool, getListTool, getFolderTool, getSpaceTagsTool, getWorkspaceMembersTool, findMemberByNameTool, resolveAssigneesTool, ...(config.documentSupport === 'true' ? [ getDocumentTool, listDocumentsTool, listDocumentPagesTool, getDocumentPagesTool ] : []) ]; return { tools: tools.filter(tool => !config.disabledTools.includes(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: 12, // Updated count for read-only tools categories: ["workspace", "task", "time-tracking", "list", "folder", "tag", "document", "member"] }); 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 disabled or not a read-only tool const readOnlyToolNames = [ workspaceHierarchyTool.name, getTaskTool.name, getTaskCommentsTool.name, getWorkspaceTasksTool.name, getTaskTimeEntriesTool.name, getCurrentTimeEntryTool.name, getListTool.name, getFolderTool.name, getSpaceTagsTool.name, getWorkspaceMembersTool.name, findMemberByNameTool.name, resolveAssigneesTool.name, ...(config.documentSupport === 'true' ? [ getDocumentTool.name, listDocumentsTool.name, listDocumentPagesTool.name, getDocumentPagesTool.name ] : []) ]; if (config.disabledTools.includes(name) || !readOnlyToolNames.includes(name)) { logger.warn(`Tool execution blocked: Tool '${name}' is disabled or not allowed in read-only mode.`); throw { code: -32601, message: `Tool '${name}' is disabled or not allowed in read-only mode.` }; } try { // Handle tool calls by routing to the appropriate handler switch (name) { case "get_workspace_hierarchy": return handleGetWorkspaceHierarchy(); case "get_task": return handleGetTask(params); case "get_task_comments": return handleGetTaskComments(params); case "get_workspace_tasks": return handleGetWorkspaceTasks(params); case "get_task_time_entries": return handleGetTaskTimeEntries(params); case "get_current_time_entry": return handleGetCurrentTimeEntry(params); case "get_list": return handleGetList(params); case "get_folder": return handleGetFolder(params); case "get_space_tags": return handleGetSpaceTags(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 };