@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 +80 -15
- package/build/server.js +1 -1
- package/build/services/clickup/task/task-comments.js +5 -2
- package/build/services/clickup/task/task-service.js +2 -2
- package/build/tools/task/bulk-operations.js +2 -0
- package/build/tools/task/handlers.js +5 -5
- package/build/tools/task/single-operations.js +160 -3
- package/package.json +1 -1
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
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
const
|
|
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
|
|
19
|
-
|
|
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 =
|
|
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(`
|
|
87
|
-
logger.debug(`Org 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:
|
|
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
|
-
//
|
|
152
|
-
|
|
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:
|
|
155
|
-
status: '
|
|
156
|
-
errorMessage: 'License validation
|
|
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
|
@@ -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('
|
|
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: "
|
|
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: [
|
|
609
|
+
required: []
|
|
453
610
|
}
|
|
454
611
|
};
|
|
455
612
|
/**
|
package/package.json
CHANGED