@taazkareem/clickup-mcp-server 0.6.0 → 0.6.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.
Files changed (46) hide show
  1. package/README.md +14 -23
  2. package/build/config.js +34 -2
  3. package/build/index.js +3 -0
  4. package/build/logger.js +8 -38
  5. package/build/mcp-tools.js +64 -0
  6. package/build/server-state.js +93 -0
  7. package/build/server.js +33 -8
  8. package/build/server.log +0 -0
  9. package/build/services/clickup/base.js +3 -0
  10. package/build/services/clickup/bulk.js +3 -0
  11. package/build/services/clickup/folder.js +3 -0
  12. package/build/services/clickup/index.js +9 -1
  13. package/build/services/clickup/list.js +3 -0
  14. package/build/services/clickup/tag.js +190 -0
  15. package/build/services/clickup/task.js +138 -0
  16. package/build/services/clickup/types.js +3 -0
  17. package/build/services/clickup/workspace.js +3 -0
  18. package/build/services/shared.js +3 -0
  19. package/build/tools/bulk-tasks.js +36 -0
  20. package/build/tools/debug.js +76 -0
  21. package/build/tools/folder.js +3 -0
  22. package/build/tools/index.js +4 -0
  23. package/build/tools/list.js +3 -0
  24. package/build/tools/logs.js +55 -0
  25. package/build/tools/tag.js +824 -0
  26. package/build/tools/task/attachments.js +3 -0
  27. package/build/tools/task/bulk-operations.js +10 -0
  28. package/build/tools/task/handlers.js +61 -2
  29. package/build/tools/task/index.js +8 -1
  30. package/build/tools/task/main.js +18 -2
  31. package/build/tools/task/single-operations.js +10 -0
  32. package/build/tools/task/utilities.js +40 -3
  33. package/build/tools/task/workspace-operations.js +222 -0
  34. package/build/tools/task.js +1554 -0
  35. package/build/tools/utils.js +3 -0
  36. package/build/tools/workspace.js +3 -0
  37. package/build/utils/color-processor.js +183 -0
  38. package/build/utils/concurrency-utils.js +3 -0
  39. package/build/utils/date-utils.js +3 -0
  40. package/build/utils/params-utils.js +39 -0
  41. package/build/utils/resolver-utils.js +3 -0
  42. package/build/utils/sponsor-analytics.js +100 -0
  43. package/build/utils/sponsor-service.js +3 -0
  44. package/build/utils/sponsor-utils.js +57 -0
  45. package/build/utils/token-utils.js +49 -0
  46. package/package.json +1 -1
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Utility functions for ClickUp MCP tools
3
6
  *
4
7
  * Re-exports specialized utilities from dedicated modules.
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * ClickUp MCP Workspace Tools
3
6
  *
4
7
  * This module defines workspace-related tools like retrieving workspace hierarchy.
