@hauptsache.net/clickup-mcp 1.0.4 → 1.1.0

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/index.js CHANGED
@@ -1,421 +1,64 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
3
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.server = void 0;
4
+ exports.serverPromise = exports.server = void 0;
8
5
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
9
6
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
10
- const zod_1 = require("zod");
11
- const clickup_text_1 = require("./clickup-text");
12
- const fuse_js_1 = __importDefault(require("fuse.js"));
13
- // --- End Simplified Event Wrapper ---
14
- const rawPrimaryLang = process.env.CLICKUP_PRIMARY_LANGUAGE || process.env.LANG;
15
- let detectedLanguageHint = undefined;
16
- if (rawPrimaryLang) {
17
- // Extract the primary language part (e.g., 'en' from 'en_US.UTF-8' or 'en-GB')
18
- // and convert to lowercase.
19
- const langPart = rawPrimaryLang.match(/^[a-zA-Z]{2,3}/);
20
- if (langPart) {
21
- detectedLanguageHint = langPart[0].toLowerCase();
22
- }
23
- }
24
- const CONFIG = {
25
- apiKey: process.env.CLICKUP_API_KEY,
26
- teamId: process.env.CLICKUP_TEAM_ID,
27
- maxImages: process.env.MAX_IMAGES ? parseInt(process.env.MAX_IMAGES) : 4,
28
- primaryLanguageHint: detectedLanguageHint, // Store the cleaned code directly
29
- };
30
- if (!CONFIG.apiKey || !CONFIG.teamId) {
31
- throw new Error("Missing Clickup API key or team ID");
32
- }
7
+ const config_1 = require("./shared/config");
8
+ const utils_1 = require("./shared/utils");
9
+ // Import tool registration functions
10
+ const task_tools_1 = require("./tools/task-tools");
11
+ const search_tools_1 = require("./tools/search-tools");
12
+ const space_tools_1 = require("./tools/space-tools");
13
+ const list_tools_1 = require("./tools/list-tools");
14
+ const time_tools_1 = require("./tools/time-tools");
33
15
  // Create an MCP server
