@taazkareem/clickup-mcp-server 0.4.72 → 0.4.74

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.
@@ -5,7 +5,5 @@
5
5
  */
6
6
  // Re-export date utilities
7
7
  export { getRelativeTimestamp, parseDueDate, formatDueDate } from '../utils/date-utils.js';
8
- // Re-export sponsor utilities
9
- export { getSponsorMessage, enhanceResponseWithSponsor } from '../utils/sponsor-utils.js';
10
8
  // Re-export resolver utilities
11
9
  export { resolveListId, resolveTaskId } from '../utils/resolver-utils.js';
@@ -5,6 +5,7 @@
5
5
  * It handles the workspace tool definitions and the implementation of their handlers.
6
6
  */
7
7
  import { Logger } from '../logger.js';
8
+ import { sponsorService } from '../utils/sponsor-service.js';
8
9
  // Create a logger for workspace tools
9
10
  const logger = new Logger('WorkspaceTool');
10
11
  // Use the workspace service imported from the server
@@ -15,7 +16,18 @@ let workspaceService;
15
16
  */
16
17
  export const workspaceHierarchyTool = {
17
18
  name: 'get_workspace_hierarchy',
18
- description: 'Get the complete workspace hierarchy including spaces, folders, and lists.',
19
+ description: `Purpose: Retrieve the complete workspace hierarchy including spaces, folders, and lists.
20
+
21
+ Valid Usage:
22
+ 1. Call without parameters to get the full hierarchy
23
+
24
+ Requirements:
25
+ - No parameters required
26
+
27
+ Notes:
28
+ - Returns a tree structure showing all spaces, folders, and lists
29
+ - Each item includes its name and ID
30
+ - Use this to navigate the workspace and understand its organization`,
19
31
  inputSchema: {
20
32
  type: 'object',
21
33
  properties: {}
@@ -42,18 +54,14 @@ export async function handleGetWorkspaceHierarchy() {
42
54
  try {
43
55
  // Get workspace hierarchy from the workspace service
44
56
  const hierarchy = await workspaceService.getWorkspaceHierarchy();
45
- const response = formatHierarchyResponse(hierarchy);
46
- return response;
57
+ // Generate tree representation
58
+ const treeOutput = formatTreeOutput(hierarchy);
59
+ return sponsorService.createResponse({
60
+ hierarchy: treeOutput
61
+ }, true);
47
62
  }
48
63
  catch (error) {
49
- return {
50
- content: [
51
- {
52
- type: "text",
53
- text: `Error getting workspace hierarchy: ${error.message}`
54
- }
55
- ]
56
- };
64
+ return sponsorService.createErrorResponse(`Error getting workspace hierarchy: ${error.message}`);
57
65
  }
58
66
  }
59
67
  /**
@@ -61,27 +69,7 @@ export async function handleGetWorkspaceHierarchy() {
61
69
  */
62
70
  function formatHierarchyResponse(hierarchy) {
63
71
  try {
64
- // Helper function to format a node and its children as a tree
65
- const formatNodeAsTree = (node, level = 0, isLast = true, parentPrefix = '') => {
66
- const lines = [];
67
- // Calculate the current line's prefix
68
- const currentPrefix = level === 0 ? '' : parentPrefix + (isLast ? '└── ' : '├── ');
69
- // Format current node with descriptive ID type
70
- const idType = 'type' in node ? `${node.type.charAt(0).toUpperCase() + node.type.slice(1)} ID` : 'Workspace ID';
71
- lines.push(`${currentPrefix}${node.name} (${idType}: ${node.id})`);
72
- // Calculate the prefix for children
73
- const childPrefix = level === 0 ? '' : parentPrefix + (isLast ? ' ' : '│ ');
74
- // Process children
75
- const children = node.children || [];
76
- children.forEach((child, index) => {
77
- const childLines = formatNodeAsTree(child, level + 1, index === children.length - 1, childPrefix);
78
- lines.push(...childLines);
79
- });
80
- return lines;
81
- };
82
- // Generate tree representation
83
- const treeLines = formatNodeAsTree(hierarchy.root);
84
- const treeOutput = treeLines.join('\n');
72
+ const treeOutput = formatTreeOutput(hierarchy);
85
73
  return {
86
74
  content: [
87
75
  {
@@ -102,3 +90,29 @@ function formatHierarchyResponse(hierarchy) {
102
90
  };
103
91
  }
104
92
  }
93
+ /**
94
+ * Format the hierarchy as a tree string
95
+ */
96
+ function formatTreeOutput(hierarchy) {
97
+ // Helper function to format a node and its children as a tree
98
+ const formatNodeAsTree = (node, level = 0, isLast = true, parentPrefix = '') => {
99
+ const lines = [];
100
+ // Calculate the current line's prefix
101
+ const currentPrefix = level === 0 ? '' : parentPrefix + (isLast ? '└── ' : '├── ');
102
+ // Format current node with descriptive ID type
103
+ const idType = 'type' in node ? `${node.type.charAt(0).toUpperCase() + node.type.slice(1)} ID` : 'Workspace ID';
104
+ lines.push(`${currentPrefix}${node.name} (${idType}: ${node.id})`);
105
+ // Calculate the prefix for children
106
+ const childPrefix = level === 0 ? '' : parentPrefix + (isLast ? ' ' : '│ ');
107
+ // Process children
108
+ const children = node.children || [];
109
+ children.forEach((child, index) => {
110
+ const childLines = formatNodeAsTree(child, level + 1, index === children.length - 1, childPrefix);
111
+ lines.push(...childLines);
112
+ });
113
+ return lines;
114
+ };
115
+ // Generate tree representation
116
+ const treeLines = formatNodeAsTree(hierarchy.root);
117
+ return treeLines.join('\n');
118
+ }
@@ -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
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Sponsor Service Module
3
+ *
4
+ * Provides configuration and utilities for sponsorship functionality
5
+ */
6
+ import { Logger } from '../logger.js';
7
+ import config from '../config.js';
8
+ // Create logger instance for this module
9
+ const logger = new Logger('SponsorService');
10
+ /**
11
+ * SponsorService - Provides sponsorship configuration and message handling
12
+ */
13
+ export class SponsorService {
14
+ constructor() {
15
+ this.isEnabled = config.enableSponsorMessage;
16
+ this.sponsorUrl = config.sponsorUrl;
17
+ logger.info('SponsorService initialized', { enabled: this.isEnabled });
18
+ }
19
+ /**
20
+ * Get sponsor information (for documentation/reference purposes)
21
+ */
22
+ getSponsorInfo() {
23
+ return {
24
+ isEnabled: this.isEnabled,
25
+ url: this.sponsorUrl
26
+ };
27
+ }
28
+ /**
29
+ * Creates a response with optional sponsorship message
30
+ */
31
+ createResponse(data, includeSponsorMessage = false) {
32
+ const content = [];
33
+ if (this.isEnabled && includeSponsorMessage) {
34
+ content.push({
35
+ type: "text",
36
+ text: `❤️ Support this project by sponsoring the developer at ${this.sponsorUrl}\n\n`
37
+ });
38
+ }
39
+ content.push({
40
+ type: "text",
41
+ text: JSON.stringify(data, null, 2)
42
+ });
43
+ return { content };
44
+ }
45
+ /**
46
+ * Creates an error response
47
+ */
48
+ createErrorResponse(error, context) {
49
+ return this.createResponse({
50
+ error: typeof error === 'string' ? error : error.message,
51
+ ...context
52
+ });
53
+ }
54
+ /**
55
+ * Creates a bulk operation response with sponsorship message
56
+ */
57
+ createBulkResponse(result) {
58
+ return this.createResponse({
59
+ success: true,
60
+ total: result.totals.total,
61
+ successful: result.totals.success,
62
+ failed: result.totals.failure,
63
+ failures: result.failed.map((failure) => ({
64
+ id: failure.item?.id || failure.item,
65
+ error: failure.error.message
66
+ }))
67
+ }, true); // Always include sponsor message for bulk operations
68
+ }
69
+ }
70
+ // Export a singleton instance
71
+ export const sponsorService = new SponsorService();
@@ -4,36 +4,44 @@
4
4
  * This module provides utilities for adding sponsor information to responses.
5
5
  */
6
6
  import config from '../config.js';
7
+ import { trackImpression, generateTrackingUrl, trackUrlGenerated } from './sponsor-analytics.js';
7
8
  /**
8
9
  * Generate a sponsor message to be included in task responses
9
10
  *
11
+ * @param source The source/endpoint requesting the sponsor message
10
12
  * @returns Object containing sponsor message and URL, or null if sponsor messages are disabled
11
13
  */
12
- export function getSponsorMessage() {
14
+ export function getSponsorMessage(source = 'unknown') {
13
15
  // Skip if sponsor message is disabled
14
16
  if (!config.enableSponsorMessage) {
15
17
  return null;
16
18
  }
19
+ // Generate tracking URL
20
+ const trackingUrl = generateTrackingUrl(config.sponsorUrl, source);
21
+ trackUrlGenerated(trackingUrl);
17
22
  return {
18
23
  message: "❤️ Support this project: If you find this integration valuable, please consider sponsoring the developer.",
19
- url: config.sponsorUrl
24
+ url: trackingUrl
20
25
  };
21
26
  }
22
27
  /**
23
28
  * Enhances a task response with sponsor information if enabled
24
29
  *
25
30
  * @param taskResponse The original task response to enhance
31
+ * @param source The source/endpoint of the response
26
32
  * @returns Enhanced task response with sponsor information
27
33
  */
28
- export function enhanceResponseWithSponsor(taskResponse) {
34
+ export function enhanceResponseWithSponsor(taskResponse, source = 'unknown') {
29
35
  // Skip if sponsor message is disabled
30
36
  if (!config.enableSponsorMessage) {
31
37
  return taskResponse;
32
38
  }
33
- const sponsorInfo = getSponsorMessage();
39
+ const sponsorInfo = getSponsorMessage(source);
34
40
  if (!sponsorInfo) {
35
41
  return taskResponse;
36
42
  }
43
+ // Track the impression
44
+ trackImpression(source);
37
45
  // Create a new response with sponsor information
38
46
  const enhancedResponse = {
39
47
  ...taskResponse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taazkareem/clickup-mcp-server",
3
- "version": "0.4.72",
3
+ "version": "0.4.74",
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",