@@ -0,0 +1,183 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Color Processor Utility
6
+ *
7
+ * Processes natural language color commands and converts them to HEX color values.
8
+ * Also generates appropriate foreground (text) colors for optimal contrast.
9
+ */
10
+ // Basic color mapping with common color names to their HEX values
11
+ const COLOR_MAP = {
12
+ // Primary colors
13
+ red: '#FF0000',
14
+ green: '#00FF00',
15
+ blue: '#0000FF',
16
+ // Secondary colors
17
+ yellow: '#FFFF00',
18
+ purple: '#800080',
19
+ orange: '#FFA500',
20
+ pink: '#FFC0CB',
21
+ brown: '#A52A2A',
22
+ // Neutrals
23
+ black: '#000000',
24
+ white: '#FFFFFF',
25
+ gray: '#808080',
26
+ grey: '#808080',
27
+ // Extended colors
28
+ navy: '#000080',
29
+ teal: '#008080',
30
+ olive: '#808000',
31
+ maroon: '#800000',
32
+ aqua: '#00FFFF',
33
+ cyan: '#00FFFF',
34
+ magenta: '#FF00FF',
35
+ fuchsia: '#FF00FF',
36
+ lime: '#00FF00',
37
+ indigo: '#4B0082',
38
+ violet: '#EE82EE',
39
+ gold: '#FFD700',
40
+ silver: '#C0C0C0',
41
+ beige: '#F5F5DC',
42
+ tan: '#D2B48C',
43
+ coral: '#FF7F50',
44
+ crimson: '#DC143C',
45
+ khaki: '#F0E68C',
46
+ lavender: '#E6E6FA',
47
+ plum: '#DDA0DD',
48
+ salmon: '#FA8072',
49
+ turquoise: '#40E0D0',
50
+ };
51
+ // Extended color variations
52
+ const COLOR_VARIATIONS = {
53
+ red: {
54
+ light: '#FF6666',
55
+ dark: '#8B0000',
56
+ bright: '#FF0000',
57
+ deep: '#8B0000',
58
+ },
59
+ blue: {
60
+ light: '#ADD8E6',
61
+ dark: '#00008B',
62
+ sky: '#87CEEB',
63
+ navy: '#000080',
64
+ royal: '#4169E1',
65
+ deep: '#00008B',
66
+ },
67
+ green: {
68
+ light: '#90EE90',
69
+ dark: '#006400',
70
+ forest: '#228B22',
71
+ lime: '#32CD32',
72
+ mint: '#98FB98',
73
+ olive: '#808000',
74
+ },
75
+ yellow: {
76
+ light: '#FFFFE0',
77
+ dark: '#BDB76B',
78
+ pale: '#FFF9C4',
79
+ gold: '#FFD700',
80
+ lemon: '#FFFACD',
81
+ },
82
+ // Add more variations for other colors as needed
83
+ };
84
+ /**
85
+ * Extracts a color name from natural language text
86
+ * @param text - Natural language text that contains a color reference
87
+ * @returns The extracted color name or null if no color is found
88
+ */
89
+ export function extractColorFromText(text) {
90
+ if (!text)
91
+ return null;
92
+ // Convert to lowercase for case-insensitive matching
93
+ const lowercaseText = text.toLowerCase();
94
+ // First check for color variations (e.g., "dark blue", "light green")
95
+ for (const [baseColor, variations] of Object.entries(COLOR_VARIATIONS)) {
96
+ for (const [variation, _] of Object.entries(variations)) {
97
+ const colorPhrase = `${variation} ${baseColor}`;
98
+ if (lowercaseText.includes(colorPhrase)) {
99
+ return colorPhrase;
100
+ }
101
+ }
102
+ }
103
+ // Then check for base colors
104
+ for (const color of Object.keys(COLOR_MAP)) {
105
+ // Use word boundary to make sure we're matching whole words
106
+ const regex = new RegExp(`\\b${color}\\b`, 'i');
107
+ if (regex.test(lowercaseText)) {
108
+ return color;
109
+ }
110
+ }
111
+ return null;
112
+ }
113
+ /**
114
+ * Converts a color name to its HEX value
115
+ * @param colorName - Name of the color to convert (e.g., "blue", "dark red")
116
+ * @returns HEX color code or null if color name is not recognized
117
+ */
118
+ export function colorNameToHex(colorName) {
119
+ if (!colorName)
120
+ return null;
121
+ const lowercaseColor = colorName.toLowerCase();
122
+ // Check if it's a color variation (e.g., "dark blue")
123
+ const parts = lowercaseColor.split(' ');
124
+ if (parts.length === 2) {
125
+ const variation = parts[0];
126
+ const baseColor = parts[1];
127
+ if (COLOR_VARIATIONS[baseColor] && COLOR_VARIATIONS[baseColor][variation]) {
128
+ return COLOR_VARIATIONS[baseColor][variation];
129
+ }
130
+ }
131
+ // Check if it's a base color
132
+ return COLOR_MAP[lowercaseColor] || null;
133
+ }
134
+ /**
135
+ * Calculates the relative luminance of a color for WCAG contrast calculations
136
+ * @param hex - HEX color code
137
+ * @returns Relative luminance value
138
+ */
139
+ function calculateLuminance(hex) {
140
+ // Remove # if present
141
+ const color = hex.startsWith('#') ? hex.slice(1) : hex;
142
+ // Convert HEX to RGB
143
+ const r = parseInt(color.substr(0, 2), 16) / 255;
144
+ const g = parseInt(color.substr(2, 2), 16) / 255;
145
+ const b = parseInt(color.substr(4, 2), 16) / 255;
146
+ // Calculate luminance using the formula from WCAG 2.0
147
+ const R = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
148
+ const G = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
149
+ const B = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
150
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B;
151
+ }
152
+ /**
153
+ * Generates a contrasting foreground color for optimal readability
154
+ * @param backgroundColor - HEX code of the background color
155
+ * @returns HEX code of the foreground color (either black or white)
156
+ */
157
+ export function generateContrastingForeground(backgroundColor) {
158
+ const luminance = calculateLuminance(backgroundColor);
159
+ // Use white text on dark backgrounds and black text on light backgrounds
160
+ // The threshold 0.5 is based on WCAG guidelines for contrast
161
+ return luminance > 0.5 ? '#000000' : '#FFFFFF';
162
+ }
163
+ /**
164
+ * Processes a natural language command to extract a color and convert it to HEX values
165
+ * @param command - Natural language command (e.g., "Create a blue tag")
166
+ * @returns Object containing background and foreground HEX colors, or null if color not recognized
167
+ */
168
+ export function processColorCommand(command) {
169
+ // Extract color name from command
170
+ const colorName = extractColorFromText(command);
171
+ if (!colorName)
172
+ return null;
173
+ // Convert color name to HEX background color
174
+ const backgroundColor = colorNameToHex(colorName);
175
+ if (!backgroundColor)
176
+ return null;
177
+ // Generate appropriate foreground color
178
+ const foregroundColor = generateContrastingForeground(backgroundColor);
179
+ return {
180
+ background: backgroundColor,
181
+ foreground: foregroundColor
182
+ };
183
+ }
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Concurrency Utilities
3
6
  *
