Skip to main content
Glama

add_dependency

Define task dependencies within Task Master by linking tasks through IDs, ensuring structured workflows and project organization for AI-driven development.

Instructions

Add a dependency relationship between two tasks

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dependsOnYesID of task that will become a dependency
fileNoAbsolute path to the tasks file (default: tasks/tasks.json)
idYesID of task that will depend on another task
projectRootYesThe directory of the project. Must be an absolute path.
tagNoTag context to operate on

Implementation Reference

  • Primary registration of the 'add_dependency' tool with the MCP server, including name, description, Zod input schema, and wrapped execute handler.
    export function registerAddDependencyTool(server) {
    	server.addTool({
    		name: 'add_dependency',
    		description: 'Add a dependency relationship between two tasks',
    		parameters: z.object({
    			id: z.string().describe('ID of task that will depend on another task'),
    			dependsOn: z
    				.string()
    				.describe('ID of task that will become a dependency'),
    			file: z
    				.string()
    				.optional()
    				.describe(
    					'Absolute 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')
    		}),
    		execute: withToolContext(
    			'add-dependency',
    			async (args, { log, session }) => {
    				try {
    					log.info(
    						`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
    					);
    					const resolvedTag = resolveTag({
    						projectRoot: args.projectRoot,
    						tag: args.tag
    					});
    					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 with the resolved path
    					const result = await addDependencyDirect(
    						{
    							// Pass the explicitly resolved path
    							tasksJsonPath: tasksJsonPath,
    							// Pass other relevant args
    							id: args.id,
    							dependsOn: args.dependsOn,
    							projectRoot: args.projectRoot,
    							tag: resolvedTag
    						},
    						log
    						// Remove context object
    					);
    
    					// Log result
    					if (result.success) {
    						log.info(`Successfully added dependency: ${result.data.message}`);
    					} else {
    						log.error(`Failed to add dependency: ${result.error.message}`);
    					}
    
    					// Use handleApiResult to format the response
    					return handleApiResult({
    						result,
    						log,
    						errorPrefix: 'Error adding dependency',
    						projectRoot: args.projectRoot,
    						tag: resolvedTag
    					});
    				} catch (error) {
    					log.error(`Error in addDependency tool: ${error.message}`);
    					return createErrorResponse(error.message);
    				}
    			}
    		)
    	});
    }
  • Zod schema for 'add_dependency' tool parameters: id (string), dependsOn (string), file (optional string), projectRoot (string), tag (optional string).
    parameters: z.object({
    	id: z.string().describe('ID of task that will depend on another task'),
    	dependsOn: z
    		.string()
    		.describe('ID of task that will become a dependency'),
    	file: z
    		.string()
    		.optional()
    		.describe(
    			'Absolute 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')
    }),
  • addDependencyDirect: Core direct function called by tool handler. Validates inputs, formats task IDs, calls the dependency manager's addDependency, handles silent mode and returns structured result.
    export async function addDependencyDirect(args, log) {
    	// Destructure expected args
    	const { tasksJsonPath, id, dependsOn, tag, projectRoot } = args;
    	try {
    		log.info(`Adding dependency with args: ${JSON.stringify(args)}`);
    
    		// Check if tasksJsonPath was provided
    		if (!tasksJsonPath) {
    			log.error('addDependencyDirect called without tasksJsonPath');
    			return {
    				success: false,
    				error: {
    					code: 'MISSING_ARGUMENT',
    					message: 'tasksJsonPath is required'
    				}
    			};
    		}
    
    		// Validate required parameters
    		if (!id) {
    			return {
    				success: false,
    				error: {
    					code: 'INPUT_VALIDATION_ERROR',
    					message: 'Task ID (id) is required'
    				}
    			};
    		}
    
    		if (!dependsOn) {
    			return {
    				success: false,
    				error: {
    					code: 'INPUT_VALIDATION_ERROR',
    					message: 'Dependency ID (dependsOn) is required'
    				}
    			};
    		}
    
    		// Use provided path
    		const tasksPath = tasksJsonPath;
    
    		// Format IDs for the core function
    		const taskId =
    			id && id.includes && id.includes('.') ? id : parseInt(id, 10);
    		const dependencyId =
    			dependsOn && dependsOn.includes && dependsOn.includes('.')
    				? dependsOn
    				: parseInt(dependsOn, 10);
    
    		log.info(
    			`Adding dependency: task ${taskId} will depend on ${dependencyId}`
    		);
    
    		// Enable silent mode to prevent console logs from interfering with JSON response
    		enableSilentMode();
    
    		// Create context object
    		const context = { projectRoot, tag };
    
    		// Call the core function using the provided path
    		await addDependency(tasksPath, taskId, dependencyId, context);
    
    		// Restore normal logging
    		disableSilentMode();
    
    		return {
    			success: true,
    			data: {
    				message: `Successfully added dependency: Task ${taskId} now depends on ${dependencyId}`,
    				taskId: taskId,
    				dependencyId: dependencyId
    			}
    		};
    	} catch (error) {
    		// Make sure to restore normal logging even if there's an error
    		disableSilentMode();
    
    		log.error(`Error in addDependencyDirect: ${error.message}`);
    		return {
    			success: false,
    			error: {
    				code: 'CORE_FUNCTION_ERROR',
    				message: error.message
    			}
    		};
    	}
    }
  • Core addDependency function: Loads tasks.json, validates task/dependency existence, self/circular deps, adds dependency ID to target.dependencies, sorts, saves file. Exact implementation logic.
    async function addDependency(tasksPath, taskId, dependencyId, context = {}) {
    	log('info', `Adding dependency ${dependencyId} to task ${taskId}...`);
    
    	const data = readJSON(tasksPath, context.projectRoot, context.tag);
    	if (!data || !data.tasks) {
    		log('error', 'No valid tasks found in tasks.json');
    		process.exit(1);
    	}
    
    	// Format the task and dependency IDs correctly
    	const formattedTaskId =
    		typeof taskId === 'string' && taskId.includes('.')
    			? taskId
    			: parseInt(taskId, 10);
    
    	const formattedDependencyId = formatTaskId(dependencyId);
    
    	// Check if the dependency task or subtask actually exists
    	if (!taskExists(data.tasks, formattedDependencyId)) {
    		log(
    			'error',
    			`Dependency target ${formattedDependencyId} does not exist in tasks.json`
    		);
    		process.exit(1);
    	}
    
    	// Find the task to update
    	let targetTask = null;
    	let isSubtask = false;
    
    	if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) {
    		// Handle dot notation for subtasks (e.g., "1.2")
    		const [parentId, subtaskId] = formattedTaskId
    			.split('.')
    			.map((id) => parseInt(id, 10));
    		const parentTask = data.tasks.find((t) => t.id === parentId);
    
    		if (!parentTask) {
    			log('error', `Parent task ${parentId} not found.`);
    			process.exit(1);
    		}
    
    		if (!parentTask.subtasks) {
    			log('error', `Parent task ${parentId} has no subtasks.`);
    			process.exit(1);
    		}
    
    		targetTask = parentTask.subtasks.find((s) => s.id === subtaskId);
    		isSubtask = true;
    
    		if (!targetTask) {
    			log('error', `Subtask ${formattedTaskId} not found.`);
    			process.exit(1);
    		}
    	} else {
    		// Regular task (not a subtask)
    		targetTask = data.tasks.find((t) => t.id === formattedTaskId);
    
    		if (!targetTask) {
    			log('error', `Task ${formattedTaskId} not found.`);
    			process.exit(1);
    		}
    	}
    
    	// Initialize dependencies array if it doesn't exist
    	if (!targetTask.dependencies) {
    		targetTask.dependencies = [];
    	}
    
    	// Check if dependency already exists
    	if (
    		targetTask.dependencies.some((d) => {
    			// Convert both to strings for comparison to handle both numeric and string IDs
    			return String(d) === String(formattedDependencyId);
    		})
    	) {
    		log(
    			'warn',
    			`Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.`
    		);
    		return;
    	}
    
    	// Check if the task is trying to depend on itself - compare full IDs (including subtask parts)
    	if (String(formattedTaskId) === String(formattedDependencyId)) {
    		log('error', `Task ${formattedTaskId} cannot depend on itself.`);
    		process.exit(1);
    	}
    
    	// For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency
    	// Check if we're dealing with subtasks with the same parent task
    	let isSelfDependency = false;
    
    	if (
    		typeof formattedTaskId === 'string' &&
    		typeof formattedDependencyId === 'string' &&
    		formattedTaskId.includes('.') &&
    		formattedDependencyId.includes('.')
    	) {
    		const [taskParentId] = formattedTaskId.split('.');
    		const [depParentId] = formattedDependencyId.split('.');
    
    		// Only treat it as a self-dependency if both the parent ID and subtask ID are identical
    		isSelfDependency = formattedTaskId === formattedDependencyId;
    
    		// Log for debugging
    		log(
    			'debug',
    			`Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}`
    		);
    		log(
    			'debug',
    			`Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}`
    		);
    	}
    
    	if (isSelfDependency) {
    		log('error', `Subtask ${formattedTaskId} cannot depend on itself.`);
    		process.exit(1);
    	}
    
    	// Check for circular dependencies
    	const dependencyChain = [formattedTaskId];
    	if (
    		!isCircularDependency(data.tasks, formattedDependencyId, dependencyChain)
    	) {
    		// Add the dependency
    		targetTask.dependencies.push(formattedDependencyId);
    
    		// Sort dependencies numerically or by parent task ID first, then subtask ID
    		targetTask.dependencies.sort((a, b) => {
    			if (typeof a === 'number' && typeof b === 'number') {
    				return a - b;
    			} else if (typeof a === 'string' && typeof b === 'string') {
    				const [aParent, aChild] = a.split('.').map(Number);
    				const [bParent, bChild] = b.split('.').map(Number);
    				return aParent !== bParent ? aParent - bParent : aChild - bChild;
    			} else if (typeof a === 'number') {
    				return -1; // Numbers come before strings
    			} else {
    				return 1; // Strings come after numbers
    			}
    		});
    
    		// Save changes
    		writeJSON(tasksPath, data, context.projectRoot, context.tag);
    		log(
    			'success',
    			`Added dependency ${formattedDependencyId} to task ${formattedTaskId}`
    		);
    
    		// Display a more visually appealing success message
    		if (!isSilentMode()) {
    			console.log(
    				boxen(
    					chalk.green(`Successfully added dependency:\n\n`) +
    						`Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`,
    					{
    						padding: 1,
    						borderColor: 'green',
    						borderStyle: 'round',
    						margin: { top: 1 }
    					}
    				)
    			);
    		}
    
    		// Generate updated task files
    		// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
    
    		log('info', 'Task files regenerated with updated dependencies.');
    	} else {
    		log(
    			'error',
    			`Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.`
    		);
    		process.exit(1);
    	}
    }
  • Tool registry entry mapping 'add_dependency' to its registration function.
    add_dependency: registerAddDependencyTool,

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