Skip to content

Commit f5e3a11

Browse files
committed
1 parent 89580cc commit f5e3a11

File tree

7 files changed

+201
-12
lines changed

7 files changed

+201
-12
lines changed

src/server.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import config from "./config.js";
1717
import { workspaceHierarchyTool, handleGetWorkspaceHierarchy } from "./tools/workspace.js";
1818
import {
1919
createTaskTool,
20+
createTaskFromTemplateTool,
21+
getTaskTemplatesTool,
2022
updateTaskTool,
2123
moveTaskTool,
2224
duplicateTaskTool,
@@ -37,6 +39,8 @@ import {
3739
deleteTimeEntryTool,
3840
getCurrentTimeEntryTool,
3941
handleCreateTask,
42+
handleCreateTaskFromTemplate,
43+
handleGetTaskTemplates,
4044
handleUpdateTask,
4145
handleMoveTask,
4246
handleDuplicateTask,
@@ -175,6 +179,8 @@ export function configureServer() {
175179
tools: [
176180
workspaceHierarchyTool,
177181
createTaskTool,
182+
createTaskFromTemplateTool,
183+
getTaskTemplatesTool,
178184
getTaskTool,
179185
updateTaskTool,
180186
moveTaskTool,
@@ -256,6 +262,10 @@ export function configureServer() {
256262
return handleGetWorkspaceHierarchy();
257263
case "create_task":
258264
return handleCreateTask(params);
265+
case "create_task_from_template":
266+
return handleCreateTaskFromTemplate(params);
267+
case "get_task_templates":
268+
return handleGetTaskTemplates(params);
259269
case "update_task":
260270
return handleUpdateTask(params);
261271
case "move_task":

src/services/clickup/task/task-core.ts

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
*/
1212

1313
import { BaseClickUpService, ErrorCode, ClickUpServiceError, ServiceResponse } from '../base.js';
14-
import {
15-
ClickUpTask,
16-
CreateTaskData,
17-
UpdateTaskData,
18-
TaskFilters,
14+
import {
15+
ClickUpTask,
16+
CreateTaskData,
17+
UpdateTaskData,
18+
TaskFilters,
1919
TasksResponse,
20-
TaskPriority
20+
TaskPriority,
21+
ClickUpTaskTemplate,
22+
TaskTemplatesResponse
2123
} from '../types.js';
2224
import { ListService } from '../list.js';
2325
import { WorkspaceService } from '../workspace.js';
@@ -192,14 +194,14 @@ export class TaskServiceCore extends BaseClickUpService {
192194
*/
193195
async createTask(listId: string, taskData: CreateTaskData): Promise<ClickUpTask> {
194196
this.logOperation('createTask', { listId, ...taskData });
195-
197+
196198
try {
197199
return await this.makeRequest(async () => {
198200
const response = await this.client.post<ClickUpTask | string>(
199201
`/list/${listId}/task`,
200202
taskData
201203
);
202-
204+
203205
// Handle both JSON and text responses
204206
const data = response.data;
205207
if (typeof data === 'string') {
@@ -215,14 +217,90 @@ export class TaskServiceCore extends BaseClickUpService {
215217
data
216218
);
217219
}
218-
220+
219221
return data;
220222
});
221223
} catch (error) {
222224
throw this.handleError(error, 'Failed to create task');
223225
}
224226
}
225227

228+
/**
229+
* Create a new task from a template in the specified list
230+
* @param listId The ID of the list to create the task in
231+
* @param templateId The ID of the template to use
232+
* @param taskName Optional name for the task (if not provided, template default is used)
233+
* @returns The created task
234+
*/
235+
async createTaskFromTemplate(listId: string, templateId: string, taskName?: string): Promise<ClickUpTask> {
236+
this.logOperation('createTaskFromTemplate', { listId, templateId, taskName });
237+
238+
try {
239+
return await this.makeRequest(async () => {
240+
const payload: { name?: string } = {};
241+
if (taskName) {
242+
payload.name = taskName;
243+
}
244+
245+
const response = await this.client.post<ClickUpTask | string>(
246+
`/list/${listId}/taskTemplate/${templateId}`,
247+
payload
248+
);
249+
250+
// Handle both JSON and text responses
251+
const data = response.data;
252+
if (typeof data === 'string') {
253+
// If we got a text response, try to extract task ID from common patterns
254+
const idMatch = data.match(/task.*?(\w{9})/i);
255+
if (idMatch) {
256+
// If we found an ID, fetch the full task details
257+
return await this.getTask(idMatch[1]);
258+
}
259+
throw new ClickUpServiceError(
260+
'Received unexpected text response from API',
261+
ErrorCode.UNKNOWN,
262+
data
263+
);
264+
}
265+
266+
return data;
267+
});
268+
} catch (error) {
269+
throw this.handleError(error, 'Failed to create task from template');
270+
}
271+
}
272+
273+
/**
274+
* Get all task templates for the team/workspace
275+
* @returns Array of task templates
276+
*/
277+
async getTaskTemplates(): Promise<ClickUpTaskTemplate[]> {
278+
this.logOperation('getTaskTemplates', { teamId: this.teamId });
279+
280+
try {
281+
return await this.makeRequest(async () => {
282+
const response = await this.client.get<TaskTemplatesResponse | string>(
283+
`/team/${this.teamId}/taskTemplate`
284+
);
285+
286+
// Handle both JSON and text responses
287+
const data = response.data;
288+
if (typeof data === 'string') {
289+
throw new ClickUpServiceError(
290+
'Received unexpected text response from API',
291+
ErrorCode.UNKNOWN,
292+
data
293+
);
294+
}
295+
296+
// The API returns an object with templates array
297+
return data.templates || [];
298+
});
299+
} catch (error) {
300+
throw this.handleError(error, 'Failed to get task templates');
301+
}
302+
}
303+
226304
/**
227305
* Get a task by its ID
228306
* Automatically detects custom task IDs and routes them appropriately

src/services/clickup/types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,33 @@ export interface ClickUpPriority {
4949
*/
5050
export type DependencyType = 0 | 1;
5151

52+
/**
53+
* Task Template as returned by the ClickUp API
54+
*/
55+
export interface ClickUpTaskTemplate {
56+
id: string;
57+
name: string;
58+
description?: string;
59+
space_id: string;
60+
team_id: string;
61+
date_created: string;
62+
date_updated: string;
63+
creator: {
64+
id: number;
65+
username: string;
66+
email: string;
67+
color: string;
68+
profilePicture?: string;
69+
};
70+
}
71+
72+
/**
73+
* Response from get task templates API
74+
*/
75+
export interface TaskTemplatesResponse {
76+
templates: ClickUpTaskTemplate[];
77+
}
78+
5279
/**
5380
* Task dependency object
5481
*/

src/tools/task/handlers.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,26 @@ export async function createTaskHandler(params) {
621621
return await taskService.createTask(listId, taskData);
622622
}
623623

624+
/**
625+
* Handler for creating a task from template
626+
*/
627+
export async function createTaskFromTemplateHandler(params) {
628+
const { templateId, name } = params;
629+
630+
if (!templateId) throw new Error("Template ID is required");
631+
632+
const listId = await getListId(params.listId, params.listName);
633+
634+
return await taskService.createTaskFromTemplate(listId, templateId, name);
635+
}
636+
637+
/**
638+
* Handler for getting task templates
639+
*/
640+
export async function getTaskTemplatesHandler() {
641+
return await taskService.getTaskTemplates();
642+
}
643+
624644

625645

626646
/**

src/tools/task/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
export * from './main.js';
1212

1313
// Re-export single task operation tools
14-
export {
14+
export {
1515
createTaskTool,
16+
createTaskFromTemplateTool,
17+
getTaskTemplatesTool,
1618
getTaskTool,
1719
getTasksTool,
1820
updateTaskTool,
@@ -64,6 +66,8 @@ export {
6466
export {
6567
// Single task operation handlers
6668
createTaskHandler,
69+
createTaskFromTemplateHandler,
70+
getTaskTemplatesHandler,
6771
getTaskHandler,
6872
getTasksHandler,
6973
updateTaskHandler,
@@ -72,13 +76,13 @@ export {
7276
deleteTaskHandler,
7377
getTaskCommentsHandler,
7478
createTaskCommentHandler,
75-
79+
7680
// Bulk task operation handlers
7781
createBulkTasksHandler,
7882
updateBulkTasksHandler,
7983
moveBulkTasksHandler,
8084
deleteBulkTasksHandler,
81-
85+
8286
// Team task operation handlers
8387
getWorkspaceTasksHandler
8488
} from './handlers.js';

src/tools/task/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import {
4747
// Import handlers
4848
import {
4949
createTaskHandler,
50+
createTaskFromTemplateHandler,
51+
getTaskTemplatesHandler,
5052
getTaskHandler,
5153
getTasksHandler,
5254
updateTaskHandler,
@@ -96,6 +98,11 @@ function createHandlerWrapper<T>(
9698
//=============================================================================
9799

98100
export const handleCreateTask = createHandlerWrapper(createTaskHandler);
101+
export const handleCreateTaskFromTemplate = createHandlerWrapper(createTaskFromTemplateHandler);
102+
export const handleGetTaskTemplates = createHandlerWrapper(getTaskTemplatesHandler, (templates) => ({
103+
templates,
104+
count: templates.length
105+
}));
99106
export const handleGetTask = createHandlerWrapper(getTaskHandler);
100107
export const handleGetTasks = createHandlerWrapper(getTasksHandler, (tasks) => ({
101108
tasks,

src/tools/task/single-operations.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,49 @@ export const createTaskTool = {
159159
}
160160
};
161161

162+
/**
163+
* Tool definition for creating a task from template
164+
*/
165+
export const createTaskFromTemplateTool = {
166+
name: "create_task_from_template",
167+
description: `Creates a task from a ClickUp template in a list. Use listId (preferred) or listName + templateId. Required: templateId + list info. Optional: custom name override.`,
168+
inputSchema: {
169+
type: "object",
170+
properties: {
171+
templateId: {
172+
type: "string",
173+
description: "REQUIRED: ID of the task template to use for creating the task."
174+
},
175+
listId: {
176+
type: "string",
177+
description: "REQUIRED (unless listName provided): ID of the list to create the task in. If you have this ID from a previous response, use it directly rather than looking up by name."
178+
},
179+
listName: {
180+
type: "string",
181+
description: "REQUIRED (unless listId provided): Name of the list to create the task in - will automatically find the list by name."
182+
},
183+
name: {
184+
type: "string",
185+
description: "Optional: Override the default name from the template. Put a relevant emoji followed by a blank space before the name if provided."
186+
}
187+
},
188+
required: ["templateId"]
189+
}
190+
};
191+
192+
/**
193+
* Tool definition for getting task templates
194+
*/
195+
export const getTaskTemplatesTool = {
196+
name: "get_task_templates",
197+
description: `Retrieves all available task templates for the workspace/team. No parameters required - returns all templates the user has access to.`,
198+
inputSchema: {
199+
type: "object",
200+
properties: {},
201+
required: []
202+
}
203+
};
204+
162205
/**
163206
* Tool definition for updating a task
164207
*/

0 commit comments

Comments
 (0)