tracker_import
Import project data from JSON format to restore tracker structure with new IDs and remapped references using atomic transactions.
Instructions
Import a project from JSON (matching tracker_export format). Creates all entities with new IDs and remaps references. Uses a transaction for atomicity.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Full export JSON object from tracker_export |
Implementation Reference
- src/tools/export-import.ts:171-358 (handler)The 'handleImport' function implements the logic for 'tracker_import', including schema validation, database transaction handling, ID remapping for projects, epics, tasks, and notes, and activity logging.
function handleImport(args: Record<string, unknown>) { const db = getDb(); const data = args.data as Record<string, unknown>; const version = data.format_version as string; if (version !== '1.0' && version !== '1.1') { throw new Error(`Unsupported format version: ${version}. Expected "1.0" or "1.1".`); } const projectData = data.project as Record<string, unknown>; if (!projectData || !projectData.name) { throw new Error('Invalid import data: missing project or project.name'); } const result = db.transaction(() => { const epicIdMap = new Map<number, number>(); const taskIdMap = new Map<number, number>(); // 1. Create project const project = db.prepare( 'INSERT INTO projects (name, description, status, tags, metadata) VALUES (?, ?, ?, ?, ?) RETURNING *' ).get( projectData.name, projectData.description ?? null, projectData.status ?? 'active', projectData.tags ?? '[]', projectData.metadata ?? '{}' ) as Record<string, unknown>; const newProjectId = project.id as number; logActivity(db, 'project', newProjectId, 'created', null, null, null, `Project '${projectData.name}' imported`); // 2. Create epics and their children const epics = (projectData.epics as Array<Record<string, unknown>>) ?? []; let epicCount = 0; let taskCount = 0; let subtaskCount = 0; let commentCount = 0; let depCount = 0; // Collect deferred dependencies (need all tasks created first) const deferredDeps: Array<{ newTaskId: number; originalDeps: number[] }> = []; for (const epicData of epics) { const epic = db.prepare( `INSERT INTO epics (project_id, name, description, status, priority, sort_order, tags, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *` ).get( newProjectId, epicData.name, epicData.description ?? null, epicData.status ?? 'planned', epicData.priority ?? 'medium', epicData.sort_order ?? 0, epicData.tags ?? '[]', epicData.metadata ?? '{}' ) as Record<string, unknown>; const newEpicId = epic.id as number; if (epicData._original_id != null) { epicIdMap.set(epicData._original_id as number, newEpicId); } epicCount++; logActivity(db, 'epic', newEpicId, 'created', null, null, null, `Epic '${epicData.name}' imported`); // 3. Create tasks const tasks = (epicData.tasks as Array<Record<string, unknown>>) ?? []; for (const taskData of tasks) { const task = db.prepare( `INSERT INTO tasks (epic_id, title, description, status, priority, sort_order, assigned_to, estimated_hours, actual_hours, due_date, source_ref, tags, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *` ).get( newEpicId, taskData.title, taskData.description ?? null, taskData.status ?? 'todo', taskData.priority ?? 'medium', taskData.sort_order ?? 0, taskData.assigned_to ?? null, taskData.estimated_hours ?? null, taskData.actual_hours ?? null, taskData.due_date ?? null, taskData.source_ref ?? null, taskData.tags ?? '[]', taskData.metadata ?? '{}' ) as Record<string, unknown>; const newTaskId = task.id as number; if (taskData._original_id != null) { taskIdMap.set(taskData._original_id as number, newTaskId); } taskCount++; logActivity(db, 'task', newTaskId, 'created', null, null, null, `Task '${taskData.title}' imported`); // Defer dependency creation const originalDeps = (taskData.depends_on as number[]) ?? []; if (originalDeps.length > 0) { deferredDeps.push({ newTaskId, originalDeps }); } // 4. Create subtasks const subtasks = (taskData.subtasks as Array<Record<string, unknown>>) ?? []; for (const subtaskData of subtasks) { const subtask = db.prepare( 'INSERT INTO subtasks (task_id, title, status, sort_order) VALUES (?, ?, ?, ?) RETURNING *' ).get( newTaskId, subtaskData.title, subtaskData.status ?? 'todo', subtaskData.sort_order ?? 0 ) as Record<string, unknown>; subtaskCount++; logActivity(db, 'subtask', subtask.id as number, 'created', null, null, null, `Subtask '${subtaskData.title}' imported`); } // 5. Create comments const comments = (taskData.comments as Array<Record<string, unknown>>) ?? []; for (const commentData of comments) { db.prepare( 'INSERT INTO comments (task_id, author, content) VALUES (?, ?, ?)' ).run(newTaskId, commentData.author ?? null, commentData.content); commentCount++; } } } // 6. Create dependencies with ID remapping const depInsert = db.prepare('INSERT INTO task_dependencies (task_id, depends_on_task_id) VALUES (?, ?)'); for (const { newTaskId, originalDeps } of deferredDeps) { for (const origDepId of originalDeps) { const newDepId = taskIdMap.get(origDepId); if (newDepId != null) { depInsert.run(newTaskId, newDepId); depCount++; } } } // 7. Create notes with ID remapping const importNotes = (data.notes as Array<Record<string, unknown>>) ?? []; let noteCount = 0; for (const noteData of importNotes) { let relatedEntityType = noteData.related_entity_type as string | null; let relatedEntityId: number | null = null; const originalId = noteData._original_related_entity_id as number | null; if (relatedEntityType && originalId != null) { if (relatedEntityType === 'project') { relatedEntityId = newProjectId; } else if (relatedEntityType === 'epic') { relatedEntityId = epicIdMap.get(originalId) ?? null; if (relatedEntityId === null) relatedEntityType = null; } else if (relatedEntityType === 'task') { relatedEntityId = taskIdMap.get(originalId) ?? null; if (relatedEntityId === null) relatedEntityType = null; } } const note = db.prepare( `INSERT INTO notes (title, content, note_type, related_entity_type, related_entity_id, tags, metadata) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *` ).get( noteData.title, noteData.content, noteData.note_type ?? 'general', relatedEntityType, relatedEntityId, noteData.tags ?? '[]', noteData.metadata ?? '{}' ) as Record<string, unknown>; noteCount++; logActivity(db, 'note', note.id as number, 'created', null, null, null, `Note '${noteData.title}' imported`); } return { message: 'Import complete.', project_id: newProjectId, project_name: projectData.name, counts: { epics: epicCount, tasks: taskCount, subtasks: subtaskCount, comments: commentCount, dependencies: depCount, notes: noteCount }, }; })(); return result; } - src/tools/export-import.ts:360-363 (registration)The 'tracker_import' tool is registered in the 'handlers' dictionary which maps tool names to their respective handler functions.
export const handlers: Record<string, ToolHandler> = { tracker_export: handleExport, tracker_import: handleImport, }; - src/tools/export-import.ts:22-37 (schema)The 'tracker_import' tool definition includes its name, description, and input schema specifying the required 'data' object.
{ name: 'tracker_import', description: 'Import a project from JSON (matching tracker_export format). Creates all entities with new IDs and remaps references. Uses a transaction for atomicity.', annotations: { title: 'Import Project', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, inputSchema: { type: 'object', properties: { data: { type: 'object', description: 'Full export JSON object from tracker_export', }, }, required: ['data'], }, },