@taazkareem/clickup-mcp-server 0.4.67 → 0.4.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # MCP Server
4
4
  A Model Context Protocol (MCP) server for integrating ClickUp tasks with AI applications. This server allows AI agents to interact with ClickUp tasks, spaces, lists, and folders through a standardized protocol.
5
5
 
6
- > 🚧 **Status Update:** -The code refactoring has been completed. -Enhanced due date functionality now supports relative time expressions like "2 hours from now" with proper time display in ClickUp. -Server has been listed on Smithery, Glama, and Pulse MCP
6
+ > 🚧 **Status Update:** -Improved task name matching and fixed workspace hierarchy display
7
7
 
8
8
  <a href="https://glama.ai/mcp/servers/iwjvs2zy63">
9
9
  <img width="380" height="200" src="https://glama.ai/mcp/servers/iwjvs2zy63/badge" alt="ClickUp Server MCP server" />
@@ -27,11 +27,11 @@ npx -y @taazkareem/clickup-mcp-server@latest \
27
27
  5. Use Natural Language to interact with your ClickUp Workspace!
28
28
 
29
29
 
30
- ## Smithery Installation (recommended)
30
+ ## Smithery Installation
31
31
 
32
32
  [![smithery badge](https://smithery.ai/badge/@TaazKareem/clickup-mcp-server)](https://smithery.ai/server/@TaazKareem/clickup-mcp-server)
33
33
 
34
- The server is hosted on Smithery. There, you can preview the available tools or copy the commands to run on various clients.
34
+ The server is also hosted on Smithery. There, you can preview the available tools or copy the commands to run on your specific client app.
35
35
 
36
36
  ## Features
37
37
 
@@ -83,7 +83,7 @@ The server is hosted on Smithery. There, you can preview the available tools or
83
83
  See [full documentation](docs/api-reference.md) for optional parameters and advanced usage.
84
84
 
85
85
  ## Available Prompts
86
- Not yet implemented (or needed. For now, you can send a follow up prompt after tool result.)
86
+ Not yet implemented (or needed) For now, you can send a follow up prompt after the tool result.
87
87
 
88
88
  | Prompt | Purpose | Features |
89
89
  |--------|---------|----------|
package/build/index.js CHANGED
@@ -38,19 +38,68 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
38
38
  import { configureServer, server } from "./server.js";
39
39
  import { clickUpServices } from "./services/shared.js";
40
40
  import { initializeWorkspaceTool } from "./tools/workspace.js";
41
+ import { info, error, warn } from "./logger.js";
42
+ import { exec } from 'child_process';
43
+ import { promisify } from 'util';
44
+ const execAsync = promisify(exec);
45
+ /**
46
+ * Check if another instance of the server is already running
47
+ * @returns Boolean indicating if this is the only instance
48
+ */
49
+ async function checkSingleInstance() {
50
+ try {
51
+ const { stdout } = await execAsync(`ps aux | grep "node.*clickup-mcp-server" | grep -v grep | wc -l`);
52
+ const instanceCount = parseInt(stdout.trim(), 10);
53
+ // If there's more than one instance (including this one), warn and exit
54
+ if (instanceCount > 1) {
55
+ error(`Multiple server instances detected (${instanceCount}). This may cause issues.`);
56
+ info("Use 'pkill -9 -f clickup-mcp-server' to kill all instances before starting a new one.");
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ catch (err) {
62
+ error("Failed to check for other running instances", err);
63
+ // Continue execution even if check fails
64
+ return true;
65
+ }
66
+ }
41
67
  /**
42
68
  * Application entry point that configures and starts the MCP server.
43
69
  */
44
70
  async function main() {
45
- // Initialize tools with services
46
- initializeWorkspaceTool(clickUpServices);
47
- // Configure the server with all handlers
48
- await configureServer();
49
- // Connect using stdio transport
50
- const transport = new StdioServerTransport();
51
- await server.connect(transport);
71
+ try {
72
+ info("Starting ClickUp MCP Server...");
73
+ // Check if we're the only instance
74
+ const isSingleInstance = await checkSingleInstance();
75
+ if (!isSingleInstance) {
76
+ warn("Continuing startup despite multiple instances detected");
77
+ }
78
+ // Log essential information about the environment
79
+ info("Server environment", {
80
+ pid: process.pid,
81
+ node: process.version,
82
+ os: process.platform,
83
+ arch: process.arch
84
+ });
85
+ // Initialize tools with services
86
+ info("Initializing workspace tools");
87
+ initializeWorkspaceTool(clickUpServices);
88
+ // Configure the server with all handlers
89
+ info("Configuring server request handlers");
90
+ await configureServer();
91
+ // Connect using stdio transport
92
+ info("Connecting to MCP stdio transport");
93
+ const transport = new StdioServerTransport();
94
+ await server.connect(transport);
95
+ info("Server startup complete - ready to handle requests");
96
+ }
97
+ catch (err) {
98
+ error("Error during server startup", err);
99
+ process.exit(1);
100
+ }
52
101
  }
53
- main().catch((error) => {
54
- console.error("Server error:", error);
102
+ main().catch((err) => {
103
+ error("Unhandled server error", err);
55
104
  process.exit(1);
56
105
  });
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Logger module for MCP Server
3
+ *
4
+ * This module provides logging functionality for the server,
5
+ * writing logs to both the console and a log file in the build folder.
6
+ */
7
+ import { createWriteStream } from 'fs';
8
+ import { join, dirname } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ // Get the directory name of the current module
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ // Current process ID for logging
13
+ const pid = process.pid;
14
+ // Create a write stream for logging - use a fixed filename in the build directory
15
+ const logFileName = 'server.log';
16
+ const logStream = createWriteStream(join(__dirname, logFileName), { flags: 'w' });
17
+ console.error(`Logging to ${join(__dirname, logFileName)}`);
18
+ // Log levels with numeric values for filtering
19
+ export var LogLevel;
20
+ (function (LogLevel) {
21
+ LogLevel[LogLevel["TRACE"] = 0] = "TRACE";
22
+ LogLevel[LogLevel["DEBUG"] = 1] = "DEBUG";
23
+ LogLevel[LogLevel["INFO"] = 2] = "INFO";
24
+ LogLevel[LogLevel["WARN"] = 3] = "WARN";
25
+ LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
26
+ })(LogLevel || (LogLevel = {}));
27
+ // Fixed log level - always use TRACE as the minimum level for complete logging
28
+ const configuredLevel = LogLevel.TRACE;
29
+ /**
30
+ * Check if a log level is enabled based on the configured level
31
+ * @param level The log level to check
32
+ * @returns True if the level should be logged
33
+ */
34
+ export function isLevelEnabled(level) {
35
+ return level >= configuredLevel;
36
+ }
37
+ /**
38
+ * Log function that writes to both console and file
39
+ * @param level Log level (trace, debug, info, warn, error)
40
+ * @param message Message to log
41
+ * @param data Optional data to include in log
42
+ */
43
+ export function log(level, message, data) {
44
+ const levelEnum = level === 'trace' ? LogLevel.TRACE
45
+ : level === 'debug' ? LogLevel.DEBUG
46
+ : level === 'info' ? LogLevel.INFO
47
+ : level === 'warn' ? LogLevel.WARN
48
+ : LogLevel.ERROR;
49
+ // Skip if level is below configured level
50
+ if (!isLevelEnabled(levelEnum)) {
51
+ return;
52
+ }
53
+ const timestamp = new Date().toISOString();
54
+ const logMessage = `[${timestamp}] [PID:${pid}] ${level.toUpperCase()}: ${message}${data ? '\n' + JSON.stringify(data, null, 2) : ''}`;
55
+ // When using stdio transport, log to stderr which is captured by host application
56
+ console.error(logMessage);
57
+ // Write to file
58
+ logStream.write(logMessage + '\n');
59
+ }
60
+ /**
61
+ * Shorthand for trace level logs
62
+ * @param message Message to log
63
+ * @param data Optional data to include in log
64
+ */
65
+ export function trace(message, data) {
66
+ log('trace', message, data);
67
+ }
68
+ /**
69
+ * Shorthand for debug level logs
70
+ * @param message Message to log
71
+ * @param data Optional data to include in log
72
+ */
73
+ export function debug(message, data) {
74
+ log('debug', message, data);
75
+ }
76
+ /**
77
+ * Shorthand for info level logs
78
+ * @param message Message to log
79
+ * @param data Optional data to include in log
80
+ */
81
+ export function info(message, data) {
82
+ log('info', message, data);
83
+ }
84
+ /**
85
+ * Shorthand for warn level logs
86
+ * @param message Message to log
87
+ * @param data Optional data to include in log
88
+ */
89
+ export function warn(message, data) {
90
+ log('warn', message, data);
91
+ }
92
+ /**
93
+ * Shorthand for error level logs
94
+ * @param message Message to log
95
+ * @param data Optional data to include in log
96
+ */
97
+ export function error(message, data) {
98
+ log('error', message, data);
99
+ }
100
+ /**
101
+ * Logger class for creating context-specific loggers
102
+ */
103
+ export class Logger {
104
+ /**
105
+ * Create a new logger with context
106
+ * @param context The context to prepend to log messages
107
+ */
108
+ constructor(context) {
109
+ this.context = context;
110
+ }
111
+ /**
112
+ * Check if a log level is enabled for this logger
113
+ * @param level The level to check
114
+ * @returns True if logging at this level is enabled
115
+ */
116
+ isLevelEnabled(level) {
117
+ return isLevelEnabled(level);
118
+ }
119
+ /**
120
+ * Log at trace level
121
+ * @param message Message to log
122
+ * @param data Optional data to include in log
123
+ */
124
+ trace(message, data) {
125
+ log('trace', `[${this.context}] ${message}`, data);
126
+ }
127
+ /**
128
+ * Log at debug level
129
+ * @param message Message to log
130
+ * @param data Optional data to include in log
131
+ */
132
+ debug(message, data) {
133
+ log('debug', `[${this.context}] ${message}`, data);
134
+ }
135
+ /**
136
+ * Log at info level
137
+ * @param message Message to log
138
+ * @param data Optional data to include in log
139
+ */
140
+ info(message, data) {
141
+ log('info', `[${this.context}] ${message}`, data);
142
+ }
143
+ /**
144
+ * Log at warn level
145
+ * @param message Message to log
146
+ * @param data Optional data to include in log
147
+ */
148
+ warn(message, data) {
149
+ log('warn', `[${this.context}] ${message}`, data);
150
+ }
151
+ /**
152
+ * Log at error level
153
+ * @param message Message to log
154
+ * @param data Optional data to include in log
155
+ */
156
+ error(message, data) {
157
+ log('error', `[${this.context}] ${message}`, data);
158
+ }
159
+ }
160
+ // Handle SIGTERM for clean shutdown
161
+ process.on('SIGTERM', () => {
162
+ log('info', 'Received SIGTERM signal, shutting down...');
163
+ logStream.end(() => {
164
+ process.exit(0);
165
+ });
166
+ });
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Server State Management Module
3
+ *
4
+ * This module provides shared state management for the MCP server,
5
+ * particularly for controlling shutdown behavior and tracking busy states.
6
+ */
7
+ // State variables
8
+ let serverBusyState = false; // Tracks if server is doing critical work
9
+ let gracePeriodActive = false; // Tracks if we're in post-initialization grace period
10
+ let gracePeriodTimer = null;
11
+ export const GRACE_PERIOD_MS = 10000; // 10 second grace period after startup
12
+ /**
13
+ * Logging helper that avoids circular dependency
14
+ */
15
+ function safeLog(level, message, data) {
16
+ // Use console as a fallback to avoid circular dependency with logger
17
+ const timestamp = new Date().toISOString();
18
+ if (level === 'error') {
19
+ console.error(`[${timestamp}] ${level.toUpperCase()}: ${message}`, data || '');
20
+ }
21
+ else if (level === 'debug' && process.env.DEBUG) {
22
+ console.debug(`[${timestamp}] ${level.toUpperCase()}: ${message}`, data || '');
23
+ }
24
+ else if (level !== 'debug') {
25
+ console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`, data || '');
26
+ }
27
+ }
28
+ /**
29
+ * Set the server as busy doing critical work that shouldn't be interrupted
30
+ * @param busy Whether the server is currently busy with critical operations
31
+ */
32
+ export function setServerBusy(busy) {
33
+ serverBusyState = busy;
34
+ safeLog('debug', `Server busy state set to: ${busy}`);
35
+ }
36
+ /**
37
+ * Start grace period after initialization to prevent immediate shutdown
38
+ */
39
+ export function startGracePeriod() {
40
+ gracePeriodActive = true;
41
+ safeLog('debug', `Starting ${GRACE_PERIOD_MS}ms grace period to prevent premature shutdown`);
42
+ if (gracePeriodTimer) {
43
+ clearTimeout(gracePeriodTimer);
44
+ }
45
+ gracePeriodTimer = setTimeout(() => {
46
+ gracePeriodActive = false;
47
+ safeLog('debug', 'Grace period ended, server will now respond to shutdown signals');
48
+ gracePeriodTimer = null;
49
+ }, GRACE_PERIOD_MS);
50
+ }
51
+ /**
52
+ * Cancel the grace period if needed
53
+ */
54
+ export function cancelGracePeriod() {
55
+ if (gracePeriodTimer) {
56
+ clearTimeout(gracePeriodTimer);
57
+ gracePeriodTimer = null;
58
+ }
59
+ gracePeriodActive = false;
60
+ safeLog('debug', 'Grace period canceled, server will now respond to shutdown signals');
61
+ }
62
+ /**
63
+ * Check if the server should ignore shutdown signals
64
+ * @returns true if shutdown signals should be ignored
65
+ */
66
+ export function shouldIgnoreShutdown() {
67
+ // Ignore shutdown if explicitly configured via environment variable
68
+ if (process.env.FORCE_KEEP_ALIVE === 'true') {
69
+ return true;
70
+ }
71
+ // Ignore shutdown during the grace period after startup
72
+ if (gracePeriodActive) {
73
+ return true;
74
+ }
75
+ // Ignore shutdown if the server is doing critical work
76
+ if (serverBusyState) {
77
+ return true;
78
+ }
79
+ // Otherwise, allow normal shutdown
80
+ return false;
81
+ }
82
+ /**
83
+ * Check if grace period is currently active
84
+ */
85
+ export function isGracePeriodActive() {
86
+ return gracePeriodActive;
87
+ }
88
+ /**
89
+ * Check if server is currently in busy state
90
+ */
91
+ export function isServerBusy() {
92
+ return serverBusyState;
93
+ }
package/build/server.js CHANGED
@@ -1,18 +1,15 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
3
- import { createClickUpServices } from "./services/clickup/index.js";
4
- import config from "./config.js";
5
3
  import { workspaceHierarchyTool, handleGetWorkspaceHierarchy } from "./tools/workspace.js";
6
- import { createTaskTool, handleCreateTask, updateTaskTool, handleUpdateTask, moveTaskTool, handleMoveTask, duplicateTaskTool, handleDuplicateTask, getTaskTool, handleGetTask, getTasksTool, handleGetTasks, deleteTaskTool, handleDeleteTask, createBulkTasksTool, handleCreateBulkTasks, updateBulkTasksTool, handleUpdateBulkTasks, moveBulkTasksTool, handleMoveBulkTasks, deleteBulkTasksTool, handleDeleteBulkTasks } from "./tools/task.js";
4
+ import { createTaskTool, handleCreateTask, updateTaskTool, handleUpdateTask, moveTaskTool, handleMoveTask, duplicateTaskTool, handleDuplicateTask, getTaskTool, getTasksTool, handleGetTasks, deleteTaskTool, handleDeleteTask, createBulkTasksTool, handleCreateBulkTasks, updateBulkTasksTool, handleUpdateBulkTasks, moveBulkTasksTool, handleMoveBulkTasks, deleteBulkTasksTool, handleDeleteBulkTasks, getTaskCommentsTool, handleGetTaskComments } from "./tools/task.js";
7
5
  import { createListTool, handleCreateList, createListInFolderTool, handleCreateListInFolder, getListTool, handleGetList, updateListTool, handleUpdateList, deleteListTool, handleDeleteList } from "./tools/list.js";
8
6
  import { createFolderTool, handleCreateFolder, getFolderTool, handleGetFolder, updateFolderTool, handleUpdateFolder, deleteFolderTool, handleDeleteFolder } from "./tools/folder.js";
9
- // Initialize ClickUp services
10
- const services = createClickUpServices({
11
- apiKey: config.clickupApiKey,
12
- teamId: config.clickupTeamId
13
- });
14
- // Extract the workspace service for use in this module
15
- const { workspace } = services;
7
+ import { Logger } from "./logger.js";
8
+ import { clickUpServices } from "./services/shared.js";
9
+ // Create a logger instance for server
10
+ const logger = new Logger('Server');
11
+ // Use existing services from shared module instead of creating new ones
12
+ const { workspace } = clickUpServices;
16
13
  /**
17
14
  * MCP Server for ClickUp integration
18
15
  */
@@ -29,7 +26,10 @@ export const server = new Server({
29
26
  * Configure the server routes and handlers
30
27
  */
31
28
  export function configureServer() {
29
+ logger.info("Registering server request handlers");
30
+ // Register ListTools handler
32
31
  server.setRequestHandler(ListToolsRequestSchema, async () => {
32
+ logger.debug("Received ListTools request");
33
33
  return {
34
34
  tools: [
35
35
  workspaceHierarchyTool,
@@ -44,6 +44,7 @@ export function configureServer() {
44
44
  updateBulkTasksTool,
45
45
  moveBulkTasksTool,
46
46
  deleteBulkTasksTool,
47
+ getTaskCommentsTool,
47
48
  createListTool,
48
49
  createListInFolderTool,
49
50
  getListTool,
@@ -56,60 +57,80 @@ export function configureServer() {
56
57
  ]
57
58
  };
58
59
  });
60
+ // Register CallTool handler with proper logging
61
+ logger.info("Registering tool handlers", {
62
+ toolCount: 22,
63
+ categories: ["workspace", "task", "list", "folder"]
64
+ });
59
65
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
60
66
  const { name, arguments: params } = req.params;
61
- // Handle tool calls by routing to the appropriate handler
62
- switch (name) {
63
- case "get_workspace_hierarchy":
64
- return handleGetWorkspaceHierarchy();
65
- case "create_task":
66
- return handleCreateTask(params);
67
- case "update_task":
68
- return handleUpdateTask(params);
69
- case "move_task":
70
- return handleMoveTask(params);
71
- case "duplicate_task":
72
- return handleDuplicateTask(params);
73
- case "get_task":
74
- return handleGetTask(params);
75
- case "get_tasks":
76
- return handleGetTasks(params);
77
- case "delete_task":
78
- return handleDeleteTask(params);
79
- case "create_bulk_tasks":
80
- return handleCreateBulkTasks(params);
81
- case "update_bulk_tasks":
82
- return handleUpdateBulkTasks(params);
83
- case "move_bulk_tasks":
84
- return handleMoveBulkTasks(params);
85
- case "delete_bulk_tasks":
86
- return handleDeleteBulkTasks(params);
87
- case "create_list":
88
- return handleCreateList(params);
89
- case "create_list_in_folder":
90
- return handleCreateListInFolder(params);
91
- case "get_list":
92
- return handleGetList(params);
93
- case "update_list":
94
- return handleUpdateList(params);
95
- case "delete_list":
96
- return handleDeleteList(params);
97
- case "create_folder":
98
- return handleCreateFolder(params);
99
- case "get_folder":
100
- return handleGetFolder(params);
101
- case "update_folder":
102
- return handleUpdateFolder(params);
103
- case "delete_folder":
104
- return handleDeleteFolder(params);
105
- default:
106
- throw new Error(`Unknown tool: ${name}`);
67
+ // Improved logging with more context
68
+ logger.info(`Received CallTool request for tool: ${name}`, {
69
+ params
70
+ });
71
+ try {
72
+ // Handle tool calls by routing to the appropriate handler
73
+ switch (name) {
74
+ case "get_workspace_hierarchy":
75
+ return handleGetWorkspaceHierarchy();
76
+ case "create_task":
77
+ return handleCreateTask(params);
78
+ case "update_task":
79
+ return handleUpdateTask(params);
80
+ case "move_task":
81
+ return handleMoveTask(params);
82
+ case "duplicate_task":
83
+ return handleDuplicateTask(params);
84
+ case "get_task":
85
+ return getTaskTool.handler(params);
86
+ case "get_tasks":
87
+ return handleGetTasks(params);
88
+ case "delete_task":
89
+ return handleDeleteTask(params);
90
+ case "create_bulk_tasks":
91
+ return handleCreateBulkTasks(params);
92
+ case "update_bulk_tasks":
93
+ return handleUpdateBulkTasks(params);
94
+ case "move_bulk_tasks":
95
+ return handleMoveBulkTasks(params);
96
+ case "delete_bulk_tasks":
97
+ return handleDeleteBulkTasks(params);
98
+ case "get_task_comments":
99
+ return handleGetTaskComments(params);
100
+ case "create_list":
101
+ return handleCreateList(params);
102
+ case "create_list_in_folder":
103
+ return handleCreateListInFolder(params);
104
+ case "get_list":
105
+ return handleGetList(params);
106
+ case "update_list":
107
+ return handleUpdateList(params);
108
+ case "delete_list":
109
+ return handleDeleteList(params);
110
+ case "create_folder":
111
+ return handleCreateFolder(params);
112
+ case "get_folder":
113
+ return handleGetFolder(params);
114
+ case "update_folder":
115
+ return handleUpdateFolder(params);
116
+ case "delete_folder":
117
+ return handleDeleteFolder(params);
118
+ default:
119
+ logger.error(`Unknown tool requested: ${name}`);
120
+ throw new Error(`Unknown tool: ${name}`);
121
+ }
122
+ }
123
+ catch (err) {
124
+ logger.error(`Error executing tool: ${name}`, err);
125
+ throw err;
107
126
  }
108
127
  });
109
128
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
129
+ logger.info("Received ListPrompts request");
110
130
  return { prompts: [] };
111
131
  });
112
132
  server.setRequestHandler(GetPromptRequestSchema, async () => {
133
+ logger.error("Received GetPrompt request, but prompts are not supported");
113
134
  throw new Error("Prompt not found");
114
135
  });
115
136
  return server;