@hauptsache.net/clickup-mcp 1.2.0 → 1.3.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.
@@ -16,6 +16,8 @@ function registerSpaceTools(server) {
16
16
  .optional()
17
17
  .describe("Array of search terms to match against space names or IDs. If not provided, returns all spaces."),
18
18
  archived: zod_1.z.boolean().optional().describe("Include archived spaces (default: false)")
19
+ }, {
20
+ readOnlyHint: true
19
21
  }, async ({ terms, archived = false }) => {
20
22
  try {
21
23
  const searchIndex = await (0, utils_1.getSpaceSearchIndex)();
@@ -30,36 +32,10 @@ function registerSpaceTools(server) {
30
32
  matchingSpaces = searchIndex._docs || [];
31
33
  }
32
34
  else {
33
- // Search with fuzzy matching
34
- const uniqueResults = new Map();
35
- terms.forEach(term => {
36
- const trimmedTerm = term.trim();
37
- if (trimmedTerm.length === 0)
38
- return;
39
- // Check if it's an exact space ID first
40
- const exactMatch = searchIndex._docs.find((space) => space.id === trimmedTerm);
41
- if (exactMatch) {
42
- uniqueResults.set(exactMatch.id, { item: exactMatch, score: 0 });
43
- return;
44
- }
45
- // Fuzzy search
46
- const results = searchIndex.search(trimmedTerm);
47
- results.forEach(result => {
48
- if (result.item && typeof result.item.id === 'string') {
49
- const currentScore = result.score ?? 1;
50
- const existing = uniqueResults.get(result.item.id);
51
- if (!existing || currentScore < existing.score) {
52
- uniqueResults.set(result.item.id, {
53
- item: result.item,
54
- score: currentScore
55
- });
56
- }
57
- }
58
- });
59
- });
60
- matchingSpaces = Array.from(uniqueResults.values())
61
- .sort((a, b) => a.score - b.score)
62
- .map(entry => entry.item);
35
+ // Perform multi-term search with aggressive boosting
36
+ matchingSpaces = await (0, utils_1.performMultiTermSearch)(searchIndex, terms
37
+ // No ID matcher or direct fetcher for spaces - they don't have direct API endpoints
38
+ );
63
39
  }
64
40
  // Filter by archived status
65
41
  if (!archived) {
@@ -75,87 +51,105 @@ function registerSpaceTools(server) {
75
51
  try {
76
52
  if (matchingSpaces.length <= 5) {
77
53
  // Detailed mode: fetch lists and folders for this space
78
- const { lists, folders } = await (0, utils_1.getSpaceContent)(space.id);
79
- return { space, lists, folders };
54
+ const { lists, folders, documents } = await (0, utils_1.getSpaceContent)(space.id);
55
+ return { space, lists, folders, documents };
80
56
  }
81
57
  else {
82
58
  // Summary mode: just return space without content
83
- return { space, lists: [], folders: [] };
59
+ return { space, lists: [], folders: [], documents: [] };
84
60
  }
85
61
  }
86
62
  catch (error) {
87
63
  console.error(`Error fetching content for space ${space.id}:`, error);
88
- return { space, lists: [], folders: [] };
64
+ return { space, lists: [], folders: [], documents: [] };
89
65
  }
90
66
  });
91
67
  const spacesWithContent = await Promise.all(spaceContentPromises);
92
68
  const contentBlocks = [];
93
- spacesWithContent.forEach(({ space, lists, folders }, index) => {
94
- const spaceLines = [];
95
- const totalLists = lists.length + folders.reduce((sum, f) => sum + (f.lists?.length || 0), 0);
96
- // Space header
97
- 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`);
98
- // Create a tree structure
99
- const hasDirectLists = lists.length > 0;
100
- const hasFolders = folders.length > 0;
101
- // Direct lists (not in folders)
102
- if (hasDirectLists) {
103
- lists.forEach((list, listIndex) => {
104
- const isLastDirectList = listIndex === lists.length - 1;
105
- const isLastOverall = !hasFolders && isLastDirectList;
106
- const treeChar = isLastOverall ? '└──' : '├──';
107
- const extraInfo = [
108
- ...(list.task_count ? [`${list.task_count} tasks`] : []),
109
- ...(list.private ? ['private'] : []),
110
- ...(list.archived ? ['archived'] : [])
111
- ].join(', ');
112
- const listLine = `${treeChar} 📝 ${list.name} (list_id: ${list.id}${extraInfo ? `, ${extraInfo}` : ''}) ${(0, utils_1.generateListUrl)(list.id)}`;
113
- spaceLines.push(listLine);
114
- });
115
- }
116
- // Folders and their lists
117
- if (hasFolders) {
118
- folders.forEach((folder, folderIndex) => {
119
- const isLastFolder = folderIndex === folders.length - 1;
120
- const folderTreeChar = isLastFolder ? '└──' : '├──';
121
- const folderContinuation = isLastFolder ? ' ' : '│ ';
122
- const folderExtraInfo = [
123
- ...(folder.lists?.length ? [`${folder.lists.length} lists`] : []),
124
- ...(folder.private ? ['private'] : []),
125
- ...(folder.archived ? ['archived'] : [])
126
- ].join(', ');
127
- const folderLine = `${folderTreeChar} 📂 ${folder.name} (folder_id: ${folder.id}${folderExtraInfo ? `, ${folderExtraInfo}` : ''}) ${(0, utils_1.generateFolderUrl)(folder.id)}`;
128
- spaceLines.push(folderLine);
129
- // Lists within this folder
130
- if (folder.lists && folder.lists.length > 0) {
131
- folder.lists.forEach((list, listIndex) => {
132
- const isLastListInFolder = listIndex === folder.lists.length - 1;
133
- const listTreeChar = isLastListInFolder ? '└──' : '├──';
134
- const listExtraInfo = [
135
- ...(list.task_count ? [`${list.task_count} tasks`] : []),
136
- ...(list.private ? ['private'] : []),
137
- ...(list.archived ? ['archived'] : [])
138
- ].join(', ');
139
- const listLine = `${folderContinuation}${listTreeChar} 📝 ${list.name} (list_id: ${list.id}${listExtraInfo ? `, ${listExtraInfo}` : ''}) ${(0, utils_1.generateListUrl)(list.id)}`;
140
- spaceLines.push(listLine);
141
- });
142
- }
69
+ const isDetailedMode = matchingSpaces.length <= 5;
70
+ if (isDetailedMode) {
71
+ // Detailed mode: create separate blocks for each space
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
+ }
135
+ // Add the complete space as a single content block
136
+ contentBlocks.push({
137
+ type: "text",
138
+ text: spaceLines.join('\n')
143
139
  });
144
- }
145
- // Add the complete space as a single content block
140
+ });
141
+ }
142
+ else {
143
+ // Summary mode: create a single combined block with all spaces
144
+ const allSpaceLines = [];
145
+ spacesWithContent.forEach(({ space }) => {
146
+ allSpaceLines.push(`🏢 SPACE: ${space.name} (space_id: ${space.id}${space.private ? ', private' : ''}${space.archived ? ', archived' : ''})`);
147
+ });
146
148
  contentBlocks.push({
147
149
  type: "text",
148
- text: spaceLines.join('\n')
150
+ text: allSpaceLines.join('\n')
149
151
  });
150
- // Add separator between spaces (except for the last one)
151
- if (index < spacesWithContent.length - 1) {
152
- contentBlocks.push({
153
- type: "text",
154
- text: '─'.repeat(50)
155
- });
156
- }
157
- });
158
- const totalLists = spacesWithContent.reduce((sum, { lists, folders }) => sum + lists.length + folders.reduce((folderSum, f) => folderSum + (f.lists?.length || 0), 0), 0);
152
+ }
159
153
  // Add tip message for summary mode (when there are too many spaces)
160
154
  if (matchingSpaces.length > 5) {
161
155
  contentBlocks.push({
@@ -168,8 +162,12 @@ function registerSpaceTools(server) {
168
162
  {
169
163
  type: "text",
170
164
  text: matchingSpaces.length <= 5
171
- ? `Found ${matchingSpaces.length} space(s) with complete tree structure (${totalLists} total lists):`
172
- : `Found ${matchingSpaces.length} space(s) - too many to show detailed list information. Please search more precisely to get complete tree structure with lists and folders:`
165
+ ? (() => {
166
+ const totalLists = spacesWithContent.reduce((sum, { lists, folders }) => sum + lists.length + folders.reduce((folderSum, f) => folderSum + (f.lists?.length || 0), 0), 0);
167
+ const totalDocuments = spacesWithContent.reduce((sum, { documents }) => sum + documents.length, 0);
168
+ return `Found ${matchingSpaces.length} space(s) with complete tree structure (${totalLists} total lists, ${totalDocuments} total documents):`;
169
+ })()
170
+ : `Found ${matchingSpaces.length} space(s) - showing names and IDs only. Use more specific search terms to get detailed information:`
173
171
  },
174
172
  ...contentBlocks
175
173
  ],
@@ -1 +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,QAwDrE;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"}
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,QA2DrE;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"}
@@ -22,6 +22,8 @@ function registerTaskToolsRead(server, userData) {
22
22
  message: "Task ID must be 6-9 alphanumeric characters only"
23
23
  })
24
24
  .describe(`The 6-9 character ID of the task to get without a prefix like "#", "CU-" or "https://app.clickup.com/t/"`),
25
+ }, {
26
+ readOnlyHint: true
25
27
  }, async ({ id }) => {
26
28
  // 1. Load base task content, comment events, and status change events in parallel
27
29
  const [taskDetailContentBlocks, commentEvents, statusChangeEvents] = await Promise.all([
@@ -91,7 +93,7 @@ async function loadTaskContent(taskId) {
91
93
  return await generateTaskMetadata(task, timeEntries, true);
92
94
  })(),
93
95
  // process markdown and download images
94
- (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments),
96
+ (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments || []),
95
97
  ]);
96
98
  return [taskMetadata, ...content];
97
99
  }
@@ -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,QA4StE"}
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"}
@@ -30,6 +30,8 @@ function registerTaskToolsWrite(server, userData) {
30
30
  })(), {
31
31
  task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to comment on"),
32
32
  comment: zod_1.z.string().min(1).describe("The comment text to add to the task"),
33
+ }, {
34
+ readOnlyHint: false
33
35
  }, async ({ task_id, comment }) => {
34
36
  try {
35
37
  const requestBody = {
@@ -105,6 +107,8 @@ function registerTaskToolsWrite(server, userData) {
105
107
  tags: taskTagsSchema.describe("Optional array of tag names (will replace existing tags)"),
106
108
  parent_task_id: zod_1.z.string().optional().describe("Optional parent task ID to change parent/child relationships"),
107
109
  assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
110
+ }, {
111
+ readOnlyHint: false
108
112
  }, async ({ task_id, name, append_description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees }) => {
109
113
  try {
110
114
  const userData = await (0, utils_1.getCurrentUser)();
@@ -213,6 +217,8 @@ function registerTaskToolsWrite(server, userData) {
213
217
  tags: taskTagsSchema,
214
218
  parent_task_id: zod_1.z.string().optional().describe("Optional parent task ID to create this as a subtask"),
215
219
  assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
220
+ }, {
221
+ readOnlyHint: false
216
222
  }, async ({ list_id, name, description, status, priority, due_date, start_date, time_estimate, tags, parent_task_id, assignees }) => {
217
223
  try {
218
224
  const userData = await (0, utils_1.getCurrentUser)();
@@ -1 +1 @@
1
- {"version":3,"file":"time-tools.d.ts","sourceRoot":"","sources":["../../src/tools/time-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA0DpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,QA6EtD;AAwLD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,QAmFvD"}
1
+ {"version":3,"file":"time-tools.d.ts","sourceRoot":"","sources":["../../src/tools/time-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA0DpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,QAgFtD;AAwLD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,QAsFvD"}
@@ -58,6 +58,8 @@ function registerTimeToolsRead(server) {
58
58
  list_id: zod_1.z.string().optional().describe("Optional single list ID to filter time entries by a specific list"),
59
59
  space_id: zod_1.z.string().optional().describe("Optional single space ID to filter time entries by a specific space"),
60
60
  include_all_users: zod_1.z.boolean().optional().describe("Optional flag to include time entries from all team members (default: false, only current user)")
61
+ }, {
62
+ readOnlyHint: true
61
63
  }, async ({ task_id, start_date, end_date, list_id, space_id, include_all_users }) => {
62
64
  try {
63
65
  // Build query parameters
@@ -268,6 +270,8 @@ function registerTimeToolsWrite(server) {
268
270
  hours: zod_1.z.number().min(0.01).max(24).describe("Hours to book (decimal format, e.g., 0.25 = 15min, 1.5 = 1h 30min)"),
269
271
  description: zod_1.z.string().optional().describe("Optional description for the time entry"),
270
272
  start_time: zod_1.z.string().optional().describe("Optional start time as ISO date string (e.g., '2024-10-06T09:00:00+02:00', defaults to current time)")
273
+ }, {
274
+ readOnlyHint: false
271
275
  }, async ({ task_id, hours, description, start_time }) => {
272
276
  try {
273
277
  // Convert hours to milliseconds (ClickUp API uses milliseconds)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hauptsache.net/clickup-mcp",
3
- "version": "1.2.0",
4
- "description": "Transform your AI assistant into a powerful ClickUp integration for both agentic coding and productivity management. Enables seamless task context sharing, intelligent search, time tracking, and complete project management workflows.",
3
+ "version": "1.3.1",
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",
7
7
  "bin": "dist/index.js",
@@ -36,9 +36,9 @@
36
36
  "name": "Marco Pfeiffer",
37
37
  "email": "marco@hauptsache.net"
38
38
  },
39
- "license": "ISC",
39
+ "license": "MIT",
40
40
  "dependencies": {
41
- "@modelcontextprotocol/sdk": "^1.9.0",
41
+ "@modelcontextprotocol/sdk": "^1.15.1",
42
42
  "fuse.js": "^7.1.0",
43
43
  "zod": "^3.24.2"
44
44
  },