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
| Name | Required | Description | Default |
|---|---|---|---|
| description | No | Optional description for the new tag | |
| file | No | Path to the tasks file (default: tasks/tasks.json) | |
| projectRoot | Yes | The directory of the project. Must be an absolute path. | |
| sourceName | Yes | Name of the source tag to copy from | |
| targetName | Yes | Name of the new tag to create |
Implementation Reference
- mcp-server/src/tools/copy-tag.js:39-81 (handler)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.') }),
- mcp-server/src/tools/tool-registry.js:13-93 (registration)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; } }