@taazkareem/clickup-mcp-server 0.11.1 → 0.11.2

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/build/license.js CHANGED
@@ -9,17 +9,68 @@
9
9
  */
10
10
  import axios from 'axios';
11
11
  import { Logger } from './logger.js';
12
+ import { createHash } from 'crypto';
12
13
  const logger = new Logger('License');
13
- // Your Polar.sh organization ID (configurable for sandbox testing)
14
- // Production: cd9f6f7c-5b51-4e5e-872b-c92dd9377bcd
15
- // Sandbox: 574925db-40b9-49dd-a02e-ceb1124367d3
16
- const POLAR_ORGANIZATION_ID = process.env.POLAR_ORGANIZATION_ID || 'cd9f6f7c-5b51-4e5e-872b-c92dd9377bcd';
14
+ // Obfuscated organization validation
15
+ // These constants work together to prevent tampering
16
+ const ORG_PART_1 = 'cd9f6f7c';
17
+ const ORG_PART_2 = '5b51';
18
+ const ORG_PART_3 = '4e5e';
19
+ const ORG_PART_4 = '872b';
20
+ const ORG_PART_5 = 'c92dd9377bcd';
21
+ // Reconstruct the org ID
22
+ const POLAR_ORGANIZATION_ID = `${ORG_PART_1}-${ORG_PART_2}-${ORG_PART_3}-${ORG_PART_4}-${ORG_PART_5}`;
23
+ // Checksum to detect tampering (SHA256 of the org ID)
24
+ const EXPECTED_CHECKSUM = 'fb75c43b43948f2f4727def0b8a0eb66322706299b5807ea9faf55b4ff12950e';
25
+ // Validate integrity at module load
26
+ function validateIntegrity() {
27
+ const actualChecksum = createHash('sha256').update(POLAR_ORGANIZATION_ID).digest('hex');
28
+ return actualChecksum === EXPECTED_CHECKSUM;
29
+ }
30
+ // Sandbox org ID for testing (split similarly)
31
+ const SANDBOX_PART_1 = '574925db';
32
+ const SANDBOX_PART_2 = '40b9';
33
+ const SANDBOX_PART_3 = '49dd';
34
+ const SANDBOX_PART_4 = 'a02e';
35
+ const SANDBOX_PART_5 = 'ceb1124367d3';
36
+ const SANDBOX_ORG_ID = `${SANDBOX_PART_1}-${SANDBOX_PART_2}-${SANDBOX_PART_3}-${SANDBOX_PART_4}-${SANDBOX_PART_5}`;
37
+ /**
38
+ * Detect if we're running from source (development) or installed npm package (production).
39
+ * Sandbox mode is ONLY allowed when running from source.
40
+ *
41
+ * Detection method: Check if our module path contains 'node_modules'.
42
+ * - Running from source: /Volumes/Code/Projects/MCP/clickup-mcp-server/build/license.js
43
+ * - Running from npm: /path/to/project/node_modules/@taazkareem/clickup-mcp-server/build/license.js
44
+ */
45
+ function isRunningFromSource() {
46
+ try {
47
+ // import.meta.url gives us the current module's file URL
48
+ const modulePath = new URL(import.meta.url).pathname;
49
+ // If path contains node_modules, we're installed as a package
50
+ return !modulePath.includes('node_modules');
51
+ }
52
+ catch {
53
+ // If we can't determine, assume production (safer)
54
+ return false;
55
+ }
56
+ }
57
+ // Allow sandbox override ONLY when running from source (development)
58
+ // When installed from npm, this will ALWAYS be false, even if env var is set
59
+ const IS_DEV_MODE = isRunningFromSource();
60
+ const USE_SANDBOX = IS_DEV_MODE && process.env.POLAR_USE_SANDBOX === 'true';
61
+ const ACTIVE_ORG_ID = USE_SANDBOX ? SANDBOX_ORG_ID : POLAR_ORGANIZATION_ID;
17
62
  // Auto-detect sandbox vs production based on org ID
18
- const SANDBOX_ORG_ID = '574925db-40b9-49dd-a02e-ceb1124367d3';
19
- const isSandbox = POLAR_ORGANIZATION_ID === SANDBOX_ORG_ID;
63
+ const isSandbox = ACTIVE_ORG_ID === SANDBOX_ORG_ID;
64
+ // Log mode on startup (helpful for debugging)
65
+ if (IS_DEV_MODE) {
66
+ console.log(`[License] Running from source (dev mode)${USE_SANDBOX ? ' - SANDBOX ENABLED' : ''}`);
67
+ }
68
+ else {
69
+ console.log('[License] Running from npm package (production mode)');
70
+ }
20
71
  // Polar.sh API endpoint for license validation (public, no auth required)
