@taazkareem/clickup-mcp-server 0.8.3 → 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.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.
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
 
@@ -138,6 +138,36 @@ Available configuration options:
138
138
  | `ENABLE_SSE` | Enable the HTTP/SSE transport | `false` |
139
139
  | `PORT` | Port for the HTTP server | `3231` |
140
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).
141
171
 
142
172
  #### n8n Integration
143
173
 
package/build/config.js CHANGED
@@ -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 || '',
@@ -100,6 +106,30 @@ const configuration = {
100
106
  ssePort: parseInteger(envArgs.ssePort || process.env.SSE_PORT, 3000),
101
107
  enableStdio: parseBoolean(envArgs.enableStdio || process.env.ENABLE_STDIO, true),
102
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,
103
133
  };
104
134
  // Don't log to console as it interferes with JSON-RPC communication
105
135
  // Validate only the required variables are present
@@ -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
+ }
package/build/server.js CHANGED
@@ -45,7 +45,7 @@ const isToolEnabled = (toolName) => {
45
45
  };
46
46
  export const server = new Server({
47
47
  name: "clickup-mcp-server",
48
- version: "0.8.3",
48
+ version: "0.8.4",
49
49
  }, {
50
50
  capabilities: {
51
51
  tools: {},
@@ -232,7 +232,7 @@ export class TaskServiceCore extends BaseClickUpService {
232
232
  this.logOperation('getSubtasks', { taskId });
233
233
  try {
234
234
  return await this.makeRequest(async () => {
235
- const response = await this.client.get(`/task/${taskId}`);
235
+ const response = await this.client.get(`/task/${taskId}?subtasks=true&include_subtasks=true`);
236
236
  // Return subtasks if present, otherwise empty array
237
237
  return response.data.subtasks || [];
238
238
  });
@@ -12,13 +12,43 @@ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
12
12
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
13
13
  import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
14
14
  import express from 'express';
15
+ import https from 'https';
16
+ import http from 'http';
17
+ import fs from 'fs';
15
18
  import { server, configureServer } from './server.js';
16
19
  import configuration from './config.js';
20
+ import { createOriginValidationMiddleware, createRateLimitMiddleware, createCorsMiddleware, createSecurityHeadersMiddleware, createSecurityLoggingMiddleware, createInputValidationMiddleware } from './middleware/security.js';
21
+ import { Logger } from './logger.js';
17
22
  const app = express();
18
- app.use(express.json());
23
+ const logger = new Logger('SSEServer');
19
24
  export function startSSEServer() {
20
25
  // Configure the unified server first
21
26
  configureServer();
27
+ // Apply security middleware (all are opt-in via environment variables)
28
+ logger.info('Configuring security middleware', {
29
+ securityFeatures: configuration.enableSecurityFeatures,
30
+ originValidation: configuration.enableOriginValidation,
31
+ rateLimit: configuration.enableRateLimit,
32
+ cors: configuration.enableCors
33
+ });
34
+ // Always apply input validation (reasonable defaults)
35
+ app.use(createInputValidationMiddleware());
36
+ // Apply optional security middleware
37
+ app.use(createSecurityLoggingMiddleware());
38
+ app.use(createSecurityHeadersMiddleware());
39
+ app.use(createCorsMiddleware());
40
+ app.use(createOriginValidationMiddleware());
41
+ app.use(createRateLimitMiddleware());
42
+ // Configure JSON parsing with configurable size limit
43
+ app.use(express.json({
44
+ limit: configuration.maxRequestSize,
45
+ verify: (req, res, buf) => {
46
+ // Additional validation can be added here if needed
47
+ if (buf.length === 0) {
48
+ logger.debug('Empty request body received');
49
+ }
50
+ }
51
+ }));
22
52
  const transports = {
23
53
  streamable: {},
24
54
  sse: {},
@@ -27,6 +57,12 @@ export function startSSEServer() {
27
57
  app.post('/mcp', async (req, res) => {
28
58
  try {
29
59
  const sessionId = req.headers['mcp-session-id'];
60
+ logger.debug('MCP request received', {
61
+ sessionId,
62
+ hasBody: !!req.body,
63
+ contentType: req.headers['content-type'],
64
+ origin: req.headers.origin
65
+ });
30
66
  let transport;
31
67
  if (sessionId && transports.streamable[sessionId]) {
32
68
  transport = transports.streamable[sessionId];
@@ -87,7 +123,11 @@ export function startSSEServer() {
87
123
  app.get('/sse', async (req, res) => {
88
124
  const transport = new SSEServerTransport('/messages', res);
89
125
  transports.sse[transport.sessionId] = transport;
90
- console.log(`New SSE connection established with sessionId: ${transport.sessionId}`);
126
+ logger.info('New SSE connection established', {
127
+ sessionId: transport.sessionId,
128
+ origin: req.headers.origin,
129
+ userAgent: req.headers['user-agent']
130
+ });
91
131
  res.on('close', () => {
92
132
  delete transports.sse[transport.sessionId];
93
133
  });
@@ -103,11 +143,135 @@ export function startSSEServer() {
103
143
  res.status(400).send('No transport found for sessionId');
104
144
  }
105
145
  });
106
- const PORT = Number(configuration.port ?? '3231');
107
- // Bind to localhost only for security
108
- app.listen(PORT, () => {
109
- console.log(`Server started on http://127.0.0.1:${PORT}`);
110
- console.log(`Streamable HTTP endpoint: http://127.0.0.1:${PORT}/mcp`);
111
- console.log(`Legacy SSE endpoint: http://127.0.0.1:${PORT}/sse`);
146
+ // Health check endpoint
147
+ app.get('/health', (req, res) => {
148
+ res.json({
149
+ status: 'healthy',
150
+ timestamp: new Date().toISOString(),
151
+ version: '0.8.3',
152
+ security: {
153
+ featuresEnabled: configuration.enableSecurityFeatures,
154
+ originValidation: configuration.enableOriginValidation,
155
+ rateLimit: configuration.enableRateLimit,
156
+ cors: configuration.enableCors
157
+ }
158
+ });
112
159
  });
160
+ // Server creation and startup
161
+ const PORT = Number(configuration.port ?? '3231');
162
+ const HTTPS_PORT = Number(configuration.httpsPort ?? '3443');
163
+ // Function to create and start HTTP server
164
+ function startHttpServer() {
165
+ const httpServer = http.createServer(app);
166
+ httpServer.listen(PORT, '127.0.0.1', () => {
167
+ logger.info('ClickUp MCP Server (HTTP) started', {
168
+ port: PORT,
169
+ protocol: 'http',
170
+ endpoints: {
171
+ streamableHttp: `http://127.0.0.1:${PORT}/mcp`,
172
+ legacySSE: `http://127.0.0.1:${PORT}/sse`,
173
+ health: `http://127.0.0.1:${PORT}/health`
174
+ },
175
+ security: {
176
+ featuresEnabled: configuration.enableSecurityFeatures,
177
+ originValidation: configuration.enableOriginValidation,
178
+ rateLimit: configuration.enableRateLimit,
179
+ cors: configuration.enableCors,
180
+ httpsEnabled: configuration.enableHttps
181
+ }
182
+ });
183
+ console.log(`✅ ClickUp MCP Server started on http://127.0.0.1:${PORT}`);
184
+ console.log(`📡 Streamable HTTP endpoint: http://127.0.0.1:${PORT}/mcp`);
185
+ console.log(`🔄 Legacy SSE endpoint: http://127.0.0.1:${PORT}/sse`);
186
+ console.log(`❤️ Health check: http://127.0.0.1:${PORT}/health`);
187
+ if (configuration.enableHttps) {
188
+ console.log(`⚠️ HTTP server running alongside HTTPS - consider disabling HTTP in production`);
189
+ }
190
+ });
191
+ return httpServer;
192
+ }
193
+ // Function to create and start HTTPS server
194
+ function startHttpsServer() {
195
+ if (!configuration.sslKeyPath || !configuration.sslCertPath) {
196
+ logger.error('HTTPS enabled but SSL certificate paths not provided', {
197
+ sslKeyPath: configuration.sslKeyPath,
198
+ sslCertPath: configuration.sslCertPath
199
+ });
200
+ console.log(`❌ HTTPS enabled but SSL_KEY_PATH and SSL_CERT_PATH not provided`);
201
+ console.log(` Set SSL_KEY_PATH and SSL_CERT_PATH environment variables`);
202
+ return null;
203
+ }
204
+ try {
205
+ // Check if certificate files exist
206
+ if (!fs.existsSync(configuration.sslKeyPath)) {
207
+ throw new Error(`SSL key file not found: ${configuration.sslKeyPath}`);
208
+ }
209
+ if (!fs.existsSync(configuration.sslCertPath)) {
210
+ throw new Error(`SSL certificate file not found: ${configuration.sslCertPath}`);
211
+ }
212
+ const httpsOptions = {
213
+ key: fs.readFileSync(configuration.sslKeyPath),
214
+ cert: fs.readFileSync(configuration.sslCertPath)
215
+ };
216
+ // Add CA certificate if provided
217
+ if (configuration.sslCaPath && fs.existsSync(configuration.sslCaPath)) {
218
+ httpsOptions.ca = fs.readFileSync(configuration.sslCaPath);
219
+ }
220
+ const httpsServer = https.createServer(httpsOptions, app);
221
+ httpsServer.listen(HTTPS_PORT, '127.0.0.1', () => {
222
+ logger.info('ClickUp MCP Server (HTTPS) started', {
223
+ port: HTTPS_PORT,
224
+ protocol: 'https',
225
+ endpoints: {
226
+ streamableHttp: `https://127.0.0.1:${HTTPS_PORT}/mcp`,
227
+ legacySSE: `https://127.0.0.1:${HTTPS_PORT}/sse`,
228
+ health: `https://127.0.0.1:${HTTPS_PORT}/health`
229
+ },
230
+ security: {
231
+ featuresEnabled: configuration.enableSecurityFeatures,
232
+ originValidation: configuration.enableOriginValidation,
233
+ rateLimit: configuration.enableRateLimit,
234
+ cors: configuration.enableCors,
235
+ httpsEnabled: true
236
+ }
237
+ });
238
+ console.log(`🔒 ClickUp MCP Server (HTTPS) started on https://127.0.0.1:${HTTPS_PORT}`);
239
+ console.log(`📡 Streamable HTTPS endpoint: https://127.0.0.1:${HTTPS_PORT}/mcp`);
240
+ console.log(`🔄 Legacy SSE HTTPS endpoint: https://127.0.0.1:${HTTPS_PORT}/sse`);
241
+ console.log(`❤️ Health check HTTPS: https://127.0.0.1:${HTTPS_PORT}/health`);
242
+ });
243
+ return httpsServer;
244
+ }
245
+ catch (error) {
246
+ logger.error('Failed to start HTTPS server', {
247
+ error: error.message,
248
+ sslKeyPath: configuration.sslKeyPath,
249
+ sslCertPath: configuration.sslCertPath
250
+ });
251
+ console.log(`❌ Failed to start HTTPS server: ${error.message}`);
252
+ return null;
253
+ }
254
+ }
255
+ // Start servers based on configuration
256
+ const servers = [];
257
+ // Always start HTTP server (for backwards compatibility)
258
+ servers.push(startHttpServer());
259
+ // Start HTTPS server if enabled
260
+ if (configuration.enableHttps) {
261
+ const httpsServer = startHttpsServer();
262
+ if (httpsServer) {
263
+ servers.push(httpsServer);
264
+ }
265
+ }
266
+ // Security status logging
267
+ if (configuration.enableSecurityFeatures) {
268
+ console.log(`🔒 Security features enabled`);
269
+ }
270
+ else {
271
+ console.log(`⚠️ Security features disabled (set ENABLE_SECURITY_FEATURES=true to enable)`);
272
+ }
273
+ if (!configuration.enableHttps) {
274
+ console.log(`⚠️ HTTPS disabled (set ENABLE_HTTPS=true with SSL certificates to enable)`);
275
+ }
276
+ return servers;
113
277
  }
@@ -206,9 +206,9 @@ export const updateBulkTasksTool = {
206
206
  description: "New status"
207
207
  },
208
208
  priority: {
209
- type: "number",
209
+ type: "string",
210
210
  nullable: true,
211
- enum: [1, 2, 3, 4, null],
211
+ enum: ["1", "2", "3", "4", null],
212
212
  description: "New priority (1-4 or null)"
213
213
  },
214
214
  dueDate: {
@@ -138,9 +138,10 @@ async function buildUpdateData(params) {
138
138
  updateData.markdown_description = params.markdown_description;
139
139
  if (params.status !== undefined)
140
140
  updateData.status = params.status;
141
- // Skip toTaskPriority conversion since we're handling priority in the main handler
142
- if (params.priority !== undefined)
143
- updateData.priority = params.priority;
141
+ // Use toTaskPriority to properly handle null values and validation
142
+ if (params.priority !== undefined) {
143
+ updateData.priority = toTaskPriority(params.priority);
144
+ }
144
145
  if (params.dueDate !== undefined) {
145
146
  updateData.due_date = parseDueDate(params.dueDate);
146
147
  updateData.due_date_time = true;
@@ -171,9 +171,9 @@ export const updateTaskTool = {
171
171
  description: "New status. Must be valid for the task's current list."
172
172
  },
173
173
  priority: {
174
- type: "number",
174
+ type: "string",
175
175
  nullable: true,
176
- enum: [1, 2, 3, 4, null],
176
+ enum: ["1", "2", "3", "4", null],
177
177
  description: "New priority: 1 (urgent) to 4 (low). Set null to clear priority."
178
178
  },
179
179
  dueDate: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
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",