4
7
  * This module provides utilities for handling concurrent operations,
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Date Utility Functions
3
6
  *
4
7
  * This module provides utilities for handling dates, timestamps, and due date parsing.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Parameter Processing Utilities
3
+ *
4
+ * Utilities for processing MCP tool parameters, especially handling
5
+ * JSON string conversion for array and object parameters.
6
+ */
7
+ import { Logger } from '../logger.js';
8
+ // Create a logger instance
9
+ const logger = new Logger('ParamsUtils');
10
+ /**
11
+ * Process parameters that might contain JSON strings
12
+ * Handles cases where the MCP protocol sends stringified JSON for array or object parameters
13
+ *
14
+ * @param params The raw parameters received from the MCP tool call
15
+ * @returns Processed parameters with JSON strings parsed to objects/arrays
16
+ */
17
+ export function processParams(params) {
18
+ if (!params)
19
+ return {};
20
+ const result = { ...params };
21
+ // Process special parameters that could be JSON strings
22
+ const jsonFields = ['tasks', 'options'];
23
+ for (const field of jsonFields) {
24
+ if (typeof result[field] === 'string') {
25
+ try {
26
+ if ((result[field].startsWith('[') && result[field].endsWith(']')) ||
27
+ (result[field].startsWith('{') && result[field].endsWith('}'))) {
28
+ result[field] = JSON.parse(result[field]);
29
+ logger.debug(`Parsed JSON parameter: ${field}`);
30
+ }
31
+ }
32
+ catch (error) {
33
+ logger.error(`Failed to parse JSON for ${field}`, { error });
34
+ // Keep original string if parse fails
35
+ }
36
+ }
37
+ }
38
+ return result;
39
+ }
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Resolver Utility Functions
3
6
  *
