tududi/backend/modules/mcp/tools/projectTools.js

321 lines
11 KiB
JavaScript

'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 };