@taazkareem/clickup-mcp-server 0.8.2 → 0.8.3

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  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.
8
8
 
9
- > 🚀 **Status Update:** v0.8.1 is now available with HTTP Streamable transport support, Legacy SSE Support, Member Management tools, and more. Next release will patch some bugs. See Unreleased section of [Changelog](changelog.md) for details.
9
+ > 🚀 **Status Update:** v0.8.3 is released with major enhancements! Enhanced workspace tasks filtering with Views API support for multi-list tasks (Issue #43), added ENABLED_TOOLS configuration option (Issue #50), and fixed automatic priority assignment in task creation. See [Release Notes](release-notes.md) for full details.
10
10
 
11
11
  ## Setup
12
12
 
@@ -55,9 +55,45 @@ Or use this npx command:
55
55
 
56
56
  **Obs: if you don't pass "DOCUMENT_SUPPORT": "true", the default is false and document support will not be active.**
57
57
 
58
- Additionally, you can use the `DISABLED_TOOLS` environment variable or `--env DISABLED_TOOLS` argument to disable specific tools. Provide a comma-separated list of tool names to disable (e.g., `create_task,delete_task`).
58
+ ### Tool Filtering
59
59
 
60
- Please disable tools you don't need if you are having issues with the number of tools or any context limitations
60
+ You can control which tools are available using two complementary environment variables:
61
+
62
+ #### ENABLED_TOOLS (Recommended)
63
+ Use `ENABLED_TOOLS` to specify exactly which tools should be available:
64
+ ```bash
65
+ # Environment variable
66
+ export ENABLED_TOOLS="create_task,get_task,update_task,get_workspace_hierarchy"
67
+
68
+ # Command line argument
69
+ --env ENABLED_TOOLS=create_task,get_task,update_task,get_workspace_hierarchy
70
+ ```
71
+
72
+ #### DISABLED_TOOLS (Legacy)
73
+ Use `DISABLED_TOOLS` to disable specific tools while keeping all others enabled:
74
+ ```bash
75
+ # Environment variable
76
+ export DISABLED_TOOLS="delete_task,delete_bulk_tasks"
77
+
78
+ # Command line argument
79
+ --env DISABLED_TOOLS=delete_task,delete_bulk_tasks
80
+ ```
81
+
82
+ #### Precedence Rules
83
+ - If `ENABLED_TOOLS` is specified, only those tools will be available (takes precedence over `DISABLED_TOOLS`)
84
+ - If only `DISABLED_TOOLS` is specified, all tools except those listed will be available
85
+ - If neither is specified, all tools are available (default behavior)
86
+
87
+ **Example:**
88
+ ```bash
89
+ # Only enable task creation and reading tools
90
+ npx -y @taazkareem/clickup-mcp-server@latest \
91
+ --env CLICKUP_API_KEY=your-api-key \
92
+ --env CLICKUP_TEAM_ID=your-team-id \
93
+ --env ENABLED_TOOLS=create_task,get_task,get_workspace_hierarchy
94
+ ```
95
+
96
+ Please filter tools you don't need if you are having issues with the number of tools or any context limitations.
61
97
 
62
98
  ## Running with HTTP Transport Support
63
99
 
@@ -97,6 +133,8 @@ Available configuration options:
97
133
 
98
134
  | Option | Description | Default |
99
135
  | ------ | ----------- | ------- |
136
+ | `ENABLED_TOOLS` | Comma-separated list of tools to enable (takes precedence) | All tools |
137
+ | `DISABLED_TOOLS` | Comma-separated list of tools to disable | None |
100
138
  | `ENABLE_SSE` | Enable the HTTP/SSE transport | `false` |
101
139
  | `PORT` | Port for the HTTP server | `3231` |
102
140
  | `ENABLE_STDIO` | Enable the STDIO transport | `true` |
@@ -142,55 +180,55 @@ npm run sse-client
142
180
 
143
181
  | Tool | Description | Required Parameters |
144
182
  | ------------------------------------------------------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
145
- | [get_workspace_hierarchy](docs/api-reference.md#workspace-navigation) | Get workspace structure | None |
146
- | [create_task](docs/api-reference.md#task-management) | Create a task | `name`, (`listId`/`listName`) |
147
- | [create_bulk_tasks](docs/api-reference.md#task-management) | Create multiple tasks | `tasks[]` |
148
- | [update_task](docs/api-reference.md#task-management) | Modify task | `taskId`/`taskName` |
149
- | [update_bulk_tasks](docs/api-reference.md#task-management) | Update multiple tasks | `tasks[]` with IDs or names |
150
- | [get_tasks](docs/api-reference.md#task-management) | Get tasks from list | `listId`/`listName` |
151
- | [get_task](docs/api-reference.md#task-management) | Get single task details | `taskId`/`taskName` (with smart disambiguation) |
152
- | [get_workspace_tasks](docs/api-reference.md#task-management) | Get tasks with filtering | At least one filter (tags, list_ids, space_ids, etc.) |
153
- | [get_task_comments](docs/api-reference.md#task-management) | Get comments on a task | `taskId`/`taskName` |
154
- | [create_task_comment](docs/api-reference.md#task-management) | Add a comment to a task | `commentText`, (`taskId`/(`taskName`+`listName`)) |
155
- | [attach_task_file](docs/api-reference.md#task-management) | Attach file to a task | `taskId`/`taskName`, (`file_data` or `file_url`) |
156
- | [delete_task](docs/api-reference.md#task-management) | Remove task | `taskId`/`taskName` |
157
- | [delete_bulk_tasks](docs/api-reference.md#task-management) | Remove multiple tasks | `tasks[]` with IDs or names |
158
- | [move_task](docs/api-reference.md#task-management) | Move task | `taskId`/`taskName`, `listId`/`listName` |
159
- | [move_bulk_tasks](docs/api-reference.md#task-management) | Move multiple tasks | `tasks[]` with IDs or names, target list |
160
- | [duplicate_task](docs/api-reference.md#task-management) | Copy task | `taskId`/`taskName`, `listId`/`listName` |
161
- | [create_list](docs/api-reference.md#list-management) | Create list in space | `name`, `spaceId`/`spaceName` |
162
- | [create_folder](docs/api-reference.md#folder-management) | Create folder | `name`, `spaceId`/`spaceName` |
163
- | [create_list_in_folder](docs/api-reference.md#list-management) | Create list in folder | `name`, `folderId`/`folderName` |
164
- | [get_folder](docs/api-reference.md#folder-management) | Get folder details | `folderId`/`folderName` |
165
- | [update_folder](docs/api-reference.md#folder-management) | Update folder properties | `folderId`/`folderName` |
166
- | [delete_folder](docs/api-reference.md#folder-management) | Delete folder | `folderId`/`folderName` |
167
- | [get_list](docs/api-reference.md#list-management) | Get list details | `listId`/`listName` |
168
- | [update_list](docs/api-reference.md#list-management) | Update list properties | `listId`/`listName` |
169
- | [delete_list](docs/api-reference.md#list-management) | Delete list | `listId`/`listName` |
170
- | [get_space_tags](docs/api-reference.md#tag-management) | Get space tags | `spaceId`/`spaceName` |
171
- | [create_space_tag](docs/api-reference.md#tag-management) | Create tag | `tagName`, `spaceId`/`spaceName` |
172
- | [update_space_tag](docs/api-reference.md#tag-management) | Update tag | `tagName`, `spaceId`/`spaceName` |
173
- | [delete_space_tag](docs/api-reference.md#tag-management) | Delete tag | `tagName`, `spaceId`/`spaceName` |
174
- | [add_tag_to_task](docs/api-reference.md#tag-management) | Add tag to task | `tagName`, `taskId`/(`taskName`+`listName`) |
175
- | [remove_tag_from_task](docs/api-reference.md#tag-management) | Remove tag from task | `tagName`, `taskId`/(`taskName`+`listName`) |
176
- | [get_task_time_entries](docs/api-reference.md#time-tracking) | Get time entries for a task | `taskId`/`taskName` |
177
- | [start_time_tracking](docs/api-reference.md#time-tracking) | Start time tracking on a task | `taskId`/`taskName` |
178
- | [stop_time_tracking](docs/api-reference.md#time-tracking) | Stop current time tracking | None |
179
- | [add_time_entry](docs/api-reference.md#time-tracking) | Add manual time entry to a task | `taskId`/`taskName`, `start`, `duration` |
180
- | [delete_time_entry](docs/api-reference.md#time-tracking) | Delete a time entry | `timeEntryId` |
181
- | [get_current_time_entry](docs/api-reference.md#time-tracking) | Get currently running timer | None |
182
- | [get_workspace_members](docs/api-reference.md#member-management) | Get all workspace members | None |
183
- | [find_member_by_name](docs/api-reference.md#member-management) | Find member by name or email | `nameOrEmail` |
184
- | [resolve_assignees](docs/api-reference.md#member-management) | Resolve member names to IDs | `assignees[]` |
185
- | [create_document](docs/api-reference.md#document-management) | Create a document | `workspaceId`, `name`, `parentId`/`parentType`, `visibility`, `create_pages` |
186
- | [get_document](docs/api-reference.md#document-management) | Get a document | `workspaceId`/`documentId` |
187
- | [list_documents](docs/api-reference.md#document-management) | List documents | `workspaceId`, `documentId`/`creator`/`deleted`/`archived`/`parent_id`/`parent_type`/`limit`/`next_cursor` |
188
- | [list_document_pages](docs/api-reference.md#document-management) | List document pages | `documentId`/`documentName` |
189
- | [get_document_pages](docs/api-reference.md#document-management) | Get document pages | `documentId`/`documentName`, `pageIds` |
190
- | [create_document_pages](docs/api-reference.md#document-management) | Create a document page | `workspaceId`/`documentId`, `parent_page_id`/`name`/`sub_title`,`content`/`content_format` |
191
- | [update_document_page](docs/api-reference.md#document-management) | Update a document page | `workspaceId`/`documentId`, `name`/`sub_title`,`content`/`content_edit_mode`/`content_format` |
192
-
193
- See [full documentation](docs/api-reference.md) for optional parameters and advanced usage.
183
+ | [get_workspace_hierarchy](docs/user-guide.md#workspace-navigation) | Get workspace structure | None |
184
+ | [create_task](docs/user-guide.md#task-management) | Create a task | `name`, (`listId`/`listName`) |
185
+ | [create_bulk_tasks](docs/user-guide.md#task-management) | Create multiple tasks | `tasks[]` |
186
+ | [update_task](docs/user-guide.md#task-management) | Modify task | `taskId`/`taskName` |
187
+ | [update_bulk_tasks](docs/user-guide.md#task-management) | Update multiple tasks | `tasks[]` with IDs or names |
188
+ | [get_tasks](docs/user-guide.md#task-management) | Get tasks from list | `listId`/`listName` |
189
+ | [get_task](docs/user-guide.md#task-management) | Get single task details | `taskId`/`taskName` (with smart disambiguation) |
190
+ | [get_workspace_tasks](docs/user-guide.md#task-management) | Get tasks with filtering | At least one filter (tags, list_ids, space_ids, etc.) |
191
+ | [get_task_comments](docs/user-guide.md#task-management) | Get comments on a task | `taskId`/`taskName` |
192
+ | [create_task_comment](docs/user-guide.md#task-management) | Add a comment to a task | `commentText`, (`taskId`/(`taskName`+`listName`)) |
193
+ | [attach_task_file](docs/user-guide.md#task-management) | Attach file to a task | `taskId`/`taskName`, (`file_data` or `file_url`) |
194
+ | [delete_task](docs/user-guide.md#task-management) | Remove task | `taskId`/`taskName` |
195
+ | [delete_bulk_tasks](docs/user-guide.md#task-management) | Remove multiple tasks | `tasks[]` with IDs or names |
196
+ | [move_task](docs/user-guide.md#task-management) | Move task | `taskId`/`taskName`, `listId`/`listName` |
197
+ | [move_bulk_tasks](docs/user-guide.md#task-management) | Move multiple tasks | `tasks[]` with IDs or names, target list |
198
+ | [duplicate_task](docs/user-guide.md#task-management) | Copy task | `taskId`/`taskName`, `listId`/`listName` |
199
+ | [create_list](docs/user-guide.md#list-management) | Create list in space | `name`, `spaceId`/`spaceName` |
200
+ | [create_folder](docs/user-guide.md#folder-management) | Create folder | `name`, `spaceId`/`spaceName` |
201
+ | [create_list_in_folder](docs/user-guide.md#list-management) | Create list in folder | `name`, `folderId`/`folderName` |
202
+ | [get_folder](docs/user-guide.md#folder-management) | Get folder details | `folderId`/`folderName` |
203
+ | [update_folder](docs/user-guide.md#folder-management) | Update folder properties | `folderId`/`folderName` |
204
+ | [delete_folder](docs/user-guide.md#folder-management) | Delete folder | `folderId`/`folderName` |
205
+ | [get_list](docs/user-guide.md#list-management) | Get list details | `listId`/`listName` |
206
+ | [update_list](docs/user-guide.md#list-management) | Update list properties | `listId`/`listName` |
207
+ | [delete_list](docs/user-guide.md#list-management) | Delete list | `listId`/`listName` |
208
+ | [get_space_tags](docs/user-guide.md#tag-management) | Get space tags | `spaceId`/`spaceName` |
209
+ | [create_space_tag](docs/user-guide.md#tag-management) | Create tag | `tagName`, `spaceId`/`spaceName` |
210
+ | [update_space_tag](docs/user-guide.md#tag-management) | Update tag | `tagName`, `spaceId`/`spaceName` |
211
+ | [delete_space_tag](docs/user-guide.md#tag-management) | Delete tag | `tagName`, `spaceId`/`spaceName` |
212
+ | [add_tag_to_task](docs/user-guide.md#tag-management) | Add tag to task | `tagName`, `taskId`/(`taskName`+`listName`) |
213
+ | [remove_tag_from_task](docs/user-guide.md#tag-management) | Remove tag from task | `tagName`, `taskId`/(`taskName`+`listName`) |
214
+ | [get_task_time_entries](docs/user-guide.md#time-tracking) | Get time entries for a task | `taskId`/`taskName` |
215
+ | [start_time_tracking](docs/user-guide.md#time-tracking) | Start time tracking on a task | `taskId`/`taskName` |
216
+ | [stop_time_tracking](docs/user-guide.md#time-tracking) | Stop current time tracking | None |
217
+ | [add_time_entry](docs/user-guide.md#time-tracking) | Add manual time entry to a task | `taskId`/`taskName`, `start`, `duration` |
218
+ | [delete_time_entry](docs/user-guide.md#time-tracking) | Delete a time entry | `timeEntryId` |
219
+ | [get_current_time_entry](docs/user-guide.md#time-tracking) | Get currently running timer | None |
220
+ | [get_workspace_members](docs/user-guide.md#member-management) | Get all workspace members | None |
221
+ | [find_member_by_name](docs/user-guide.md#member-management) | Find member by name or email | `nameOrEmail` |
222
+ | [resolve_assignees](docs/user-guide.md#member-management) | Resolve member names to IDs | `assignees[]` |
223
+ | [create_document](docs/user-guide.md#document-management) | Create a document | `workspaceId`, `name`, `parentId`/`parentType`, `visibility`, `create_pages` |
224
+ | [get_document](docs/user-guide.md#document-management) | Get a document | `workspaceId`/`documentId` |
225
+ | [list_documents](docs/user-guide.md#document-management) | List documents | `workspaceId`, `documentId`/`creator`/`deleted`/`archived`/`parent_id`/`parent_type`/`limit`/`next_cursor` |
226
+ | [list_document_pages](docs/user-guide.md#document-management) | List document pages | `documentId`/`documentName` |
227
+ | [get_document_pages](docs/user-guide.md#document-management) | Get document pages | `documentId`/`documentName`, `pageIds` |
228
+ | [create_document_pages](docs/user-guide.md#document-management) | Create a document page | `workspaceId`/`documentId`, `parent_page_id`/`name`/`sub_title`,`content`/`content_format` |
229
+ | [update_document_page](docs/user-guide.md#document-management) | Update a document page | `workspaceId`/`documentId`, `name`/`sub_title`,`content`/`content_edit_mode`/`content_format` |
230
+
231
+ See [full documentation](docs/user-guide.md) for optional parameters and advanced usage.
194
232
 
195
233
  ## Member Management Tools
196
234
 
@@ -221,9 +259,9 @@ Not yet implemented and not supported by all client apps. Request a feature for
221
259
 
222
260
  | Prompt | Purpose | Features |
223
261
  | -------------------------------------------------- | ------------------------- | ----------------------------------------- |
224
- | [summarize_tasks](docs/api-reference.md#prompts) | Task overview | Status summary, priorities, relationships |
225
- | [analyze_priorities](docs/api-reference.md#prompts) | Priority optimization | Distribution analysis, sequencing |
226
- | [generate_description](docs/api-reference.md#prompts) | Task description creation | Objectives, criteria, dependencies |
262
+ | [summarize_tasks](docs/user-guide.md#prompts) | Task overview | Status summary, priorities, relationships |
263
+ | [analyze_priorities](docs/user-guide.md#prompts) | Priority optimization | Distribution analysis, sequencing |
264
+ | [generate_description](docs/user-guide.md#prompts) | Task description creation | Objectives, criteria, dependencies |
227
265
 
228
266
  ## Error Handling
229
267
 
package/build/config.js CHANGED
@@ -12,6 +12,10 @@
12
12
  * The default value is 'false' (string), which means document support will be disabled if
13
13
  * no parameter is passed. Pass it as 'true' (string) to enable it.
14
14
  *
15
+ * Tool filtering options:
16
+ * - ENABLED_TOOLS: Comma-separated list of tools to enable (takes precedence over DISABLED_TOOLS)
17
+ * - DISABLED_TOOLS: Comma-separated list of tools to disable (ignored if ENABLED_TOOLS is specified)
18
+ *
15
19
  * Server transport options:
16
20
  * - ENABLE_SSE: Enable Server-Sent Events transport (default: false)
17
21
  * - SSE_PORT: Port for SSE server (default: 3000)
@@ -29,16 +33,12 @@ for (let i = 0; i < args.length; i++) {
29
33
  envArgs.clickupTeamId = value;
30
34
  if (key === 'DOCUMENT_SUPPORT')
31
35
  envArgs.documentSupport = value;
32
- if (key === 'DOCUMENT_MODEL')
33
- envArgs.documentSupport = value; // Backward compatibility
34
- if (key === 'DOCUMENT_MODULE')
35
- envArgs.documentSupport = value; // Backward compatibility
36
36
  if (key === 'LOG_LEVEL')
37
37
  envArgs.logLevel = value;
38
38
  if (key === 'DISABLED_TOOLS')
39
39
  envArgs.disabledTools = value;
40
- if (key === 'DISABLED_COMMANDS')
41
- envArgs.disabledTools = value; // Backward compatibility
40
+ if (key === 'ENABLED_TOOLS')
41
+ envArgs.enabledTools = value;
42
42
  if (key === 'ENABLE_SSE')
43
43
  envArgs.enableSSE = value;
44
44
  if (key === 'SSE_PORT')
@@ -60,7 +60,7 @@ export var LogLevel;
60
60
  LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
61
61
  })(LogLevel || (LogLevel = {}));
62
62
  // Parse LOG_LEVEL string to LogLevel enum
63
- export const parseLogLevel = (levelStr) => {
63
+ const parseLogLevel = (levelStr) => {
64
64
  if (!levelStr)
65
65
  return LogLevel.ERROR; // Default to ERROR if not specified
66
66
  switch (levelStr.toUpperCase()) {
@@ -95,6 +95,7 @@ const configuration = {
95
95
  documentSupport: envArgs.documentSupport || process.env.DOCUMENT_SUPPORT || process.env.DOCUMENT_MODULE || process.env.DOCUMENT_MODEL || 'false',
96
96
  logLevel: parseLogLevel(envArgs.logLevel || process.env.LOG_LEVEL),
97
97
  disabledTools: ((envArgs.disabledTools || process.env.DISABLED_TOOLS || process.env.DISABLED_COMMANDS)?.split(',').map(cmd => cmd.trim()).filter(cmd => cmd !== '') || []),
98
+ enabledTools: ((envArgs.enabledTools || process.env.ENABLED_TOOLS)?.split(',').map(cmd => cmd.trim()).filter(cmd => cmd !== '') || []),
98
99
  enableSSE: parseBoolean(envArgs.enableSSE || process.env.ENABLE_SSE, false),
99
100
  ssePort: parseInteger(envArgs.ssePort || process.env.SSE_PORT, 3000),
100
101
  enableStdio: parseBoolean(envArgs.enableStdio || process.env.ENABLE_STDIO, true),
package/build/logger.js CHANGED
@@ -29,7 +29,7 @@ export { LogLevel };
29
29
  * @param level The log level to check
30
30
  * @returns True if the level should be logged
31
31
  */
32
- export function isLevelEnabled(level) {
32
+ function isLevelEnabled(level) {
33
33
  return level >= configuredLevel;
34
34
  }
35
35
  /**
@@ -38,7 +38,7 @@ export function isLevelEnabled(level) {
38
38
  * @param message Message to log
39
39
  * @param data Optional data to include in log
40
40
  */
41
- export function log(level, message, data) {
41
+ function log(level, message, data) {
42
42
  const levelEnum = level === 'trace' ? LogLevel.TRACE
43
43
  : level === 'debug' ? LogLevel.DEBUG
44
44
  : level === 'info' ? LogLevel.INFO
@@ -78,22 +78,6 @@ export function log(level, message, data) {
78
78
  // Write to file only, not to stderr which would interfere with JSON-RPC
79
79
  logStream.write(logMessage + '\n');
80
80
  }
81
- /**
82
- * Shorthand for trace level logs
83
- * @param message Message to log
84
- * @param data Optional data to include in log
85
- */
86
- export function trace(message, data) {
87
- log('trace', message, data);
88
- }
89
- /**
90
- * Shorthand for debug level logs
91
- * @param message Message to log
92
- * @param data Optional data to include in log
93
- */
94
- export function debug(message, data) {
95
- log('debug', message, data);
96
- }
97
81
  /**
98
82
  * Shorthand for info level logs
99
83
  * @param message Message to log
@@ -102,14 +86,6 @@ export function debug(message, data) {
102
86
  export function info(message, data) {
103
87
  log('info', message, data);
104
88
  }
105
- /**
106
- * Shorthand for warn level logs
107
- * @param message Message to log
108
- * @param data Optional data to include in log
109
- */
110
- export function warn(message, data) {
111
- log('warn', message, data);
112
- }
113
89
  /**
114
90
  * Shorthand for error level logs
115
91
  * @param message Message to log
package/build/server.js CHANGED
@@ -20,9 +20,32 @@ import { clickUpServices } from "./services/shared.js";
20
20
  const logger = new Logger('Server');
21
21
  // Use existing services from shared module instead of creating new ones
22
22
  const { workspace } = clickUpServices;
23
+ /**
24
+ * Determines if a tool should be enabled based on ENABLED_TOOLS and DISABLED_TOOLS configuration.
25
+ *
26
+ * Logic:
27
+ * 1. If ENABLED_TOOLS is specified, only tools in that list are enabled (ENABLED_TOOLS takes precedence)
28
+ * 2. If ENABLED_TOOLS is not specified but DISABLED_TOOLS is, all tools except those in DISABLED_TOOLS are enabled
29
+ * 3. If neither is specified, all tools are enabled
30
+ *
31
+ * @param toolName - The name of the tool to check
32
+ * @returns true if the tool should be enabled, false otherwise
33
+ */
34
+ const isToolEnabled = (toolName) => {
35
+ // If ENABLED_TOOLS is specified, it takes precedence
36
+ if (config.enabledTools.length > 0) {
37
+ return config.enabledTools.includes(toolName);
38
+ }
39
+ // If only DISABLED_TOOLS is specified, enable all tools except those disabled
40
+ if (config.disabledTools.length > 0) {
41
+ return !config.disabledTools.includes(toolName);
42
+ }
43
+ // If neither is specified, enable all tools
44
+ return true;
45
+ };
23
46
  export const server = new Server({
24
47
  name: "clickup-mcp-server",
25
- version: "0.8.2",
48
+ version: "0.8.3",
26
49
  }, {
27
50
  capabilities: {
28
51
  tools: {},
@@ -93,7 +116,7 @@ export function configureServer() {
93
116
  findMemberByNameTool,
94
117
  resolveAssigneesTool,
95
118
  ...documentModule()
96
- ].filter(tool => !config.disabledTools.includes(tool.name))
119
+ ].filter(tool => isToolEnabled(tool.name))
97
120
  };
98
121
  });
99
122
  // Add handler for resources/list
@@ -112,12 +135,15 @@ export function configureServer() {
112
135
  logger.info(`Received CallTool request for tool: ${name}`, {
113
136
  params
114
137
  });
115
- // Check if the tool is disabled
116
- if (config.disabledTools.includes(name)) {
117
- logger.warn(`Tool execution blocked: Tool '${name}' is disabled.`);
138
+ // Check if the tool is enabled
139
+ if (!isToolEnabled(name)) {
140
+ const reason = config.enabledTools.length > 0
141
+ ? `Tool '${name}' is not in the enabled tools list.`
142
+ : `Tool '${name}' is disabled.`;
143
+ logger.warn(`Tool execution blocked: ${reason}`);
118
144
  throw {
119
145
  code: -32601,
120
- message: `Tool '${name}' is disabled.`
146
+ message: reason
121
147
  };
122
148
  }
123
149
  try {
@@ -193,6 +193,169 @@ export class TaskServiceSearch extends TaskServiceCore {
193
193
  async getTaskSummaries(filters = {}) {
194
194
  return this.getWorkspaceTasks({ ...filters, detail_level: 'summary' });
195
195
  }
196
+ /**
197
+ * Get all views for a given list and identify the default "List" view ID
198
+ * @param listId The ID of the list to get views for
199
+ * @returns The ID of the default list view, or null if not found
200
+ */
201
+ async getListViews(listId) {
202
+ try {
203
+ this.logOperation('getListViews', { listId });
204
+ const response = await this.makeRequest(async () => {
205
+ return await this.client.get(`/list/${listId}/view`);
206
+ });
207
+ // First try to get the default list view from required_views.list
208
+ if (response.data.required_views?.list?.id) {
209
+ this.logOperation('getListViews', {
210
+ listId,
211
+ foundDefaultView: response.data.required_views.list.id,
212
+ source: 'required_views.list'
213
+ });
214
+ return response.data.required_views.list.id;
215
+ }
216
+ // Fallback: look for a view with type "list" in the views array
217
+ const listView = response.data.views?.find(view => view.type?.toLowerCase() === 'list' ||
218
+ view.name?.toLowerCase().includes('list'));
219
+ if (listView?.id) {
220
+ this.logOperation('getListViews', {
221
+ listId,
222
+ foundDefaultView: listView.id,
223
+ source: 'views_array_fallback',
224
+ viewName: listView.name
225
+ });
226
+ return listView.id;
227
+ }
228
+ // If no specific list view found, use the first available view
229
+ if (response.data.views?.length > 0) {
230
+ const firstView = response.data.views[0];
231
+ this.logOperation('getListViews', {
232
+ listId,
233
+ foundDefaultView: firstView.id,
234
+ source: 'first_available_view',
235
+ viewName: firstView.name,
236
+ warning: 'No specific list view found, using first available view'
237
+ });
238
+ return firstView.id;
239
+ }
240
+ this.logOperation('getListViews', {
241
+ listId,
242
+ error: 'No views found for list',
243
+ responseData: response.data
244
+ });
245
+ return null;
246
+ }
247
+ catch (error) {
248
+ this.logOperation('getListViews', {
249
+ listId,
250
+ error: error.message,
251
+ status: error.response?.status
252
+ });
253
+ throw this.handleError(error, `Failed to get views for list ${listId}`);
254
+ }
255
+ }
256
+ /**
257
+ * Retrieve tasks from a specific view, applying supported filters
258
+ * @param viewId The ID of the view to get tasks from
259
+ * @param filters Task filters to apply (only supported filters will be used)
260
+ * @returns Array of ClickUpTask objects from the view
261
+ */
262
+ async getTasksFromView(viewId, filters = {}) {
263
+ try {
264
+ this.logOperation('getTasksFromView', { viewId, filters });
265
+ // Build query parameters for supported filters
266
+ const params = {};
267
+ // Map supported filters to query parameters
268
+ if (filters.subtasks !== undefined)
269
+ params.subtasks = filters.subtasks;
270
+ if (filters.include_closed !== undefined)
271
+ params.include_closed = filters.include_closed;
272
+ if (filters.archived !== undefined)
273
+ params.archived = filters.archived;
274
+ if (filters.page !== undefined)
275
+ params.page = filters.page;
276
+ if (filters.order_by)
277
+ params.order_by = filters.order_by;
278
+ if (filters.reverse !== undefined)
279
+ params.reverse = filters.reverse;
280
+ // Status filtering
281
+ if (filters.statuses && filters.statuses.length > 0) {
282
+ params.statuses = filters.statuses;
283
+ }
284
+ // Assignee filtering
285
+ if (filters.assignees && filters.assignees.length > 0) {
286
+ params.assignees = filters.assignees;
287
+ }
288
+ // Date filters
289
+ if (filters.date_created_gt)
290
+ params.date_created_gt = filters.date_created_gt;
291
+ if (filters.date_created_lt)
292
+ params.date_created_lt = filters.date_created_lt;
293
+ if (filters.date_updated_gt)
294
+ params.date_updated_gt = filters.date_updated_gt;
295
+ if (filters.date_updated_lt)
296
+ params.date_updated_lt = filters.date_updated_lt;
297
+ if (filters.due_date_gt)
298
+ params.due_date_gt = filters.due_date_gt;
299
+ if (filters.due_date_lt)
300
+ params.due_date_lt = filters.due_date_lt;
301
+ // Custom fields
302
+ if (filters.custom_fields) {
303
+ params.custom_fields = filters.custom_fields;
304
+ }
305
+ let allTasks = [];
306
+ let currentPage = filters.page || 0;
307
+ let hasMore = true;
308
+ const maxPages = 50; // Safety limit to prevent infinite loops
309
+ let pageCount = 0;
310
+ while (hasMore && pageCount < maxPages) {
311
+ const pageParams = { ...params, page: currentPage };
312
+ const response = await this.makeRequest(async () => {
313
+ return await this.client.get(`/view/${viewId}/task`, {
314
+ params: pageParams
315
+ });
316
+ });
317
+ const tasks = response.data.tasks || [];
318
+ allTasks = allTasks.concat(tasks);
319
+ // Check if there are more pages
320
+ hasMore = response.data.has_more === true && tasks.length > 0;
321
+ currentPage++;
322
+ pageCount++;
323
+ this.logOperation('getTasksFromView', {
324
+ viewId,
325
+ page: currentPage - 1,
326
+ tasksInPage: tasks.length,
327
+ totalTasksSoFar: allTasks.length,
328
+ hasMore
329
+ });
330
+ // If we're not paginating (original request had no page specified),
331
+ // only get the first page
332
+ if (filters.page === undefined && currentPage === 1) {
333
+ break;
334
+ }
335
+ }
336
+ if (pageCount >= maxPages) {
337
+ this.logOperation('getTasksFromView', {
338
+ viewId,
339
+ warning: `Reached maximum page limit (${maxPages}) while fetching tasks`,
340
+ totalTasks: allTasks.length
341
+ });
342
+ }
343
+ this.logOperation('getTasksFromView', {
344
+ viewId,
345
+ totalTasks: allTasks.length,
346
+ totalPages: pageCount
347
+ });
348
+ return allTasks;
349
+ }
350
+ catch (error) {
351
+ this.logOperation('getTasksFromView', {
352
+ viewId,
353
+ error: error.message,
354
+ status: error.response?.status
355
+ });
356
+ throw this.handleError(error, `Failed to get tasks from view ${viewId}`);
357
+ }
358
+ }
196
359
  /**
197
360
  * Get detailed task data
198
361
  * @param filters Task filters to apply
@@ -480,12 +480,3 @@ export async function handleUpdateDocumentPage(parameters) {
480
480
  return sponsorService.createErrorResponse(`Failed to update document page: ${error.message}`);
481
481
  }
482
482
  }
483
- export const documentTools = [
484
- createDocumentTool,
485
- getDocumentTool,
486
- listDocumentsTool,
487
- listDocumentPagesTool,
488
- getDocumentPagesTool,
489
- createDocumentPageTool,
490
- updateDocumentPageTool
491
- ];
@@ -434,13 +434,16 @@ export async function createTaskHandler(params) {
434
434
  description,
435
435
  markdown_description,
436
436
  status,
437
- priority,
438
437
  parent,
439
438
  tags,
440
439
  custom_fields,
441
440
  check_required_custom_fields,
442
441
  assignees: resolvedAssignees
443
442
  };
443
+ // Only include priority if explicitly provided by the user
444
+ if (priority !== undefined) {
445
+ taskData.priority = priority;
446
+ }
444
447
  // Add due date if specified
445
448
  if (dueDate) {
446
449
  taskData.due_date = parseDueDate(dueDate);
@@ -596,8 +599,135 @@ export async function getWorkspaceTasksHandler(taskService, params) {
596
599
  if (!hasFilter) {
597
600
  throw new Error('At least one filter parameter is required (tags, list_ids, folder_ids, space_ids, statuses, assignees, or date filters)');
598
601
  }
599
- // For workspace tasks, we'll continue to use the direct getWorkspaceTasks method
600
- // since it supports specific workspace-wide filters that aren't part of the unified findTasks
602
+ // Check if list_ids are provided for enhanced filtering via Views API
603
+ if (params.list_ids && params.list_ids.length > 0) {
604
+ logger.info('Using Views API for enhanced list filtering', {
605
+ listIds: params.list_ids,
606
+ listCount: params.list_ids.length
607
+ });
608
+ // Warning for broad queries
609
+ const hasOnlyListIds = Object.keys(params).filter(key => params[key] !== undefined && key !== 'list_ids' && key !== 'detail_level').length === 0;
610
+ if (hasOnlyListIds && params.list_ids.length > 5) {
611
+ logger.warn('Broad query detected: many lists with no additional filters', {
612
+ listCount: params.list_ids.length,
613
+ recommendation: 'Consider adding additional filters (tags, statuses, assignees, etc.) for better performance'
614
+ });
615
+ }
616
+ // Use Views API for enhanced list filtering
617
+ let allTasks = [];
618
+ const processedTaskIds = new Set();
619
+ // Create promises for concurrent fetching
620
+ const fetchPromises = params.list_ids.map(async (listId) => {
621
+ try {
622
+ // Get the default list view ID
623
+ const viewId = await taskService.getListViews(listId);
624
+ if (!viewId) {
625
+ logger.warn(`No default view found for list ${listId}, skipping`);
626
+ return [];
627
+ }
628
+ // Extract filters supported by the Views API
629
+ const supportedFilters = {
630
+ subtasks: params.subtasks,
631
+ include_closed: params.include_closed,
632
+ archived: params.archived,
633
+ order_by: params.order_by,
634
+ reverse: params.reverse,
635
+ page: params.page,
636
+ statuses: params.statuses,
637
+ assignees: params.assignees,
638
+ date_created_gt: params.date_created_gt,
639
+ date_created_lt: params.date_created_lt,
640
+ date_updated_gt: params.date_updated_gt,
641
+ date_updated_lt: params.date_updated_lt,
642
+ due_date_gt: params.due_date_gt,
643
+ due_date_lt: params.due_date_lt,
644
+ custom_fields: params.custom_fields
645
+ };
646
+ // Get tasks from the view
647
+ const tasksFromView = await taskService.getTasksFromView(viewId, supportedFilters);
648
+ return tasksFromView;
649
+ }
650
+ catch (error) {
651
+ logger.error(`Failed to get tasks from list ${listId}`, { error: error.message });
652
+ return []; // Continue with other lists even if one fails
653
+ }
654
+ });
655
+ // Execute all fetches concurrently
656
+ const taskArrays = await Promise.all(fetchPromises);
657
+ // Aggregate tasks and remove duplicates
658
+ for (const tasks of taskArrays) {
659
+ for (const task of tasks) {
660
+ if (!processedTaskIds.has(task.id)) {
661
+ allTasks.push(task);
662
+ processedTaskIds.add(task.id);
663
+ }
664
+ }
665
+ }
666
+ logger.info('Aggregated tasks from Views API', {
667
+ totalTasks: allTasks.length,
668
+ uniqueTasks: processedTaskIds.size
669
+ });
670
+ // Apply client-side filtering for unsupported filters
671
+ if (params.tags && params.tags.length > 0) {
672
+ allTasks = allTasks.filter(task => params.tags.every((tag) => task.tags.some(t => t.name === tag)));
673
+ logger.debug('Applied client-side tag filtering', {
674
+ tags: params.tags,
675
+ remainingTasks: allTasks.length
676
+ });
677
+ }
678
+ if (params.folder_ids && params.folder_ids.length > 0) {
679
+ allTasks = allTasks.filter(task => task.folder && params.folder_ids.includes(task.folder.id));
680
+ logger.debug('Applied client-side folder filtering', {
681
+ folderIds: params.folder_ids,
682
+ remainingTasks: allTasks.length
683
+ });
684
+ }
685
+ if (params.space_ids && params.space_ids.length > 0) {
686
+ allTasks = allTasks.filter(task => params.space_ids.includes(task.space.id));
687
+ logger.debug('Applied client-side space filtering', {
688
+ spaceIds: params.space_ids,
689
+ remainingTasks: allTasks.length
690
+ });
691
+ }
692
+ // Check token limit and format response
693
+ const shouldUseSummary = params.detail_level === 'summary' || wouldExceedTokenLimit({ tasks: allTasks });
694
+ if (shouldUseSummary) {
695
+ logger.info('Using summary format for Views API response', {
696
+ totalTasks: allTasks.length,
697
+ reason: params.detail_level === 'summary' ? 'requested' : 'token_limit'
698
+ });
699
+ return {
700
+ summaries: allTasks.map(task => ({
701
+ id: task.id,
702
+ name: task.name,
703
+ status: task.status.status,
704
+ list: {
705
+ id: task.list.id,
706
+ name: task.list.name
707
+ },
708
+ due_date: task.due_date,
709
+ url: task.url,
710
+ priority: task.priority?.priority || null,
711
+ tags: task.tags.map(tag => ({
712
+ name: tag.name,
713
+ tag_bg: tag.tag_bg,
714
+ tag_fg: tag.tag_fg
715
+ }))
716
+ })),
717
+ total_count: allTasks.length,
718
+ has_more: false,
719
+ next_page: 0
720
+ };
721
+ }
722
+ return {
723
+ tasks: allTasks,
724
+ total_count: allTasks.length,
725
+ has_more: false,
726
+ next_page: 0
727
+ };
728
+ }
729
+ // Fallback to existing workspace-wide task retrieval when list_ids are not provided
730
+ logger.info('Using standard workspace task retrieval');
601
731
  const filters = {
602
732
  tags: params.tags,
603
733
  list_ids: params.list_ids,
@@ -664,11 +794,15 @@ export async function createBulkTasksHandler(params) {
664
794
  description: task.description,
665
795
  markdown_description: task.markdown_description,
666
796
  status: task.status,
667
- priority: toTaskPriority(task.priority),
668
797
  tags: task.tags,
669
798
  custom_fields: task.custom_fields,
670
799
  assignees: resolvedAssignees
671
800
  };
801
+ // Only include priority if explicitly provided by the user
802
+ const priority = toTaskPriority(task.priority);
803
+ if (priority !== undefined) {
804
+ taskData.priority = priority;
805
+ }
672
806
  // Add due date if specified
673
807
  if (task.dueDate) {
674
808
  taskData.due_date = parseDueDate(task.dueDate);
@@ -35,6 +35,7 @@ Notes:
35
35
  - "summary": Returns lightweight task data (name, status, list, tags)
36
36
  - "detailed": Returns complete task data with all fields (DEFAULT if not specified)
37
37
  - Responses exceeding 50,000 tokens automatically switch to summary format to avoid hitting LLM token limits
38
+ - **Enhanced List Filtering**: When list_ids are provided, the tool leverages ClickUp's Views API to include tasks that are *associated with* the specified lists, including tasks that have been added to multiple lists. This provides comprehensive coverage of all tasks related to your specified lists, not just tasks that were originally created in those lists.
38
39
  `,
39
40
  parameters: {
40
41
  type: 'object',
@@ -86,7 +86,7 @@ const COLOR_VARIATIONS = {
86
86
  * @param text - Natural language text that contains a color reference
87
87
  * @returns The extracted color name or null if no color is found
88
88
  */
89
- export function extractColorFromText(text) {
89
+ function extractColorFromText(text) {
90
90
  if (!text)
91
91
  return null;
92
92
  // Convert to lowercase for case-insensitive matching
@@ -115,7 +115,7 @@ export function extractColorFromText(text) {
115
115
  * @param colorName - Name of the color to convert (e.g., "blue", "dark red")
116
116
  * @returns HEX color code or null if color name is not recognized
117
117
  */
118
- export function colorNameToHex(colorName) {
118
+ function colorNameToHex(colorName) {
119
119
  if (!colorName)
120
120
  return null;
121
121
  const lowercaseColor = colorName.toLowerCase();
@@ -154,7 +154,7 @@ function calculateLuminance(hex) {
154
154
  * @param backgroundColor - HEX code of the background color
155
155
  * @returns HEX code of the foreground color (either black or white)
156
156
  */
157
- export function generateContrastingForeground(backgroundColor) {
157
+ function generateContrastingForeground(backgroundColor) {
158
158
  const luminance = calculateLuminance(backgroundColor);
159
159
  // Use white text on dark backgrounds and black text on light backgrounds
160
160
  // The threshold 0.5 is based on WCAG guidelines for contrast
@@ -37,7 +37,7 @@ export function getRelativeTimestamp(minutes = 0, hours = 0, days = 0, weeks = 0
37
37
  * Get the start of today (midnight) in Unix milliseconds
38
38
  * @returns Timestamp in milliseconds for start of current day
39
39
  */
40
- export function getStartOfDay() {
40
+ function getStartOfDay() {
41
41
  const now = new Date();
42
42
  now.setHours(0, 0, 0, 0);
43
43
  return now.getTime();
@@ -46,7 +46,7 @@ export function getStartOfDay() {
46
46
  * Get the end of today (23:59:59.999) in Unix milliseconds
47
47
  * @returns Timestamp in milliseconds for end of current day
48
48
  */
49
- export function getEndOfDay() {
49
+ function getEndOfDay() {
50
50
  const now = new Date();
51
51
  now.setHours(23, 59, 59, 999);
52
52
  return now.getTime();
@@ -55,7 +55,7 @@ export function getEndOfDay() {
55
55
  * Get the current time in Unix milliseconds
56
56
  * @returns Current timestamp in milliseconds
57
57
  */
58
- export function getCurrentTimestamp() {
58
+ function getCurrentTimestamp() {
59
59
  return new Date().getTime();
60
60
  }
61
61
  /**
@@ -310,30 +310,6 @@ export function formatDueDate(timestamp) {
310
310
  throw new Error(`Invalid timestamp: ${timestamp}`);
311
311
  }
312
312
  }
313
- /**
314
- * Checks if a timestamp is for today
315
- *
316
- * @param timestamp Unix timestamp in milliseconds
317
- * @returns Boolean indicating if the timestamp is for today
318
- */
319
- export function isToday(timestamp) {
320
- const date = new Date(timestamp);
321
- const today = new Date();
322
- return date.getDate() === today.getDate() &&
323
- date.getMonth() === today.getMonth() &&
324
- date.getFullYear() === today.getFullYear();
325
- }
326
- /**
327
- * Get timestamp range for today (start to end)
328
- *
329
- * @returns Object with start and end timestamps for today
330
- */
331
- export function getTodayRange() {
332
- return {
333
- start: getStartOfDay(),
334
- end: getEndOfDay()
335
- };
336
- }
337
313
  /**
338
314
  * Format a date for display in errors and messages
339
315
  * @param timestamp The timestamp to format
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -1,13 +0,0 @@
1
- // src/schemas/member.ts
2
- import { z } from 'zod';
3
- export const MemberSchema = z.object({
4
- id: z.number(),
5
- username: z.string().optional(),
6
- email: z.string().optional(),
7
- full_name: z.string().optional(),
8
- profile_picture: z.string().optional(),
9
- role: z.number(),
10
- role_name: z.string().optional(),
11
- initials: z.string().optional(),
12
- last_active: z.string().optional(),
13
- });