@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.
- package/README.md +14 -23
- package/build/config.js +34 -2
- package/build/index.js +3 -0
- package/build/logger.js +8 -38
- package/build/mcp-tools.js +64 -0
- package/build/server-state.js +93 -0
- package/build/server.js +33 -8
- package/build/server.log +0 -0
- package/build/services/clickup/base.js +3 -0
- package/build/services/clickup/bulk.js +3 -0
- package/build/services/clickup/folder.js +3 -0
- package/build/services/clickup/index.js +9 -1
- package/build/services/clickup/list.js +3 -0
- package/build/services/clickup/tag.js +190 -0
- package/build/services/clickup/task.js +138 -0
- package/build/services/clickup/types.js +3 -0
- package/build/services/clickup/workspace.js +3 -0
- package/build/services/shared.js +3 -0
- package/build/tools/bulk-tasks.js +36 -0
- package/build/tools/debug.js +76 -0
- package/build/tools/folder.js +3 -0
- package/build/tools/index.js +4 -0
- package/build/tools/list.js +3 -0
- package/build/tools/logs.js +55 -0
- package/build/tools/tag.js +824 -0
- package/build/tools/task/attachments.js +3 -0
- package/build/tools/task/bulk-operations.js +10 -0
- package/build/tools/task/handlers.js +61 -2
- package/build/tools/task/index.js +8 -1
- package/build/tools/task/main.js +18 -2
- package/build/tools/task/single-operations.js +10 -0
- package/build/tools/task/utilities.js +40 -3
- package/build/tools/task/workspace-operations.js +222 -0
- package/build/tools/task.js +1554 -0
- package/build/tools/utils.js +3 -0
- package/build/tools/workspace.js +3 -0
- package/build/utils/color-processor.js +183 -0
- package/build/utils/concurrency-utils.js +3 -0
- package/build/utils/date-utils.js +3 -0
- package/build/utils/params-utils.js +39 -0
- package/build/utils/resolver-utils.js +3 -0
- package/build/utils/sponsor-analytics.js +100 -0
- package/build/utils/sponsor-service.js +3 -0
- package/build/utils/sponsor-utils.js +57 -0
- package/build/utils/token-utils.js +49 -0
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Tag Service
|
|
6
|
+
*
|
|
7
|
+
* Provides access to ClickUp API endpoints for tag management:
|
|
8
|
+
* - Space tags (get, create, update, delete)
|
|
9
|
+
* - Task tags (add, remove)
|
|
10
|
+
*/
|
|
11
|
+
import { BaseClickUpService } from './base.js';
|
|
12
|
+
/**
|
|
13
|
+
* ClickUp Tag Service class for managing tags
|
|
14
|
+
*/
|
|
15
|
+
export class ClickUpTagService extends BaseClickUpService {
|
|
16
|
+
/**
|
|
17
|
+
* Get all tags in a space
|
|
18
|
+
* @param spaceId - ID of the space to get tags from
|
|
19
|
+
* @returns Promise with tags array
|
|
20
|
+
*/
|
|
21
|
+
async getSpaceTags(spaceId) {
|
|
22
|
+
try {
|
|
23
|
+
this.logger.debug(`Getting tags for space: ${spaceId}`);
|
|
24
|
+
const response = await this.client.get(`/space/${spaceId}/tag`);
|
|
25
|
+
return {
|
|
26
|
+
success: true,
|
|
27
|
+
data: response.data.tags
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.logger.error(`Failed to get tags for space: ${spaceId}`, error);
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: {
|
|
35
|
+
message: error.message || 'Failed to get space tags',
|
|
36
|
+
code: error.code,
|
|
37
|
+
details: error.data
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a new tag in a space
|
|
44
|
+
* @param spaceId - ID of the space
|
|
45
|
+
* @param tagData - Tag data (name, background color, foreground color)
|
|
46
|
+
* @returns Promise with created tag
|
|
47
|
+
*/
|
|
48
|
+
async createSpaceTag(spaceId, tagData) {
|
|
49
|
+
try {
|
|
50
|
+
this.logger.debug(`Creating tag "${tagData.tag_name}" in space: ${spaceId}`);
|
|
51
|
+
// Send tag data wrapped in a 'tag' object
|
|
52
|
+
const response = await this.client.post(`/space/${spaceId}/tag`, {
|
|
53
|
+
tag: {
|
|
54
|
+
name: tagData.tag_name,
|
|
55
|
+
tag_bg: tagData.tag_bg,
|
|
56
|
+
tag_fg: tagData.tag_fg
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
data: response.data.tag
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.logger.error(`Failed to create tag in space: ${spaceId}`, error);
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: {
|
|
69
|
+
message: error.message || 'Failed to create space tag',
|
|
70
|
+
code: error.code,
|
|
71
|
+
details: error.data
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Update an existing tag in a space
|
|
78
|
+
* @param spaceId - ID of the space
|
|
79
|
+
* @param tagName - Current name of the tag to update
|
|
80
|
+
* @param updateData - Tag data to update (name, colors)
|
|
81
|
+
* @returns Promise with updated tag
|
|
82
|
+
*/
|
|
83
|
+
async updateSpaceTag(spaceId, tagName, updateData) {
|
|
84
|
+
try {
|
|
85
|
+
this.logger.debug(`Updating tag "${tagName}" in space: ${spaceId}`);
|
|
86
|
+
// Encode the tag name in the URL
|
|
87
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
88
|
+
const response = await this.client.put(`/space/${spaceId}/tag/${encodedTagName}`, updateData);
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
data: response.data.tag
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
this.logger.error(`Failed to update tag "${tagName}" in space: ${spaceId}`, error);
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: {
|
|
99
|
+
message: error.message || 'Failed to update space tag',
|
|
100
|
+
code: error.code,
|
|
101
|
+
details: error.data
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete a tag from a space
|
|
108
|
+
* @param spaceId - ID of the space
|
|
109
|
+
* @param tagName - Name of the tag to delete
|
|
110
|
+
* @returns Promise with success status
|
|
111
|
+
*/
|
|
112
|
+
async deleteSpaceTag(spaceId, tagName) {
|
|
113
|
+
try {
|
|
114
|
+
this.logger.debug(`Deleting tag "${tagName}" from space: ${spaceId}`);
|
|
115
|
+
// Encode the tag name in the URL
|
|
116
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
117
|
+
await this.client.delete(`/space/${spaceId}/tag/${encodedTagName}`);
|
|
118
|
+
return {
|
|
119
|
+
success: true
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.logger.error(`Failed to delete tag "${tagName}" from space: ${spaceId}`, error);
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: {
|
|
127
|
+
message: error.message || 'Failed to delete space tag',
|
|
128
|
+
code: error.code,
|
|
129
|
+
details: error.data
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Add a tag to a task
|
|
136
|
+
* @param taskId - ID of the task
|
|
137
|
+
* @param tagName - Name of the tag to add
|
|
138
|
+
* @returns Promise with success status
|
|
139
|
+
*/
|
|
140
|
+
async addTagToTask(taskId, tagName) {
|
|
141
|
+
try {
|
|
142
|
+
this.logger.debug(`Adding tag "${tagName}" to task: ${taskId}`);
|
|
143
|
+
// Encode the tag name in the URL
|
|
144
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
145
|
+
await this.client.post(`/task/${taskId}/tag/${encodedTagName}`, {});
|
|
146
|
+
return {
|
|
147
|
+
success: true
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
this.logger.error(`Failed to add tag "${tagName}" to task: ${taskId}`, error);
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
error: {
|
|
155
|
+
message: error.message || 'Failed to add tag to task',
|
|
156
|
+
code: error.code,
|
|
157
|
+
details: error.data
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Remove a tag from a task
|
|
164
|
+
* @param taskId - ID of the task
|
|
165
|
+
* @param tagName - Name of the tag to remove
|
|
166
|
+
* @returns Promise with success status
|
|
167
|
+
*/
|
|
168
|
+
async removeTagFromTask(taskId, tagName) {
|
|
169
|
+
try {
|
|
170
|
+
this.logger.debug(`Removing tag "${tagName}" from task: ${taskId}`);
|
|
171
|
+
// Encode the tag name in the URL
|
|
172
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
173
|
+
await this.client.delete(`/task/${taskId}/tag/${encodedTagName}`);
|
|
174
|
+
return {
|
|
175
|
+
success: true
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
this.logger.error(`Failed to remove tag "${tagName}" from task: ${taskId}`, error);
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
error: {
|
|
183
|
+
message: error.message || 'Failed to remove tag from task',
|
|
184
|
+
code: error.code,
|
|
185
|
+
details: error.data
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
2
5
|
* ClickUp Task Service
|
|
3
6
|
*
|
|
4
7
|
* Handles all operations related to tasks in ClickUp, including:
|
|
@@ -10,6 +13,7 @@
|
|
|
10
13
|
*/
|
|
11
14
|
import { BaseClickUpService, ErrorCode, ClickUpServiceError } from './base.js';
|
|
12
15
|
import { ListService } from './list.js';
|
|
16
|
+
import { estimateTokensFromObject, wouldExceedTokenLimit } from '../../utils/token-utils.js';
|
|
13
17
|
export class TaskService extends BaseClickUpService {
|
|
14
18
|
constructor(apiKey, teamId, baseUrl, workspaceService) {
|
|
15
19
|
super(apiKey, teamId, baseUrl);
|
|
@@ -59,6 +63,27 @@ export class TaskService extends BaseClickUpService {
|
|
|
59
63
|
if (filters.assignees && filters.assignees.length > 0) {
|
|
60
64
|
filters.assignees.forEach(assignee => params.append('assignees[]', assignee));
|
|
61
65
|
}
|
|
66
|
+
// Team tasks endpoint specific parameters
|
|
67
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
68
|
+
filters.tags.forEach(tag => params.append('tags[]', tag));
|
|
69
|
+
}
|
|
70
|
+
if (filters.list_ids && filters.list_ids.length > 0) {
|
|
71
|
+
filters.list_ids.forEach(id => params.append('list_ids[]', id));
|
|
72
|
+
}
|
|
73
|
+
if (filters.folder_ids && filters.folder_ids.length > 0) {
|
|
74
|
+
filters.folder_ids.forEach(id => params.append('folder_ids[]', id));
|
|
75
|
+
}
|
|
76
|
+
if (filters.space_ids && filters.space_ids.length > 0) {
|
|
77
|
+
filters.space_ids.forEach(id => params.append('space_ids[]', id));
|
|
78
|
+
}
|
|
79
|
+
if (filters.archived !== undefined)
|
|
80
|
+
params.append('archived', String(filters.archived));
|
|
81
|
+
if (filters.include_closed_lists !== undefined)
|
|
82
|
+
params.append('include_closed_lists', String(filters.include_closed_lists));
|
|
83
|
+
if (filters.include_archived_lists !== undefined)
|
|
84
|
+
params.append('include_archived_lists', String(filters.include_archived_lists));
|
|
85
|
+
if (filters.include_compact_time_entries !== undefined)
|
|
86
|
+
params.append('include_compact_time_entries', String(filters.include_compact_time_entries));
|
|
62
87
|
// Date filters
|
|
63
88
|
if (filters.due_date_gt)
|
|
64
89
|
params.append('due_date_gt', String(filters.due_date_gt));
|
|
@@ -560,4 +585,117 @@ export class TaskService extends BaseClickUpService {
|
|
|
560
585
|
throw this.handleError(error, `Failed to upload attachment from URL to task ${taskId}`);
|
|
561
586
|
}
|
|
562
587
|
}
|
|
588
|
+
/**
|
|
589
|
+
* Format task data for summary view
|
|
590
|
+
* @param task The task to format
|
|
591
|
+
* @returns TaskSummary object
|
|
592
|
+
*/
|
|
593
|
+
formatTaskSummary(task) {
|
|
594
|
+
return {
|
|
595
|
+
id: task.id,
|
|
596
|
+
name: task.name,
|
|
597
|
+
status: task.status.status,
|
|
598
|
+
list: {
|
|
599
|
+
id: task.list.id,
|
|
600
|
+
name: task.list.name
|
|
601
|
+
},
|
|
602
|
+
due_date: task.due_date,
|
|
603
|
+
url: task.url,
|
|
604
|
+
priority: this.extractPriorityValue(task),
|
|
605
|
+
tags: task.tags.map(tag => ({
|
|
606
|
+
name: tag.name,
|
|
607
|
+
tag_bg: tag.tag_bg,
|
|
608
|
+
tag_fg: tag.tag_fg
|
|
609
|
+
}))
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Estimates token count for a task in JSON format
|
|
614
|
+
* @param task ClickUp task
|
|
615
|
+
* @returns Estimated token count
|
|
616
|
+
*/
|
|
617
|
+
estimateTaskTokens(task) {
|
|
618
|
+
return estimateTokensFromObject(task);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get filtered tasks across the entire team/workspace using tags and other filters
|
|
622
|
+
* @param filters Task filters to apply including tags, list/folder/space filtering
|
|
623
|
+
* @returns Either a DetailedTaskResponse or WorkspaceTasksResponse depending on detail_level
|
|
624
|
+
*/
|
|
625
|
+
async getWorkspaceTasks(filters = {}) {
|
|
626
|
+
try {
|
|
627
|
+
this.logOperation('getWorkspaceTasks', { filters });
|
|
628
|
+
const params = this.buildTaskFilterParams(filters);
|
|
629
|
+
const response = await this.client.get(`/team/${this.teamId}/task`, {
|
|
630
|
+
params
|
|
631
|
+
});
|
|
632
|
+
const tasks = response.data.tasks;
|
|
633
|
+
const totalCount = tasks.length; // Note: This is just the current page count
|
|
634
|
+
const hasMore = totalCount === 100; // ClickUp returns max 100 tasks per page
|
|
635
|
+
const nextPage = (filters.page || 0) + 1;
|
|
636
|
+
// If the estimated token count exceeds 50,000 or detail_level is 'summary',
|
|
637
|
+
// return summary format for efficiency and to avoid hitting token limits
|
|
638
|
+
const TOKEN_LIMIT = 50000;
|
|
639
|
+
// Estimate tokens for the full response
|
|
640
|
+
let tokensExceedLimit = false;
|
|
641
|
+
if (filters.detail_level !== 'summary' && tasks.length > 0) {
|
|
642
|
+
// We only need to check token count if detailed was requested
|
|
643
|
+
// For summary requests, we always return summary format
|
|
644
|
+
// First check with a sample task - if one task exceeds the limit, we definitely need summary
|
|
645
|
+
const sampleTask = tasks[0];
|
|
646
|
+
// Check if all tasks would exceed the token limit
|
|
647
|
+
const estimatedTokensPerTask = this.estimateTaskTokens(sampleTask);
|
|
648
|
+
const estimatedTotalTokens = estimatedTokensPerTask * tasks.length;
|
|
649
|
+
// Add 10% overhead for the response wrapper
|
|
650
|
+
tokensExceedLimit = estimatedTotalTokens * 1.1 > TOKEN_LIMIT;
|
|
651
|
+
// Double-check with more precise estimation if we're close to the limit
|
|
652
|
+
if (!tokensExceedLimit && estimatedTotalTokens * 1.1 > TOKEN_LIMIT * 0.8) {
|
|
653
|
+
// More precise check - build a representative sample and extrapolate
|
|
654
|
+
tokensExceedLimit = wouldExceedTokenLimit({ tasks, total_count: totalCount, has_more: hasMore, next_page: nextPage }, TOKEN_LIMIT);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// Determine if we should return summary or detailed based on request and token limit
|
|
658
|
+
const shouldUseSummary = filters.detail_level === 'summary' || tokensExceedLimit;
|
|
659
|
+
this.logOperation('getWorkspaceTasks', {
|
|
660
|
+
totalTasks: tasks.length,
|
|
661
|
+
estimatedTokens: tasks.reduce((count, task) => count + this.estimateTaskTokens(task), 0),
|
|
662
|
+
usingDetailedFormat: !shouldUseSummary,
|
|
663
|
+
requestedFormat: filters.detail_level || 'auto'
|
|
664
|
+
});
|
|
665
|
+
if (shouldUseSummary) {
|
|
666
|
+
return {
|
|
667
|
+
summaries: tasks.map(task => this.formatTaskSummary(task)),
|
|
668
|
+
total_count: totalCount,
|
|
669
|
+
has_more: hasMore,
|
|
670
|
+
next_page: nextPage
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
tasks,
|
|
675
|
+
total_count: totalCount,
|
|
676
|
+
has_more: hasMore,
|
|
677
|
+
next_page: nextPage
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
this.logOperation('getWorkspaceTasks', { error: error.message, status: error.response?.status });
|
|
682
|
+
throw this.handleError(error, 'Failed to get workspace tasks');
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get task summaries for lightweight retrieval
|
|
687
|
+
* @param filters Task filters to apply
|
|
688
|
+
* @returns WorkspaceTasksResponse with task summaries
|
|
689
|
+
*/
|
|
690
|
+
async getTaskSummaries(filters = {}) {
|
|
691
|
+
return this.getWorkspaceTasks({ ...filters, detail_level: 'summary' });
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Get detailed task data
|
|
695
|
+
* @param filters Task filters to apply
|
|
696
|
+
* @returns DetailedTaskResponse with full task data
|
|
697
|
+
*/
|
|
698
|
+
async getTaskDetails(filters = {}) {
|
|
699
|
+
return this.getWorkspaceTasks({ ...filters, detail_level: 'detailed' });
|
|
700
|
+
}
|
|
563
701
|
}
|
package/build/services/shared.js
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternative implementation for bulk tasks creation
|
|
3
|
+
*/
|
|
4
|
+
import { handleCreateBulkTasks } from './task.js';
|
|
5
|
+
/**
|
|
6
|
+
* Alternative tool definition for bulk task creation to work around MCP validation issues
|
|
7
|
+
*/
|
|
8
|
+
export const createTasksBulkTool = {
|
|
9
|
+
name: "create_tasks_bulk",
|
|
10
|
+
description: "Create multiple tasks in a list efficiently. You MUST provide:\n1. An array of tasks with required properties\n2. Either listId or listName to specify the target list",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
listId: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "ID of list for new tasks (preferred). Use this instead of listName if you have it."
|
|
17
|
+
},
|
|
18
|
+
listName: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Name of list for new tasks. Only use if you don't have listId."
|
|
21
|
+
},
|
|
22
|
+
tasks: {
|
|
23
|
+
// Define minimally to avoid validation issues
|
|
24
|
+
description: "Array of tasks to create. Each task must have at least a name."
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
required: ["tasks"]
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Handler for create_tasks_bulk tool
|
|
32
|
+
*/
|
|
33
|
+
export async function handleCreateTasksBulk(parameters) {
|
|
34
|
+
// Use the same implementation as handleCreateBulkTasks
|
|
35
|
+
return handleCreateBulkTasks(parameters);
|
|
36
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug Tools
|
|
3
|
+
*
|
|
4
|
+
* This module provides tools for debugging and monitoring the ClickUp MCP server.
|
|
5
|
+
*/
|
|
6
|
+
// In-memory log storage
|
|
7
|
+
const DEBUG_LOGS = [];
|
|
8
|
+
/**
|
|
9
|
+
* Log a message to the in-memory debug logs
|
|
10
|
+
*/
|
|
11
|
+
export function logDebug(message, level = 'info') {
|
|
12
|
+
const timestamp = new Date().toISOString();
|
|
13
|
+
DEBUG_LOGS.push({ timestamp, message, level });
|
|
14
|
+
// Optional: Also print to console for development
|
|
15
|
+
if (level === 'error') {
|
|
16
|
+
console.error(`[${timestamp}] ERROR: ${message}`);
|
|
17
|
+
}
|
|
18
|
+
else if (level === 'warn') {
|
|
19
|
+
console.warn(`[${timestamp}] WARN: ${message}`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log(`[${timestamp}] INFO: ${message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Tool definition for checking debug logs
|
|
27
|
+
*/
|
|
28
|
+
export const checkDebugLogsTool = {
|
|
29
|
+
name: 'check_debug_logs',
|
|
30
|
+
description: 'Check the server debug logs collected since the server started.',
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Tool definition for clearing debug logs
|
|
38
|
+
*/
|
|
39
|
+
export const clearDebugLogsTool = {
|
|
40
|
+
name: 'clear_debug_logs',
|
|
41
|
+
description: 'Clear all server debug logs.',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Handler for the check_debug_logs tool
|
|
49
|
+
*/
|
|
50
|
+
export function handleCheckDebugLogs() {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: DEBUG_LOGS.length > 0
|
|
56
|
+
? DEBUG_LOGS.map(log => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`).join('\n')
|
|
57
|
+
: "No debug logs available."
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Handler for the clear_debug_logs tool
|
|
64
|
+
*/
|
|
65
|
+
export function handleClearDebugLogs() {
|
|
66
|
+
const count = DEBUG_LOGS.length;
|
|
67
|
+
DEBUG_LOGS.length = 0;
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Cleared ${count} debug log entries.`
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
}
|
package/build/tools/folder.js
CHANGED
package/build/tools/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
2
5
|
* Tools Index
|
|
3
6
|
*
|
|
4
7
|
* This file exports all tool definitions and handlers for the ClickUp MCP server.
|
|
@@ -9,3 +12,4 @@ export * from './workspace.js';
|
|
|
9
12
|
export * from './task/index.js';
|
|
10
13
|
export * from './list.js';
|
|
11
14
|
export * from './folder.js';
|
|
15
|
+
export * from './tag.js';
|
package/build/tools/list.js
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Logs Tool
|
|
3
|
+
*
|
|
4
|
+
* Provides a basic tool for reading server logs.
|
|
5
|
+
*/
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
// Define the log file path - using __dirname to get absolute path
|
|
9
|
+
const LOG_FILE = join(dirname(__dirname), 'server.log');
|
|
10
|
+
// Tool definition
|
|
11
|
+
export const checkLogsTool = {
|
|
12
|
+
name: "check_logs",
|
|
13
|
+
description: "Check server logs with optional filtering by log level",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
level: {
|
|
18
|
+
type: "string",
|
|
19
|
+
enum: ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"],
|
|
20
|
+
description: "Filter logs by level"
|
|
21
|
+
},
|
|
22
|
+
limit: {
|
|
23
|
+
type: "number",
|
|
24
|
+
description: "Maximum number of log entries to return (default: 50)"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// Simple handler implementation using async/await with promises
|
|
30
|
+
export async function handleCheckLogs(params) {
|
|
31
|
+
try {
|
|
32
|
+
// Read log file using promises
|
|
33
|
+
const content = await fs.readFile(LOG_FILE, 'utf-8');
|
|
34
|
+
// Split into lines and filter out empty lines
|
|
35
|
+
let lines = content.split('\n').filter(Boolean);
|
|
36
|
+
// Apply level filtering if specified
|
|
37
|
+
if (params?.level) {
|
|
38
|
+
lines = lines.filter(line => line.includes(`${params.level}:`));
|
|
39
|
+
}
|
|
40
|
+
// Apply limit (default to 50)
|
|
41
|
+
const limit = params?.limit || 50;
|
|
42
|
+
const result = lines.slice(-limit);
|
|
43
|
+
// Return results
|
|
44
|
+
return {
|
|
45
|
+
logs: result.length > 0 ? result : ['No matching logs found.']
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
// Simple error handling
|
|
50
|
+
if (error.code === 'ENOENT') {
|
|
51
|
+
return { logs: ['Log file not found.'] };
|
|
52
|
+
}
|
|
53
|
+
return { logs: [`Error reading logs: ${error.message}`] };
|
|
54
|
+
}
|
|
55
|
+
}
|