createTask
Create a new task in a specified task list with options for assignees, attachments, due dates, and more.
Instructions
Creates a task. Create a new task in the provided task list.
Input 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 main handler function that executes when the createTask tool is called. It resolves the tasklistId (from input or .teamwork file), validates the task request, calls teamworkService.createTask(), and returns the 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 createTaskDefinition object containing the tool name 'createTask', description, inputSchema (with nested properties for taskRequest, attachments, task details, options, etc.), and annotations.
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)Import and registration of createTask definition and handler in the toolPairs array (line 74), exported as a tool pair for the MCP server.
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 }, - The service-level helper function that actually makes the API call to Teamwork's POST /tasklists/{tasklistId}/tasks.json endpoint.
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}`); } }; export default createTask; - src/utils/config.ts:251-257 (registration)Registration of 'createTask' in the 'Tasks' tool group within the configuration, used for allow/deny list expansion.
'Tasks': ['getTasks', 'getTasksByProjectId', 'getTaskListsByProjectId', 'getTaskById', 'createTask', 'createSubTask', 'updateTask', 'deleteTask', 'getTasksMetricsComplete', 'getTasksMetricsLate', 'getTaskSubtasks', 'getTaskComments'], 'People': ['getPeople', 'getPersonById', 'getProjectPeople', 'addPeopleToProject', 'deletePerson', 'getProjectsPeopleMetricsPerformance', 'getProjectsPeopleUtilization', 'getProjectPerson'], 'Reporting': ['getProjectsReportingUserTaskCompletion', 'getProjectsReportingUtilization'], 'Time': ['getTime', 'getProjectsAllocationsTime', 'getTimezones'], 'Comments': ['createComment'], 'Companies': ['createCompany', 'updateCompany', 'deleteCompany', 'getCompanies', 'getCompanyById'] };