@sebastienheyd/clickup-mcp 1.6.1

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +303 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +184 -0
  6. package/dist/clickup-text.d.ts +83 -0
  7. package/dist/clickup-text.d.ts.map +1 -0
  8. package/dist/clickup-text.js +563 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +133 -0
  12. package/dist/resources/space-resources.d.ts +6 -0
  13. package/dist/resources/space-resources.d.ts.map +1 -0
  14. package/dist/resources/space-resources.js +95 -0
  15. package/dist/shared/config.d.ts +11 -0
  16. package/dist/shared/config.d.ts.map +1 -0
  17. package/dist/shared/config.js +61 -0
  18. package/dist/shared/data-uri.d.ts +14 -0
  19. package/dist/shared/data-uri.d.ts.map +1 -0
  20. package/dist/shared/data-uri.js +34 -0
  21. package/dist/shared/image-processing.d.ts +13 -0
  22. package/dist/shared/image-processing.d.ts.map +1 -0
  23. package/dist/shared/image-processing.js +199 -0
  24. package/dist/shared/types.d.ts +21 -0
  25. package/dist/shared/types.d.ts.map +1 -0
  26. package/dist/shared/types.js +2 -0
  27. package/dist/shared/utils.d.ts +95 -0
  28. package/dist/shared/utils.d.ts.map +1 -0
  29. package/dist/shared/utils.js +630 -0
  30. package/dist/test-utils.d.ts +23 -0
  31. package/dist/test-utils.d.ts.map +1 -0
  32. package/dist/test-utils.js +44 -0
  33. package/dist/tools/doc-tools.d.ts +4 -0
  34. package/dist/tools/doc-tools.d.ts.map +1 -0
  35. package/dist/tools/doc-tools.js +436 -0
  36. package/dist/tools/list-tools.d.ts +4 -0
  37. package/dist/tools/list-tools.d.ts.map +1 -0
  38. package/dist/tools/list-tools.js +175 -0
  39. package/dist/tools/search-tools.d.ts +3 -0
  40. package/dist/tools/search-tools.d.ts.map +1 -0
  41. package/dist/tools/search-tools.js +188 -0
  42. package/dist/tools/space-tools.d.ts +3 -0
  43. package/dist/tools/space-tools.d.ts.map +1 -0
  44. package/dist/tools/space-tools.js +144 -0
  45. package/dist/tools/task-tools.d.ts +8 -0
  46. package/dist/tools/task-tools.d.ts.map +1 -0
  47. package/dist/tools/task-tools.js +325 -0
  48. package/dist/tools/task-write-tools.d.ts +3 -0
  49. package/dist/tools/task-write-tools.d.ts.map +1 -0
  50. package/dist/tools/task-write-tools.js +575 -0
  51. package/dist/tools/time-tools.d.ts +4 -0
  52. package/dist/tools/time-tools.d.ts.map +1 -0
  53. package/dist/tools/time-tools.js +338 -0
  54. package/package.json +72 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-tools.d.ts","sourceRoot":"","sources":["../../src/tools/search-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAQlE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QAiNnE"}
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSearchTools = registerSearchTools;
4
+ const zod_1 = require("zod");
5
+ const config_1 = require("../shared/config");
6
+ const utils_1 = require("../shared/utils");
7
+ const task_tools_1 = require("./task-tools");
8
+ const MAX_SEARCH_RESULTS = 50;
9
+ function registerSearchTools(server, userData) {
10
+ // Dynamically construct the searchTasks description
11
+ const searchTasksDescriptionBase = [
12
+ "Searches tasks (sometimes called Tickets or Cards) by name, content, assignees, and ID with fuzzy matching and support for multiple search terms (OR logic).",
13
+ "Can filter by multiple list_ids, space_ids, todo status, or tasks assigned to the current user. If no search terms provided, returns most recently updated tasks.",
14
+ "Can also be used to find tasks for the current user by providing the assigned_to_me flag."
15
+ ];
16
+ if (config_1.CONFIG.primaryLanguageHint && config_1.CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
17
+ searchTasksDescriptionBase.push(`For optimal results, as your ClickUp tasks may be primarily in '${config_1.CONFIG.primaryLanguageHint}', consider providing search terms in English and '${config_1.CONFIG.primaryLanguageHint}'.`);
18
+ }
19
+ searchTasksDescriptionBase.push("Always reference tasks by their URLs when discussing search results or suggesting actions.");
20
+ searchTasksDescriptionBase.push("You'll get a rough overview of the tasks that match the search terms, sorted by relevance.");
21
+ searchTasksDescriptionBase.push("Always use getTaskById to get more specific information if a task is relevant, and always share the task URL.");
22
+ server.tool("searchTasks", searchTasksDescriptionBase.join("\n"), {
23
+ terms: zod_1.z
24
+ .array(zod_1.z.string())
25
+ .optional()
26
+ .describe("Array of search terms (OR logic). Can include task IDs. Optional - if not provided, returns most recent tasks."),
27
+ list_ids: zod_1.z
28
+ .array(zod_1.z.string())
29
+ .optional()
30
+ .describe("Filter tasks to specific list IDs"),
31
+ space_ids: zod_1.z
32
+ .array(zod_1.z.string())
33
+ .optional()
34
+ .describe("Filter tasks to specific space IDs"),
35
+ only_todo: zod_1.z
36
+ .boolean()
37
+ .optional()
38
+ .describe("Filter for open/todo tasks only (exclude done and closed tasks)"),
39
+ status: zod_1.z
40
+ .array(zod_1.z.string())
41
+ .optional()
42
+ .describe("Filter for tasks with specific status names (overrides only_todo if provided)"),
43
+ assigned_to_me: zod_1.z
44
+ .boolean()
45
+ .optional()
46
+ .describe(`Filter for tasks assigned to the current user (${userData.user.username} (${userData.user.id}))`),
47
+ }, {
48
+ readOnlyHint: true
49
+ }, async ({ terms, list_ids, space_ids, only_todo, status, assigned_to_me }) => {
50
+ // Get current user ID if filtering by assigned_to_me
51
+ const assignees = assigned_to_me ? [userData.user.id] : [];
52
+ const searchIndex = await (0, utils_1.getTaskSearchIndex)(space_ids, list_ids, assignees);
53
+ if (!searchIndex) {
54
+ return {
55
+ content: [
56
+ {
57
+ type: "text",
58
+ text: "No tasks available or index could not be built.",
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ // Early return for no search terms
64
+ if (!terms || terms.length === 0) {
65
+ let allTasks = searchIndex._docs || [];
66
+ // Apply status filtering
67
+ if (status && status.length > 0) {
68
+ const statusLower = status.map(s => s.toLowerCase());
69
+ allTasks = allTasks.filter((task) => statusLower.includes(task.status.status.toLowerCase()));
70
+ }
71
+ else if (only_todo) {
72
+ allTasks = allTasks.filter((task) => task.status.type !== "done" && task.status.type !== "closed");
73
+ }
74
+ // Sort by updated date (most recent first) and limit
75
+ const resultTasks = allTasks
76
+ .sort((a, b) => {
77
+ const dateA = parseInt(a.date_updated || "0");
78
+ const dateB = parseInt(b.date_updated || "0");
79
+ return dateB - dateA;
80
+ })
81
+ .slice(0, MAX_SEARCH_RESULTS);
82
+ if (resultTasks.length === 0) {
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: "No tasks found.",
88
+ },
89
+ ],
90
+ };
91
+ }
92
+ return {
93
+ content: await Promise.all(resultTasks.map((task) => (0, task_tools_1.generateTaskMetadata)(task))),
94
+ };
95
+ }
96
+ // Create a results map to track unique tasks with scores
97
+ const uniqueResults = new Map();
98
+ // Perform multi-term search with aggressive boosting
99
+ const searchResults = await (0, utils_1.performMultiTermSearch)(searchIndex, terms);
100
+ searchResults.forEach(task => {
101
+ uniqueResults.set(task.id, { item: task, score: 0.1 }); // Give search results a good score
102
+ });
103
+ // Task ID Fallback Logic (internal IDs and custom IDs)
104
+ const potentialTaskIds = terms.filter(utils_1.isTaskId);
105
+ const potentialCustomIds = terms.filter(id => (0, utils_1.isCustomTaskId)(id) && !(0, utils_1.isTaskId)(id));
106
+ const foundTaskIdsByFuse = new Set(Array.from(uniqueResults.keys()).map(id => id.toLowerCase()));
107
+ const taskIdsToFetchDirectly = potentialTaskIds.filter(id => {
108
+ const lowerId = id.toLowerCase();
109
+ return !foundTaskIdsByFuse.has(lowerId);
110
+ });
111
+ // Fetch internal task IDs not found in index
112
+ if (taskIdsToFetchDirectly.length > 0) {
113
+ console.error(`Attempting direct fetch for task IDs: ${taskIdsToFetchDirectly.join(', ')}`);
114
+ const directFetchPromises = taskIdsToFetchDirectly.map(async (id) => {
115
+ try {
116
+ const response = await fetch(`https://api.clickup.com/api/v2/task/${id}`, { headers: { Authorization: config_1.CONFIG.apiKey } });
117
+ if (response.ok) {
118
+ const task = await response.json();
119
+ if (task && typeof task.id === 'string') {
120
+ const existing = uniqueResults.get(task.id);
121
+ if (!existing || 0 < existing.score) {
122
+ uniqueResults.set(task.id, { item: task, score: 0 });
123
+ }
124
+ }
125
+ return task;
126
+ }
127
+ return null;
128
+ }
129
+ catch (error) {
130
+ console.error(`Error directly fetching task ${id}:`, error);
131
+ return null;
132
+ }
133
+ });
134
+ await Promise.all(directFetchPromises);
135
+ }
136
+ // Fetch custom task IDs via the custom_task_ids API parameter
137
+ if (potentialCustomIds.length > 0) {
138
+ console.error(`Attempting direct fetch for custom task IDs: ${potentialCustomIds.join(', ')}`);
139
+ const customFetchPromises = potentialCustomIds.map(async (customId) => {
140
+ try {
141
+ const response = await fetch(`https://api.clickup.com/api/v2/task/${customId}?custom_task_ids=true&team_id=${config_1.CONFIG.teamId}`, { headers: { Authorization: config_1.CONFIG.apiKey } });
142
+ if (response.ok) {
143
+ const task = await response.json();
144
+ if (task && typeof task.id === 'string') {
145
+ const existing = uniqueResults.get(task.id);
146
+ if (!existing || 0 < existing.score) {
147
+ uniqueResults.set(task.id, { item: task, score: 0 });
148
+ }
149
+ }
150
+ return task;
151
+ }
152
+ return null;
153
+ }
154
+ catch (error) {
155
+ console.error(`Error directly fetching custom task ${customId}:`, error);
156
+ return null;
157
+ }
158
+ });
159
+ await Promise.all(customFetchPromises);
160
+ }
161
+ let resultTasks = Array.from(uniqueResults.values())
162
+ .sort((a, b) => a.score - b.score)
163
+ .map(entry => entry.item);
164
+ // Apply status filtering
165
+ if (status && status.length > 0) {
166
+ const statusLower = status.map(s => s.toLowerCase());
167
+ resultTasks = resultTasks.filter((task) => statusLower.includes(task.status.status.toLowerCase()));
168
+ }
169
+ else if (only_todo) {
170
+ resultTasks = resultTasks.filter((task) => task.status.type !== "done" && task.status.type !== "closed");
171
+ }
172
+ // Apply result limit
173
+ resultTasks = resultTasks.slice(0, MAX_SEARCH_RESULTS);
174
+ if (resultTasks.length === 0) {
175
+ return {
176
+ content: [
177
+ {
178
+ type: "text",
179
+ text: "No tasks found matching the search criteria.",
180
+ },
181
+ ],
182
+ };
183
+ }
184
+ return {
185
+ content: await Promise.all(resultTasks.map((task) => (0, task_tools_1.generateTaskMetadata)(task))),
186
+ };
187
+ });
188
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSpaceTools(server: McpServer): void;
3
+ //# sourceMappingURL=space-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-tools.d.ts","sourceRoot":"","sources":["../../src/tools/space-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,QA8JnD"}
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSpaceTools = registerSpaceTools;
4
+ const zod_1 = require("zod");
5
+ const utils_1 = require("../shared/utils");
6
+ function registerSpaceTools(server) {
7
+ server.tool("searchSpaces", [
8
+ "Searches spaces (sometimes called projects) by name or ID with fuzzy matching.",
9
+ "If 5 or fewer spaces match, automatically fetches all lists (sometimes called boards) and folders within those spaces to provide a complete tree structure.",
10
+ "If more than 5 spaces match, returns only space information with guidance to search more precisely.",
11
+ "You can search by space name (fuzzy matching) or provide an exact space ID.",
12
+ "You can also provide a folder_id to get details about a specific folder (its lists, statuses, and parent space).",
13
+ "Always reference spaces by their URLs when discussing projects or suggesting actions."
14
+ ].join("\n"), {
15
+ terms: zod_1.z
16
+ .array(zod_1.z.string())
17
+ .optional()
18
+ .describe("Array of search terms to match against space names or IDs. If not provided, returns all spaces. Ignored when folder_id is provided."),
19
+ folder_id: zod_1.z
20
+ .string()
21
+ .optional()
22
+ .describe("A ClickUp folder ID to get details about a specific folder including its lists and parent space. When provided, terms and archived are ignored."),
23
+ archived: zod_1.z.boolean().optional().describe("Include archived spaces (default: false)")
24
+ }, {
25
+ readOnlyHint: true
26
+ }, async ({ terms, folder_id, archived = false }) => {
27
+ try {
28
+ // If folder_id is provided, return folder details directly
29
+ if (folder_id) {
30
+ const folder = await (0, utils_1.getFolderDetails)(folder_id);
31
+ const folderText = (0, utils_1.formatFolderTree)(folder);
32
+ return {
33
+ content: [
34
+ { type: "text", text: `Folder details for folder_id: ${folder_id}:` },
35
+ { type: "text", text: folderText },
36
+ ],
37
+ };
38
+ }
39
+ const searchIndex = await (0, utils_1.getSpaceSearchIndex)();
40
+ if (!searchIndex) {
41
+ return {
42
+ content: [{ type: "text", text: "Error: Could not build space search index." }],
43
+ };
44
+ }
45
+ let matchingSpaces = [];
46
+ if (!terms || terms.length === 0) {
47
+ // Return all spaces if no search terms
48
+ matchingSpaces = searchIndex._docs || [];
49
+ }
50
+ else {
51
+ // Perform multi-term search with aggressive boosting
52
+ matchingSpaces = await (0, utils_1.performMultiTermSearch)(searchIndex, terms
53
+ // No ID matcher or direct fetcher for spaces - they don't have direct API endpoints
54
+ );
55
+ }
56
+ // Filter by archived status
57
+ if (!archived) {
58
+ matchingSpaces = matchingSpaces.filter((space) => !space.archived);
59
+ }
60
+ if (matchingSpaces.length === 0) {
61
+ return {
62
+ content: [{ type: "text", text: "No spaces found matching the search criteria." }],
63
+ };
64
+ }
65
+ // Conditionally fetch detailed content based on result count
66
+ const spaceContentPromises = matchingSpaces.map(async (space) => {
67
+ try {
68
+ if (matchingSpaces.length <= 5) {
69
+ // Detailed mode: fetch lists and folders for this space
70
+ const { lists, folders, documents } = await (0, utils_1.getSpaceContent)(space.id);
71
+ return { space, lists, folders, documents };
72
+ }
73
+ else {
74
+ // Summary mode: just return space without content
75
+ return { space, lists: [], folders: [], documents: [] };
76
+ }
77
+ }
78
+ catch (error) {
79
+ console.error(`Error fetching content for space ${space.id}:`, error);
80
+ return { space, lists: [], folders: [], documents: [] };
81
+ }
82
+ });
83
+ const spacesWithContent = await Promise.all(spaceContentPromises);
84
+ const contentBlocks = [];
85
+ const isDetailedMode = matchingSpaces.length <= 5;
86
+ if (isDetailedMode) {
87
+ // Detailed mode: create separate blocks for each space
88
+ spacesWithContent.forEach(({ space, lists, folders, documents }) => {
89
+ // Use shared tree formatting function
90
+ const spaceTreeText = (0, utils_1.formatSpaceTree)(space, lists, folders, documents);
91
+ // Add the complete space as a single content block
92
+ contentBlocks.push({
93
+ type: "text",
94
+ text: spaceTreeText
95
+ });
96
+ });
97
+ }
98
+ else {
99
+ // Summary mode: create a single combined block with all spaces
100
+ const allSpaceLines = [];
101
+ spacesWithContent.forEach(({ space }) => {
102
+ allSpaceLines.push(`🏢 SPACE: ${space.name} (space_id: ${space.id}${space.private ? ', private' : ''}${space.archived ? ', archived' : ''})`);
103
+ });
104
+ contentBlocks.push({
105
+ type: "text",
106
+ text: allSpaceLines.join('\n')
107
+ });
108
+ }
109
+ // Add tip message for summary mode (when there are too many spaces)
110
+ if (matchingSpaces.length > 5) {
111
+ contentBlocks.push({
112
+ type: "text",
113
+ text: `\n💡 Tip: Use more specific search terms to get detailed list information (≤5 spaces will show complete structure)`
114
+ });
115
+ }
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: matchingSpaces.length <= 5
121
+ ? (() => {
122
+ const totalLists = spacesWithContent.reduce((sum, { lists, folders }) => sum + lists.length + folders.reduce((folderSum, f) => folderSum + (f.lists?.length || 0), 0), 0);
123
+ const totalDocuments = spacesWithContent.reduce((sum, { documents }) => sum + documents.length, 0);
124
+ return `Found ${matchingSpaces.length} space(s) with complete tree structure (${totalLists} total lists, ${totalDocuments} total documents):`;
125
+ })()
126
+ : `Found ${matchingSpaces.length} space(s) - showing names and IDs only. Use more specific search terms to get detailed information:`
127
+ },
128
+ ...contentBlocks
129
+ ],
130
+ };
131
+ }
132
+ catch (error) {
133
+ console.error('Error searching spaces:', error);
134
+ return {
135
+ content: [
136
+ {
137
+ type: "text",
138
+ text: `Error searching spaces: ${error instanceof Error ? error.message : 'Unknown error'}`,
139
+ },
140
+ ],
141
+ };
142
+ }
143
+ });
144
+ }
@@ -0,0 +1,8 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ContentBlock } from "../shared/types";
3
+ export declare function registerTaskToolsRead(server: McpServer, userData: any): void;
4
+ /**
5
+ * Helper function to generate consistent task metadata
6
+ */
7
+ export declare function generateTaskMetadata(task: any, timeEntries?: any[], isDetailView?: boolean): Promise<ContentBlock>;
8
+ //# sourceMappingURL=task-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAyC,MAAM,iBAAiB,CAAC;AAOtF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QA6DrE;AA+MD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,EAAE,YAAY,GAAE,OAAe,GAAG,OAAO,CAAC,YAAY,CAAC,CA+G/H"}