4
7
  * This module provides utilities for resolving entity IDs from names or other identifiers.
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Sponsor Analytics Module
3
+ *
4
+ * This module provides analytics tracking for sponsor messages, including:
5
+ * - Impression tracking
6
+ * - URL tracking
7
+ * - Analytics aggregation
8
+ */
9
+ import { Logger } from '../logger.js';
10
+ import config from '../config.js';
11
+ // Create logger instance for this module
12
+ const logger = new Logger('SponsorAnalytics');
13
+ // Initialize analytics storage
14
+ const analytics = {
15
+ impressions: {
16
+ total: 0,
17
+ byEndpoint: {},
18
+ byDate: {}
19
+ },
20
+ urlClicks: {
21
+ total: 0,
22
+ byUrl: {}
23
+ }
24
+ };
25
+ /**
26
+ * Track a sponsor message impression
27
+ * @param endpoint The endpoint/operation where the message was shown
28
+ */
29
+ export function trackImpression(endpoint) {
30
+ if (!config.enableSponsorMessage)
31
+ return;
32
+ const today = new Date().toISOString().split('T')[0];
33
+ // Update total impressions
34
+ analytics.impressions.total++;
35
+ // Update impressions by endpoint
36
+ analytics.impressions.byEndpoint[endpoint] =
37
+ (analytics.impressions.byEndpoint[endpoint] || 0) + 1;
38
+ // Update impressions by date
39
+ analytics.impressions.byDate[today] =
40
+ (analytics.impressions.byDate[today] || 0) + 1;
41
+ logger.debug('Sponsor message impression tracked', {
42
+ endpoint,
43
+ total: analytics.impressions.total,
44
+ endpointTotal: analytics.impressions.byEndpoint[endpoint],
45
+ dateTotal: analytics.impressions.byDate[today]
46
+ });
47
+ }
48
+ /**
49
+ * Generate a tracking URL for the sponsor link
50
+ * @param originalUrl The original sponsor URL
51
+ * @param source The source/endpoint that generated the URL
52
+ * @returns Tracking URL
53
+ */
54
+ export function generateTrackingUrl(originalUrl, source) {
55
+ // Add UTM parameters for tracking
56
+ const url = new URL(originalUrl);
57
+ // If it's a GitHub profile URL, modify it to be a sponsor URL
58
+ if (url.pathname === '/taazkareem') {
59
+ url.pathname = '/sponsors/taazkareem';
60
+ }
61
+ // Add minimal tracking parameters
62
+ url.searchParams.set('ref', source);
63
+ return url.toString();
64
+ }
65
+ /**
66
+ * Track a sponsor URL click (called when tracking URL is generated)
67
+ * @param url The tracking URL that was generated
68
+ */
69
+ export function trackUrlGenerated(url) {
70
+ if (!config.enableSponsorMessage)
71
+ return;
72
+ // Update total clicks
73
+ analytics.urlClicks.total++;
74
+ // Update clicks by URL
75
+ analytics.urlClicks.byUrl[url] =
76
+ (analytics.urlClicks.byUrl[url] || 0) + 1;
77
+ logger.debug('Sponsor URL tracking link generated', {
78
+ url,
79
+ total: analytics.urlClicks.total,
80
+ urlTotal: analytics.urlClicks.byUrl[url]
81
+ });
82
+ }
83
+ /**
84
+ * Get current analytics data
85
+ * @returns Copy of current analytics data
86
+ */
87
+ export function getAnalytics() {
88
+ return JSON.parse(JSON.stringify(analytics));
89
+ }
90
+ /**
91
+ * Reset analytics data
92
+ */
93
+ export function resetAnalytics() {
94
+ analytics.impressions.total = 0;
95
+ analytics.impressions.byEndpoint = {};
96
+ analytics.impressions.byDate = {};
97
+ analytics.urlClicks.total = 0;
98
+ analytics.urlClicks.byUrl = {};
99
+ logger.info('Sponsor analytics data reset');
100
+ }
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
2
5
  * Sponsor Service Module
3
6
  *
