Skip to main content
Glama

copy_tag

Duplicates an existing tag within the Task Master server to create a new tag, retaining all associated tasks and metadata. Specify source and target tag names for efficient task organization in AI-driven projects.

Instructions

Copy an existing tag to create a new tag with all tasks and metadata

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
descriptionNoOptional description for the new tag
fileNoPath to the tasks file (default: tasks/tasks.json)
projectRootYesThe directory of the project. Must be an absolute path.
sourceNameYesName of the source tag to copy from
targetNameYesName of the new tag to create

Implementation Reference

  • MCP tool execute handler for 'copy_tag'. Normalizes project root, finds tasks.json path, performs validation, and delegates to copyTagDirect direct function.
    	execute: withNormalizedProjectRoot(async (args, { log, session }) => {
    		try {
    			log.info(`Starting copy-tag with args: ${JSON.stringify(args)}`);
    
    			// 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 copyTagDirect(
    				{
    					tasksJsonPath: tasksJsonPath,
    					sourceName: args.sourceName,
    					targetName: args.targetName,
    					description: args.description,
    					projectRoot: args.projectRoot
    				},
    				log,
    				{ session }
    			);
    
    			return handleApiResult({
    				result,
    				log: log,
    				errorPrefix: 'Error copying tag',
    				projectRoot: args.projectRoot
    			});
    		} catch (error) {
    			log.error(`Error in copy-tag tool: ${error.message}`);
    			return createErrorResponse(error.message);
    		}
    	})
    });
  • Zod input schema defining parameters for the copy_tag tool: sourceName, targetName, optional description, file, projectRoot.
    parameters: z.object({
    	sourceName: z.string().describe('Name of the source tag to copy from'),
    	targetName: z.string().describe('Name of the new tag to create'),
    	description: z
    		.string()
    		.optional()
    		.describe('Optional description for the new tag'),
    	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.')
    }),
  • Tool registry that imports registerCopyTagTool from copy-tag.js and maps 'copy_tag' to it for dynamic registration on MCP server.
    import { registerCopyTagTool } from './copy-tag.js';
    import { registerDeleteTagTool } from './delete-tag.js';
    import { registerExpandAllTool } from './expand-all.js';
    import { registerExpandTaskTool } from './expand-task.js';
    import { registerFixDependenciesTool } from './fix-dependencies.js';
    import { registerInitializeProjectTool } from './initialize-project.js';
    import { registerListTagsTool } from './list-tags.js';
    import { registerModelsTool } from './models.js';
    import { registerMoveTaskTool } from './move-task.js';
    import { registerNextTaskTool } from './next-task.js';
    import { registerParsePRDTool } from './parse-prd.js';
    import { registerRemoveDependencyTool } from './remove-dependency.js';
    import { registerRemoveSubtaskTool } from './remove-subtask.js';
    import { registerRemoveTaskTool } from './remove-task.js';
    import { registerRenameTagTool } from './rename-tag.js';
    import { registerResearchTool } from './research.js';
    import { registerResponseLanguageTool } from './response-language.js';
    import { registerRulesTool } from './rules.js';
    import { registerScopeDownTool } from './scope-down.js';
    import { registerScopeUpTool } from './scope-up.js';
    import { registerUpdateSubtaskTool } from './update-subtask.js';
    import { registerUpdateTaskTool } from './update-task.js';
    import { registerUpdateTool } from './update.js';
    import { registerUseTagTool } from './use-tag.js';
    import { registerValidateDependenciesTool } from './validate-dependencies.js';
    
    // Import TypeScript tools from apps/mcp
    import {
    	registerAutopilotAbortTool,
    	registerAutopilotCommitTool,
    	registerAutopilotCompleteTool,
    	registerAutopilotFinalizeTool,
    	registerAutopilotNextTool,
    	registerAutopilotResumeTool,
    	registerAutopilotStartTool,
    	registerAutopilotStatusTool,
    	registerGenerateTool,
    	registerGetTaskTool,
    	registerGetTasksTool,
    	registerSetTaskStatusTool
    } from '@tm/mcp';
    
    /**
     * Comprehensive tool registry mapping tool names to their registration functions
     * Used for dynamic tool registration and validation
     */
    export const toolRegistry = {
    	initialize_project: registerInitializeProjectTool,
    	models: registerModelsTool,
    	rules: registerRulesTool,
    	parse_prd: registerParsePRDTool,
    	'response-language': registerResponseLanguageTool,
    	analyze_project_complexity: registerAnalyzeProjectComplexityTool,
    	expand_task: registerExpandTaskTool,
    	expand_all: registerExpandAllTool,
    	scope_up_task: registerScopeUpTool,
    	scope_down_task: registerScopeDownTool,
    	get_tasks: registerGetTasksTool,
    	get_task: registerGetTaskTool,
    	next_task: registerNextTaskTool,
    	complexity_report: registerComplexityReportTool,
    	set_task_status: registerSetTaskStatusTool,
    	add_task: registerAddTaskTool,
    	add_subtask: registerAddSubtaskTool,
    	update: registerUpdateTool,
    	update_task: registerUpdateTaskTool,
    	update_subtask: registerUpdateSubtaskTool,
    	remove_task: registerRemoveTaskTool,
    	remove_subtask: registerRemoveSubtaskTool,
    	clear_subtasks: registerClearSubtasksTool,
    	move_task: registerMoveTaskTool,
    	add_dependency: registerAddDependencyTool,
    	remove_dependency: registerRemoveDependencyTool,
    	validate_dependencies: registerValidateDependenciesTool,
    	fix_dependencies: registerFixDependenciesTool,
    	list_tags: registerListTagsTool,
    	add_tag: registerAddTagTool,
    	delete_tag: registerDeleteTagTool,
    	use_tag: registerUseTagTool,
    	rename_tag: registerRenameTagTool,
    	copy_tag: registerCopyTagTool,
  • Direct function copyTagDirect: MCP wrapper for core copyTag. Handles silent mode, input validation, logging, and formats JSON response.
    export async function copyTagDirect(args, log, context = {}) {
    	// Destructure expected args
    	const { tasksJsonPath, sourceName, targetName, description, projectRoot } =
    		args;
    	const { session } = 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('copyTagDirect called without tasksJsonPath');
    			disableSilentMode();
    			return {
    				success: false,
    				error: {
    					code: 'MISSING_ARGUMENT',
    					message: 'tasksJsonPath is required'
    				}
    			};
    		}
    
    		// Check required parameters
    		if (!sourceName || typeof sourceName !== 'string') {
    			log.error('Missing required parameter: sourceName');
    			disableSilentMode();
    			return {
    				success: false,
    				error: {
    					code: 'MISSING_PARAMETER',
    					message: 'Source tag name is required and must be a string'
    				}
    			};
    		}
    
    		if (!targetName || typeof targetName !== 'string') {
    			log.error('Missing required parameter: targetName');
    			disableSilentMode();
    			return {
    				success: false,
    				error: {
    					code: 'MISSING_PARAMETER',
    					message: 'Target tag name is required and must be a string'
    				}
    			};
    		}
    
    		log.info(`Copying tag from "${sourceName}" to "${targetName}"`);
    
    		// Prepare options
    		const options = {
    			description
    		};
    
    		// Call the copyTag function
    		const result = await copyTag(
    			tasksJsonPath,
    			sourceName,
    			targetName,
    			options,
    			{
    				session,
    				mcpLog,
    				projectRoot
    			},
    			'json' // outputFormat - use 'json' to suppress CLI UI
    		);
    
    		// Restore normal logging
    		disableSilentMode();
    
    		return {
    			success: true,
    			data: {
    				sourceName: result.sourceName,
    				targetName: result.targetName,
    				copied: result.copied,
    				tasksCopied: result.tasksCopied,
    				description: result.description,
    				message: `Successfully copied tag from "${result.sourceName}" to "${result.targetName}"`
    			}
    		};
    	} catch (error) {
    		// Make sure to restore normal logging even if there's an error
    		disableSilentMode();
    
    		log.error(`Error in copyTagDirect: ${error.message}`);
    		return {
    			success: false,
    			error: {
    				code: error.code || 'COPY_TAG_ERROR',
    				message: error.message
    			}
    		};
    	}
    }
  • Core copyTag function: reads tasks.json, deep-copies tasks from source tag to new target tag with copied metadata, writes back to file.
    async function copyTag(
    	tasksPath,
    	sourceName,
    	targetName,
    	options = {},
    	context = {},
    	outputFormat = 'text'
    ) {
    	const { mcpLog, projectRoot } = context;
    	const { description } = options;
    
    	// Create a consistent logFn object regardless of context
    	const logFn = mcpLog || {
    		info: (...args) => log('info', ...args),
    		warn: (...args) => log('warn', ...args),
    		error: (...args) => log('error', ...args),
    		debug: (...args) => log('debug', ...args),
    		success: (...args) => log('success', ...args)
    	};
    
    	try {
    		// Validate parameters
    		if (!sourceName || typeof sourceName !== 'string') {
    			throw new Error('Source tag name is required and must be a string');
    		}
    		if (!targetName || typeof targetName !== 'string') {
    			throw new Error('Target tag name is required and must be a string');
    		}
    
    		// Validate target tag name format
    		if (!/^[a-zA-Z0-9_-]+$/.test(targetName)) {
    			throw new Error(
    				'Target tag name can only contain letters, numbers, hyphens, and underscores'
    			);
    		}
    
    		// Reserved tag names
    		const reservedNames = ['master', 'main', 'default'];
    		if (reservedNames.includes(targetName.toLowerCase())) {
    			throw new Error(`"${targetName}" is a reserved tag name`);
    		}
    
    		logFn.info(`Copying tag from "${sourceName}" to "${targetName}"`);
    
    		// Read current tasks data
    		const data = readJSON(tasksPath, projectRoot);
    		if (!data) {
    			throw new Error(`Could not read tasks file at ${tasksPath}`);
    		}
    
    		// Use raw tagged data for tag operations
    		const rawData = data._rawTaggedData || data;
    
    		// Check if source tag exists
    		if (!rawData[sourceName]) {
    			throw new Error(`Source tag "${sourceName}" does not exist`);
    		}
    
    		// Check if target tag already exists
    		if (rawData[targetName]) {
    			throw new Error(`Target tag "${targetName}" already exists`);
    		}
    
    		// Get source tasks
    		const sourceTasks = getTasksForTag(rawData, sourceName);
    
    		// Create deep copy of the source tag data
    		rawData[targetName] = {
    			tasks: JSON.parse(JSON.stringify(sourceTasks)), // Deep copy tasks
    			metadata: {
    				created: new Date().toISOString(),
    				updated: new Date().toISOString(),
    				description:
    					description ||
    					`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`,
    				copiedFrom: {
    					tag: sourceName,
    					date: new Date().toISOString()
    				}
    			}
    		};
    
    		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
    		const cleanData = {};
    		for (const [key, value] of Object.entries(rawData)) {
    			if (key !== '_rawTaggedData') {
    				cleanData[key] = value;
    			}
    		}
    
    		// Write the clean data back to file with proper context to avoid tag corruption
    		writeJSON(tasksPath, cleanData, projectRoot);
    
    		logFn.success(
    			`Successfully copied tag from "${sourceName}" to "${targetName}"`
    		);
    
    		// For JSON output, return structured data
    		if (outputFormat === 'json') {
    			return {
    				sourceName,
    				targetName,
    				copied: true,
    				description:
    					description ||
    					`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`
    			};
    		}
    
    		// For text output, display success message
    		if (outputFormat === 'text') {
    			console.log(
    				boxen(
    					chalk.green.bold('✓ Tag Copied Successfully') +
    						`\n\nSource Tag: ${chalk.cyan(sourceName)}` +
    						`\nTarget Tag: ${chalk.green.bold(targetName)}` +
    						`\nTasks Copied: ${chalk.yellow(sourceTasks.length)}` +
    						(description ? `\nDescription: ${chalk.gray(description)}` : ''),
    					{
    						padding: 1,
    						borderColor: 'green',
    						borderStyle: 'round',
    						margin: { top: 1, bottom: 1 }
    					}
    				)
    			);
    		}
    
    		return {
    			sourceName,
    			targetName,
    			copied: true,
    			description:
    				description ||
    				`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`
    		};
    	} catch (error) {
    		logFn.error(`Error copying tag: ${error.message}`);
    		throw error;
    	}
    }

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