@hauptsache.net/clickup-mcp 1.3.2 → 1.4.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.
package/dist/cli.js CHANGED
@@ -13,9 +13,81 @@ async function main() {
13
13
  console.log(server.server._instructions || "No instructions configured");
14
14
  process.exit(0);
15
15
  }
16
+ // Special commands for testing resources
17
+ if (args.length === 1 && args[0] === 'resources') {
18
+ console.log("Listing available resources...");
19
+ try {
20
+ // @ts-ignore - Accessing private property for testing purposes
21
+ const resourceTemplates = server._registeredResourceTemplates;
22
+ if (resourceTemplates && Object.keys(resourceTemplates).length > 0) {
23
+ for (const [name, template] of Object.entries(resourceTemplates)) {
24
+ console.log(`Resource template: ${name}`);
25
+ // @ts-ignore - Access template properties
26
+ const uriTemplate = template.resourceTemplate.uriTemplate;
27
+ console.log(` URI Template: ${uriTemplate}`);
28
+ // Test the list callback if available
29
+ // @ts-ignore - Access template properties
30
+ if (template.resourceTemplate._callbacks.list) {
31
+ try {
32
+ // @ts-ignore - Call list callback
33
+ const result = await template.resourceTemplate._callbacks.list();
34
+ console.log(` Resources found: ${result.resources.length}`);
35
+ result.resources.slice(0, 3).forEach((res, idx) => {
36
+ console.log(` ${idx + 1}. ${res.name} (${res.uri})`);
37
+ });
38
+ if (result.resources.length > 3) {
39
+ console.log(` ... and ${result.resources.length - 3} more`);
40
+ }
41
+ }
42
+ catch (error) {
43
+ console.log(` Error listing resources: ${error instanceof Error ? error.message : 'Unknown error'}`);
44
+ }
45
+ }
46
+ console.log("");
47
+ }
48
+ }
49
+ else {
50
+ console.log("No resource templates registered.");
51
+ }
52
+ }
53
+ catch (error) {
54
+ console.error("Error accessing resources:", error instanceof Error ? error.message : 'Unknown error');
55
+ }
56
+ process.exit(0);
57
+ }
58
+ // Special command to read a specific resource
59
+ if (args.length === 2 && args[0] === 'resource') {
60
+ const resourceUri = args[1];
61
+ console.log(`Reading resource: ${resourceUri}`);
62
+ try {
63
+ // @ts-ignore - Accessing private property for testing purposes
64
+ const resourceTemplates = server._registeredResourceTemplates;
65
+ // Find matching template and call read callback
66
+ for (const [name, template] of Object.entries(resourceTemplates)) {
67
+ try {
68
+ // @ts-ignore - Access template properties
69
+ const result = await template.readCallback(new URL(resourceUri), {}, {});
70
+ console.dir(result, { depth: null });
71
+ process.exit(0);
72
+ }
73
+ catch (error) {
74
+ // Continue to next template if this one doesn't match
75
+ continue;
76
+ }
77
+ }
78
+ console.error("No matching resource template found for URI:", resourceUri);
79
+ process.exit(1);
80
+ }
81
+ catch (error) {
82
+ console.error("Error reading resource:", error instanceof Error ? error.message : 'Unknown error');
83
+ process.exit(1);
84
+ }
85
+ }
16
86
  if (args.length < 1) {
17
87
  console.error("Usage: npm run cli <tool-name> [param1=value1 param2=value2 ...]");
18
88
  console.error(" npm run cli instructions");
89
+ console.error(" npm run cli resources");
90
+ console.error(" npm run cli resource <uri>");
19
91
  console.error("\nAvailable tools:");
20
92
  // @ts-ignore - Accessing private property for testing purposes
21
93
  const tools = server._registeredTools;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiIpE,QAAA,MAAM,aAAa,oBAAqB,CAAC;AAIzC,OAAO,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqIpE,QAAA,MAAM,aAAa,oBAAqB,CAAC;AAIzC,OAAO,EAAE,aAAa,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ const space_tools_1 = require("./tools/space-tools");
14
14
  const list_tools_1 = require("./tools/list-tools");
15
15
  const time_tools_1 = require("./tools/time-tools");
16
16
  const doc_tools_1 = require("./tools/doc-tools");
17
+ const space_resources_1 = require("./resources/space-resources");
17
18
  // Create server variable that will be initialized later
18
19
  let server;
19
20
  // Register tools based on mode with user data for enhanced documentation
@@ -97,6 +98,7 @@ Use the ClickUp search tools to find tasks assigned to me, and get detailed info
97
98
  (0, task_tools_1.registerTaskToolsRead)(server, userData);
98
99
  (0, search_tools_1.registerSearchTools)(server, userData);
99
100
  (0, space_tools_1.registerSpaceTools)(server);
101
+ (0, space_resources_1.registerSpaceResources)(server);
100
102
  (0, list_tools_1.registerListToolsRead)(server);
101
103
  (0, time_tools_1.registerTimeToolsRead)(server);
102
104
  (0, doc_tools_1.registerDocumentToolsRead)(server);
@@ -107,6 +109,7 @@ Use the ClickUp search tools to find tasks assigned to me, and get detailed info
107
109
  (0, task_write_tools_1.registerTaskToolsWrite)(server, userData);
108
110
  (0, search_tools_1.registerSearchTools)(server, userData);
109
111
  (0, space_tools_1.registerSpaceTools)(server);
112
+ (0, space_resources_1.registerSpaceResources)(server);
110
113
  (0, list_tools_1.registerListToolsRead)(server);
111
114
  (0, list_tools_1.registerListToolsWrite)(server);
112
115
  (0, time_tools_1.registerTimeToolsRead)(server);
@@ -120,7 +123,9 @@ Use the ClickUp search tools to find tasks assigned to me, and get detailed info
120
123
  const serverPromise = initializeServer();
121
124
  exports.serverPromise = serverPromise;
122
125
  // Only connect to the transport if this file is being run directly (not imported)
123
- if (require.main === module) {
126
+ // OR if not being imported by CLI (to support Claude Desktop's module loading)
127
+ const isCliMode = process.argv.some(arg => arg.includes('cli.ts') || arg.includes('cli.js'));
128
+ if (require.main === module || !isCliMode) {
124
129
  // Start receiving messages on stdin and sending messages on stdout after initialization
125
130
  serverPromise.then(() => {
126
131
  const transport = new stdio_js_1.StdioServerTransport();
@@ -0,0 +1,6 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Register ClickUp space resources using resource templates for dynamic discovery
4
+ */
5
+ export declare function registerSpaceResources(server: McpServer): void;
6
+ //# sourceMappingURL=space-resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-resources.d.ts","sourceRoot":"","sources":["../../src/resources/space-resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAiBtF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,QAuFvD"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSpaceResources = registerSpaceResources;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const utils_1 = require("../shared/utils");
6
+ /**
7
+ * Extract space ID from clickup:// URI
8
+ */
9
+ function extractSpaceIdFromUri(uriString) {
10
+ try {
11
+ const url = new URL(uriString);
12
+ const pathParts = url.pathname.split('/');
13
+ return pathParts[pathParts.length - 1];
14
+ }
15
+ catch (error) {
16
+ throw new Error(`Invalid ClickUp space URI: ${uriString}`);
17
+ }
18
+ }
19
+ /**
20
+ * Register ClickUp space resources using resource templates for dynamic discovery
21
+ */
22
+ function registerSpaceResources(server) {
23
+ // Create resource template for ClickUp spaces
24
+ const spaceTemplate = new mcp_js_1.ResourceTemplate("clickup://space/{spaceId}", {
25
+ list: async () => {
26
+ try {
27
+ const searchIndex = await (0, utils_1.getSpaceSearchIndex)();
28
+ if (!searchIndex) {
29
+ return { resources: [] };
30
+ }
31
+ const spaces = searchIndex._docs || [];
32
+ // Filter out archived spaces for resource listing
33
+ const activeSpaces = spaces.filter((space) => !space.archived);
34
+ return {
35
+ resources: activeSpaces.map((space) => ({
36
+ uri: `clickup://space/${space.id}`,
37
+ name: space.name,
38
+ mimeType: "text/plain"
39
+ }))
40
+ };
41
+ }
42
+ catch (error) {
43
+ console.error("Error listing space resources:", error);
44
+ return { resources: [] };
45
+ }
46
+ }
47
+ });
48
+ // Register resource template for ClickUp spaces
49
+ server.registerResource("clickup-spaces", spaceTemplate, {
50
+ title: "ClickUp Spaces",
51
+ description: "Access ClickUp spaces with their complete structure including lists, folders, and documents",
52
+ }, async (uri) => {
53
+ try {
54
+ const spaceId = extractSpaceIdFromUri(uri.toString());
55
+ // Fetch space content including lists, folders, and documents
56
+ const { lists, folders, documents } = await (0, utils_1.getSpaceContent)(spaceId);
57
+ // Get space details from the search index
58
+ const searchIndex = await (0, utils_1.getSpaceSearchIndex)();
59
+ const spaces = searchIndex._docs || [];
60
+ const space = spaces.find((s) => s.id === spaceId);
61
+ if (!space) {
62
+ return {
63
+ contents: [{
64
+ uri: uri.toString(),
65
+ text: `Space with ID ${spaceId} not found.`,
66
+ }]
67
+ };
68
+ }
69
+ // Format the content using the shared tree formatting function
70
+ const treeContent = (0, utils_1.formatSpaceTree)(space, lists, folders, documents);
71
+ // Add resource metadata
72
+ const metadata = [
73
+ '\n---',
74
+ `ℹ️ Resource last updated: ${new Date().toISOString()}`,
75
+ `💡 For real-time data, use the searchSpaces tool`
76
+ ].join('\n');
77
+ return {
78
+ contents: [{
79
+ uri: uri.toString(),
80
+ text: treeContent + metadata,
81
+ }]
82
+ };
83
+ }
84
+ catch (error) {
85
+ console.error("Error reading space resource:", error);
86
+ return {
87
+ contents: [{
88
+ uri: uri.toString(),
89
+ text: `Error reading space: ${error instanceof Error ? error.message : 'Unknown error'}`,
90
+ }]
91
+ };
92
+ }
93
+ });
94
+ }
@@ -39,6 +39,11 @@ export declare function generateFolderUrl(folderId: string): string;
39
39
  * Generate a ClickUp document URL from a document ID and optional page ID
40
40
  */
41
41
  export declare function generateDocumentUrl(docId: string, pageId?: string): string;
42
+ /**
43
+ * Format space content as tree structure
44
+ * Shared function used by both searchSpaces tool and space resources
45
+ */
46
+ export declare function formatSpaceTree(space: any, lists: any[], folders: any[], documents: any[]): string;
42
47
  /**
43
48
  * Get or refresh the space search index
44
49
  * Caches promise to prevent race conditions on concurrent calls
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,SAAS,CAAC;AAI3B;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG7C;AAKD;;;GAGG;AACH,wBAAsB,cAAc,iBA6BnC;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CA0B7D;AAKD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,EACnB,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA+B3B;AAkED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1E;AAKD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA+CrE;AAKD;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAAC,OAAO,EAAE,GAAG,EAAE,CAAC;IAAC,SAAS,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,CAgFlH;AAKD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CA+C3D;AAKD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA6B3B;AAuID;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,CAAC,EAAE,CAAC,CA8Dd"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,SAAS,CAAC;AAI3B;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG7C;AAKD;;;GAGG;AACH,wBAAsB,cAAc,iBA6BnC;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CA0B7D;AAKD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,EACnB,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA+B3B;AAkED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,MAAM,CA4ElG;AAKD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA+CrE;AAKD;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAAC,OAAO,EAAE,GAAG,EAAE,CAAC;IAAC,SAAS,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,CAgFlH;AAKD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CA+C3D;AAKD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CA6B3B;AAuID;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,CAAC,EAAE,CAAC,CA8Dd"}
@@ -13,6 +13,7 @@ exports.generateListUrl = generateListUrl;
13
13
  exports.generateSpaceUrl = generateSpaceUrl;
14
14
  exports.generateFolderUrl = generateFolderUrl;
15
15
  exports.generateDocumentUrl = generateDocumentUrl;
16
+ exports.formatSpaceTree = formatSpaceTree;
16
17
  exports.getSpaceSearchIndex = getSpaceSearchIndex;
17
18
  exports.getSpaceContent = getSpaceContent;
18
19
  exports.getAllTeamMembers = getAllTeamMembers;
@@ -209,6 +210,75 @@ function generateDocumentUrl(docId, pageId) {
209
210
  }
210
211
  return `https://app.clickup.com/${config_1.CONFIG.teamId}/v/dc/${docId}`;
211
212
  }
213
+ /**
214
+ * Format space content as tree structure
215
+ * Shared function used by both searchSpaces tool and space resources
216
+ */
217
+ function formatSpaceTree(space, lists, folders, documents) {
218
+ const spaceLines = [];
219
+ const totalLists = lists.length + folders.reduce((sum, f) => sum + (f.lists?.length || 0), 0);
220
+ // Space header
221
+ spaceLines.push(`🏢 SPACE: ${space.name} (space_id: ${space.id}${space.private ? ', private' : ''}${space.archived ? ', archived' : ''}) ${generateSpaceUrl(space.id)}`, ` ${totalLists} lists, ${folders.length} folders, ${documents.length} documents`);
222
+ // Create a tree structure
223
+ const hasDirectLists = lists.length > 0;
224
+ const hasFolders = folders.length > 0;
225
+ const hasDocuments = documents.length > 0;
226
+ // Direct lists (not in folders)
227
+ if (hasDirectLists) {
228
+ lists.forEach((list, listIndex) => {
229
+ const isLastDirectList = listIndex === lists.length - 1;
230
+ const isLastOverall = !hasFolders && !hasDocuments && isLastDirectList;
231
+ const treeChar = isLastOverall ? '└──' : '├──';
232
+ const extraInfo = [
233
+ ...(list.task_count ? [`${list.task_count} tasks`] : []),
234
+ ...(list.private ? ['private'] : []),
235
+ ...(list.archived ? ['archived'] : [])
236
+ ].join(', ');
237
+ const listLine = `${treeChar} 📝 ${list.name} (list_id: ${list.id}${extraInfo ? `, ${extraInfo}` : ''}) ${generateListUrl(list.id)}`;
238
+ spaceLines.push(listLine);
239
+ });
240
+ }
241
+ // Folders and their lists
242
+ if (hasFolders) {
243
+ folders.forEach((folder, folderIndex) => {
244
+ const isLastFolder = folderIndex === folders.length - 1;
245
+ const isLastOverall = !hasDocuments && isLastFolder;
246
+ const folderTreeChar = isLastOverall ? '└──' : '├──';
247
+ const folderContinuation = isLastOverall ? ' ' : '│ ';
248
+ const folderExtraInfo = [
249
+ ...(folder.lists?.length ? [`${folder.lists.length} lists`] : []),
250
+ ...(folder.private ? ['private'] : []),
251
+ ...(folder.archived ? ['archived'] : [])
252
+ ].join(', ');
253
+ const folderLine = `${folderTreeChar} 📂 ${folder.name} (folder_id: ${folder.id}${folderExtraInfo ? `, ${folderExtraInfo}` : ''}) ${generateFolderUrl(folder.id)}`;
254
+ spaceLines.push(folderLine);
255
+ // Lists within this folder
256
+ if (folder.lists && folder.lists.length > 0) {
257
+ folder.lists.forEach((list, listIndex) => {
258
+ const isLastListInFolder = listIndex === folder.lists.length - 1;
259
+ const listTreeChar = isLastListInFolder ? '└──' : '├──';
260
+ const listExtraInfo = [
261
+ ...(list.task_count ? [`${list.task_count} tasks`] : []),
262
+ ...(list.private ? ['private'] : []),
263
+ ...(list.archived ? ['archived'] : [])
264
+ ].join(', ');
265
+ const listLine = `${folderContinuation}${listTreeChar} 📝 ${list.name} (list_id: ${list.id}${listExtraInfo ? `, ${listExtraInfo}` : ''}) ${generateListUrl(list.id)}`;
266
+ spaceLines.push(listLine);
267
+ });
268
+ }
269
+ });
270
+ }
271
+ // Documents attached to this space
272
+ if (hasDocuments) {
273
+ documents.forEach((document, docIndex) => {
274
+ const isLastDocument = docIndex === documents.length - 1;
275
+ const docTreeChar = isLastDocument ? '└──' : '├──';
276
+ const docLine = `${docTreeChar} 📄 ${document.name} (doc_id: ${document.id}) ${generateDocumentUrl(document.id)}`;
277
+ spaceLines.push(docLine);
278
+ });
279
+ }
280
+ return spaceLines.join('\n');
281
+ }
212
282
  // Space search index cache - cache promise to prevent race conditions
213
283
  let spaceSearchIndexPromise = null;
214
284
  /**
@@ -1 +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,QAoNnD"}
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,QA6InD"}
@@ -70,72 +70,12 @@ function registerSpaceTools(server) {
70
70
  if (isDetailedMode) {
71
71
  // Detailed mode: create separate blocks for each space
72
72
  spacesWithContent.forEach(({ space, lists, folders, documents }) => {
73
- const spaceLines = [];
74
- const totalLists = lists.length + folders.reduce((sum, f) => sum + (f.lists?.length || 0), 0);
75
- // Space header
76
- spaceLines.push(`🏢 SPACE: ${space.name} (space_id: ${space.id}${space.private ? ', private' : ''}${space.archived ? ', archived' : ''}) ${(0, utils_1.generateSpaceUrl)(space.id)}`, ` ${totalLists} lists, ${folders.length} folders, ${documents.length} documents`);
77
- // Create a tree structure
78
- const hasDirectLists = lists.length > 0;
79
- const hasFolders = folders.length > 0;
80
- const hasDocuments = documents.length > 0;
81
- // Direct lists (not in folders)
82
- if (hasDirectLists) {
83
- lists.forEach((list, listIndex) => {
84
- const isLastDirectList = listIndex === lists.length - 1;
85
- const isLastOverall = !hasFolders && !hasDocuments && isLastDirectList;
86
- const treeChar = isLastOverall ? '└──' : '├──';
87
- const extraInfo = [
88
- ...(list.task_count ? [`${list.task_count} tasks`] : []),
89
- ...(list.private ? ['private'] : []),
90
- ...(list.archived ? ['archived'] : [])
91
- ].join(', ');
92
- const listLine = `${treeChar} 📝 ${list.name} (list_id: ${list.id}${extraInfo ? `, ${extraInfo}` : ''}) ${(0, utils_1.generateListUrl)(list.id)}`;
93
- spaceLines.push(listLine);
94
- });
95
- }
96
- // Folders and their lists
97
- if (hasFolders) {
98
- folders.forEach((folder, folderIndex) => {
99
- const isLastFolder = folderIndex === folders.length - 1;
100
- const isLastOverall = !hasDocuments && isLastFolder;
101
- const folderTreeChar = isLastOverall ? '└──' : '├──';
102
- const folderContinuation = isLastOverall ? ' ' : '│ ';
103
- const folderExtraInfo = [
104
- ...(folder.lists?.length ? [`${folder.lists.length} lists`] : []),
105
- ...(folder.private ? ['private'] : []),
106
- ...(folder.archived ? ['archived'] : [])
107
- ].join(', ');
108
- const folderLine = `${folderTreeChar} 📂 ${folder.name} (folder_id: ${folder.id}${folderExtraInfo ? `, ${folderExtraInfo}` : ''}) ${(0, utils_1.generateFolderUrl)(folder.id)}`;
109
- spaceLines.push(folderLine);
110
- // Lists within this folder
111
- if (folder.lists && folder.lists.length > 0) {
112
- folder.lists.forEach((list, listIndex) => {
113
- const isLastListInFolder = listIndex === folder.lists.length - 1;
114
- const listTreeChar = isLastListInFolder ? '└──' : '├──';
115
- const listExtraInfo = [
116
- ...(list.task_count ? [`${list.task_count} tasks`] : []),
117
- ...(list.private ? ['private'] : []),
118
- ...(list.archived ? ['archived'] : [])
119
- ].join(', ');
120
- const listLine = `${folderContinuation}${listTreeChar} 📝 ${list.name} (list_id: ${list.id}${listExtraInfo ? `, ${listExtraInfo}` : ''}) ${(0, utils_1.generateListUrl)(list.id)}`;
121
- spaceLines.push(listLine);
122
- });
123
- }
124
- });
125
- }
126
- // Documents attached to this space
127
- if (hasDocuments) {
128
- documents.forEach((document, docIndex) => {
129
- const isLastDocument = docIndex === documents.length - 1;
130
- const docTreeChar = isLastDocument ? '└──' : '├──';
131
- const docLine = `${docTreeChar} 📄 ${document.name} (doc_id: ${document.id}) ${(0, utils_1.generateDocumentUrl)(document.id)}`;
132
- spaceLines.push(docLine);
133
- });
134
- }
73
+ // Use shared tree formatting function
74
+ const spaceTreeText = (0, utils_1.formatSpaceTree)(space, lists, folders, documents);
135
75
  // Add the complete space as a single content block
136
76
  contentBlocks.push({
137
77
  type: "text",
138
- text: spaceLines.join('\n')
78
+ text: spaceTreeText
139
79
  });
140
80
  });
141
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"task-write-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-write-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QAqTtE"}
1
+ {"version":3,"file":"task-write-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-write-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QAwXtE"}
@@ -120,6 +120,48 @@ function registerTaskToolsWrite(server, userData) {
120
120
  throw new Error(`Error fetching task: ${taskResponse.status} ${taskResponse.statusText}`);
121
121
  }
122
122
  const taskData = await taskResponse.json();
123
+ // Handle tags separately since they need individual API calls
124
+ let tagUpdateResults = [];
125
+ if (tags !== undefined) {
126
+ // Get current tags
127
+ const currentTags = taskData.tags?.map((t) => t.name) || [];
128
+ const tagsToAdd = tags.filter(tag => !currentTags.includes(tag));
129
+ const tagsToRemove = currentTags.filter((tag) => !tags.includes(tag));
130
+ // Add new tags
131
+ for (const tagName of tagsToAdd) {
132
+ try {
133
+ const addTagResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/tag/${encodeURIComponent(tagName)}`, {
134
+ method: 'POST',
135
+ headers: { Authorization: config_1.CONFIG.apiKey }
136
+ });
137
+ if (!addTagResponse.ok) {
138
+ console.error(`Failed to add tag "${tagName}": ${addTagResponse.status}`);
139
+ tagUpdateResults.push(`Failed to add tag: ${tagName}`);
140
+ }
141
+ }
142
+ catch (error) {
143
+ console.error(`Error adding tag "${tagName}":`, error);
144
+ tagUpdateResults.push(`Error adding tag: ${tagName}`);
145
+ }
146
+ }
147
+ // Remove old tags
148
+ for (const tagName of tagsToRemove) {
149
+ try {
150
+ const removeTagResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/tag/${encodeURIComponent(tagName)}`, {
151
+ method: 'DELETE',
152
+ headers: { Authorization: config_1.CONFIG.apiKey }
153
+ });
154
+ if (!removeTagResponse.ok) {
155
+ console.error(`Failed to remove tag "${tagName}": ${removeTagResponse.status}`);
156
+ tagUpdateResults.push(`Failed to remove tag: ${tagName}`);
157
+ }
158
+ }
159
+ catch (error) {
160
+ console.error(`Error removing tag "${tagName}":`, error);
161
+ tagUpdateResults.push(`Error removing tag: ${tagName}`);
162
+ }
163
+ }
164
+ }
123
165
  // Handle append-only description update with markdown support
124
166
  let finalDescription;
125
167
  if (append_description) {
@@ -128,9 +170,9 @@ function registerTaskToolsWrite(server, userData) {
128
170
  const separator = currentDescription.trim() ? "\n\n---\n" : "";
129
171
  finalDescription = currentDescription + separator + `**Edit (${timestamp}):** ${append_description}`;
130
172
  }
131
- // Build update body using shared utility (without description since we handle it separately)
173
+ // Build update body without tags (they're handled separately)
132
174
  const updateBody = buildTaskRequestBody({
133
- name, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees
175
+ name, status, priority, due_date, start_date, time_estimate, parent_task_id, assignees
134
176
  });
135
177
  // Add markdown description if we have content to append
136
178
  if (finalDescription !== undefined) {
@@ -140,8 +182,8 @@ function registerTaskToolsWrite(server, userData) {
140
182
  if (assignees !== undefined) {
141
183
  updateBody.assignees = { add: assignees, rem: [] }; // Add new assignees, remove none
142
184
  }
143
- // Check if there's anything to update
144
- if (Object.keys(updateBody).length === 0) {
185
+ // Check if there's anything to update (including tags which were handled separately)
186
+ if (Object.keys(updateBody).length === 0 && tags === undefined) {
145
187
  return {
146
188
  content: [
147
189
  {
@@ -151,23 +193,39 @@ function registerTaskToolsWrite(server, userData) {
151
193
  ],
152
194
  };
153
195
  }
154
- // Update the task
155
- const updateResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}`, {
156
- method: 'PUT',
157
- headers: {
158
- Authorization: config_1.CONFIG.apiKey,
159
- 'Content-Type': 'application/json'
160
- },
161
- body: JSON.stringify(updateBody)
162
- });
163
- if (!updateResponse.ok) {
164
- const errorData = await updateResponse.json().catch(() => ({}));
165
- throw new Error(`Error updating task: ${updateResponse.status} ${updateResponse.statusText} - ${JSON.stringify(errorData)}`);
196
+ // Update the task (if there are non-tag updates)
197
+ let updatedTask = taskData;
198
+ if (Object.keys(updateBody).length > 0) {
199
+ const updateResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}`, {
200
+ method: 'PUT',
201
+ headers: {
202
+ Authorization: config_1.CONFIG.apiKey,
203
+ 'Content-Type': 'application/json'
204
+ },
205
+ body: JSON.stringify(updateBody)
206
+ });
207
+ if (!updateResponse.ok) {
208
+ const errorData = await updateResponse.json().catch(() => ({}));
209
+ throw new Error(`Error updating task: ${updateResponse.status} ${updateResponse.statusText} - ${JSON.stringify(errorData)}`);
210
+ }
211
+ updatedTask = await updateResponse.json();
212
+ }
213
+ // If only tags were updated, fetch the task again to get the updated state
214
+ if (tags !== undefined && Object.keys(updateBody).length === 0) {
215
+ const refreshResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}`, {
216
+ headers: { Authorization: config_1.CONFIG.apiKey },
217
+ });
218
+ if (refreshResponse.ok) {
219
+ updatedTask = await refreshResponse.json();
220
+ }
166
221
  }
167
- const updatedTask = await updateResponse.json();
168
222
  const responseLines = formatTaskResponse(updatedTask, 'updated', {
169
223
  name, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees
170
224
  }, userData);
225
+ // Add tag update results if any
226
+ if (tagUpdateResults.length > 0) {
227
+ responseLines.push('tag_warnings: ' + tagUpdateResults.join('; '));
228
+ }
171
229
  return {
172
230
  content: [
173
231
  {
@@ -329,9 +387,8 @@ function buildTaskRequestBody(params, currentUserId) {
329
387
  if (params.time_estimate !== undefined) {
330
388
  requestBody.time_estimate = Math.round(params.time_estimate * 60 * 60 * 1000);
331
389
  }
332
- if (params.tags !== undefined && params.tags.length > 0) {
333
- requestBody.tags = params.tags;
334
- }
390
+ // Tags are handled separately via dedicated API endpoints
391
+ // Do not include in the update request body
335
392
  if (params.assignees !== undefined) {
336
393
  requestBody.assignees = params.assignees;
337
394
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hauptsache.net/clickup-mcp",
3
- "version": "1.3.2",
3
+ "version": "1.4.1",
4
4
  "description": "Search, create, and retrieve tasks, add comments, and track time through natural language commands.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,7 +16,8 @@
16
16
  "cli": "npx ts-node src/cli.ts",
17
17
  "prettier": "prettier --write src/**/*.ts",
18
18
  "prepublishOnly": "rm -r dist && npm run build",
19
- "release": "npm run build && npm publish --access public && git add . && git commit -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git tag -a v$(node -p 'require(\"./package.json\").version') -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git push && git push --tags"
19
+ "release": "npm run build && npm publish --access public && git add . && git commit -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git tag -a v$(node -p 'require(\"./package.json\").version') -m \"Release v$(node -p 'require(\"./package.json\").version')\" && git push && git push --tags",
20
+ "dxt": "npm run build && npx dxt pack"
20
21
  },
21
22
  "keywords": [
22
23
  "clickup",