importProject
Create a new project by importing JSON data validated for structure and size. Automatically generates a project name if not provided, returning a unique project_id upon successful import.
Instructions
Creates a new project by importing data from a JSON string. The JSON data must conform to the structure previously generated by the 'exportProject' tool. Performs validation on the input data (parsing, basic structure, size limit). Returns the unique project_id of the newly created project upon success.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| new_project_name | No | Optional name for the newly created project (max 255 chars). If omitted, a name based on the original project name and import timestamp will be used. | |
| project_data | Yes | Required. A JSON string containing the full project data, conforming to the export structure. Max size e.g., 10MB. |
Implementation Reference
- src/tools/importProjectTool.ts:16-47 (handler)The MCP tool handler: processes importProject tool calls by invoking ProjectService.importProject, handling errors, and returning MCP-formatted response with new project_id.const processRequest = async (args: ImportProjectArgs) => { logger.info(`[${TOOL_NAME}] Received request (project name: ${args.new_project_name || 'Default'})`); try { // Call the service method to import the project const result = await projectService.importProject( args.project_data, args.new_project_name ); // Format the successful response const responsePayload = { project_id: result.project_id }; logger.info(`[${TOOL_NAME}] Successfully imported project. New ID: ${result.project_id}`); return { content: [{ type: "text" as const, text: JSON.stringify(responsePayload) }] }; } catch (error: unknown) { // Handle potential errors logger.error(`[${TOOL_NAME}] Error processing request:`, error); if (error instanceof ValidationError) { // JSON parsing, schema validation, size limit, or other data issues throw new McpError(ErrorCode.InvalidParams, error.message); } else { // Generic internal error (likely database related from the transaction) const message = error instanceof Error ? error.message : 'An unknown error occurred during project import.'; throw new McpError(ErrorCode.InternalError, message); } } };
- Zod schema (TOOL_PARAMS) and TypeScript type (ImportProjectArgs) defining the input parameters for the importProject tool.export const TOOL_PARAMS = z.object({ project_data: z.string() .min(1, "Project data cannot be empty.") // Size validation happens in the service layer before parsing .describe("Required. A JSON string containing the full project data, conforming to the export structure. Max size e.g., 10MB."), // Required, string new_project_name: z.string() .max(255, "New project name cannot exceed 255 characters.") .optional() .describe("Optional name for the newly created project (max 255 chars). If omitted, a name based on the original project name and import timestamp will be used."), // Optional, string, max length }); // Define the expected type for arguments based on the Zod schema export type ImportProjectArgs = z.infer<typeof TOOL_PARAMS>;
- src/tools/importProjectTool.ts:14-53 (registration)Registration function that defines and registers the importProject tool handler with the MCP server using server.tool().export const importProjectTool = (server: McpServer, projectService: ProjectService): void => { const processRequest = async (args: ImportProjectArgs) => { logger.info(`[${TOOL_NAME}] Received request (project name: ${args.new_project_name || 'Default'})`); try { // Call the service method to import the project const result = await projectService.importProject( args.project_data, args.new_project_name ); // Format the successful response const responsePayload = { project_id: result.project_id }; logger.info(`[${TOOL_NAME}] Successfully imported project. New ID: ${result.project_id}`); return { content: [{ type: "text" as const, text: JSON.stringify(responsePayload) }] }; } catch (error: unknown) { // Handle potential errors logger.error(`[${TOOL_NAME}] Error processing request:`, error); if (error instanceof ValidationError) { // JSON parsing, schema validation, size limit, or other data issues throw new McpError(ErrorCode.InvalidParams, error.message); } else { // Generic internal error (likely database related from the transaction) const message = error instanceof Error ? error.message : 'An unknown error occurred during project import.'; throw new McpError(ErrorCode.InternalError, message); } } }; // Register the tool with the server server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest); logger.info(`[${TOOL_NAME}] Tool registered successfully.`); };
- src/tools/index.ts:19-61 (registration)Invocation of the importProjectTool registration function within the central tools index, passing server and projectService.import { importProjectTool } from "./importProjectTool.js"; import { updateTaskTool } from "./updateTaskTool.js"; // Import the new tool import { deleteTaskTool } from "./deleteTaskTool.js"; // Import deleteTask tool import { deleteProjectTool } from "./deleteProjectTool.js"; // Import deleteProject tool // import { yourTool } from "./yourTool.js"; // Add other new tool imports here /** * Register all defined tools with the MCP server instance. * This function centralizes tool registration logic. * It also instantiates necessary services and repositories. */ export function registerTools(server: McpServer): void { logger.info("Registering tools..."); const configManager = ConfigurationManager.getInstance(); // --- Instantiate Dependencies --- // Note: Consider dependency injection frameworks for larger applications try { const dbManager = DatabaseManager.getInstance(); const db = dbManager.getDb(); // Get the initialized DB connection // Instantiate Repositories const projectRepository = new ProjectRepository(db); const taskRepository = new TaskRepository(db); // Instantiate TaskRepository // Instantiate Services const projectService = new ProjectService(db, projectRepository, taskRepository); // Pass db and both repos const taskService = new TaskService(db, taskRepository, projectRepository); // Instantiate TaskService, passing db and repos // --- Register Tools --- // Register each tool, passing necessary services // exampleTool(server, configManager.getExampleServiceConfig()); // Example commented out createProjectTool(server, projectService); addTaskTool(server, taskService); listTasksTool(server, taskService); showTaskTool(server, taskService); setTaskStatusTool(server, taskService); expandTaskTool(server, taskService); getNextTaskTool(server, taskService); exportProjectTool(server, projectService); importProjectTool(server, projectService); // Register importProjectTool (uses ProjectService)
- Core business logic for importing a project: parses export JSON, creates new project/tasks/dependencies with fresh UUIDs in a DB transaction.public async importProject(projectDataString: string, newProjectName?: string): Promise<{ project_id: string }> { logger.info(`[ProjectService] Attempting to import project...`); let importData: ExportData; try { if (projectDataString.length > 10 * 1024 * 1024) { // Example 10MB limit throw new ValidationError('Input data exceeds size limit (e.g., 10MB).'); } importData = JSON.parse(projectDataString); // TODO: Implement rigorous schema validation (Zod?) if (!importData || !importData.project_metadata || !Array.isArray(importData.tasks)) { throw new ValidationError('Invalid import data structure: Missing required fields.'); } logger.debug(`[ProjectService] Successfully parsed import data.`); } catch (error) { logger.error('[ProjectService] Failed to parse or validate import JSON:', error); if (error instanceof SyntaxError) { throw new ValidationError(`Invalid JSON format: ${error.message}`); } throw new ValidationError(`Invalid import data: ${error instanceof Error ? error.message : 'Unknown validation error'}`); } const importTransaction = this.db.transaction(() => { const newProjectId = uuidv4(); const now = new Date().toISOString(); const finalProjectName = newProjectName?.trim() || `${importData.project_metadata.name} (Imported ${now})`; const newProject: ProjectData = { project_id: newProjectId, name: finalProjectName.substring(0, 255), created_at: now, }; this.projectRepository.create(newProject); logger.info(`[ProjectService] Created new project ${newProjectId} for import.`); const idMap = new Map<string, string>(); const processTask = (task: ExportTask, parentDbId: string | null) => { const newTaskId = uuidv4(); idMap.set(task.task_id, newTaskId); const newTaskData: TaskData = { task_id: newTaskId, project_id: newProjectId, parent_task_id: parentDbId, description: task.description, status: task.status, priority: task.priority, created_at: task.created_at, updated_at: task.updated_at, }; this.taskRepository.create(newTaskData, []); // Create task first if (task.subtasks && task.subtasks.length > 0) { task.subtasks.forEach(subtask => processTask(subtask, newTaskId)); } }; importData.tasks.forEach(rootTask => processTask(rootTask, null)); logger.info(`[ProjectService] Processed ${idMap.size} tasks for import.`); const insertDependencyStmt = this.db.prepare(` INSERT INTO task_dependencies (task_id, depends_on_task_id) VALUES (?, ?) ON CONFLICT DO NOTHING `); let depCount = 0; const processDeps = (task: ExportTask) => { const newTaskId = idMap.get(task.task_id); if (newTaskId && task.dependencies && task.dependencies.length > 0) { for (const oldDepId of task.dependencies) { const newDepId = idMap.get(oldDepId); if (newDepId) { insertDependencyStmt.run(newTaskId, newDepId); depCount++; } else { logger.warn(`[ProjectService] Dependency task ID ${oldDepId} not found in import map for task ${task.task_id}. Skipping dependency.`); } } } if (task.subtasks && task.subtasks.length > 0) { task.subtasks.forEach(processDeps); } }; importData.tasks.forEach(processDeps); logger.info(`[ProjectService] Processed ${depCount} dependencies for import.`); return { project_id: newProjectId }; }); try { const result = importTransaction(); logger.info(`[ProjectService] Successfully imported project. New project ID: ${result.project_id}`); return result; } catch (error) { logger.error(`[ProjectService] Error during import transaction:`, error); if (error instanceof NotFoundError || error instanceof ValidationError || error instanceof ConflictError) { throw error; } throw new Error(`Failed to import project: ${error instanceof Error ? error.message : 'Unknown database error'}`); } }