innov-mcp-tasks 1.2.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.
- package/README.md +4 -2
- package/index.mjs +158 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,11 +52,13 @@ 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
|
|
61
|
+
- `task_attachment_download` — baixa anexo por `attachment_id` (conteúdo em base64; ids em `task_get` → `attachments`)
|
|
60
62
|
|
|
61
63
|
### Anotações / documentação
|
|
62
64
|
|
package/index.mjs
CHANGED
|
@@ -50,13 +50,86 @@ async function apiFetch(path, init = {}) {
|
|
|
50
50
|
return data;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
async function apiFetchBinary(path) {
|
|
54
|
+
if (!base || !token) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'Defina INNOV_API_BASE_URL e INNOV_API_TOKEN no ambiente ou num .env.',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const url = `${base}${path.startsWith('/') ? path : `/${path}`}`;
|
|
60
|
+
const res = await fetch(url, {
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${token}`,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const text = await res.text();
|
|
67
|
+
let msg = text.slice(0, 800);
|
|
68
|
+
try {
|
|
69
|
+
const data = text ? JSON.parse(text) : null;
|
|
70
|
+
if (data && (data.message || data.error)) {
|
|
71
|
+
msg = String(data.message || data.error);
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
/* texto bruto */
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`HTTP ${res.status}: ${msg}`);
|
|
77
|
+
}
|
|
78
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
79
|
+
const disposition = res.headers.get('content-disposition') || '';
|
|
80
|
+
const filenameMatch = disposition.match(/filename="?([^";\n]+)"?/i);
|
|
81
|
+
const filename = filenameMatch?.[1]?.trim() || 'attachment';
|
|
82
|
+
return {
|
|
83
|
+
buffer,
|
|
84
|
+
contentType: res.headers.get('content-type') || 'application/octet-stream',
|
|
85
|
+
filename,
|
|
86
|
+
size: buffer.length,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
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
|
+
|
|
53
118
|
const server = new McpServer({ name: 'innov-tasks', version: '1.0.0' });
|
|
54
119
|
|
|
55
120
|
server.registerTool(
|
|
56
121
|
'tasks_list',
|
|
57
122
|
{
|
|
58
|
-
description:
|
|
59
|
-
|
|
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
|
+
},
|
|
60
133
|
},
|
|
61
134
|
async (args) => {
|
|
62
135
|
try {
|
|
@@ -64,7 +137,10 @@ server.registerTool(
|
|
|
64
137
|
args.project_id != null
|
|
65
138
|
? `/api/v1/tasks?project_id=${encodeURIComponent(String(args.project_id))}`
|
|
66
139
|
: '/api/v1/tasks/kanban';
|
|
67
|
-
|
|
140
|
+
let data = await apiFetch(path);
|
|
141
|
+
if (args.milestones_only) {
|
|
142
|
+
data = filterTasksMilestonesOnly(data);
|
|
143
|
+
}
|
|
68
144
|
return jsonText(data);
|
|
69
145
|
} catch (e) {
|
|
70
146
|
return jsonError(e instanceof Error ? e.message : String(e));
|
|
@@ -92,7 +168,8 @@ server.registerTool(
|
|
|
92
168
|
server.registerTool(
|
|
93
169
|
'task_get',
|
|
94
170
|
{
|
|
95
|
-
description:
|
|
171
|
+
description:
|
|
172
|
+
'Obtém uma tarefa por id (GET /tasks/{id}). Inclui is_milestone, start_date e duration.',
|
|
96
173
|
inputSchema: { task_id: z.number().int().positive() },
|
|
97
174
|
},
|
|
98
175
|
async (args) => {
|
|
@@ -105,6 +182,34 @@ server.registerTool(
|
|
|
105
182
|
},
|
|
106
183
|
);
|
|
107
184
|
|
|
185
|
+
server.registerTool(
|
|
186
|
+
'task_attachment_download',
|
|
187
|
+
{
|
|
188
|
+
description:
|
|
189
|
+
'Baixa anexo de tarefa (GET /task-attachments/{id}/download). Requer Bearer Sanctum. ' +
|
|
190
|
+
'Retorna metadados e conteúdo em base64 (útil para zip, pdf, docx, txt, srt, etc.). ' +
|
|
191
|
+
'Use task_get para listar attachments[].id da tarefa.',
|
|
192
|
+
inputSchema: { attachment_id: z.number().int().positive() },
|
|
193
|
+
},
|
|
194
|
+
async (args) => {
|
|
195
|
+
try {
|
|
196
|
+
const file = await apiFetchBinary(
|
|
197
|
+
`/api/v1/task-attachments/${args.attachment_id}/download`,
|
|
198
|
+
);
|
|
199
|
+
return jsonText({
|
|
200
|
+
attachment_id: args.attachment_id,
|
|
201
|
+
filename: file.filename,
|
|
202
|
+
content_type: file.contentType,
|
|
203
|
+
size_bytes: file.size,
|
|
204
|
+
content_base64: file.buffer.toString('base64'),
|
|
205
|
+
hint: 'Decodifique content_base64 para gravar o ficheiro localmente.',
|
|
206
|
+
});
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
|
|
108
213
|
server.registerTool(
|
|
109
214
|
'projects_list',
|
|
110
215
|
{
|
|
@@ -168,7 +273,8 @@ server.registerTool(
|
|
|
168
273
|
server.registerTool(
|
|
169
274
|
'task_create',
|
|
170
275
|
{
|
|
171
|
-
description:
|
|
276
|
+
description:
|
|
277
|
+
'Cria tarefa (POST /tasks). Marcos: is_milestone=true exige project_id; opcional start_date e duration (dias).',
|
|
172
278
|
inputSchema: {
|
|
173
279
|
title: z.string().min(1).max(255),
|
|
174
280
|
project_id: z.number().int().positive().optional(),
|
|
@@ -176,18 +282,24 @@ server.registerTool(
|
|
|
176
282
|
description: z.string().optional(),
|
|
177
283
|
assigned_to: z.number().int().positive().optional(),
|
|
178
284
|
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
285
|
+
due_date: z.string().optional().describe('Prazo (YYYY-MM-DD)'),
|
|
286
|
+
...taskMilestoneInputSchema,
|
|
179
287
|
},
|
|
180
288
|
},
|
|
181
289
|
async (args) => {
|
|
182
290
|
try {
|
|
183
|
-
const body = {
|
|
291
|
+
const body = pickDefined({
|
|
184
292
|
title: args.title,
|
|
185
|
-
project_id: args.project_id
|
|
186
|
-
team_id: args.team_id
|
|
187
|
-
description: args.description
|
|
188
|
-
assigned_to: args.assigned_to
|
|
293
|
+
project_id: args.project_id,
|
|
294
|
+
team_id: args.team_id,
|
|
295
|
+
description: args.description,
|
|
296
|
+
assigned_to: args.assigned_to,
|
|
189
297
|
priority: args.priority ?? 'medium',
|
|
190
|
-
|
|
298
|
+
due_date: args.due_date,
|
|
299
|
+
is_milestone: args.is_milestone,
|
|
300
|
+
start_date: args.start_date,
|
|
301
|
+
duration: args.duration,
|
|
302
|
+
});
|
|
191
303
|
const data = await apiFetch('/api/v1/tasks', {
|
|
192
304
|
method: 'POST',
|
|
193
305
|
body: JSON.stringify(body),
|
|
@@ -199,6 +311,41 @@ server.registerTool(
|
|
|
199
311
|
},
|
|
200
312
|
);
|
|
201
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
|
+
|
|
202
349
|
server.registerTool(
|
|
203
350
|
'task_update_status',
|
|
204
351
|
{
|