innov-mcp-tasks 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/index.mjs +93 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -52,11 +52,12 @@ Na raiz do repo existe [`.cursor/mcp.json`](../.cursor/mcp.json) com **dois** se
52
52
 
53
53
  ### Tarefas e projetos
54
54
 
55
- - `tasks_list` — opcional `project_id`
55
+ - `tasks_list` — opcional `project_id`, `milestones_only` (filtra marcos no cliente)
56
56
  - `my_tasks` — tarefas do utilizador do token (endpoint “minhas” da API)
57
57
  - `projects_list` — lista projetos visíveis (`GET /api/v1/projects`)
58
58
  - `project_create` — cria projeto (`POST /api/v1/projects`)
59
- - `task_get`, `task_create`, `task_update_status`, `task_assign`, `task_assign_to_me`
59
+ - `task_get`, `task_create`, `task_update`, `task_update_status`, `task_assign`, `task_assign_to_me`
60
+ - `task_create` / `task_update` — marcos: `is_milestone`, `start_date` (YYYY-MM-DD), `duration` (dias); marco exige `project_id` na criação
60
61
  - `task_attachment_download` — baixa anexo por `attachment_id` (conteúdo em base64; ids em `task_get` → `attachments`)
61
62
 
62
63
  ### Anotações / documentação
package/index.mjs CHANGED
@@ -87,13 +87,49 @@ async function apiFetchBinary(path) {
87
87
  };
88
88
  }
89
89
 
90
+ /** Campos de marco / cronograma (is_milestone exige project_id na API). */
91
+ const taskMilestoneInputSchema = {
92
+ is_milestone: z.boolean().optional(),
93
+ start_date: z
94
+ .string()
95
+ .optional()
96
+ .describe('Data de início (YYYY-MM-DD); usada em marcos e Gantt'),
97
+ duration: z
98
+ .number()
99
+ .int()
100
+ .min(1)
101
+ .optional()
102
+ .describe('Duração em dias (marco/cronograma)'),
103
+ };
104
+
105
+ function pickDefined(fields) {
106
+ return Object.fromEntries(
107
+ Object.entries(fields).filter(([, v]) => v !== undefined),
108
+ );
109
+ }
110
+
111
+ function filterTasksMilestonesOnly(data) {
112
+ if (!Array.isArray(data)) {
113
+ return data;
114
+ }
115
+ return data.filter((t) => t && t.is_milestone);
116
+ }
117
+
90
118
  const server = new McpServer({ name: 'innov-tasks', version: '1.0.0' });
91
119
 
