321 lines
11 KiB
JavaScript
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 };
|