createTask
Add new tasks to Teamwork project lists with assignees, due dates, attachments, and custom fields to organize team workflows.
Instructions
Creates a task. Create a new task in the provided task list.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| taskRequest | Yes | Request body: taskRequest | |
| tasklistId | Yes | Path parameter: tasklistId |
Implementation Reference
- src/tools/tasks/createTask.ts:505-631 (handler)The primary handler function for the createTask MCP tool. It processes the tool input, resolves the tasklistId (falling back to .teamwork config), validates the task request, calls the teamwork service to create the task, and returns a formatted response.export async function handleCreateTask(input: any) { logger.info("=== createTask tool called ==="); logger.info(`Input: ${JSON.stringify(input || {})}`); try { // Get the tasklistId from input let tasklistId = input.tasklistId ? String(input.tasklistId) : null; // If tasklistId is not provided, try to get it from .teamwork file if (!tasklistId) { try { const fs = require("fs"); if (fs.existsSync(".teamwork")) { const teamworkConfig = fs.readFileSync(".teamwork", "utf8"); const tasklistIdMatch = teamworkConfig.match(/TASKLISTID=(\d+)/); if (tasklistIdMatch) { tasklistId = String(parseInt(tasklistIdMatch[1])); logger.info(`Using tasklistId ${tasklistId} from .teamwork file`); } else { // Check if there"s a project ID and try to get tasklists const projectIdMatch = teamworkConfig.match(/PROJECTID=(\d+)/); if (projectIdMatch) { const projectId = parseInt(projectIdMatch[1]); logger.info(`Found projectId ${projectId} in .teamwork file, fetching tasklists`); const tasklists = await teamworkService.getTaskListsByProjectId(projectId); if (tasklists && tasklists.length === 1) { // If there"s only one tasklist, use it and update .teamwork file tasklistId = String(tasklists[0].id); logger.info(`Found single tasklist ${tasklistId}, using it and updating .teamwork file`); // Update .teamwork file with tasklistId const updatedConfig = teamworkConfig + `\nTASKLISTID=${tasklistId}`; fs.writeFileSync(".teamwork", updatedConfig); } else if (tasklists && tasklists.length > 1) { // If there are multiple tasklists, we can"t automatically choose logger.info(`Multiple tasklists found for project ${projectId}`); return { content: [{ type: "text", text: `Multiple tasklists found for project ${projectId}. Please specify a tasklistId in your request.` }] }; } } } } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Error reading .teamwork file: ${errorMessage}`); } } // If we still don"t have a tasklistId, return an error if (!tasklistId) { logger.info("No tasklistId provided and couldn't find one in .teamwork file"); return { content: [{ type: "text", text: "No tasklistId provided and couldn't find one in .teamwork file. Please provide a tasklistId." }] }; } // Prepare the task request const taskRequest = input.taskRequest as TaskRequest; // Validate task request if (!taskRequest || !taskRequest.task) { logger.error("Invalid task request: missing task object"); return { content: [{ type: "text", text: "Invalid task request: missing task object. Please provide a taskRequest.task object." }] }; } // Ensure task has name (Teamwork API requires 'name' for the task title) if (!taskRequest.task.name) { logger.error("Invalid task request: missing task name"); return { content: [{ type: "text", text: "Invalid task request: missing task name. Please provide taskRequest.task.name." }] }; } logger.info(`Creating task "${taskRequest.task.name}" in tasklist ${tasklistId}`); // Call the service to create the task const createdTask = await teamworkService.createTask(String(tasklistId), taskRequest); logger.info("Task created successfully"); logger.info(`Created task response: ${JSON.stringify(createdTask).substring(0, 200)}...`); // Ensure we return a properly formatted response const response = { content: [{ type: "text", text: JSON.stringify(createdTask, null, 2) }] }; // Validate that the response can be serialized try { JSON.stringify(response); logger.info("Response is valid JSON"); } catch (jsonError: any) { logger.error(`Invalid JSON in response: ${jsonError.message}`); // Return a safe response return { content: [{ type: "text", text: "Task created successfully, but there was an error formatting the response." }] }; } logger.info("=== createTask tool completed successfully ==="); return response; } catch (error: any) { return createErrorResponse(error, 'Creating task'); } }
- src/tools/tasks/createTask.ts:11-503 (schema)The tool definition including name, description, detailed inputSchema for taskRequest and tasklistId, and annotations. Note: the full schema is extensive, covering all Teamwork task fields.export const createTaskDefinition = { name: "createTask", description: "Creates a task. Create a new task in the provided task list. ", inputSchema: { type: "object", properties: { taskRequest: { type: "object", properties: { attachmentOptions: { type: "object", properties: { removeOtherFiles: { type: "boolean" } }, required: [] }, attachments: { type: "object", properties: { files: { type: "array", items: { type: "object", properties: { categoryId: { type: "integer" }, id: { type: "integer" } }, required: [], description: "File stores information about a uploaded file." } }, pendingFiles: { type: "array", items: { type: "object", properties: { categoryId: { type: "integer" }, reference: { type: "string" } }, required: [], description: "PendingFile stores information about a file uploaded on-the-fly." } } }, required: [] }, card: { type: "object", properties: { columnId: { type: "integer" } }, required: [], description: "Card stores information about the card created with the task." }, predecessors: { type: "array", items: { type: "object", properties: { id: { type: "integer" }, type: { type: "string" } }, required: [], description: "Predecessor stores information about task predecessors." } }, tags: { type: "array", items: { type: "object", properties: { color: { type: "string" }, name: { type: "string" }, projectId: { type: "integer" } }, required: [], description: "Tag contains all the information returned from a tag." } }, task: { type: "object", properties: { assignees: { type: "object", properties: { companyIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, teamIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, userIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." } }, required: [], description: "UserGroups are common lists for storing users, companies and teams ids together." }, attachmentIds: { type: "array", items: { type: "integer" } }, changeFollowers: { type: "object", properties: { companyIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, teamIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, userIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." } }, required: [], description: "UserGroups are common lists for storing users, companies and teams ids together." }, commentFollowers: { type: "object", properties: { companyIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, teamIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, userIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." } }, required: [], description: "UserGroups are common lists for storing users, companies and teams ids together." }, completedAt: { type: "string" }, completedBy: { type: "integer" }, createdAt: { type: "string" }, createdBy: { type: "integer" }, crmDealIds: { type: "array", items: { type: "integer" } }, customFields: { type: "object", properties: { Values: { type: "array", items: { type: "object", properties: { countryCode: { type: "string" }, currencySymbol: { type: "string" }, customfieldId: { type: "integer" }, urlTextToDisplay: { type: "string" }, value: { type: "string" } }, required: [], description: "CustomFieldValue contains all the information returned from a customfield." } } }, required: [], description: "CustomFields is the custom fields type." }, description: { type: "string" }, descriptionContentType: { type: "string" }, dueAt: { type: "string", format: "date", description: "NullableDate implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted. Date format `2006-01-02`" }, estimatedMinutes: { type: "integer" }, grantAccessTo: { type: "object", properties: { companyIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, teamIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, userIds: { type: "array", items: { type: "integer" }, description: "NullableInt64Slice implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." } }, required: [], description: "UserGroups are common lists for storing users, companies and teams ids together." }, hasDeskTickets: { type: "boolean" }, name: { type: "string" }, originalDueDate: { type: "string", format: "date", description: "NullableDate implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted. Date format `2006-01-02`" }, parentTaskId: { type: "integer" }, priority: { type: "string", enum: [ "low", "normal", "high" ], description: "NullableTaskPriority implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, private: { type: "boolean" }, progress: { type: "integer" }, reminders: { type: "array", items: { type: "object", properties: { isRelative: { type: "boolean" }, note: { type: "string" }, relativeNumberDays: { type: "integer" }, remindAt: { type: "string" }, type: { type: "string" }, userId: { type: "integer" } }, required: [], description: "Reminder stores all necessary information to create a task reminder." } }, repeatOptions: { type: "object", properties: { duration: { type: "integer" }, editOption: { type: "string" }, endsAt: { type: "string", format: "date", description: "NullableDate implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted. Date format `2006-01-02`" }, frequency: { type: "object", description: "NullableTaskRepeatFrequency implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, monthlyRepeatType: { type: "object", description: "NullableTaskRepeatMonthlyType implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." }, rrule: { type: "string", description: "Adds the RRule definition for repeating tasks. It replaces all other repeating fields." }, selectedDays: { type: "object", description: "NullableWorkingHourEntryWeekdays implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted." } }, required: [], description: "RepeatOptions stores recurring information for the task." }, startAt: { type: "string", format: "date", description: "NullableDate implements json.Unmarshaler to allow testing between a value that explicitly set to null or omitted. Date format `2006-01-02`" }, status: { type: "string" }, tagIds: { type: "array", items: { type: "integer" } }, taskgroupId: { type: "integer" }, tasklistId: { type: "integer" }, templateRoleName: { type: "string" }, ticketId: { type: "integer" } }, required: [], description: "Task contains all the information returned from a task." }, taskOptions: { type: "object", properties: { appendAssignees: { type: "boolean" }, checkInvalidusers: { type: "boolean" }, everyoneMustDo: { type: "boolean" }, fireWebhook: { type: "boolean" }, isTemplate: { type: "boolean" }, logActivity: { type: "boolean" }, notify: { type: "boolean" }, parseInlineTags: { type: "boolean" }, positionAfterTaskId: { type: "integer" }, pushDependents: { type: "boolean" }, pushSubtasks: { type: "boolean" }, shiftProjectDates: { type: "boolean" }, useDefaults: { type: "boolean" }, useNotifyViaTWIM: { type: "boolean" } }, required: [], description: "Options contains any options which can be set for the task request" }, workflows: { type: "object", properties: { positionAfterTask: { type: "integer" }, stageId: { type: "integer" }, workflowId: { type: "integer" } }, required: [], description: "Workflows stores information about where the task lives in the workflow" } }, required: [], description: "Request body: taskRequest" }, tasklistId: { type: "integer", description: "Path parameter: tasklistId" } }, required: [ "taskRequest", "tasklistId" ] }, annotations: { title: "Create a Task", readOnlyHint: false, destructiveHint: false, openWorldHint: false } };
- src/tools/index.ts:17-74 (registration)Registration of the createTask tool in the tools index: import of definition and handler, and inclusion in the toolPairs array which populates toolDefinitions and toolHandlersMap.import { createTaskDefinition as createTask, handleCreateTask } from './tasks/createTask.js'; import { createSubTaskDefinition as createSubTask, handleCreateSubTask } from './tasks/createSubTask.js'; import { updateTaskDefinition as updateTask, handleUpdateTask } from './tasks/updateTask.js'; import { deleteTaskDefinition as deleteTask, handleDeleteTask } from './tasks/deleteTask.js'; import { getTasksMetricsCompleteDefinition as getTasksMetricsComplete, handleGetTasksMetricsComplete } from './tasks/getTasksMetricsComplete.js'; import { getTasksMetricsLateDefinition as getTasksMetricsLate, handleGetTasksMetricsLate } from './tasks/getTasksMetricsLate.js'; import { getTaskSubtasksDefinition as getTaskSubtasks, handleGetTaskSubtasks } from './tasks/getTaskSubtasks.js'; import { getTaskCommentsDefinition as getTaskComments, handleGetTaskComments } from './tasks/getTaskComments.js'; // Comments import { createCommentDefinition as createComment, handleCreateComment } from './comments/createComment.js'; // People import { getPeopleDefinition as getPeople, handleGetPeople } from './people/getPeople.js'; import { getPersonByIdDefinition as getPersonById, handleGetPersonById } from './people/getPersonById.js'; import { getProjectPeopleDefinition as getProjectPeople, handleGetProjectPeople } from './people/getProjectPeople.js'; import { addPeopleToProjectDefinition as addPeopleToProject, handleAddPeopleToProject } from './people/addPeopleToProject.js'; import { deletePersonDefinition as deletePerson, handleDeletePerson } from './people/deletePerson.js'; import { updatePersonDefinition as updatePerson, handleUpdatePerson } from './people/updatePerson.js'; // Companies import { createCompanyDefinition as createCompany, handleCreateCompany } from './companies/createCompany.js'; import { updateCompanyDefinition as updateCompany, handleUpdateCompany } from './companies/updateCompany.js'; import { deleteCompanyDefinition as deleteCompany, handleDeleteCompany } from './companies/deleteCompany.js'; import { getCompaniesDefinition as getCompanies, handleGetCompanies } from './companies/getCompanies.js'; import { getCompanyByIdDefinition as getCompanyById, handleGetCompanyById } from './companies/getCompanyById.js'; // Reporting import { getProjectsPeopleMetricsPerformanceDefinition as getProjectsPeopleMetricsPerformance, handleGetProjectsPeopleMetricsPerformance } from './people/getPeopleMetricsPerformance.js'; import { getProjectsPeopleUtilizationDefinition as getProjectsPeopleUtilization, handleGetProjectsPeopleUtilization } from './people/getPeopleUtilization.js'; import { getProjectPersonDefinition as getProjectPerson, handleGetProjectPerson } from './people/getProjectPerson.js'; import { getProjectsReportingUserTaskCompletionDefinition as getProjectsReportingUserTaskCompletion, handleGetProjectsReportingUserTaskCompletion } from './reporting/getUserTaskCompletion.js'; import { getProjectsReportingUtilizationDefinition as getProjectsReportingUtilization, handleGetProjectsReportingUtilization } from './people/getUtilization.js'; // Time-related imports import { getTimeDefinition as getTime, handleGetTime } from './time/getTime.js'; import { getProjectsAllocationsTimeDefinition as getAllocationTime, handleGetProjectsAllocationsTime } from './time/getAllocationTime.js'; // Core import { getTimezonesDefinition as getTimezones, handleGetTimezones } from './core/getTimezones.js'; // Define a structure that pairs tool definitions with their handlers interface ToolPair { definition: any; handler: Function; } // Create an array of tool pairs const toolPairs: ToolPair[] = [ { definition: getProjects, handler: handleGetProjects }, { definition: getCurrentProject, handler: handleGetCurrentProject }, { definition: createProject, handler: handleCreateProject }, { definition: getTasks, handler: handleGetTasks }, { definition: getTasksByProjectId, handler: handleGetTasksByProjectId }, { definition: getTaskListsByProjectId, handler: handleGetTaskListsByProjectId }, { definition: getTasksByTaskListId, handler: handleGetTasksByTaskListId }, { definition: getTaskById, handler: handleGetTaskById }, { definition: createTask, handler: handleCreateTask },
- Supporting service function that performs the actual API call to Teamwork's /tasklists/{tasklistId}/tasks.json endpoint to create the task. Called by the tool handler.export const createTask = async (tasklistId: string, taskData: TaskRequest) => { try { logger.info(`Creating task in tasklist ${tasklistId}`); // Ensure we have a valid task object if (!taskData || !taskData.task) { throw new Error('Invalid task data: missing task object'); } // Ensure task has name field (Teamwork API requires 'name' for the task title) // Note: In the API docs it might be called 'content', but in our model it's 'name' if (!taskData.task.name) { throw new Error('Invalid task data: missing task name'); } logger.info(`Task data: ${JSON.stringify(taskData).substring(0, 200)}...`); const api = ensureApiClient(); const response = await api.post(`/tasklists/${tasklistId}/tasks.json`, taskData); logger.info(`Task creation successful, status: ${response.status}`); // Validate response data if (!response.data) { logger.warn('Task created but response data is empty'); return { success: true, message: 'Task created successfully, but no details returned' }; } // Ensure response data is serializable try { JSON.stringify(response.data); logger.info('Response data is valid JSON'); } catch (error: any) { logger.error(`Response data is not valid JSON: ${error.message}`); return { success: true, message: 'Task created successfully, but response contains non-serializable data', partial: typeof response.data === 'object' ? Object.keys(response.data) : typeof response.data }; } return response.data; } catch (error: any) { logger.error(`Error creating task in tasklist ${tasklistId}: ${error.message}`); // Log detailed error information if (error.response) { logger.error(`API error status: ${error.response.status}`); logger.error(`API error data: ${JSON.stringify(error.response.data || {})}`); } if (error.stack) { logger.error(`Stack trace: ${error.stack}`); } throw new Error(`Failed to create task in tasklist ${tasklistId}: ${error.message}`); } };