92
120
  server.registerTool(
93
121
  'tasks_list',
94
122
  {
95
- description: 'Lista tarefas. Com project_id usa GET /tasks?project_id=…; sem project_id usa GET /tasks/kanban.',
96
- inputSchema: { project_id: z.number().int().positive().optional() },
123
+ description:
124
+ 'Lista tarefas. Com project_id usa GET /tasks?project_id=…; sem project_id usa GET /tasks/kanban. ' +
125
+ 'Cada item inclui is_milestone, start_date e duration. Com milestones_only=true filtra só marcos (no cliente).',
126
+ inputSchema: {
127
+ project_id: z.number().int().positive().optional(),
128
+ milestones_only: z
129
+ .boolean()
130
+ .optional()
131
+ .describe('Se true, devolve apenas tarefas com is_milestone=true'),
132
+ },
97
133
  },
98
134
  async (args) => {
99
135
  try {
@@ -101,7 +137,10 @@ server.registerTool(
101
137
  args.project_id != null
102
138
  ? `/api/v1/tasks?project_id=${encodeURIComponent(String(args.project_id))}`
103
139
  : '/api/v1/tasks/kanban';
104
- const data = await apiFetch(path);
140
+ let data = await apiFetch(path);
141
+ if (args.milestones_only) {
142
+ data = filterTasksMilestonesOnly(data);
143
+ }
105
144
  return jsonText(data);
106
145
  } catch (e) {
107
146
  return jsonError(e instanceof Error ? e.message : String(e));
@@ -129,7 +168,8 @@ server.registerTool(
129
168
  server.registerTool(
130
169
  'task_get',
131
170
  {
132
- description: 'Obtém uma tarefa por id (GET /tasks/{id}).',
171
+ description:
172
+ 'Obtém uma tarefa por id (GET /tasks/{id}). Inclui is_milestone, start_date e duration.',
133
173
  inputSchema: { task_id: z.number().int().positive() },
134
174
  },
135
175
  async (args) => {
@@ -233,7 +273,8 @@ server.registerTool(
233
273
  server.registerTool(
234
274
  'task_create',
235
275
  {
236
- description: 'Cria tarefa (POST /tasks).',
276
+ description:
277
+ 'Cria tarefa (POST /tasks). Marcos: is_milestone=true exige project_id; opcional start_date e duration (dias).',
237
278
  inputSchema: {
238
279
  title: z.string().min(1).max(255),
239
280
  project_id: z.number().int().positive().optional(),
@@ -241,18 +282,24 @@ server.registerTool(
241
282
  description: z.string().optional(),
242
283
  assigned_to: z.number().int().positive().optional(),
243
284
  priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
285
+ due_date: z.string().optional().describe('Prazo (YYYY-MM-DD)'),
286
+ ...taskMilestoneInputSchema,
244
287
  },
245
288
  },
246
289
  async (args) => {
247
290
  try {
248
- const body = {
291
+ const body = pickDefined({
249
292
  title: args.title,
250
- project_id: args.project_id ?? null,
251
- team_id: args.team_id ?? null,
252
- description: args.description ?? null,
253
- assigned_to: args.assigned_to ?? null,
293
+ project_id: args.project_id,
294
+ team_id: args.team_id,
295
+ description: args.description,
296
+ assigned_to: args.assigned_to,
254
297
  priority: args.priority ?? 'medium',
255
- };
298
+ due_date: args.due_date,
299
+ is_milestone: args.is_milestone,
300
+ start_date: args.start_date,
301
+ duration: args.duration,
302
+ });
256
303
  const data = await apiFetch('/api/v1/tasks', {
257
304
  method: 'POST',
258
305
  body: JSON.stringify(body),
@@ -264,6 +311,41 @@ server.registerTool(
264
311
  },
265
312
  );
266
313
 
314
+ server.registerTool(
315
+ 'task_update',
316
+ {
317
+ description:
318
+ 'Atualiza tarefa (PATCH /tasks/{id}). Campos opcionais; marcos: is_milestone, start_date, duration (dias). ' +
319
+ 'Marco exige projeto e não pode ser subtarefa (regras da API).',
320
+ inputSchema: {
321
+ task_id: z.number().int().positive(),
322
+ title: z.string().min(1).max(255).optional(),
323
+ description: z.string().optional(),
324
+ status: z
325
+ .enum(['pending', 'in_progress', 'in_review', 'completed', 'cancelled'])
326
+ .optional(),
327
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
328
+ due_date: z.string().optional(),
329
+ assigned_to: z.number().int().positive().optional(),
330
+ progress: z.number().int().min(0).max(100).optional(),
331
+ ...taskMilestoneInputSchema,
332
+ },
333
+ },
334
+ async (args) => {
335
+ try {
336
+ const { task_id, ...fields } = args;
337
+ const body = pickDefined(fields);
338
+ const data = await apiFetch(`/api/v1/tasks/${task_id}`, {
339
+ method: 'PATCH',
340
+ body: JSON.stringify(body),
341
+ });
342
+ return jsonText(data);
343
+ } catch (e) {
344
+ return jsonError(e instanceof Error ? e.message : String(e));
345
+ }
346
+ },
347
+ );
348
+
267
349
  server.registerTool(
268
350
  'task_update_status',
269
351
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "innov-mcp-tasks",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "MCP stdio — tarefas e anotações Innov (INNOV_API_BASE_URL + token Sanctum)",
5
5
  "type": "module",
6
6
  "main": "index.mjs",