'use strict'; const { Project, Area, Tag } = require('../../../models'); const { Op } = require('sequelize'); /** * Register all project-related MCP tools */ function registerProjectTools(server, context, tools) { // 1. list_projects - List projects tools.push({ name: 'list_projects', description: 'List projects from tududi with optional filtering', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: [ 'not_started', 'planned', 'in_progress', 'waiting', 'done', 'cancelled', 'all', ], description: 'Filter by project status', }, area_id: { type: 'number', description: 'Filter by area ID', }, limit: { type: 'number', description: 'Maximum number of projects to return', default: 30, }, }, }, handler: async (params) => { const where = { user_id: context.userId }; const limit = params.limit || 30; // Apply status filter if (params.status && params.status !== 'all') { where.status = params.status; } // Apply area filter if (params.area_id) { where.area_id = params.area_id; } const projects = await Project.findAll({ where: where, include: [ { model: Area, as: 'Area' }, { model: Tag, as: 'Tags' }, ], limit: limit, order: [['created_at', 'DESC']], }); const serialized = projects.map((p) => { const proj = p.toJSON(); return { id: proj.id, uid: proj.uid, name: proj.name, description: proj.description, status: proj.status, priority: proj.priority, area: proj.Area ? proj.Area.name : null, tags: proj.Tags ? proj.Tags.map((t) => t.name) : [], due_date_at: proj.due_date_at, pin_to_sidebar: proj.pin_to_sidebar, created_at: proj.created_at, updated_at: proj.updated_at, }; }); return { content: [ { type: 'text', text: JSON.stringify( { count: serialized.length, projects: serialized, }, null, 2 ), }, ], }; }, }); // 2. create_project - Create new project tools.push({ name: 'create_project', description: 'Create a new project in tududi', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Project name (required)', }, description: { type: 'string', description: 'Project description', }, priority: { type: 'number', description: 'Priority (0=low, 1=medium, 2=high)', }, status: { type: 'string', enum: [ 'not_started', 'planned', 'in_progress', 'waiting', 'done', 'cancelled', ], }, area_id: { type: 'number', description: 'Area ID', }, due_date_at: { type: 'string', description: 'Due date (ISO 8601)', }, tags: { type: 'array', items: { type: 'string' }, description: 'Array of tag names', }, }, required: ['name'], }, handler: async (params) => { const projectData = { user_id: context.userId, name: params.name, description: params.description || '', priority: params.priority !== undefined ? params.priority : 1, status: params.status || 'not_started', area_id: params.area_id || null, due_date_at: params.due_date_at || null, }; const project = await Project.create(projectData); // Handle tags if provided if (params.tags && params.tags.length > 0) { const tagInstances = await Promise.all( params.tags.map(async (tagName) => { const [tag] = await Tag.findOrCreate({ where: { name: tagName, user_id: context.userId }, }); return tag; }) ); await project.setTags(tagInstances); } // Reload with associations const reloadedProject = await Project.findByPk(project.id, { include: [ { model: Area, as: 'Area' }, { model: Tag, as: 'Tags' }, ], }); const serialized = { id: reloadedProject.id, uid: reloadedProject.uid, name: reloadedProject.name, description: reloadedProject.description, status: reloadedProject.status, priority: reloadedProject.priority, area: reloadedProject.Area ? reloadedProject.Area.name : null, tags: reloadedProject.Tags ? reloadedProject.Tags.map((t) => t.name) : [], due_date_at: reloadedProject.due_date_at, created_at: reloadedProject.created_at, }; return { content: [ { type: 'text', text: JSON.stringify( { message: 'Project created successfully', project: serialized, }, null, 2 ), }, ], }; }, }); // 3. update_project - Update existing project tools.push({ name: 'update_project', description: 'Update an existing project', inputSchema: { type: 'object', properties: { uid: { type: 'string', description: 'Project UID (required)', }, name: { type: 'string', description: 'New project name' }, description: { type: 'string', description: 'New description', }, priority: { type: 'number', description: 'New priority', }, status: { type: 'string', enum: [ 'not_started', 'planned', 'in_progress', 'waiting', 'done', 'cancelled', ], }, area_id: { type: 'number', description: 'New area ID', }, pinned: { type: 'boolean', description: 'Pin to sidebar', }, }, required: ['uid'], }, handler: async (params) => { const project = await Project.findOne({ where: { uid: params.uid, user_id: context.userId }, }); if (!project) { throw new Error(`Project not found: ${params.uid}`); } const updates = {}; if (params.name !== undefined) updates.name = params.name; if (params.description !== undefined) updates.description = params.description; if (params.priority !== undefined) updates.priority = params.priority; if (params.status !== undefined) updates.status = params.status; if (params.area_id !== undefined) updates.area_id = params.area_id; if (params.pinned !== undefined) updates.pin_to_sidebar = params.pinned; await project.update(updates); // Reload with associations const reloadedProject = await Project.findByPk(project.id, { include: [ { model: Area, as: 'Area' }, { model: Tag, as: 'Tags' }, ], }); const serialized = { id: reloadedProject.id, uid: reloadedProject.uid, name: reloadedProject.name, description: reloadedProject.description, status: reloadedProject.status, priority: reloadedProject.priority, area: reloadedProject.Area ? reloadedProject.Area.name : null, tags: reloadedProject.Tags ? reloadedProject.Tags.map((t) => t.name) : [], due_date_at: reloadedProject.due_date_at, pin_to_sidebar: reloadedProject.pin_to_sidebar, updated_at: reloadedProject.updated_at, }; return { content: [ { type: 'text', text: JSON.stringify( { message: 'Project updated successfully', project: serialized, }, null, 2 ), }, ], }; }, }); } module.exports = { registerProjectTools };