34
- exports.server = new mcp_js_1.McpServer({
16
+ const server = new mcp_js_1.McpServer({
35
17
  name: "Clickup MCP",
36
18
  version: "1.0.0",
37
19
  });
38
- /**
39
- * Checks if a string looks like a valid ClickUp task ID
40
- * Valid task IDs are 7-9 characters long and contain only alphanumeric characters
41
- */
42
- function isTaskId(str) {
43
- // Task IDs are 7-9 characters long and contain only alphanumeric characters
44
- return /^[a-z0-9]{7,9}$/i.test(str);
45
- }
46
- exports.server.tool("getTaskById", "Get a Clickup task with images and comments by ID", {
47
- id: zod_1.z
48
- .string()
49
- .min(7)
50
- .max(9)
51
- .refine(val => isTaskId(val), {
52
- message: "Task ID must be 7-9 alphanumeric characters only"
53
- })
54
- .describe(`The 7-9 character ID of the task to get without a prefix like "#", "CU-" or "https://app.clickup.com/t/"`),
55
- }, async ({ id }) => {
56
- // 1. Load base task content, comment events, and status change events in parallel
57
- const [taskDetailContentBlocks, commentEvents, statusChangeEvents] = await Promise.all([
58
- loadTaskContent(id), // Returns Promise<ContentBlock[]>
59
- loadTaskComments(id), // Returns Promise<DatedContentEvent[]>
60
- loadTimeInStatusHistory(id), // Returns Promise<DatedContentEvent[]>
61
- ]);
62
- // 2. Combine comment and status change events
63
- const allDatedEvents = [...commentEvents, ...statusChangeEvents];
64
- // 3. Sort all dated events chronologically
65
- allDatedEvents.sort((a, b) => {
66
- const dateA = a.date ? parseInt(a.date) : 0;
67
- const dateB = b.date ? parseInt(b.date) : 0;
68
- return dateA - dateB;
69
- });
70
- // 4. Flatten sorted events into a single ContentBlock stream
71
- let processedEventBlocks = [];
72
- for (const event of allDatedEvents) {
73
- processedEventBlocks.push(...event.contentBlocks);
74
- }
75
- // 5. Combine task details with processed event blocks
76
- const allContentBlocks = [...taskDetailContentBlocks, ...processedEventBlocks];
77
- // 6. Limit images
78
- const limitedContent = limitImages(allContentBlocks, CONFIG.maxImages);
79
- return {
80
- content: limitedContent,
81
- };
82
- });
83
- async function loadTaskContent(taskId) {
84
- const response = await fetch(`https://api.clickup.com/api/v2/task/${taskId}?include_markdown_description=true&include_subtasks=true`, { headers: { Authorization: CONFIG.apiKey } });
85
- const task = await response.json();
86
- const content = await (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments);
87
- // Create the task metadata block using the helper function
88
- const taskMetadata = await generateTaskMetadata(task);
89
- return [taskMetadata, ...content];
90
- }
91
- async function loadTaskComments(id) {
92
- const response = await fetch(`https://api.clickup.com/api/v2/task/${id}/comment?start_date=0`, // Ensure all comments are fetched
93
- { headers: { Authorization: CONFIG.apiKey } });
94
- if (!response.ok) {
95
- console.error(`Error fetching comments for task ${id}: ${response.status} ${response.statusText}`);
96
- return [];
97
- }
98
- const commentsData = await response.json();
99
- if (!commentsData.comments || !Array.isArray(commentsData.comments)) {
100
- console.error(`Unexpected comment data structure for task ${id}`);
101
- return [];
102
- }
103
- const commentEvents = await Promise.all(commentsData.comments.map(async (comment) => {
104
- const headerBlock = {
105
- type: "text",
106
- text: `Comment by ${comment.user.username} on ${new Date(+comment.date)}:`,
107
- };
108
- const commentBodyBlocks = await (0, clickup_text_1.processClickUpText)(comment.comment);
109
- return {
110
- date: comment.date, // String timestamp from ClickUp for sorting
111
- contentBlocks: [headerBlock, ...commentBodyBlocks],
112
- };
113
- }));
114
- return commentEvents;
115
- }
116
- async function loadTimeInStatusHistory(taskId) {
117
- const url = `https://api.clickup.com/api/v2/task/${taskId}/time_in_status`;
118
- try {
119
- const response = await fetch(url, { headers: { Authorization: CONFIG.apiKey } });
120
- if (!response.ok) {
121
- console.error(`Error fetching time in status for task ${taskId}: ${response.status} ${response.statusText}`);
122
- return [];
123
- }
124
- // Using 'any' for less strict typing as per user preference, but keeping structure for clarity
125
- const data = await response.json();
126
- const events = [];
127
- const processStatusEntry = (entry) => {
128
- if (!entry || !entry.total_time || !entry.total_time.since || !entry.status)
129
- return null;
130
- return {
131
- date: entry.total_time.since,
132
- contentBlocks: [{
133
- type: "text",
134
- text: `Status set to '${entry.status}' on ${new Date(+entry.total_time.since)}`,
135
- }],
136
- };
137
- };
138
- if (data.status_history && Array.isArray(data.status_history)) {
139
- data.status_history.forEach((historyEntry) => {
140
- const event = processStatusEntry(historyEntry);
141
- if (event)
142
- events.push(event);
143
- });
144
- }
145
- if (data.current_status) {
146
- const event = processStatusEntry(data.current_status);
147
- // Ensure current_status is only added if it's distinct or more recent than the last history item.
148
- // The deduplication logic below handles if it's the same as the last history entry.
149
- if (event)
150
- events.push(event);
151
- }
152
- // Deduplicate events based on date and status name to avoid adding current_status if it's identical to the last history entry
153
- const uniqueEvents = Array.from(new Map(events.map(event => [`${event.date}-${event.contentBlocks[0]?.text}`, event] // Keying by date and text content of first block
154
- )).values());
155
- return uniqueEvents;
156
- }
157
- catch (error) {
158
- console.error(`Exception fetching time in status for task ${taskId}:`, error);
159
- return [];
160
- }
161
- }
162
- /**
163
- * Helper function to generate consistent task metadata
164
- */
165
- async function generateTaskMetadata(task) {
166
- let spaceName = task.space?.name || 'Unknown Space';
167
- let spaceIdForDisplay = task.space?.id || 'N/A';
168
- if (spaceName === 'Unknown Space' && task.space?.id) {
169
- const spaceDetails = await getSpaceDetails(task.space.id);
170
- if (spaceDetails && spaceDetails.name) {
171
- spaceName = spaceDetails.name;
172
- }
173
- }
174
- const metadataLines = [
175
- `task_id: ${task.id}`,
176
- `name: ${task.name}`,
177
- `status: ${task.status.status}`,
178
- `date_created: ${new Date(+task.date_created)}`,
179
- `date_updated: ${new Date(+task.date_updated)}`,
180
- `creator: ${task.creator.username}`,
181
- `assignee: ${task.assignees.map((a) => a.username).join(', ')}`,
182
- `list: ${task.list.name} (${task.list.id})`,
183
- `space: ${spaceName} (${spaceIdForDisplay})`,
184
- ];
185
- // Add parent task information if it exists
186
- if (typeof task.parent === "string") {
187
- metadataLines.push(`parent_task_id: ${task.parent}`);
188
- }
189
- // Add child task information if it exists
190
- if (task.subtasks && task.subtasks.length > 0) {
191
- metadataLines.push(`child_task_ids: ${task.subtasks.map((st) => st.id).join(', ')}`);
192
- }
193
- return {
194
- type: "text",
195
- text: metadataLines.join("\n"),
196
- };
197
- }
198
- const spaceCache = new Map(); // Global cache for space details promises
199
- /**
200
- * Function to get space details, using a cache to avoid redundant fetches
201
- */
202
- async function getSpaceDetails(spaceId) {
203
- if (!spaceId) {
204
- return null;
205
- }
206
- if (!spaceCache.has(spaceId)) {
207
- const fetchPromise = fetch(`https://api.clickup.com/api/v2/space/${spaceId}`, {
208
- headers: { Authorization: CONFIG.apiKey },
209
- })
210
- .then(res => {
211
- if (!res.ok) {
212
- // Don't cache failed requests, or handle errors more gracefully
213
- console.error(`Error fetching space ${spaceId}: ${res.status}`);
214
- spaceCache.delete(spaceId); // Allow retry on next call
215
- return null;
216
- }
217
- return res.json();
218
- })
219
- .catch(error => {
220
- console.error(`Network error fetching space ${spaceId}:`, error);
221
- spaceCache.delete(spaceId); // Allow retry on next call
222
- return null;
223
- });
224
- spaceCache.set(spaceId, fetchPromise);
225
- }
226
- return spaceCache.get(spaceId);
20
+ exports.server = server;
21
+ // Register tools based on mode with user data for enhanced documentation
22
+ async function initializeServer() {
23
+ console.error(`Starting ClickUp MCP in ${config_1.CONFIG.mode} mode`);
24
+ // Fetch current user for enhanced tool documentation and API health check
25
+ const userData = await (0, utils_1.getCurrentUser)();
26
+ console.error(`Connected as: ${userData.user.username} (${userData.user.email})`);
27
+ if (config_1.CONFIG.mode === 'read-minimal') {
28
+ // Core task context tools for AI coding assistance
29
+ // Only getTaskById and searchTasks
30
+ (0, task_tools_1.registerTaskToolsRead)(server, userData);
31
+ (0, search_tools_1.registerSearchTools)(server);
32
+ }
33
+ else if (config_1.CONFIG.mode === 'read') {
34
+ // All read-only tools
35
+ (0, task_tools_1.registerTaskToolsRead)(server, userData);
36
+ (0, search_tools_1.registerSearchTools)(server);
37
+ (0, space_tools_1.registerSpaceTools)(server);
38
+ (0, list_tools_1.registerListToolsRead)(server);
39
+ (0, time_tools_1.registerTimeToolsRead)(server);
40
+ }
41
+ else if (config_1.CONFIG.mode === 'write') {
42
+ // All tools (full functionality)
43
+ (0, task_tools_1.registerTaskToolsRead)(server, userData);
44
+ (0, task_tools_1.registerTaskToolsWrite)(server, userData);
45
+ (0, search_tools_1.registerSearchTools)(server);
46
+ (0, space_tools_1.registerSpaceTools)(server);
47
+ (0, list_tools_1.registerListToolsRead)(server);
48
+ (0, list_tools_1.registerListToolsWrite)(server);
49
+ (0, time_tools_1.registerTimeToolsRead)(server);
50
+ (0, time_tools_1.registerTimeToolsWrite)(server);
51
+ }
52
+ return server;
227
53
  }
228
- /**
229
- * Limits the number of images in the content array, replacing excess images with text placeholders
230
- * Prioritizes keeping the most recent images (assumes content is ordered with newest items last)
231
- *
232
- * @param content Array of content blocks that may contain images
233
- * @param maxImages Maximum number of images to keep
234
- * @returns Modified content array with limited images
235
- */
236
- function limitImages(content, maxImages) {
237
- // Count how many images we have
238
- const imageIndices = [];
239
- // Find all image blocks
240
- content.forEach((block, index) => {
241
- if (block.type === "image") {
242
- imageIndices.push(index);
243
- }
244
- });
245
- // If we have fewer images than the limit, return the original content
246
- if (imageIndices.length <= maxImages) {
247
- return content;
248
- }
249
- // Determine which images to keep (the most recent ones)
250
- // We want to keep the last 'maxImages' images
251
- const imagesToRemove = imageIndices.slice(0, imageIndices.length - maxImages);
252
- // Create a new content array with excess images replaced by text
253
- return content.map((block, index) => {
254
- if (block.type === "image" && imagesToRemove.includes(index)) {
255
- return {
256
- type: "text",
257
- text: "[Image removed due to size limitations. Only the most recent images are shown.]",
258
- };
259
- }
260
- return block;
261
- });
262
- }
263
- let taskSearchIndex = null;
264
- let lastIndexUpdateTime = 0;
265
- const INDEX_REFRESH_INTERVAL = 60000; // 60 seconds
266
- const MAX_SEARCH_RESULTS = 50;
267
- // Dynamically construct the searchTask description
268
- const searchTaskDescriptionBase = [
269
- "Searches tasks by name, content, assignees, and ID (case insensitive) with fuzzy matching and support for multiple search terms (OR logic).",
270
- // Placeholder for language-specific guidance
271
- "You'll get a rough overview of the tasks that match the search terms, sorted by relevance.",
272
- "Always use getTaskById to get more specific information if a task is relevant.",
273
- ];
274
- if (CONFIG.primaryLanguageHint && CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
275
- searchTaskDescriptionBase.splice(1, 0, `For optimal results, as your ClickUp tasks may be primarily in '${CONFIG.primaryLanguageHint}', consider providing search terms in English and '${CONFIG.primaryLanguageHint}'.`);
276
- }
277
- exports.server.tool("searchTask", searchTaskDescriptionBase.join("\n"), {
278
- terms: zod_1.z
279
- .string()
280
- .min(3)
281
- .describe("Search terms separated by '|' for OR logic (e.g., 'term1|term2|term3') or a direct task ID"),
282
- }, async ({ terms }) => {
283
- const now = Date.now();
284
- if (!taskSearchIndex || (now - lastIndexUpdateTime > INDEX_REFRESH_INTERVAL)) {
285
- console.error('Refreshing ClickUp task index...');
286
- const taskListsPromises = [...Array(30)].map((_, i) => {
287
- return fetch(`https://api.clickup.com/api/v2/team/${CONFIG.teamId}/task?order_by=updated&page=${i}&subtasks=true`, { headers: { Authorization: CONFIG.apiKey } }).then((res) => res.json()).catch(e => {
288
- console.error(`Error fetching page ${i} for index:`, e);
289
- return { tasks: [] };
290
- });
291
- });
292
- const taskLists = await Promise.all(taskListsPromises);
293
- const allFetchedTasks = taskLists.flatMap(taskList => taskList.tasks);
294
- if (allFetchedTasks.length > 0) {
295
- taskSearchIndex = new fuse_js_1.default(allFetchedTasks, {
296
- keys: [
297
- { name: 'name', weight: 0.7 },
298
- { name: 'id', weight: 0.6 },
299
- { name: 'text_content', weight: 0.5 }, // Task description/content
300
- { name: 'tags.name', weight: 0.4 }, // Task Tags
301
- { name: 'assignees.username', weight: 0.4 }, // Task Assignees
302
- { name: 'list.name', weight: 0.3 }, // Name of the List the task is in
303
- { name: 'folder.name', weight: 0.2 }, // Name of the Folder the task is in
304
- { name: 'space.name', weight: 0.1 } // Name of the Space the task is in
305
- ],
306
- includeScore: true,
307
- threshold: 0.4,
308
- minMatchCharLength: 2,
309
- });
310
- lastIndexUpdateTime = now;
311
- console.error(`Task index refreshed with ${allFetchedTasks.length} tasks.`);
312
- }
313
- else {
314
- console.error('No tasks fetched to build search index.');
315
- // Potentially keep the old index if fetching failed, or clear it
316
- // For now, if fetching fails to get any tasks, the index won't update.
317
- }
318
- }
319
- const searchTermsArray = terms
320
- .split("|")
321
- .map((term) => term.trim())
322
- .filter(term => term.length > 0);
323
- if (searchTermsArray.length === 0) {
324
- return { content: [{ type: "text", text: "No search terms provided." }] };
325
- }
326
- const uniqueResults = new Map();
327
- if (taskSearchIndex) {
328
- searchTermsArray.forEach(term => {
329
- const results = taskSearchIndex.search(term.toLowerCase());
330
- results.forEach(result => {
331
- if (result.item && typeof result.item.id === 'string') {
332
- const currentScore = result.score ?? 1; // Default to 1 if undefined
333
- const existing = uniqueResults.get(result.item.id);
334
- if (!existing || currentScore < existing.score) {
335
- uniqueResults.set(result.item.id, { item: result.item, score: currentScore });
336
- }
337
- }
338
- });
339
- });
340
- }
341
- // Task ID Fallback Logic
342
- const potentialTaskIds = searchTermsArray.filter(isTaskId);
343
- const foundTaskIdsByFuse = new Set(Array.from(uniqueResults.keys()).map(id => id.toLowerCase())); // Store lowercase for comparison
344
- // Filter task IDs that were not found by Fuse, comparing case-insensitively
345
- const taskIdsToFetchDirectly = potentialTaskIds.filter(id => {
346
- const lowerId = id.toLowerCase();
347
- return !foundTaskIdsByFuse.has(lowerId);
348
- });
349
- if (taskIdsToFetchDirectly.length > 0) {
350
- console.error(`Attempting direct fetch for task IDs: ${taskIdsToFetchDirectly.join(', ')}`);
351
- const directFetchPromises = taskIdsToFetchDirectly.map(async (id) => {
352
- try {
353
- const response = await fetch(`https://api.clickup.com/api/v2/task/${id}`, { headers: { Authorization: CONFIG.apiKey } });
354
- if (response.ok) {
355
- const task = await response.json();
356
- if (task && typeof task.id === 'string') {
357
- // Add/update with a perfect score if fetched directly, unless a better Fuse score already exists
358
- const existing = uniqueResults.get(task.id);
359
- if (!existing || 0 < existing.score) { // 0 is a perfect score
360
- uniqueResults.set(task.id, { item: task, score: 0 });
361
- }
362
- }
363
- return task;
364
- }
365
- return null;
366
- }
367
- catch (error) {
368
- console.error(`Error directly fetching task ${id}:`, error);
369
- return null;
370
- }
371
- });
372
- await Promise.all(directFetchPromises);
373
- }
374
- const sortedResults = Array.from(uniqueResults.values())
375
- .sort((a, b) => a.score - b.score) // Sort by score, ascending (lower is better)
376
- .map(entry => entry.item)
377
- .slice(0, MAX_SEARCH_RESULTS); // Limit the number of results
378
- if (sortedResults.length === 0) {
379
- return {
380
- content: [
381
- {
382
- type: "text",
383
- text: "No tasks found matching the search terms.",
384
- },
385
- ],
386
- };
387
- }
388
- return {
389
- content: await Promise.all(sortedResults.map((task) => generateTaskMetadata(task))),
390
- };
391
- });
392
- exports.server.tool("listTodo", "Lists all open tasks for the current user.", async () => {
393
- // fetch current user ID
394
- const userResp = await fetch("https://api.clickup.com/api/v2/user", {
395
- headers: { Authorization: CONFIG.apiKey },
396
- }).then((res) => res.json());
397
- const userId = userResp.user.id;
398
- // page through team tasks assigned to this user
399
- const taskLists = await Promise.all([...Array(10)].map((_, i) => fetch(`https://api.clickup.com/api/v2/team/${CONFIG.teamId}/task?order_by=updated&page=${i}&assignees[]=${userId}`, { headers: { Authorization: CONFIG.apiKey } }).then((res) => res.json())));
400
- const tasks = taskLists.flatMap((tl) => tl.tasks);
401
- // filter out closed tasks
402
- const openTasks = tasks
403
- .filter((task) => task.status.type !== "done") // done is not closed but also not a todo
404
- .slice(0, 50);
405
- if (openTasks.length === 0) {
406
- return {
407
- content: [
408
- { type: "text", text: "No open tasks found for the current user." },
409
- ],
410
- };
411
- }
412
- return {
413
- content: await Promise.all(openTasks.map((task) => generateTaskMetadata(task))),
414
- };
415
- });
54
+ // Initialize server with enhanced documentation and export
55
+ const serverPromise = initializeServer();
56
+ exports.serverPromise = serverPromise;
416
57
  // Only connect to the transport if this file is being run directly (not imported)
417
58
  if (require.main === module) {
418
- // Start receiving messages on stdin and sending messages on stdout
419
- const transport = new stdio_js_1.StdioServerTransport();
420
- exports.server.connect(transport);
59
+ // Start receiving messages on stdin and sending messages on stdout after initialization
60
+ serverPromise.then(() => {
61
+ const transport = new stdio_js_1.StdioServerTransport();
62
+ server.connect(transport);
63
+ }).catch(console.error);
421
64
  }
@@ -0,0 +1,10 @@
1
+ export declare const rawPrimaryLang: string | undefined;
2
+ export type McpMode = 'read-minimal' | 'read' | 'write';
3
+ export declare const CONFIG: {
4
+ apiKey: string;
5
+ teamId: string;
6
+ maxImages: number;
7
+ primaryLanguageHint: string | undefined;
8
+ mode: McpMode;
9
+ };
10
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/shared/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,oBAA2D,CAAC;AAavF,MAAM,MAAM,OAAO,GAAG,cAAc,GAAG,MAAM,GAAG,OAAO,CAAC;AAUxD,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONFIG = exports.rawPrimaryLang = void 0;
4
+ exports.rawPrimaryLang = process.env.CLICKUP_PRIMARY_LANGUAGE || process.env.LANG;
5
+ let detectedLanguageHint = undefined;
6
+ if (exports.rawPrimaryLang) {
7
+ // Extract the primary language part (e.g., 'en' from 'en_US.UTF-8' or 'en-GB')
8
+ // and convert to lowercase.
9
+ const langPart = exports.rawPrimaryLang.match(/^[a-zA-Z]{2,3}/);
10
+ if (langPart) {
11
+ detectedLanguageHint = langPart[0].toLowerCase();
12
+ }
13
+ }
14
+ const rawMode = process.env.CLICKUP_MCP_MODE?.toLowerCase();
15
+ let mcpMode = 'write'; // Default to write (full functionality)
16
+ if (rawMode === 'read-minimal' || rawMode === 'read') {
17
+ mcpMode = rawMode;
18
+ }
19
+ else if (rawMode && rawMode !== 'write') {
20
+ console.error(`Invalid CLICKUP_MCP_MODE "${rawMode}". Using default "write". Valid options: read-minimal, read, write`);
21
+ }
22
+ exports.CONFIG = {
23
+ apiKey: process.env.CLICKUP_API_KEY,
24
+ teamId: process.env.CLICKUP_TEAM_ID,
25
+ maxImages: process.env.MAX_IMAGES ? parseInt(process.env.MAX_IMAGES) : 4,
26
+ primaryLanguageHint: detectedLanguageHint, // Store the cleaned code directly
27
+ mode: mcpMode,
28
+ };
29
+ if (!exports.CONFIG.apiKey || !exports.CONFIG.teamId) {
30
+ throw new Error("Missing Clickup API key or team ID");
31
+ }
@@ -0,0 +1,13 @@
1
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types";
2
+ export type ContentBlock = CallToolResult['content'][number];
3
+ export interface DatedContentEvent {
4
+ date: string;
5
+ contentBlocks: ContentBlock[];
6
+ }
7
+ export interface Config {
8
+ apiKey: string;
9
+ teamId: string;
10
+ maxImages: number;
11
+ primaryLanguageHint: string | undefined;
12
+ }
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGjE,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAG7D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,YAAY,EAAE,CAAC;CAC/B;AAGD,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,30 @@
1
+ import { ContentBlock } from "./types";
2
+ import Fuse from 'fuse.js';
3
+ /**
4
+ * Checks if a string looks like a valid ClickUp task ID
5
+ * Valid task IDs are 6-9 characters long and contain only alphanumeric characters
6
+ */
7
+ export declare function isTaskId(str: string): boolean;
8
+ /**
9
+ * Get current authenticated user information from ClickUp API
10
+ * Caches the result after first successful fetch to avoid repeated API calls during the session
11
+ */
12
+ export declare function getCurrentUser(): Promise<any>;
13
+ /**
14
+ * Limits the number of images in the content array, replacing excess images with text placeholders
15
+ * Prioritizes keeping the most recent images (assumes content is ordered with newest items last)
16
+ *
17
+ * @param content Array of content blocks that may contain images
18
+ * @param maxImages Maximum number of images to keep
19
+ * @returns Modified content array with limited images
20
+ */
21
+ export declare function limitImages(content: ContentBlock[], maxImages: number): ContentBlock[];
22
+ /**
23
+ * Function to get space details, using a cache to avoid redundant fetches
24
+ */
25
+ export declare function getSpaceDetails(spaceId: string): Promise<any>;
26
+ /**
27
+ * Get or create a task search index with specified filters
28
+ */
29
+ export declare function getTaskSearchIndex(space_ids?: string[], list_ids?: string[], assignees?: string[]): Promise<Fuse<any> | null>;
30
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AAErC,OAAO,IAAI,MAAM,SAAS,CAAC;AAI3B;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG7C;AAKD;;;GAGG;AACH,wBAAsB,cAAc,iBAiBnC;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CA8BtF;AAID;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CA0B7D;AAKD;;GAEG;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,CA4B3B"}