4
7
  * Provides configuration and utilities for sponsorship functionality
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Sponsor Utility Functions
3
+ *
4
+ * This module provides utilities for adding sponsor information to responses.
5
+ */
6
+ import config from '../config.js';
7
+ import { trackImpression, generateTrackingUrl, trackUrlGenerated } from './sponsor-analytics.js';
8
+ /**
9
+ * Generate a sponsor message to be included in task responses
10
+ *
11
+ * @param source The source/endpoint requesting the sponsor message
12
+ * @returns Object containing sponsor message and URL, or null if sponsor messages are disabled
13
+ */
14
+ export function getSponsorMessage(source = 'unknown') {
15
+ // Skip if sponsor message is disabled
16
+ if (!config.enableSponsorMessage) {
17
+ return null;
18
+ }
19
+ // Generate tracking URL
20
+ const trackingUrl = generateTrackingUrl(config.sponsorUrl, source);
21
+ trackUrlGenerated(trackingUrl);
22
+ return {
23
+ message: "❤️ Support this project: If you find this integration valuable, please consider sponsoring the developer.",
24
+ url: trackingUrl
25
+ };
26
+ }
27
+ /**
28
+ * Enhances a task response with sponsor information if enabled
29
+ *
30
+ * @param taskResponse The original task response to enhance
31
+ * @param source The source/endpoint of the response
32
+ * @returns Enhanced task response with sponsor information
33
+ */
34
+ export function enhanceResponseWithSponsor(taskResponse, source = 'unknown') {
35
+ // Skip if sponsor message is disabled
36
+ if (!config.enableSponsorMessage) {
37
+ return taskResponse;
38
+ }
39
+ const sponsorInfo = getSponsorMessage(source);
40
+ if (!sponsorInfo) {
41
+ return taskResponse;
42
+ }
43
+ // Track the impression
44
+ trackImpression(source);
45
+ // Create a new response with sponsor information
46
+ const enhancedResponse = {
47
+ ...taskResponse,
48
+ content: [
49
+ ...(taskResponse.content || []),
50
+ {
51
+ type: "text",
52
+ text: `\n\n${sponsorInfo.message}\n${sponsorInfo.url}`
53
+ }
54
+ ]
55
+ };
56
+ return enhancedResponse;
57
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Token Utilities
6
+ *
7
+ * Functions for estimating token counts for LLM processing
8
+ */
9
+ /**
10
+ * Simple heuristic to estimate token count from text
11
+ * Based on the approximate ratio of 4 characters per token for English text
12
+ *
13
+ * @param text Text to estimate tokens for
14
+ * @returns Estimated token count
15
+ */
16
+ export function estimateTokensFromText(text) {
17
+ if (!text)
18
+ return 0;
19
+ // Characters per token varies by language, but ~4 chars per token
20
+ // is a reasonable approximation for English text
21
+ const CHARS_PER_TOKEN = 4;
22
+ // Add some overhead for non-text elements and special tokens
23
+ const OVERHEAD_FACTOR = 1.1;
24
+ return Math.ceil((text.length / CHARS_PER_TOKEN) * OVERHEAD_FACTOR);
25
+ }
26
+ /**
27
+ * Estimate tokens for a JSON object
28
+ *
29
+ * @param obj Object to estimate tokens for
30
+ * @returns Estimated token count
31
+ */
32
+ export function estimateTokensFromObject(obj) {
33
+ // Convert to JSON string
34
+ const jsonString = JSON.stringify(obj);
35
+ // Use text estimation on the JSON string
36
+ // JSON has more special chars than plain text, so we adjust overhead
37
+ return Math.ceil(estimateTokensFromText(jsonString) * 1.2);
38
+ }
39
+ /**
40
+ * Check if an object would exceed the token limit when serialized to JSON
41
+ *
42
+ * @param obj Object to check
43
+ * @param tokenLimit Token limit to check against
44
+ * @returns Whether the object would exceed the token limit
45
+ */
46
+ export function wouldExceedTokenLimit(obj, tokenLimit) {
47
+ const estimatedTokens = estimateTokensFromObject(obj);
48
+ return estimatedTokens > tokenLimit;
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.6.0",
3
+ "version": "0.6.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",