Skip to main content
Glama

add_task

Create and manage tasks efficiently with AI-driven task generation in the Task Master MCP server. Define task details manually or use prompts for automated creation, ensuring structured and prioritized task management.

Instructions

Add a new task using AI

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dependenciesNoComma-separated list of task IDs this task depends on
descriptionNoTask description (for manual task creation)
detailsNoImplementation details (for manual task creation)
fileNoPath to the tasks file (default: tasks/tasks.json)
priorityNoTask priority (high, medium, low)
projectRootYesThe directory of the project. Must be an absolute path.
promptNoDescription of the task to add (required if not using manual fields)
researchNoWhether to use research capabilities for task creation
tagNoTag context to operate on
testStrategyNoTest strategy (for manual task creation)
titleNoTask title (for manual task creation)

Implementation Reference

  • Registers the MCP 'add_task' tool including input schema (parameters) and execute handler that normalizes project root, finds tasks path, and delegates to addTaskDirect
    export function registerAddTaskTool(server) {
    	server.addTool({
    		name: 'add_task',
    		description: 'Add a new task using AI',
    		parameters: z.object({
    			prompt: z
    				.string()
    				.optional()
    				.describe(
    					'Description of the task to add (required if not using manual fields)'
    				),
    			title: z
    				.string()
    				.optional()
    				.describe('Task title (for manual task creation)'),
    			description: z
    				.string()
    				.optional()
    				.describe('Task description (for manual task creation)'),
    			details: z
    				.string()
    				.optional()
    				.describe('Implementation details (for manual task creation)'),
    			testStrategy: z
    				.string()
    				.optional()
    				.describe('Test strategy (for manual task creation)'),
    			dependencies: z
    				.string()
    				.optional()
    				.describe('Comma-separated list of task IDs this task depends on'),
    			priority: z
    				.string()
    				.optional()
    				.describe('Task priority (high, medium, low)'),
    			file: z
    				.string()
    				.optional()
    				.describe('Path to the tasks file (default: tasks/tasks.json)'),
    			projectRoot: z
    				.string()
    				.describe('The directory of the project. Must be an absolute path.'),
    			tag: z.string().optional().describe('Tag context to operate on'),
    			research: z
    				.boolean()
    				.optional()
    				.describe('Whether to use research capabilities for task creation')
    		}),
    		execute: withNormalizedProjectRoot(async (args, { log, session }) => {
    			try {
    				log.info(`Starting add-task with args: ${JSON.stringify(args)}`);
    
    				const resolvedTag = resolveTag({
    					projectRoot: args.projectRoot,
    					tag: args.tag
    				});
    
    				// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
    				let tasksJsonPath;
    				try {
    					tasksJsonPath = findTasksPath(
    						{ projectRoot: args.projectRoot, file: args.file },
    						log
    					);
    				} catch (error) {
    					log.error(`Error finding tasks.json: ${error.message}`);
    					return createErrorResponse(
    						`Failed to find tasks.json: ${error.message}`
    					);
    				}
    
    				// Call the direct function
    				const result = await addTaskDirect(
    					{
    						tasksJsonPath: tasksJsonPath,
    						prompt: args.prompt,
    						title: args.title,
    						description: args.description,
    						details: args.details,
    						testStrategy: args.testStrategy,
    						dependencies: args.dependencies,
    						priority: args.priority,
    						research: args.research,
    						projectRoot: args.projectRoot,
    						tag: resolvedTag
    					},
    					log,
    					{ session }
    				);
    
    				return handleApiResult({
    					result,
    					log: log,
    					errorPrefix: 'Error adding task',
    					projectRoot: args.projectRoot
    				});
    			} catch (error) {
    				log.error(`Error in add-task tool: ${error.message}`);
    				return createErrorResponse(error.message);
    			}
    		})
    	});
    }
  • Wrapper function addTaskDirect that handles MCP-specific logging, validation, manual vs AI task creation, and calls the core addTask function
    export async function addTaskDirect(args, log, context = {}) {
    	// Destructure expected args (including research and projectRoot)
    	const {
    		tasksJsonPath,
    		prompt,
    		dependencies,
    		priority,
    		research,
    		projectRoot,
    		tag
    	} = args;
    	const { session } = context; // Destructure session from context
    
    	// Enable silent mode to prevent console logs from interfering with JSON response
    	enableSilentMode();
    
    	// Create logger wrapper using the utility
    	const mcpLog = createLogWrapper(log);
    
    	try {
    		// Check if tasksJsonPath was provided
    		if (!tasksJsonPath) {
    			log.error('addTaskDirect called without tasksJsonPath');
    			disableSilentMode(); // Disable before returning
    			return {
    				success: false,
    				error: {
    					code: 'MISSING_ARGUMENT',
    					message: 'tasksJsonPath is required'
    				}
    			};
    		}
    
    		// Use provided path
    		const tasksPath = tasksJsonPath;
    
    		// Check if this is manual task creation or AI-driven task creation
    		const isManualCreation = args.title && args.description;
    
    		// Check required parameters
    		if (!args.prompt && !isManualCreation) {
    			log.error(
    				'Missing required parameters: either prompt or title+description must be provided'
    			);
    			disableSilentMode();
    			return {
    				success: false,
    				error: {
    					code: 'MISSING_PARAMETER',
    					message:
    						'Either the prompt parameter or both title and description parameters are required for adding a task'
    				}
    			};
    		}
    
    		// Extract and prepare parameters
    		const taskDependencies = Array.isArray(dependencies)
    			? dependencies // Already an array if passed directly
    			: dependencies // Check if dependencies exist and are a string
    				? String(dependencies)
    						.split(',')
    						.map((id) => parseInt(id.trim(), 10)) // Split, trim, and parse
    				: []; // Default to empty array if null/undefined
    		const taskPriority = priority || 'medium'; // Default priority
    
    		let manualTaskData = null;
    		let newTaskId;
    		let telemetryData;
    		let tagInfo;
    
    		if (isManualCreation) {
    			// Create manual task data object
    			manualTaskData = {
    				title: args.title,
    				description: args.description,
    				details: args.details || '',
    				testStrategy: args.testStrategy || ''
    			};
    
    			log.info(
    				`Adding new task manually with title: "${args.title}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
    			);
    
    			// Call the addTask function with manual task data
    			const result = await addTask(
    				tasksPath,
    				null, // prompt is null for manual creation
    				taskDependencies,
    				taskPriority,
    				{
    					session,
    					mcpLog,
    					projectRoot,
    					commandName: 'add-task',
    					outputType: 'mcp',
    					tag
    				},
    				'json', // outputFormat
    				manualTaskData, // Pass the manual task data
    				false // research flag is false for manual creation
    			);
    			newTaskId = result.newTaskId;
    			telemetryData = result.telemetryData;
    			tagInfo = result.tagInfo;
    		} else {
    			// AI-driven task creation
    			log.info(
    				`Adding new task with prompt: "${prompt}", dependencies: [${taskDependencies.join(', ')}], priority: ${taskPriority}, research: ${research}`
    			);
    
    			// Call the addTask function, passing the research flag
    			const result = await addTask(
    				tasksPath,
    				prompt, // Use the prompt for AI creation
    				taskDependencies,
    				taskPriority,
    				{
    					session,
    					mcpLog,
    					projectRoot,
    					commandName: 'add-task',
    					outputType: 'mcp',
    					tag
    				},
    				'json', // outputFormat
    				null, // manualTaskData is null for AI creation
    				research // Pass the research flag
    			);
    			newTaskId = result.newTaskId;
    			telemetryData = result.telemetryData;
    			tagInfo = result.tagInfo;
    		}
    
    		// Restore normal logging
    		disableSilentMode();
    
    		return {
    			success: true,
    			data: {
    				taskId: newTaskId,
    				message: `Successfully added new task #${newTaskId}`,
    				telemetryData: telemetryData,
    				tagInfo: tagInfo
    			}
    		};
    	} catch (error) {
    		// Make sure to restore normal logging even if there's an error
    		disableSilentMode();
    
    		log.error(`Error in addTaskDirect: ${error.message}`);
    		// Add specific error code checks if needed
    		return {
    			success: false,
    			error: {
    				code: error.code || 'ADD_TASK_ERROR', // Use error code if available
    				message: error.message
    			}
    		};
    	}
    }
  • Core addTask implementation handling both manual and AI-driven task creation, including context gathering, prompt loading, AI service call with schema validation, dependency graph building, task persistence, and telemetry
    async function addTask(
    	tasksPath,
    	prompt,
    	dependencies = [],
    	priority = null,
    	context = {},
    	outputFormat = 'text', // Default to text for CLI
    	manualTaskData = null,
    	useResearch = false
    ) {
    	const { session, mcpLog, projectRoot, commandName, outputType, tag } =
    		context;
    	const isMCP = !!mcpLog;
    
    	// Create a consistent logFn object regardless of context
    	const logFn = isMCP
    		? mcpLog // Use MCP logger if provided
    		: {
    				// Create a wrapper around consoleLog for CLI
    				info: (...args) => consoleLog('info', ...args),
    				warn: (...args) => consoleLog('warn', ...args),
    				error: (...args) => consoleLog('error', ...args),
    				debug: (...args) => consoleLog('debug', ...args),
    				success: (...args) => consoleLog('success', ...args)
    			};
    
    	// Validate priority - only accept high, medium, or low
    	let effectivePriority =
    		priority || getDefaultPriority(projectRoot) || DEFAULT_TASK_PRIORITY;
    
    	// If priority is provided, validate and normalize it
    	if (priority) {
    		const normalizedPriority = normalizeTaskPriority(priority);
    		if (normalizedPriority) {
    			effectivePriority = normalizedPriority;
    		} else {
    			if (outputFormat === 'text') {
    				consoleLog(
    					'warn',
    					`Invalid priority "${priority}". Using default priority "${DEFAULT_TASK_PRIORITY}".`
    				);
    			}
    			effectivePriority = DEFAULT_TASK_PRIORITY;
    		}
    	}
    
    	logFn.info(
    		`Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(', ') || 'None'}, Research: ${useResearch}, ProjectRoot: ${projectRoot}`
    	);
    	if (tag) {
    		logFn.info(`Using tag context: ${tag}`);
    	}
    
    	let loadingIndicator = null;
    	let aiServiceResponse = null; // To store the full response from AI service
    
    	// Create custom reporter that checks for MCP log
    	const report = (message, level = 'info') => {
    		if (mcpLog) {
    			mcpLog[level](message);
    		} else if (outputFormat === 'text') {
    			consoleLog(level, message);
    		}
    	};
    
    	/**
    	 * Recursively builds a dependency graph for a given task
    	 * @param {Array} tasks - All tasks from tasks.json
    	 * @param {number} taskId - ID of the task to analyze
    	 * @param {Set} visited - Set of already visited task IDs
    	 * @param {Map} depthMap - Map of task ID to its depth in the graph
    	 * @param {number} depth - Current depth in the recursion
    	 * @return {Object} Dependency graph data
    	 */
    	function buildDependencyGraph(
    		tasks,
    		taskId,
    		visited = new Set(),
    		depthMap = new Map(),
    		depth = 0
    	) {
    		// Skip if we've already visited this task or it doesn't exist
    		if (visited.has(taskId)) {
    			return null;
    		}
    
    		// Find the task
    		const task = tasks.find((t) => t.id === taskId);
    		if (!task) {
    			return null;
    		}
    
    		// Mark as visited
    		visited.add(taskId);
    
    		// Update depth if this is a deeper path to this task
    		if (!depthMap.has(taskId) || depth < depthMap.get(taskId)) {
    			depthMap.set(taskId, depth);
    		}
    
    		// Process dependencies
    		const dependencyData = [];
    		if (task.dependencies && task.dependencies.length > 0) {
    			for (const depId of task.dependencies) {
    				const depData = buildDependencyGraph(
    					tasks,
    					depId,
    					visited,
    					depthMap,
    					depth + 1
    				);
    				if (depData) {
    					dependencyData.push(depData);
    				}
    			}
    		}
    
    		return {
    			id: task.id,
    			title: task.title,
    			description: task.description,
    			status: task.status,
    			dependencies: dependencyData
    		};
    	}
    
    	try {
    		// Read the existing tasks - IMPORTANT: Read the raw data without tag resolution
    		let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter
    
    		// Handle the case where readJSON returns resolved data with _rawTaggedData
    		if (rawData && rawData._rawTaggedData) {
    			// Use the raw tagged data and discard the resolved view
    			rawData = rawData._rawTaggedData;
    		}
    
    		// If file doesn't exist or is invalid, create a new structure in memory
    		if (!rawData) {
    			report(
    				'tasks.json not found or invalid. Initializing new structure.',
    				'info'
    			);
    			rawData = {
    				master: {
    					tasks: [],
    					metadata: {
    						created: new Date().toISOString(),
    						description: 'Default tasks context'
    					}
    				}
    			};
    			// Do not write the file here; it will be written later with the new task.
    		}
    
    		// Handle legacy format migration using utilities
    		if (rawData && Array.isArray(rawData.tasks) && !rawData._rawTaggedData) {
    			report('Legacy format detected. Migrating to tagged format...', 'info');
    
    			// This is legacy format - migrate it to tagged format
    			rawData = {
    				master: {
    					tasks: rawData.tasks,
    					metadata: rawData.metadata || {
    						created: new Date().toISOString(),
    						updated: new Date().toISOString(),
    						description: 'Tasks for master context'
    					}
    				}
    			};
    			// Ensure proper metadata using utility
    			ensureTagMetadata(rawData.master, {
    				description: 'Tasks for master context'
    			});
    			// Do not write the file here; it will be written later with the new task.
    
    			// Perform complete migration (config.json, state.json)
    			performCompleteTagMigration(tasksPath);
    			markMigrationForNotice(tasksPath);
    
    			report('Successfully migrated to tagged format.', 'success');
    		}
    
    		// Use the provided tag, or the current active tag, or default to 'master'
    		const targetTag = tag;
    
    		// Ensure the target tag exists
    		if (!rawData[targetTag]) {
    			report(
    				`Tag "${targetTag}" does not exist. Please create it first using the 'add-tag' command.`,
    				'error'
    			);
    			throw new Error(`Tag "${targetTag}" not found.`);
    		}
    
    		// Ensure the target tag has a tasks array and metadata object
    		if (!rawData[targetTag].tasks) {
    			rawData[targetTag].tasks = [];
    		}
    		if (!rawData[targetTag].metadata) {
    			rawData[targetTag].metadata = {
    				created: new Date().toISOString(),
    				updated: new Date().toISOString(),
    				description: ``
    			};
    		}
    
    		// Get a flat list of ALL tasks across ALL tags to validate dependencies
    		const allTasks = getAllTasks(rawData);
    
    		// Find the highest task ID *within the target tag* to determine the next ID
    		const tasksInTargetTag = rawData[targetTag].tasks;
    		const highestId =
    			tasksInTargetTag.length > 0
    				? Math.max(...tasksInTargetTag.map((t) => t.id))
    				: 0;
    		const newTaskId = highestId + 1;
    
    		// Only show UI box for CLI mode
    		if (outputFormat === 'text') {
    			console.log(
    				boxen(chalk.white.bold(`Creating New Task #${newTaskId}`), {
    					padding: 1,
    					borderColor: 'blue',
    					borderStyle: 'round',
    					margin: { top: 1, bottom: 1 }
    				})
    			);
    		}
    
    		// Validate dependencies before proceeding
    		const invalidDeps = dependencies.filter((depId) => {
    			// Ensure depId is parsed as a number for comparison
    			const numDepId = parseInt(depId, 10);
    			return Number.isNaN(numDepId) || !allTasks.some((t) => t.id === numDepId);
    		});
    
    		if (invalidDeps.length > 0) {
    			report(
    				`The following dependencies do not exist or are invalid: ${invalidDeps.join(', ')}`,
    				'warn'
    			);
    			report('Removing invalid dependencies...', 'info');
    			dependencies = dependencies.filter(
    				(depId) => !invalidDeps.includes(depId)
    			);
    		}
    		// Ensure dependencies are numbers
    		const numericDependencies = dependencies.map((dep) => parseInt(dep, 10));
    
    		// Build dependency graphs for explicitly specified dependencies
    		const dependencyGraphs = [];
    		const allRelatedTaskIds = new Set();
    		const depthMap = new Map();
    
    		// First pass: build a complete dependency graph for each specified dependency
    		for (const depId of numericDependencies) {
    			const graph = buildDependencyGraph(allTasks, depId, new Set(), depthMap);
    			if (graph) {
    				dependencyGraphs.push(graph);
    			}
    		}
    
    		// Second pass: build a set of all related task IDs for flat analysis
    		for (const [taskId, depth] of depthMap.entries()) {
    			allRelatedTaskIds.add(taskId);
    		}
    
    		let taskData;
    
    		// Check if manual task data is provided
    		if (manualTaskData) {
    			report('Using manually provided task data', 'info');
    			taskData = manualTaskData;
    			report('DEBUG: Taking MANUAL task data path.', 'debug');
    
    			// Basic validation for manual data
    			if (
    				!taskData.title ||
    				typeof taskData.title !== 'string' ||
    				!taskData.description ||
    				typeof taskData.description !== 'string'
    			) {
    				throw new Error(
    					'Manual task data must include at least a title and description.'
    				);
    			}
    		} else {
    			report('DEBUG: Taking AI task generation path.', 'debug');
    			// --- Refactored AI Interaction ---
    			report(`Generating task data with AI with prompt:\n${prompt}`, 'info');
    
    			// --- Use the new ContextGatherer ---
    			const contextGatherer = new ContextGatherer(projectRoot, tag);
    			const gatherResult = await contextGatherer.gather({
    				semanticQuery: prompt,
    				dependencyTasks: numericDependencies,
    				format: 'research'
    			});
    
    			const gatheredContext = gatherResult.context;
    			const analysisData = gatherResult.analysisData;
    
    			// Display context analysis if not in silent mode
    			if (outputFormat === 'text' && analysisData) {
    				displayContextAnalysis(analysisData, prompt, gatheredContext.length);
    			}
    
    			// Add any manually provided details to the prompt for context
    			let contextFromArgs = '';
    			if (manualTaskData?.title)
    				contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`;
    			if (manualTaskData?.description)
    				contextFromArgs += `\n- Suggested Description: "${manualTaskData.description}"`;
    			if (manualTaskData?.details)
    				contextFromArgs += `\n- Additional Details Context: "${manualTaskData.details}"`;
    			if (manualTaskData?.testStrategy)
    				contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`;
    
    			// Load prompts using PromptManager
    			const promptManager = getPromptManager();
    			const { systemPrompt, userPrompt } = await promptManager.loadPrompt(
    				'add-task',
    				{
    					prompt,
    					newTaskId,
    					existingTasks: allTasks,
    					gatheredContext,
    					contextFromArgs,
    					useResearch,
    					priority: effectivePriority,
    					dependencies: numericDependencies,
    					hasCodebaseAnalysis: hasCodebaseAnalysis(
    						useResearch,
    						projectRoot,
    						session
    					),
    					projectRoot: projectRoot
    				}
    			);
    
    			// Start the loading indicator - only for text mode
    			if (outputFormat === 'text') {
    				loadingIndicator = startLoadingIndicator(
    					`Generating new task with ${useResearch ? 'Research' : 'Main'} AI... \n`
    				);
    			}
    
    			try {
    				const serviceRole = useResearch ? 'research' : 'main';
    				report('DEBUG: Calling generateObjectService...', 'debug');
    
    				aiServiceResponse = await generateObjectService({
    					// Capture the full response
    					role: serviceRole,
    					session: session,
    					projectRoot: projectRoot,
    					schema: COMMAND_SCHEMAS['add-task'],
    					objectName: 'newTaskData',
    					systemPrompt: systemPrompt,
    					prompt: userPrompt,
    					commandName: commandName || 'add-task', // Use passed commandName or default
    					outputType: outputType || (isMCP ? 'mcp' : 'cli') // Use passed outputType or derive
    				});
    				report('DEBUG: generateObjectService returned successfully.', 'debug');
    
    				if (!aiServiceResponse || !aiServiceResponse.mainResult) {
    					throw new Error(
    						'AI service did not return the expected object structure.'
    					);
    				}
    
    				// Prefer mainResult if it looks like a valid task object, otherwise try mainResult.object
    				if (
    					aiServiceResponse.mainResult.title &&
    					aiServiceResponse.mainResult.description
    				) {
    					taskData = aiServiceResponse.mainResult;
    				} else if (
    					aiServiceResponse.mainResult.object &&
    					aiServiceResponse.mainResult.object.title &&
    					aiServiceResponse.mainResult.object.description
    				) {
    					taskData = aiServiceResponse.mainResult.object;
    				} else {
    					throw new Error('AI service did not return a valid task object.');
    				}
    
    				report('Successfully generated task data from AI.', 'success');
    
    				// Success! Show checkmark
    				if (loadingIndicator) {
    					succeedLoadingIndicator(
    						loadingIndicator,
    						'Task generated successfully'
    					);
    					loadingIndicator = null; // Clear it
    				}
    			} catch (error) {
    				// Failure! Show X
    				if (loadingIndicator) {
    					failLoadingIndicator(loadingIndicator, 'AI generation failed');
    					loadingIndicator = null;
    				}
    				report(
    					`DEBUG: generateObjectService caught error: ${error.message}`,
    					'debug'
    				);
    				report(`Error generating task with AI: ${error.message}`, 'error');
    				throw error; // Re-throw error after logging
    			} finally {
    				report('DEBUG: generateObjectService finally block reached.', 'debug');
    				// Clean up if somehow still running
    				if (loadingIndicator) {
    					stopLoadingIndicator(loadingIndicator);
    				}
    			}
    			// --- End Refactored AI Interaction ---
    		}
    
    		// Create the new task object
    		const newTask = {
    			id: newTaskId,
    			title: taskData.title,
    			description: taskData.description,
    			details: taskData.details || '',
    			testStrategy: taskData.testStrategy || '',
    			status: 'pending',
    			dependencies: taskData.dependencies?.length
    				? taskData.dependencies
    				: numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified
    			priority: effectivePriority,
    			subtasks: [] // Initialize with empty subtasks array
    		};
    
    		// Additional check: validate all dependencies in the AI response
    		if (taskData.dependencies?.length) {
    			const allValidDeps = taskData.dependencies.every((depId) => {
    				const numDepId = parseInt(depId, 10);
    				return (
    					!Number.isNaN(numDepId) && allTasks.some((t) => t.id === numDepId)
    				);
    			});
    
    			if (!allValidDeps) {
    				report(
    					'AI suggested invalid dependencies. Filtering them out...',
    					'warn'
    				);
    				newTask.dependencies = taskData.dependencies.filter((depId) => {
    					const numDepId = parseInt(depId, 10);
    					return (
    						!Number.isNaN(numDepId) && allTasks.some((t) => t.id === numDepId)
    					);
    				});
    			}
    		}
    
    		// Add the task to the tasks array OF THE CORRECT TAG
    		rawData[targetTag].tasks.push(newTask);
    		// Update the tag's metadata
    		ensureTagMetadata(rawData[targetTag], {
    			description: `Tasks for ${targetTag} context`
    		});
    
    		report('DEBUG: Writing tasks.json...', 'debug');
    		// Write the updated raw data back to the file
    		// The writeJSON function will automatically filter out _rawTaggedData
    		writeJSON(tasksPath, rawData, projectRoot, targetTag);
    		report('DEBUG: tasks.json written.', 'debug');
    
    		// Show success message - only for text output (CLI)
    		if (outputFormat === 'text') {
    			const table = new Table({
    				head: [
    					chalk.cyan.bold('ID'),
    					chalk.cyan.bold('Title'),
    					chalk.cyan.bold('Description')
    				],
    				colWidths: [5, 30, 50] // Adjust widths as needed
    			});
    
    			table.push([
    				newTask.id,
    				truncate(newTask.title, 27),
    				truncate(newTask.description, 47)
    			]);
    
    			console.log(chalk.green('✓ New task created successfully:'));
    			console.log(table.toString());
    
    			// Helper to get priority color
    			const getPriorityColor = (p) => {
    				switch (p?.toLowerCase()) {
    					case 'high':
    						return 'red';
    					case 'low':
    						return 'gray';
    					default:
    						return 'yellow';
    				}
    			};
    
    			// Check if AI added new dependencies that weren't explicitly provided
    			const aiAddedDeps = newTask.dependencies.filter(
    				(dep) => !numericDependencies.includes(dep)
    			);
    
    			// Check if AI removed any dependencies that were explicitly provided
    			const aiRemovedDeps = numericDependencies.filter(
    				(dep) => !newTask.dependencies.includes(dep)
    			);
    
    			// Get task titles for dependencies to display
    			const depTitles = {};
    			newTask.dependencies.forEach((dep) => {
    				const depTask = allTasks.find((t) => t.id === dep);
    				if (depTask) {
    					depTitles[dep] = truncate(depTask.title, 30);
    				}
    			});
    
    			// Prepare dependency display string
    			let dependencyDisplay = '';
    			if (newTask.dependencies.length > 0) {
    				dependencyDisplay = chalk.white('Dependencies:') + '\n';
    				newTask.dependencies.forEach((dep) => {
    					const isAiAdded = aiAddedDeps.includes(dep);
    					const depType = isAiAdded ? chalk.yellow(' (AI suggested)') : '';
    					dependencyDisplay +=
    						chalk.white(
    							`  - ${dep}: ${depTitles[dep] || 'Unknown task'}${depType}`
    						) + '\n';
    				});
    			} else {
    				dependencyDisplay = chalk.white('Dependencies: None') + '\n';
    			}
    
    			// Add info about removed dependencies if any
    			if (aiRemovedDeps.length > 0) {
    				dependencyDisplay +=
    					chalk.gray('\nUser-specified dependencies that were not used:') +
    					'\n';
    				aiRemovedDeps.forEach((dep) => {
    					const depTask = allTasks.find((t) => t.id === dep);
    					const title = depTask ? truncate(depTask.title, 30) : 'Unknown task';
    					dependencyDisplay += chalk.gray(`  - ${dep}: ${title}`) + '\n';
    				});
    			}
    
    			// Add dependency analysis summary
    			let dependencyAnalysis = '';
    			if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) {
    				dependencyAnalysis =
    					'\n' + chalk.white.bold('Dependency Analysis:') + '\n';
    				if (aiAddedDeps.length > 0) {
    					dependencyAnalysis +=
    						chalk.green(
    							`AI identified ${aiAddedDeps.length} additional dependencies`
    						) + '\n';
    				}
    				if (aiRemovedDeps.length > 0) {
    					dependencyAnalysis +=
    						chalk.yellow(
    							`AI excluded ${aiRemovedDeps.length} user-provided dependencies`
    						) + '\n';
    				}
    			}
    
    			// Show success message box
    			console.log(
    				boxen(
    					chalk.white.bold(`Task ${newTaskId} Created Successfully`) +
    						'\n\n' +
    						chalk.white(`Title: ${newTask.title}`) +
    						'\n' +
    						chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) +
    						'\n' +
    						chalk.white(
    							`Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}`
    						) +
    						'\n\n' +
    						dependencyDisplay +
    						dependencyAnalysis +
    						'\n' +
    						chalk.white.bold('Next Steps:') +
    						'\n' +
    						chalk.cyan(
    							`1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details`
    						) +
    						'\n' +
    						chalk.cyan(
    							`2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it`
    						) +
    						'\n' +
    						chalk.cyan(
    							`3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks`
    						),
    					{ padding: 1, borderColor: 'green', borderStyle: 'round' }
    				)
    			);
    
    			// Display AI Usage Summary if telemetryData is available
    			if (
    				aiServiceResponse &&
    				aiServiceResponse.telemetryData &&
    				(outputType === 'cli' || outputType === 'text')
    			) {
    				displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli');
    			}
    		}
    
    		report(
    			`DEBUG: Returning new task ID: ${newTaskId} and telemetry.`,
    			'debug'
    		);
    		return {
    			newTaskId: newTaskId,
    			telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null,
    			tagInfo: aiServiceResponse ? aiServiceResponse.tagInfo : null
    		};
    	} catch (error) {
    		// Stop any loading indicator on error
    		if (loadingIndicator) {
    			stopLoadingIndicator(loadingIndicator);
    		}
    
    		report(`Error adding task: ${error.message}`, 'error');
    		if (outputFormat === 'text') {
    			console.error(chalk.red(`Error: ${error.message}`));
    		}
    		// In MCP mode, we let the direct function handler catch and format
    		throw error;
    	}
    }
    
    export default addTask;
  • Zod schema defining the structure of task data generated by AI for the add-task tool, used in generateObjectService
    export const AddTaskResponseSchema = z.object({
    	title: z.string().describe('Clear, concise title for the task'),
    	description: z
    		.string()
    		.describe('A one or two sentence description of the task'),
    	details: z
    		.string()
    		.describe('In-depth implementation details, considerations, and guidance'),
    	testStrategy: z
    		.string()
    		.describe('Detailed approach for verifying task completion'),
    	dependencies: z
    		.array(z.number())
    		.nullable()
    		.describe(
    			'Array of task IDs that this task depends on (must be completed before this task can start)'
    		)
    });
  • Input parameter schema for the 'add_task' MCP tool, defining all accepted arguments with Zod validation
    parameters: z.object({
    	prompt: z
    		.string()
    		.optional()
    		.describe(
    			'Description of the task to add (required if not using manual fields)'
    		),
    	title: z
    		.string()
    		.optional()
    		.describe('Task title (for manual task creation)'),
    	description: z
    		.string()
    		.optional()
    		.describe('Task description (for manual task creation)'),
    	details: z
    		.string()
    		.optional()
    		.describe('Implementation details (for manual task creation)'),
    	testStrategy: z
    		.string()
    		.optional()
    		.describe('Test strategy (for manual task creation)'),
    	dependencies: z
    		.string()
    		.optional()
    		.describe('Comma-separated list of task IDs this task depends on'),
    	priority: z
    		.string()
    		.optional()
    		.describe('Task priority (high, medium, low)'),
    	file: z
    		.string()
    		.optional()
    		.describe('Path to the tasks file (default: tasks/tasks.json)'),
    	projectRoot: z
    		.string()
    		.describe('The directory of the project. Must be an absolute path.'),
    	tag: z.string().optional().describe('Tag context to operate on'),
    	research: z
    		.boolean()
    		.optional()
    		.describe('Whether to use research capabilities for task creation')
    }),

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/eyaltoledano/claude-task-master'

If you have feedback or need assistance with the MCP directory API, please join our Discord server