@hauptsache.net/clickup-mcp 1.1.0 → 1.2.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.
@@ -1,112 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerTaskToolsRead = registerTaskToolsRead;
4
- exports.registerTaskToolsWrite = registerTaskToolsWrite;
5
4
  exports.generateTaskMetadata = generateTaskMetadata;
6
5
  const zod_1 = require("zod");
7
6
  const clickup_text_1 = require("../clickup-text");
8
7
  const config_1 = require("../shared/config");
9
8
  const utils_1 = require("../shared/utils");
10
- // Shared schemas for task parameters
11
- const taskNameSchema = zod_1.z.string().min(1).describe("The name/title of the task");
12
- const taskDescriptionSchema = zod_1.z.string().optional().describe("Optional description for the task");
13
- const taskPrioritySchema = zod_1.z.enum(["urgent", "high", "normal", "low"]).optional().describe("Optional priority level");
14
- const taskDueDateSchema = zod_1.z.string().optional().describe("Optional due date as ISO date string (e.g., '2024-10-06T23:59:59+02:00')");
15
- const taskStartDateSchema = zod_1.z.string().optional().describe("Optional start date as ISO date string (e.g., '2024-10-06T09:00:00+02:00')");
16
- const taskTimeEstimateSchema = zod_1.z.number().optional().describe("Optional time estimate in hours (will be converted to milliseconds)");
17
- const taskTagsSchema = zod_1.z.array(zod_1.z.string()).optional().describe("Optional array of tag names");
18
- // Shared utility functions
19
- function createAssigneeDescription(userData) {
20
- const user = userData.user;
21
- return `Optional array of user IDs to assign to the task (defaults to current user: ${user.username} - ${user.email} (${user.id}))`;
22
- }
23
- function convertPriorityToNumber(priority) {
24
- switch (priority) {
25
- case "urgent": return 1;
26
- case "high": return 2;
27
- case "normal": return 3;
28
- case "low": return 4;
29
- default: return 3;
30
- }
31
- }
32
- function convertPriorityToString(priority) {
33
- const priorityMap = { 1: 'urgent', 2: 'high', 3: 'normal', 4: 'low' };
34
- return priorityMap[priority] || 'unknown';
35
- }
36
- function formatTimeEstimate(hours) {
37
- const displayHours = Math.floor(hours);
38
- const displayMinutes = Math.round((hours - displayHours) * 60);
39
- return displayHours > 0 ? `${displayHours}h ${displayMinutes}m` : `${displayMinutes}m`;
40
- }
41
- function buildTaskRequestBody(params, currentUserId) {
42
- const requestBody = {};
43
- if (params.name !== undefined) {
44
- requestBody.name = params.name;
45
- }
46
- if (params.priority !== undefined) {
47
- requestBody.priority = convertPriorityToNumber(params.priority);
48
- }
49
- if (params.due_date !== undefined) {
50
- requestBody.due_date = new Date(params.due_date).getTime();
51
- }
52
- if (params.start_date !== undefined) {
53
- requestBody.start_date = new Date(params.start_date).getTime();
54
- }
55
- if (params.time_estimate !== undefined) {
56
- requestBody.time_estimate = Math.round(params.time_estimate * 60 * 60 * 1000);
57
- }
58
- if (params.tags !== undefined && params.tags.length > 0) {
59
- requestBody.tags = params.tags;
60
- }
61
- if (params.assignees !== undefined) {
62
- requestBody.assignees = params.assignees;
63
- }
64
- else if (currentUserId) {
65
- requestBody.assignees = [currentUserId];
66
- }
67
- if (params.parent !== undefined) {
68
- requestBody.parent = params.parent;
69
- }
70
- return requestBody;
71
- }
72
- function formatTaskResponse(task, operation, params, userData) {
73
- const responseLines = [
74
- `Task ${operation} successfully!`,
75
- `task_id: ${task.id}`,
76
- `name: ${task.name}`,
77
- ...(operation === 'created' ? [`url: ${task.url}`] : []),
78
- `status: ${task.status?.status || 'Unknown'}`,
79
- `assignees: ${task.assignees?.map((a) => `${a.username} (${a.id})`).join(', ') || 'None'}`,
80
- ...(operation === 'created' && params.list_id ? [`list_id: ${params.list_id}`] : []),
81
- ...(operation === 'updated' ? [
82
- `updated_by: ${userData.user.username}`,
83
- `updated_at: ${timestampToIso(Date.now())}`
84
- ] : [])
85
- ];
86
- if (params.priority !== undefined || task.priority) {
87
- const priority = task.priority ? convertPriorityToString(task.priority.priority) :
88
- params.priority ? params.priority : 'unknown';
89
- responseLines.push(`priority: ${priority}`);
90
- }
91
- if (params.due_date !== undefined) {
92
- responseLines.push(`due_date: ${params.due_date}`);
93
- }
94
- if (params.start_date !== undefined) {
95
- responseLines.push(`start_date: ${params.start_date}`);
96
- }
97
- if (params.time_estimate !== undefined) {
98
- responseLines.push(`time_estimate: ${formatTimeEstimate(params.time_estimate)}`);
99
- }
100
- if (params.tags !== undefined && params.tags.length > 0) {
101
- responseLines.push(`tags: ${params.tags.join(', ')}`);
102
- }
103
- if (params.parent !== undefined) {
104
- responseLines.push(`parent_task: ${params.parent}`);
105
- }
106
- return responseLines;
107
- }
9
+ const image_processing_1 = require("../shared/image-processing");
10
+ // Read-specific utility functions
108
11
  function registerTaskToolsRead(server, userData) {
109
- server.tool("getTaskById", "Get a Clickup task with images and comments by ID", {
12
+ server.tool("getTaskById", [
13
+ "Get a ClickUp task with images and comments by ID.",
14
+ "Always use this URL when referencing tasks in conversations or sharing with others.",
15
+ "The response provides complete context including task details, comments, and status history."
16
+ ].join("\n"), {
110
17
  id: zod_1.z
111
18
  .string()
112
19
  .min(6)
@@ -137,242 +44,55 @@ function registerTaskToolsRead(server, userData) {
137
44
  }
138
45
  // 5. Combine task details with processed event blocks
139
46
  const allContentBlocks = [...taskDetailContentBlocks, ...processedEventBlocks];
140
- // 6. Limit images
141
- const limitedContent = (0, utils_1.limitImages)(allContentBlocks, config_1.CONFIG.maxImages);
47
+ // 6. Download images with smart size limiting
48
+ const limitedContent = await (0, image_processing_1.downloadImages)(allContentBlocks);
142
49
  return {
143
50
  content: limitedContent,
144
51
  };
145
52
  });
146
53
  }
147
- function registerTaskToolsWrite(server, userData) {
148
- server.tool("addComment", [
149
- "Adds a comment to a specific task.",
150
- "BEST PRACTICES: Include progress information and current status updates.",
151
- "If external links are provided, verify they are publicly accessible and incorporate relevant information from those links into the comment.",
152
- "Check the task's current status - if it's in 'backlog' or similar inactive states, suggest moving it to an active status like 'in progress' when work is being done."
153
- ].join("\n"), {
154
- task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to comment on"),
155
- comment: zod_1.z.string().min(1).describe("The comment text to add to the task"),
156
- }, async ({ task_id, comment }) => {
157
- try {
158
- const requestBody = {
159
- comment_text: comment,
160
- notify_all: true
161
- };
162
- const response = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/comment`, {
163
- method: 'POST',
164
- headers: {
165
- Authorization: config_1.CONFIG.apiKey,
166
- 'Content-Type': 'application/json'
167
- },
168
- body: JSON.stringify(requestBody)
169
- });
170
- if (!response.ok) {
171
- const errorData = await response.json().catch(() => ({}));
172
- throw new Error(`Error adding comment: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
173
- }
174
- const commentData = await response.json();
175
- return {
176
- content: [
177
- {
178
- type: "text",
179
- text: [
180
- `Comment added successfully!`,
181
- `comment_id: ${commentData.id || 'N/A'}`,
182
- `task_id: ${task_id}`,
183
- `comment: ${comment}`,
184
- `date: ${timestampToIso(commentData.date || Date.now())}`,
185
- `user: ${commentData.user?.username || 'Current user'}`,
186
- ].join('\n')
187
- }
188
- ],
189
- };
190
- }
191
- catch (error) {
192
- console.error('Error adding comment:', error);
193
- return {
194
- content: [
195
- {
196
- type: "text",
197
- text: `Error adding comment: ${error instanceof Error ? error.message : 'Unknown error'}`,
198
- },
199
- ],
200
- };
201
- }
202
- });
203
- server.tool("updateTask", [
204
- "Updates various aspects of an existing task.",
205
- "Use getListInfo first to see valid status options.",
206
- "SAFETY FEATURE: Description updates are APPEND-ONLY to prevent data loss - existing content is preserved.",
207
- "IMPORTANT: When updating tasks (especially when booking time or adding progress), ensure the status makes sense for the work being done - tasks in 'backlog' or 'closed' states usually shouldn't have active work.",
208
- "Suggest appropriate status transitions."
209
- ].join("\n"), {
210
- task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to update"),
211
- name: taskNameSchema.optional(),
212
- append_description: zod_1.z.string().optional().describe("Optional markdown content to APPEND to existing task description (preserves existing content for safety)"),
213
- status: zod_1.z.string().optional().describe("Optional new status name - use getListInfo to see valid options"),
214
- priority: taskPrioritySchema,
215
- due_date: taskDueDateSchema,
216
- start_date: taskStartDateSchema,
217
- time_estimate: taskTimeEstimateSchema,
218
- tags: taskTagsSchema.describe("Optional array of tag names (will replace existing tags)"),
219
- assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
220
- }, async ({ task_id, name, append_description, status, priority, due_date, start_date, time_estimate, tags, assignees }) => {
221
- try {
222
- const userData = await (0, utils_1.getCurrentUser)();
223
- // Get task details including current markdown description
224
- const taskResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}?include_markdown_description=true`, {
225
- headers: { Authorization: config_1.CONFIG.apiKey },
226
- });
227
- if (!taskResponse.ok) {
228
- throw new Error(`Error fetching task: ${taskResponse.status} ${taskResponse.statusText}`);
229
- }
230
- const taskData = await taskResponse.json();
231
- // Handle append-only description update with markdown support
232
- let finalDescription;
233
- if (append_description) {
234
- const currentDescription = taskData.markdown_description || "";
235
- const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
236
- const separator = currentDescription.trim() ? "\n\n---\n" : "";
237
- finalDescription = currentDescription + separator + `**Edit (${timestamp}):** ${append_description}`;
238
- }
239
- // Build update body using shared utility (without description since we handle it separately)
240
- const updateBody = buildTaskRequestBody({
241
- name, priority, due_date, start_date, time_estimate, tags, assignees
242
- });
243
- // Add markdown description if we have content to append
244
- if (finalDescription !== undefined) {
245
- updateBody.markdown_description = finalDescription;
246
- }
247
- // Add status field (not handled by buildTaskRequestBody since it's update-specific)
248
- if (status !== undefined) {
249
- updateBody.status = status;
250
- }
251
- // Handle assignees for updates (different from creates)
252
- if (assignees !== undefined) {
253
- updateBody.assignees = { add: assignees, rem: [] }; // Add new assignees, remove none
254
- }
255
- // Check if there's anything to update
256
- if (Object.keys(updateBody).length === 0) {
257
- return {
258
- content: [
259
- {
260
- type: "text",
261
- text: "No updates provided. Please specify at least one field to update.",
262
- },
263
- ],
264
- };
265
- }
266
- // Update the task
267
- const updateResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}`, {
268
- method: 'PUT',
269
- headers: {
270
- Authorization: config_1.CONFIG.apiKey,
271
- 'Content-Type': 'application/json'
272
- },
273
- body: JSON.stringify(updateBody)
274
- });
275
- if (!updateResponse.ok) {
276
- const errorData = await updateResponse.json().catch(() => ({}));
277
- throw new Error(`Error updating task: ${updateResponse.status} ${updateResponse.statusText} - ${JSON.stringify(errorData)}`);
278
- }
279
- const updatedTask = await updateResponse.json();
280
- const responseLines = formatTaskResponse(updatedTask, 'updated', {
281
- name, append_description, status, priority, due_date, start_date, time_estimate, tags, assignees
282
- }, userData);
283
- return {
284
- content: [
285
- {
286
- type: "text",
287
- text: responseLines.join('\n')
288
- }
289
- ],
290
- };
291
- }
292
- catch (error) {
293
- console.error('Error updating task:', error);
294
- return {
295
- content: [
296
- {
297
- type: "text",
298
- text: `Error updating task: ${error instanceof Error ? error.message : 'Unknown error'}`,
299
- },
300
- ],
301
- };
302
- }
303
- });
304
- server.tool("createTask", [
305
- "Creates a new task in a specific list and assigns it to specified users (defaults to current user).",
306
- "IMPORTANT: Before creating, always search for similar existing tasks first using searchTasks to avoid duplicates - ask the user if they want to use an existing task instead.",
307
- "If related tasks exist, reference them with links in the description (format: https://app.clickup.com/t/TASK_ID).",
308
- "Use getListInfo first to understand the list context and available statuses.",
309
- "Task descriptions support full markdown formatting including **bold**, *italic*, lists, links, and code blocks."
310
- ].join("\n"), {
311
- list_id: zod_1.z.string().min(1).describe("The ID of the list where the task will be created"),
312
- name: taskNameSchema,
313
- description: zod_1.z.string().optional().describe("Optional markdown description for the task - supports full markdown formatting"),
314
- priority: taskPrioritySchema,
315
- due_date: taskDueDateSchema,
316
- start_date: taskStartDateSchema,
317
- time_estimate: taskTimeEstimateSchema,
318
- tags: taskTagsSchema,
319
- parent: zod_1.z.string().optional().describe("Optional parent task ID to create this as a subtask"),
320
- assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
321
- }, async ({ list_id, name, description, priority, due_date, start_date, time_estimate, tags, parent, assignees }) => {
322
- try {
323
- const userData = await (0, utils_1.getCurrentUser)();
324
- const currentUserId = userData.user.id;
325
- const requestBody = buildTaskRequestBody({
326
- name, priority, due_date, start_date, time_estimate, tags, assignees, parent
327
- }, currentUserId);
328
- // Add markdown description if provided
329
- if (description) {
330
- requestBody.markdown_description = description;
331
- }
332
- const response = await fetch(`https://api.clickup.com/api/v2/list/${list_id}/task`, {
333
- method: 'POST',
334
- headers: {
335
- Authorization: config_1.CONFIG.apiKey,
336
- 'Content-Type': 'application/json'
337
- },
338
- body: JSON.stringify(requestBody)
339
- });
340
- if (!response.ok) {
341
- const errorData = await response.json().catch(() => ({}));
342
- throw new Error(`Error creating task: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
343
- }
344
- const createdTask = await response.json();
345
- const responseLines = formatTaskResponse(createdTask, 'created', {
346
- list_id, name, description, priority, due_date, start_date, time_estimate, tags, parent, assignees
347
- }, userData);
348
- return {
349
- content: [
350
- {
351
- type: "text",
352
- text: responseLines.join('\n')
353
- }
354
- ],
355
- };
54
+ /**
55
+ * Fetch time entries for a specific task (all time, not date-limited for detail view)
56
+ */
57
+ async function fetchTaskTimeEntries(taskId) {
58
+ try {
59
+ // Get all team members for assignee filter
60
+ const teamMembers = await (0, utils_1.getAllTeamMembers)();
61
+ const params = new URLSearchParams({
62
+ task_id: taskId,
63
+ include_location_names: 'true',
64
+ start_date: '0', // overwrite the default 30 days
65
+ });
66
+ if (teamMembers.length > 0) {
67
+ params.append('assignee', teamMembers.join(','));
356
68
  }
357
- catch (error) {
358
- console.error('Error creating task:', error);
359
- return {
360
- content: [
361
- {
362
- type: "text",
363
- text: `Error creating task: ${error instanceof Error ? error.message : 'Unknown error'}`,
364
- },
365
- ],
366
- };
69
+ const response = await fetch(`https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/time_entries?${params}`, {
70
+ headers: { Authorization: config_1.CONFIG.apiKey },
71
+ });
72
+ if (!response.ok) {
73
+ console.error(`Error fetching time entries for task ${taskId}: ${response.status} ${response.statusText}`);
74
+ return [];
367
75
  }
368
- });
76
+ const data = await response.json();
77
+ return data.data || [];
78
+ }
79
+ catch (error) {
80
+ console.error('Error fetching task time entries:', error);
81
+ return [];
82
+ }
369
83
  }
370
84
  async function loadTaskContent(taskId) {
371
85
  const response = await fetch(`https://api.clickup.com/api/v2/task/${taskId}?include_markdown_description=true&include_subtasks=true`, { headers: { Authorization: config_1.CONFIG.apiKey } });
372
86
  const task = await response.json();
373
- const content = await (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments);
374
- // Create the task metadata block using the helper function
375
- const taskMetadata = await generateTaskMetadata(task);
87
+ const [taskMetadata, content] = await Promise.all([
88
+ // Create the task metadata block using the helper functions
89
+ (async () => {
90
+ const timeEntries = await fetchTaskTimeEntries(task.id);
91
+ return await generateTaskMetadata(task, timeEntries, true);
92
+ })(),
93
+ // process markdown and download images
94
+ (0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments),
95
+ ]);
376
96
  return [taskMetadata, ...content];
377
97
  }
378
98
  async function loadTaskComments(id) {
@@ -437,8 +157,11 @@ async function loadTimeInStatusHistory(taskId) {
437
157
  events.push(event);
438
158
  }
439
159
  // Deduplicate events based on date and status name to avoid adding current_status if it's identical to the last history entry
440
- 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
441
- )).values());
160
+ const uniqueEvents = Array.from(new Map(events.map(event => {
161
+ const firstBlock = event.contentBlocks[0];
162
+ const textKey = firstBlock && 'text' in firstBlock ? firstBlock.text : 'unknown';
163
+ return [`${event.date}-${textKey}`, event];
164
+ })).values());
442
165
  return uniqueEvents;
443
166
  }
444
167
  catch (error) {
@@ -446,52 +169,6 @@ async function loadTimeInStatusHistory(taskId) {
446
169
  return [];
447
170
  }
448
171
  }
449
- /**
450
- * Helper function to fetch time entries for a task
451
- */
452
- async function getTaskTimeEntries(taskId) {
453
- try {
454
- const response = await fetch(`https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/time_entries?task_id=${taskId}`, {
455
- headers: { Authorization: config_1.CONFIG.apiKey },
456
- });
457
- if (!response.ok) {
458
- return null;
459
- }
460
- const data = await response.json();
461
- if (!data.data || !Array.isArray(data.data) || data.data.length === 0) {
462
- return null;
463
- }
464
- // Group time entries by user
465
- const timeByUser = new Map();
466
- data.data.forEach((entry) => {
467
- const username = entry.user?.username || 'Unknown User';
468
- const currentTime = timeByUser.get(username) || 0;
469
- const entryDurationMs = parseInt(entry.duration) || 0;
470
- timeByUser.set(username, currentTime + entryDurationMs);
471
- });
472
- // Format results
473
- const userTimeEntries = [];
474
- let totalTimeMs = 0;
475
- for (const [username, totalMs] of timeByUser.entries()) {
476
- const hours = totalMs / (1000 * 60 * 60);
477
- const displayHours = Math.floor(hours);
478
- const displayMinutes = Math.round((hours - displayHours) * 60);
479
- const timeDisplay = displayHours > 0 ?
480
- `${displayHours}h ${displayMinutes}m` :
481
- `${displayMinutes}m`;
482
- userTimeEntries.push(`${username}: ${timeDisplay}`);
483
- totalTimeMs += totalMs;
484
- }
485
- if (userTimeEntries.length === 0) {
486
- return null;
487
- }
488
- return userTimeEntries.join(', ');
489
- }
490
- catch (error) {
491
- console.error(`Error fetching time entries for task ${taskId}:`, error);
492
- return null;
493
- }
494
- }
495
172
  /**
496
173
  * Formats timestamp to ISO string with local timezone (not UTC)
497
174
  */
@@ -510,10 +187,43 @@ function timestampToIso(timestamp) {
510
187
  const timezoneOffset = sign + String(offsetHours).padStart(2, '0') + ':' + String(offsetMinutes).padStart(2, '0');
511
188
  return `${year}-${month}-${day}T${hours}:${minutes}${timezoneOffset}`;
512
189
  }
190
+ /**
191
+ * Helper function to filter and format time entries for a specific task
192
+ */
193
+ function filterTaskTimeEntries(taskId, timeEntries) {
194
+ if (!timeEntries || timeEntries.length === 0) {
195
+ return null;
196
+ }
197
+ // Filter entries for this specific task
198
+ const taskEntries = timeEntries.filter((entry) => entry.task?.id === taskId);
199
+ if (taskEntries.length === 0) {
200
+ return null;
201
+ }
202
+ // Group time entries by user (same logic as original getTaskTimeEntries)
203
+ const timeByUser = new Map();
204
+ taskEntries.forEach((entry) => {
205
+ const username = entry.user?.username || 'Unknown User';
206
+ const currentTime = timeByUser.get(username) || 0;
207
+ const entryDurationMs = parseInt(entry.duration) || 0;
208
+ timeByUser.set(username, currentTime + entryDurationMs);
209
+ });
210
+ // Format results (same logic as original)
211
+ const userTimeEntries = [];
212
+ for (const [username, totalMs] of timeByUser.entries()) {
213
+ const hours = totalMs / (1000 * 60 * 60);
214
+ const displayHours = Math.floor(hours);
215
+ const displayMinutes = Math.round((hours - displayHours) * 60);
216
+ const timeDisplay = displayHours > 0 ?
217
+ `${displayHours}h ${displayMinutes}m` :
218
+ `${displayMinutes}m`;
219
+ userTimeEntries.push(`${username}: ${timeDisplay}`);
220
+ }
221
+ return userTimeEntries.length > 0 ? userTimeEntries.join(', ') : null;
222
+ }
513
223
  /**
514
224
  * Helper function to generate consistent task metadata
515
225
  */
516
- async function generateTaskMetadata(task) {
226
+ async function generateTaskMetadata(task, timeEntries, isDetailView = false) {
517
227
  let spaceName = task.space?.name || 'Unknown Space';
518
228
  let spaceIdForDisplay = task.space?.id || 'N/A';
519
229
  if (spaceName === 'Unknown Space' && task.space?.id) {
@@ -524,6 +234,7 @@ async function generateTaskMetadata(task) {
524
234
  }
525
235
  const metadataLines = [
526
236
  `task_id: ${task.id}`,
237
+ `task_url: ${task.url}`,
527
238
  `name: ${task.name}`,
528
239
  `status: ${task.status.status}`,
529
240
  `date_created: ${timestampToIso(task.date_created)}`,
@@ -552,10 +263,13 @@ async function generateTaskMetadata(task) {
552
263
  const minutes = Math.floor((task.time_estimate % 3600000) / 60000);
553
264
  metadataLines.push(`time_estimate: ${hours}h ${minutes}m`);
554
265
  }
555
- // Add time booked (tracked time entries)
556
- const timeBooked = await getTaskTimeEntries(task.id);
557
- if (timeBooked) {
558
- metadataLines.push(`time_booked: ${timeBooked}`);
266
+ // Add time booked (tracked time entries) - only if timeEntries provided
267
+ if (timeEntries) {
268
+ const timeBooked = filterTaskTimeEntries(task.id, timeEntries);
269
+ if (timeBooked) {
270
+ const disclaimer = isDetailView ? "" : " (last 30 days)";
271
+ metadataLines.push(`time_booked${disclaimer}: ${timeBooked}`);
272
+ }
559
273
  }
560
274
  // Add tags if they exist
561
275
  if (task.tags && task.tags.length > 0) {
@@ -573,8 +287,6 @@ async function generateTaskMetadata(task) {
573
287
  if (task.subtasks && task.subtasks.length > 0) {
574
288
  metadataLines.push(`child_task_ids: ${task.subtasks.map((st) => st.id).join(', ')}`);
575
289
  }
576
- // Add task URL
577
- metadataLines.push(`url: ${task.url}`);
578
290
  // Add archived status if true
579
291
  if (task.archived) {
580
292
  metadataLines.push(`archived: true`);
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTaskToolsWrite(server: McpServer, userData: any): void;
3
+ //# sourceMappingURL=task-write-tools.d.ts.map
@@ -0,0 +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"}