@taazkareem/clickup-mcp-server 0.8.2 → 0.8.4

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.4 is released with security features and compatibility improvements! Added comprehensive opt-in enhanced security features, fixed Gemini compatibility (Issue #79), and resolved priority handling and subtask retrieval issues. 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,9 +133,41 @@ 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` |
141
+ | `ENABLE_SECURITY_FEATURES` | Enable security headers and logging | `false` |
142
+ | `ENABLE_HTTPS` | Enable HTTPS/TLS encryption | `false` |
143
+ | `ENABLE_ORIGIN_VALIDATION` | Validate Origin header against whitelist | `false` |
144
+ | `ENABLE_RATE_LIMIT` | Enable rate limiting protection | `false` |
145
+
146
+ ### 🔒 Security Features
147
+
148
+ The server includes optional security enhancements for production deployments. All security features are **opt-in** and **disabled by default** to maintain backwards compatibility.
149
+
150
+ **Quick security setup:**
151
+ ```bash
152
+ # Generate SSL certificates for HTTPS
153
+ ./scripts/generate-ssl-cert.sh
154
+
155
+ # Start with full security
156
+ ENABLE_SECURITY_FEATURES=true \
157
+ ENABLE_HTTPS=true \
158
+ ENABLE_ORIGIN_VALIDATION=true \
159
+ ENABLE_RATE_LIMIT=true \
160
+ SSL_KEY_PATH=./ssl/server.key \
161
+ SSL_CERT_PATH=./ssl/server.crt \
162
+ npx @taazkareem/clickup-mcp-server@latest --env CLICKUP_API_KEY=your-key --env CLICKUP_TEAM_ID=your-team --env ENABLE_SSE=true
163
+ ```
164
+
165
+ **HTTPS Endpoints:**
166
+ - **Primary**: `https://127.0.0.1:3443/mcp` (Streamable HTTPS)
167
+ - **Legacy**: `https://127.0.0.1:3443/sse` (SSE HTTPS for backwards compatibility)
168
+ - **Health**: `https://127.0.0.1:3443/health` (Health check)
169
+
170
+ For detailed security configuration, see [Security Features Documentation](docs/security-features.md).
103
171
 
104
172
  #### n8n Integration
105
173
 
@@ -142,55 +210,55 @@ npm run sse-client
142
210
 
143
211
  | Tool | Description | Required Parameters |
144
212
  | ------------------------------------------------------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
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.
213
+ | [get_workspace_hierarchy](docs/user-guide.md#workspace-navigation) | Get workspace structure | None |
214
+ | [create_task](docs/user-guide.md#task-management) | Create a task | `name`, (`listId`/`listName`) |
215
+ | [create_bulk_tasks](docs/user-guide.md#task-management) | Create multiple tasks | `tasks[]` |
216
+ | [update_task](docs/user-guide.md#task-management) | Modify task | `taskId`/`taskName` |
217
+ | [update_bulk_tasks](docs/user-guide.md#task-management) | Update multiple tasks | `tasks[]` with IDs or names |
218
+ | [get_tasks](docs/user-guide.md#task-management) | Get tasks from list | `listId`/`listName` |
219
+ | [get_task](docs/user-guide.md#task-management) | Get single task details | `taskId`/`taskName` (with smart disambiguation) |
220
+ | [get_workspace_tasks](docs/user-guide.md#task-management) | Get tasks with filtering | At least one filter (tags, list_ids, space_ids, etc.) |
221
+ | [get_task_comments](docs/user-guide.md#task-management) | Get comments on a task | `taskId`/`taskName` |
222
+ | [create_task_comment](docs/user-guide.md#task-management) | Add a comment to a task | `commentText`, (`taskId`/(`taskName`+`listName`)) |
223
+ | [attach_task_file](docs/user-guide.md#task-management) | Attach file to a task | `taskId`/`taskName`, (`file_data` or `file_url`) |
224
+ | [delete_task](docs/user-guide.md#task-management) | Remove task | `taskId`/`taskName` |
225
+ | [delete_bulk_tasks](docs/user-guide.md#task-management) | Remove multiple tasks | `tasks[]` with IDs or names |
226
+ | [move_task](docs/user-guide.md#task-management) | Move task | `taskId`/`taskName`, `listId`/`listName` |
227
+ | [move_bulk_tasks](docs/user-guide.md#task-management) | Move multiple tasks | `tasks[]` with IDs or names, target list |
228
+ | [duplicate_task](docs/user-guide.md#task-management) | Copy task | `taskId`/`taskName`, `listId`/`listName` |
229
+ | [create_list](docs/user-guide.md#list-management) | Create list in space | `name`, `spaceId`/`spaceName` |
230
+ | [create_folder](docs/user-guide.md#folder-management) | Create folder | `name`, `spaceId`/`spaceName` |
231
+ | [create_list_in_folder](docs/user-guide.md#list-management) | Create list in folder | `name`, `folderId`/`folderName` |
232
+ | [get_folder](docs/user-guide.md#folder-management) | Get folder details | `folderId`/`folderName` |
233
+ | [update_folder](docs/user-guide.md#folder-management) | Update folder properties | `folderId`/`folderName` |
234
+ | [delete_folder](docs/user-guide.md#folder-management) | Delete folder | `folderId`/`folderName` |
235
+ | [get_list](docs/user-guide.md#list-management) | Get list details | `listId`/`listName` |
236
+ | [update_list](docs/user-guide.md#list-management) | Update list properties | `listId`/`listName` |
237
+ | [delete_list](docs/user-guide.md#list-management) | Delete list | `listId`/`listName` |
238
+ | [get_space_tags](docs/user-guide.md#tag-management) | Get space tags | `spaceId`/`spaceName` |
239
+ | [create_space_tag](docs/user-guide.md#tag-management) | Create tag | `tagName`, `spaceId`/`spaceName` |
240
+ | [update_space_tag](docs/user-guide.md#tag-management) | Update tag | `tagName`, `spaceId`/`spaceName` |
241
+ | [delete_space_tag](docs/user-guide.md#tag-management) | Delete tag | `tagName`, `spaceId`/`spaceName` |
242
+ | [add_tag_to_task](docs/user-guide.md#tag-management) | Add tag to task | `tagName`, `taskId`/(`taskName`+`listName`) |
243
+ | [remove_tag_from_task](docs/user-guide.md#tag-management) | Remove tag from task | `tagName`, `taskId`/(`taskName`+`listName`) |
244
+ | [get_task_time_entries](docs/user-guide.md#time-tracking) | Get time entries for a task | `taskId`/`taskName` |
245
+ | [start_time_tracking](docs/user-guide.md#time-tracking) | Start time tracking on a task | `taskId`/`taskName` |
246
+ | [stop_time_tracking](docs/user-guide.md#time-tracking) | Stop current time tracking | None |
247
+ | [add_time_entry](docs/user-guide.md#time-tracking) | Add manual time entry to a task | `taskId`/`taskName`, `start`, `duration` |
248
+ | [delete_time_entry](docs/user-guide.md#time-tracking) | Delete a time entry | `timeEntryId` |
249
+ | [get_current_time_entry](docs/user-guide.md#time-tracking) | Get currently running timer | None |
250
+ | [get_workspace_members](docs/user-guide.md#member-management) | Get all workspace members | None |
251
+ | [find_member_by_name](docs/user-guide.md#member-management) | Find member by name or email | `nameOrEmail` |
252
+ | [resolve_assignees](docs/user-guide.md#member-management) | Resolve member names to IDs | `assignees[]` |
253
+ | [create_document](docs/user-guide.md#document-management) | Create a document | `workspaceId`, `name`, `parentId`/`parentType`, `visibility`, `create_pages` |
254
+ | [get_document](docs/user-guide.md#document-management) | Get a document | `workspaceId`/`documentId` |
255
+ | [list_documents](docs/user-guide.md#document-management) | List documents | `workspaceId`, `documentId`/`creator`/`deleted`/`archived`/`parent_id`/`parent_type`/`limit`/`next_cursor` |
256
+ | [list_document_pages](docs/user-guide.md#document-management) | List document pages | `documentId`/`documentName` |
257
+ | [get_document_pages](docs/user-guide.md#document-management) | Get document pages | `documentId`/`documentName`, `pageIds` |
258
+ | [create_document_pages](docs/user-guide.md#document-management) | Create a document page | `workspaceId`/`documentId`, `parent_page_id`/`name`/`sub_title`,`content`/`content_format` |
259
+ | [update_document_page](docs/user-guide.md#document-management) | Update a document page | `workspaceId`/`documentId`, `name`/`sub_title`,`content`/`content_edit_mode`/`content_format` |
260
+
261
+ See [full documentation](docs/user-guide.md) for optional parameters and advanced usage.
194
262
 
195
263
  ## Member Management Tools
196
264
 
@@ -221,9 +289,9 @@ Not yet implemented and not supported by all client apps. Request a feature for
221
289
 
222
290
  | Prompt | Purpose | Features |
223
291
  | -------------------------------------------------- | ------------------------- | ----------------------------------------- |
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 |
292
+ | [summarize_tasks](docs/user-guide.md#prompts) | Task overview | Status summary, priorities, relationships |
293
+ | [analyze_priorities](docs/user-guide.md#prompts) | Priority optimization | Distribution analysis, sequencing |
294
+ | [generate_description](docs/user-guide.md#prompts) | Task description creation | Objectives, criteria, dependencies |
227
295
 
228
296
  ## Error Handling
229
297
 
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()) {
@@ -87,6 +87,12 @@ const parseInteger = (value, defaultValue) => {
87
87
  const parsed = parseInt(value, 10);
88
88
  return isNaN(parsed) ? defaultValue : parsed;
89
89
  };
90
+ // Parse comma-separated origins list
91
+ const parseOrigins = (value, defaultValue) => {
92
+ if (!value)
93
+ return defaultValue;
94
+ return value.split(',').map(origin => origin.trim()).filter(origin => origin !== '');
95
+ };
90
96
  // Load configuration from command line args or environment variables
91
97
  const configuration = {
92
98
  clickupApiKey: envArgs.clickupApiKey || process.env.CLICKUP_API_KEY || '',
@@ -95,10 +101,35 @@ const configuration = {
95
101
  documentSupport: envArgs.documentSupport || process.env.DOCUMENT_SUPPORT || process.env.DOCUMENT_MODULE || process.env.DOCUMENT_MODEL || 'false',
96
102
  logLevel: parseLogLevel(envArgs.logLevel || process.env.LOG_LEVEL),
97
103
  disabledTools: ((envArgs.disabledTools || process.env.DISABLED_TOOLS || process.env.DISABLED_COMMANDS)?.split(',').map(cmd => cmd.trim()).filter(cmd => cmd !== '') || []),
104
+ enabledTools: ((envArgs.enabledTools || process.env.ENABLED_TOOLS)?.split(',').map(cmd => cmd.trim()).filter(cmd => cmd !== '') || []),
98
105
  enableSSE: parseBoolean(envArgs.enableSSE || process.env.ENABLE_SSE, false),
99
106
  ssePort: parseInteger(envArgs.ssePort || process.env.SSE_PORT, 3000),
100
107
  enableStdio: parseBoolean(envArgs.enableStdio || process.env.ENABLE_STDIO, true),
101
108
  port: envArgs.port || process.env.PORT || '3231',
109
+ // Security configuration (opt-in for backwards compatibility)
110
+ enableSecurityFeatures: parseBoolean(process.env.ENABLE_SECURITY_FEATURES, false),
111
+ enableOriginValidation: parseBoolean(process.env.ENABLE_ORIGIN_VALIDATION, false),
112
+ enableRateLimit: parseBoolean(process.env.ENABLE_RATE_LIMIT, false),
113
+ enableCors: parseBoolean(process.env.ENABLE_CORS, false),
114
+ allowedOrigins: parseOrigins(process.env.ALLOWED_ORIGINS, [
115
+ 'http://127.0.0.1:3231',
116
+ 'http://localhost:3231',
117
+ 'http://127.0.0.1:3000',
118
+ 'http://localhost:3000',
119
+ 'https://127.0.0.1:3443',
120
+ 'https://localhost:3443',
121
+ 'https://127.0.0.1:3231',
122
+ 'https://localhost:3231'
123
+ ]),
124
+ rateLimitMax: parseInteger(process.env.RATE_LIMIT_MAX, 100),
125
+ rateLimitWindowMs: parseInteger(process.env.RATE_LIMIT_WINDOW_MS, 60000),
126
+ maxRequestSize: process.env.MAX_REQUEST_SIZE || '10mb',
127
+ // HTTPS configuration
128
+ enableHttps: parseBoolean(process.env.ENABLE_HTTPS, false),
129
+ httpsPort: process.env.HTTPS_PORT || '3443',
130
+ sslKeyPath: process.env.SSL_KEY_PATH,
131
+ sslCertPath: process.env.SSL_CERT_PATH,
132
+ sslCaPath: process.env.SSL_CA_PATH,
102
133
  };
103
134
  // Don't log to console as it interferes with JSON-RPC communication
104
135
  // Validate only the required variables are present
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
@@ -0,0 +1,231 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Security Middleware for ClickUp MCP Server
6
+ *
7
+ * This module provides optional security enhancements that can be enabled
8
+ * without breaking existing functionality. All security features are opt-in
9
+ * to maintain backwards compatibility.
10
+ */
11
+ import rateLimit from 'express-rate-limit';
12
+ import cors from 'cors';
13
+ import config from '../config.js';
14
+ import { Logger } from '../logger.js';
15
+ const logger = new Logger('Security');
16
+ /**
17
+ * Origin validation middleware - validates Origin header against whitelist
18
+ * Only enabled when ENABLE_ORIGIN_VALIDATION=true
19
+ */
20
+ export function createOriginValidationMiddleware() {
21
+ return (req, res, next) => {
22
+ if (!config.enableOriginValidation) {
23
+ next();
24
+ return;
25
+ }
26
+ const origin = req.headers.origin;
27
+ const referer = req.headers.referer;
28
+ // For non-browser requests (like n8n, MCP Inspector), origin might be undefined
29
+ // In such cases, we allow the request but log it for monitoring
30
+ if (!origin && !referer) {
31
+ logger.debug('Request without Origin/Referer header - allowing (likely non-browser client)', {
32
+ userAgent: req.headers['user-agent'],
33
+ ip: req.ip,
34
+ path: req.path
35
+ });
36
+ next();
37
+ return;
38
+ }
39
+ // Check if origin is in allowed list
40
+ if (origin && !config.allowedOrigins.includes(origin)) {
41
+ logger.warn('Blocked request from unauthorized origin', {
42
+ origin,
43
+ ip: req.ip,
44
+ path: req.path,
45
+ userAgent: req.headers['user-agent']
46
+ });
47
+ res.status(403).json({
48
+ jsonrpc: '2.0',
49
+ error: {
50
+ code: -32000,
51
+ message: 'Forbidden: Origin not allowed'
52
+ },
53
+ id: null
54
+ });
55
+ return;
56
+ }
57
+ // If referer is present, validate it too
58
+ if (referer) {
59
+ try {
60
+ const refererOrigin = new URL(referer).origin;
61
+ if (!config.allowedOrigins.includes(refererOrigin)) {
62
+ logger.warn('Blocked request from unauthorized referer', {
63
+ referer,
64
+ refererOrigin,
65
+ ip: req.ip,
66
+ path: req.path
67
+ });
68
+ res.status(403).json({
69
+ jsonrpc: '2.0',
70
+ error: {
71
+ code: -32000,
72
+ message: 'Forbidden: Referer not allowed'
73
+ },
74
+ id: null
75
+ });
76
+ return;
77
+ }
78
+ }
79
+ catch (error) {
80
+ logger.warn('Invalid referer URL', { referer, error: error.message });
81
+ // Continue processing if referer is malformed
82
+ }
83
+ }
84
+ logger.debug('Origin validation passed', { origin, referer });
85
+ next();
86
+ };
87
+ }
88
+ /**
89
+ * Rate limiting middleware - protects against DoS attacks
90
+ * Only enabled when ENABLE_RATE_LIMIT=true
91
+ */
92
+ export function createRateLimitMiddleware() {
93
+ if (!config.enableRateLimit) {
94
+ return (_req, _res, next) => next();
95
+ }
96
+ return rateLimit({
97
+ windowMs: config.rateLimitWindowMs,
98
+ max: config.rateLimitMax,
99
+ message: {
100
+ jsonrpc: '2.0',
101
+ error: {
102
+ code: -32000,
103
+ message: 'Too many requests, please try again later'
104
+ },
105
+ id: null
106
+ },
107
+ standardHeaders: true,
108
+ legacyHeaders: false,
109
+ handler: (req, res) => {
110
+ logger.warn('Rate limit exceeded', {
111
+ ip: req.ip,
112
+ path: req.path,
113
+ userAgent: req.headers['user-agent']
114
+ });
115
+ res.status(429).json({
116
+ jsonrpc: '2.0',
117
+ error: {
118
+ code: -32000,
119
+ message: 'Too many requests, please try again later'
120
+ },
121
+ id: null
122
+ });
123
+ }
124
+ });
125
+ }
126
+ /**
127
+ * CORS middleware - configures cross-origin resource sharing
128
+ * Only enabled when ENABLE_CORS=true
129
+ */
130
+ export function createCorsMiddleware() {
131
+ if (!config.enableCors) {
132
+ return (_req, _res, next) => next();
133
+ }
134
+ return cors({
135
+ origin: (origin, callback) => {
136
+ // Allow requests with no origin (like mobile apps, Postman, etc.)
137
+ if (!origin)
138
+ return callback(null, true);
139
+ if (config.allowedOrigins.includes(origin)) {
140
+ callback(null, true);
141
+ }
142
+ else {
143
+ logger.warn('CORS blocked origin', { origin });
144
+ callback(new Error('Not allowed by CORS'));
145
+ }
146
+ },
147
+ credentials: true,
148
+ methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
149
+ allowedHeaders: ['Content-Type', 'mcp-session-id', 'Authorization'],
150
+ exposedHeaders: ['mcp-session-id']
151
+ });
152
+ }
153
+ /**
154
+ * Security headers middleware - adds security-related HTTP headers
155
+ * Only enabled when ENABLE_SECURITY_FEATURES=true
156
+ */
157
+ export function createSecurityHeadersMiddleware() {
158
+ return (req, res, next) => {
159
+ if (!config.enableSecurityFeatures) {
160
+ return next();
161
+ }
162
+ // Add security headers
163
+ res.setHeader('X-Content-Type-Options', 'nosniff');
164
+ res.setHeader('X-Frame-Options', 'DENY');
165
+ res.setHeader('X-XSS-Protection', '1; mode=block');
166
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
167
+ // Only add HSTS for HTTPS
168
+ if (req.secure || req.headers['x-forwarded-proto'] === 'https') {
169
+ res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
170
+ }
171
+ logger.debug('Security headers applied');
172
+ next();
173
+ };
174
+ }
175
+ /**
176
+ * Request logging middleware for security monitoring
177
+ */
178
+ export function createSecurityLoggingMiddleware() {
179
+ return (req, res, next) => {
180
+ if (!config.enableSecurityFeatures) {
181
+ return next();
182
+ }
183
+ const startTime = Date.now();
184
+ res.on('finish', () => {
185
+ const duration = Date.now() - startTime;
186
+ const logData = {
187
+ method: req.method,
188
+ path: req.path,
189
+ statusCode: res.statusCode,
190
+ duration,
191
+ ip: req.ip,
192
+ userAgent: req.headers['user-agent'],
193
+ origin: req.headers.origin,
194
+ sessionId: req.headers['mcp-session-id']
195
+ };
196
+ if (res.statusCode >= 400) {
197
+ logger.warn('HTTP error response', logData);
198
+ }
199
+ else {
200
+ logger.debug('HTTP request completed', logData);
201
+ }
202
+ });
203
+ next();
204
+ };
205
+ }
206
+ /**
207
+ * Input validation middleware - validates request size and content
208
+ */
209
+ export function createInputValidationMiddleware() {
210
+ return (req, res, next) => {
211
+ // Always enforce reasonable request size limits
212
+ const contentLength = req.headers['content-length'];
213
+ if (contentLength && parseInt(contentLength) > 50 * 1024 * 1024) { // 50MB hard limit
214
+ logger.warn('Request too large', {
215
+ contentLength,
216
+ ip: req.ip,
217
+ path: req.path
218
+ });
219
+ res.status(413).json({
220
+ jsonrpc: '2.0',
221
+ error: {
222
+ code: -32000,
223
+ message: 'Request entity too large'
224
+ },
225
+ id: null
226
+ });
227
+ return;
228
+ }
229
+ next();
230
+ };
231
+ }