21
72
  const POLAR_API_BASE = isSandbox ? 'https://sandbox-api.polar.sh/v1' : 'https://api.polar.sh/v1';
22
- const POLAR_VALIDATE_URL = process.env.POLAR_API_URL || `${POLAR_API_BASE}/customer-portal/license-keys/validate`;
73
+ const POLAR_VALIDATE_URL = `${POLAR_API_BASE}/customer-portal/license-keys/validate`;
23
74
  // Purchase link for error messages
24
75
  const PURCHASE_URL = 'https://buy.polar.sh/polar_cl_3xQojQLgzQXKCLzsxc49YfL6z8hzSBBqh9ivy1qZdwW';
25
76
  // Global license status - validated once at startup
@@ -38,6 +89,11 @@ export function getLicenseStatus() {
38
89
  * Check if the license is valid
39
90
  */
40
91
  export function isLicenseValid() {
92
+ // Check integrity on every validation call
93
+ if (!validateIntegrity() && !USE_SANDBOX) {
94
+ logger.error('Code integrity check failed - possible tampering detected');
95
+ return false;
96
+ }
41
97
  return licenseStatus.isValid;
42
98
  }
43
99
  /**
@@ -74,6 +130,14 @@ Then restart your MCP client.
74
130
  * Validates a license key with Polar.sh
75
131
  */
76
132
  async function validateLicenseKey(licenseKey) {
133
+ // Check integrity first
134
+ if (!validateIntegrity() && !USE_SANDBOX) {
135
+ return {
136
+ isValid: false,
137
+ status: 'tampered',
138
+ errorMessage: 'Code integrity check failed. Please reinstall from official npm package.'
139
+ };
140
+ }
77
141
  if (!licenseKey || licenseKey.trim() === '') {
78
142
  return {
79
143
  isValid: false,
@@ -83,12 +147,12 @@ async function validateLicenseKey(licenseKey) {
83
147
  }
84
148
  try {
85
149
  logger.debug(`Validating license key with Polar.sh...`);
86
- logger.debug(`PI URL: ${POLAR_VALIDATE_URL}`);
87
- logger.debug(`Org ID: ${POLAR_ORGANIZATION_ID}`);
150
+ logger.debug(`API URL: ${POLAR_VALIDATE_URL}`);
151
+ logger.debug(`Org ID: ${ACTIVE_ORG_ID.substring(0, 8)}...`);
88
152
  logger.debug(`Key: ${licenseKey.substring(0, 10)}...`);
89
153
  const response = await axios.post(POLAR_VALIDATE_URL, {
90
154
  key: licenseKey.trim(),
91
- organization_id: POLAR_ORGANIZATION_ID
155
+ organization_id: ACTIVE_ORG_ID
92
156
  }, {
93
157
  headers: {
94
158
  'Content-Type': 'application/json'
@@ -148,12 +212,13 @@ async function validateLicenseKey(licenseKey) {
148
212
  };
149
213
  }
150
214
  }
151
- // Network or other errors - fail open with warning to avoid blocking due to temporary issues
152
- logger.warn('Could not validate license key due to network error. Allowing access.');
215
+ // SECURITY CHANGE: Fail CLOSED instead of open
216
+ // If we can't validate, deny access (prevents network blocking bypass)
217
+ logger.error('Could not validate license key due to network error. DENYING access for security.');
153
218
  return {
154
- isValid: true,
155
- status: 'granted',
156
- errorMessage: 'License validation skipped due to network error (allowing access)'
219
+ isValid: false,
220
+ status: 'error',
221
+ errorMessage: 'License validation failed - please check your network connection and try again'
157
222
  };
158
223
  }
159
224
  }
package/build/server.js CHANGED
@@ -46,7 +46,7 @@ const isToolEnabled = (toolName) => {
46
46
  };
47
47
  export const server = new Server({
48
48
  name: "clickup-mcp-server",
49
- version: "0.11.1",
49
+ version: "0.11.2",
50
50
  }, {
51
51
  capabilities: {
52
52
  tools: {},
@@ -82,8 +82,8 @@ export class TaskServiceComments {
82
82
  * @param assignee Optional user ID to assign the comment to
83
83
  * @returns The created comment
84
84
  */
85
- async createTaskComment(taskId, commentText, notifyAll = false, assignee) {
86
- this.core.logOperation('createTaskComment', { taskId, commentText, notifyAll, assignee });
85
+ async createTaskComment(taskId, commentText, notifyAll = false, assignee, formattedComment) {
86
+ this.core.logOperation('createTaskComment', { taskId, commentText, notifyAll, assignee, hasFormattedComment: !!formattedComment });
87
87
  try {
88
88
  const payload = {
89
89
  comment_text: commentText,
@@ -92,6 +92,9 @@ export class TaskServiceComments {
92
92
  if (assignee) {
93
93
  payload.assignee = assignee;
94
94
  }
95
+ if (formattedComment) {
96
+ payload.comment = formattedComment;
97
+ }
95
98
  // Make the request directly without using makeRequest for better error handling
96
99
  const response = await this.core.client.post(`/task/${taskId}/comment`, payload);
97
100
  // Handle different response formats from ClickUp API
@@ -74,8 +74,8 @@ export class TaskService extends TaskServiceCore {
74
74
  async getTaskComments(taskId, start, startId, includeReplies = false) {
75
75
  return this.comments.getTaskComments(taskId, start, startId, includeReplies);
76
76
  }
77
- async createTaskComment(taskId, commentText, notifyAll, assignee) {
78
- return this.comments.createTaskComment(taskId, commentText, notifyAll, assignee);
77
+ async createTaskComment(taskId, commentText, notifyAll, assignee, formattedComment) {
78
+ return this.comments.createTaskComment(taskId, commentText, notifyAll, assignee, formattedComment);
79
79
  }
80
80
  // ===== DELEGATED TAG METHODS =====
81
81
  async addTagToTask(taskId, tagName) {
@@ -124,6 +124,7 @@ export const createBulkTasksTool = {
124
124
  description: "ID of the custom field"
125
125
  },
126
126
  value: {
127
+ type: "string",
127
128
  description: "Value for the custom field. Type depends on the field type."
128
129
  }
129
130
  },
@@ -225,6 +226,7 @@ export const updateBulkTasksTool = {
225
226
  description: "ID of the custom field"
226
227
  },
227
228
  value: {
229
+ type: "string",
228
230
  description: "Value for the custom field. Type depends on the field type."
229
231
  }
230
232
  },
@@ -572,17 +572,17 @@ export async function getTaskCommentsHandler(params) {
572
572
  * Handler for creating a task comment
573
573
  */
574
574
  export async function createTaskCommentHandler(params) {
575
- // Validate required parameters
576
- if (!params.commentText) {
577
- throw new Error('Comment text is required');
575
+ // Validate required parameters - either commentText or formattedComment must be provided
576
+ if (!params.commentText && !params.formattedComment) {
577
+ throw new Error('Either comment text or formatted comment is required');
578
578
  }
579
579
  try {
580
580
  // Resolve the task ID
581
581
  const taskId = await getTaskId(params.taskId, params.taskName, params.listName);
582
582
  // Extract other parameters with defaults
583
- const { commentText, notifyAll = false, assignee = null } = params;
583
+ const { commentText = '', formattedComment, notifyAll = false, assignee = null } = params;
584
584
  // Create the comment
585
- return await taskService.createTaskComment(taskId, commentText, notifyAll, assignee);
585
+ return await taskService.createTaskComment(taskId, commentText, notifyAll, assignee, formattedComment);
586
586
  }
587
587
  catch (error) {
588
588
  // If this is a task lookup error, provide more helpful message
@@ -109,6 +109,7 @@ export const createTaskTool = {
109
109
  description: "ID of the custom field"
110
110
  },
111
111
  value: {
112
+ type: "string",
112
113
  description: "Value for the custom field. Type depends on the field type."
113
114
  }
114
115
  },
@@ -203,6 +204,7 @@ export const updateTaskTool = {
203
204
  description: "ID of the custom field"
204
205
  },
205
206
  value: {
207
+ type: "string",
206
208
  description: "Value for the custom field. Type depends on the field type."
207
209
  }
208
210
  },
@@ -420,7 +422,7 @@ export const getTaskCommentsTool = {
420
422
  */
421
423
  export const createTaskCommentTool = {
422
424
  name: "create_task_comment",
423
- description: `Creates task comment. Use taskId (preferred) or taskName + listName. Required: commentText. Optional: notifyAll to notify assignees, assignee to assign comment.`,
425
+ description: `Creates task comment with optional formatting. Use taskId (preferred) or taskName + listName. Required: commentText OR formattedComment. Optional: notifyAll to notify assignees, assignee to assign comment.`,
424
426
  inputSchema: {
425
427
  type: "object",
426
428
  properties: {
@@ -438,7 +440,162 @@ export const createTaskCommentTool = {
438
440
  },
439
441
  commentText: {
440
442
  type: "string",
441
- description: "REQUIRED: Text content of the comment to create."
443
+ description: "Plain text content of the comment. Required if formattedComment is not provided."
444
+ },
445
+ formattedComment: {
446
+ type: "array",
447
+ description: "Formatted comment with rich text support. Array of comment blocks supporting text styling, lists, links, mentions, etc. Takes precedence over commentText when provided.",
448
+ items: {
449
+ type: "object",
450
+ oneOf: [
451
+ {
452
+ description: "Text block with optional formatting and links",
453
+ properties: {
454
+ text: {
455
+ type: "string",
456
+ description: "Text content"
457
+ },
458
+ attributes: {
459
+ type: "object",
460
+ properties: {
461
+ bold: { type: "boolean" },
462
+ italic: { type: "boolean" },
463
+ underline: { type: "boolean" },
464
+ strikethrough: { type: "boolean" },
465
+ code: { type: "boolean" },
466
+ color: { type: "string" },
467
+ background_color: { type: "string" },
468
+ link: { type: "string", description: "URL for hyperlinks" }
469
+ },
470
+ description: "Text formatting attributes. Use 'link' for hyperlinks."
471
+ }
472
+ },
473
+ required: ["text"]
474
+ },
475
+ {
476
+ description: "User mention (@mention)",
477
+ properties: {
478
+ type: { type: "string", enum: ["tag"] },
479
+ user: {
480
+ type: "object",
481
+ properties: {
482
+ id: { type: "number", description: "ClickUp user ID" }
483
+ },
484
+ required: ["id"]
485
+ }
486
+ },
487
+ required: ["type", "user"]
488
+ },
489
+ {
490
+ description: "Emoji",
491
+ properties: {
492
+ type: { type: "string", enum: ["emoji"] },
493
+ unicode: { type: "string", description: "Unicode hex value" }
494
+ },
495
+ required: ["type", "unicode"]
496
+ },
497
+ {
498
+ description: "Code block",
499
+ properties: {
500
+ type: { type: "string", enum: ["code_block"] },
501
+ text: { type: "string", description: "Code content" },
502
+ language: { type: "string", description: "Programming language (optional)" }
503
+ },
504
+ required: ["type", "text"]
505
+ },
506
+ {
507
+ description: "Bulleted list",
508
+ properties: {
509
+ type: { type: "string", enum: ["bulleted_list"] },
510
+ items: {
511
+ type: "array",
512
+ items: {
513
+ type: "object",
514
+ properties: {
515
+ text: { type: "string" },
516
+ attributes: {
517
+ type: "object",
518
+ properties: {
519
+ bold: { type: "boolean" },
520
+ italic: { type: "boolean" },
521
+ underline: { type: "boolean" },
522
+ strikethrough: { type: "boolean" },
523
+ code: { type: "boolean" },
524
+ color: { type: "string" },
525
+ background_color: { type: "string" },
526
+ link: { type: "string" }
527
+ }
528
+ }
529
+ },
530
+ required: ["text"]
531
+ }
532
+ }
533
+ },
534
+ required: ["type", "items"]
535
+ },
536
+ {
537
+ description: "Numbered list",
538
+ properties: {
539
+ type: { type: "string", enum: ["numbered_list"] },
540
+ items: {
541
+ type: "array",
542
+ items: {
543
+ type: "object",
544
+ properties: {
545
+ text: { type: "string" },
546
+ attributes: {
547
+ type: "object",
548
+ properties: {
549
+ bold: { type: "boolean" },
550
+ italic: { type: "boolean" },
551
+ underline: { type: "boolean" },
552
+ strikethrough: { type: "boolean" },
553
+ code: { type: "boolean" },
554
+ color: { type: "string" },
555
+ background_color: { type: "string" },
556
+ link: { type: "string" }
557
+ }
558
+ }
559
+ },
560
+ required: ["text"]
561
+ }
562
+ }
563
+ },
564
+ required: ["type", "items"]
565
+ },
566
+ {
567
+ description: "Checklist",
568
+ properties: {
569
+ type: { type: "string", enum: ["checklist"] },
570
+ items: {
571
+ type: "array",
572
+ items: {
573
+ type: "object",
574
+ properties: {
575
+ text: { type: "string" },
576
+ checked: { type: "boolean", description: "Whether item is checked" },
577
+ attributes: {
578
+ type: "object",
579
+ properties: {
580
+ bold: { type: "boolean" },
581
+ italic: { type: "boolean" },
582
+ underline: { type: "boolean" },
583
+ strikethrough: { type: "boolean" },
584
+ code: { type: "boolean" },
585
+ color: { type: "string" },
586
+ background_color: { type: "string" },
587
+ link: { type: "string" }
588
+ }
589
+ }
590
+ },
591
+ required: ["text"]
592
+ }
593
+ }
594
+ },
595
+ required: ["type", "items"]
596
+ }
597
+ ]
598
+ }
442
599
  },
443
600
  notifyAll: {
444
601
  type: "boolean",
@@ -449,7 +606,7 @@ export const createTaskCommentTool = {
449
606
  description: "Optional user ID to assign the comment to."
450
607
  }
451
608
  },
452
- required: ["commentText"]
609
+ required: []
453
610
  }
454
611
  };
455
612
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
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",