innov-mcp-tasks 1.0.2 → 1.1.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 +38 -10
- package/index.mjs +248 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,16 +6,18 @@ Servidor [MCP](https://modelcontextprotocol.io) em **stdio** para ferramentas de
|
|
|
6
6
|
|
|
7
7
|
## Variáveis obrigatórias
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
|
|
10
|
+
| Variável | Descrição |
|
|
11
|
+
| -------------------- | -------------------------------------------------------------------------------- |
|
|
11
12
|
| `INNOV_API_BASE_URL` | URL base **sem** `/api/v1` (definida por ti: dev local, staging, produção, etc.) |
|
|
12
|
-
| `INNOV_API_TOKEN`
|
|
13
|
+
| `INNOV_API_TOKEN` | Token pessoal Sanctum (ex.: **Perfil** → Tokens de API na app Innov) |
|
|
14
|
+
|
|
13
15
|
|
|
14
|
-
Copia [
|
|
16
|
+
Copia `[.env.example](./.env.example)` para um ficheiro `.env` à tua escolha e preenche (esse ficheiro **não** vem do npm com valores; no repo monorepo, mantém `.env` fora do Git).
|
|
15
17
|
|
|
16
18
|
## Cursor com pacote npm (`npx`)
|
|
17
19
|
|
|
18
|
-
Nome do pacote no npm:
|
|
20
|
+
Nome do pacote no npm: `**innov-mcp-tasks`** (se o nome estiver ocupado, publica como scoped, ex. `@tua-org/innov-mcp-tasks`, e ajusta os exemplos).
|
|
19
21
|
|
|
20
22
|
```json
|
|
21
23
|
{
|
|
@@ -33,28 +35,43 @@ Nome do pacote no npm: **`innov-mcp-tasks`** (se o nome estiver ocupado, publica
|
|
|
33
35
|
}
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
Segredos: prefere [
|
|
38
|
+
Segredos: prefere `[${env:INNOV_API_TOKEN}](https://cursor.com/docs/mcp)` apontando para variáveis já definidas no SO, ou `envFile` para um `.env` **fora** do repositório (ex. `C:\\Users\\…\\.config\\innov\\mcp.env`).
|
|
37
39
|
|
|
38
40
|
Também podes apontar o binário instalado globalmente: `"command": "innov-mcp-tasks"` (após `npm install -g innov-mcp-tasks`).
|
|
39
41
|
|
|
40
42
|
## Monorepo (desenvolvimento)
|
|
41
43
|
|
|
42
|
-
Na raiz do repo existe [
|
|
44
|
+
Na raiz do repo existe `[.cursor/mcp.json](../.cursor/mcp.json)` com **dois** servidores que partilham o mesmo `mcp-tasks/.env`:
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
|
|
47
|
+
| Servidor | Uso |
|
|
48
|
+
| ------------------- | -------------------------------------------------------- |
|
|
46
49
|
| `innov-tasks-local` | Corre o `index.mjs` do clon (óptimo para alterar o MCP). |
|
|
47
|
-
| `innov-tasks-npm`
|
|
50
|
+
| `innov-tasks-npm` | Usa `npx -y innov-mcp-tasks` (testa o pacote publicado). |
|
|
51
|
+
|
|
48
52
|
|
|
49
53
|
**Ativa só um** em *Settings → Features → Model Context Protocol* (dois ao mesmo tempo duplicam ferramentas com o mesmo nome). Se publicares com scope (`@org/innov-mcp-tasks`), edita os `args` em `innov-tasks-npm` para esse nome.
|
|
50
54
|
|
|
51
55
|
## Ferramentas
|
|
52
56
|
|
|
57
|
+
### Tarefas e projetos
|
|
58
|
+
|
|
53
59
|
- `tasks_list` — opcional `project_id`
|
|
54
60
|
- `my_tasks` — tarefas do utilizador do token (endpoint “minhas” da API)
|
|
61
|
+
- `projects_list` — lista projetos visíveis (`GET /api/v1/projects`)
|
|
55
62
|
- `project_create` — cria projeto (`POST /api/v1/projects`)
|
|
56
63
|
- `task_get`, `task_create`, `task_update_status`, `task_assign`, `task_assign_to_me`
|
|
57
64
|
|
|
65
|
+
### Anotações / documentação
|
|
66
|
+
|
|
67
|
+
- `notes_list` — lista com filtros opcionais (`project_id`, `note_type`, `notebook_id`)
|
|
68
|
+
- `notes_personal` — anotações pessoais
|
|
69
|
+
- `notes_by_project` — anotações de um projeto (inclui criador em `user`)
|
|
70
|
+
- `note_get`, `note_create`, `note_update`
|
|
71
|
+
- `note_delete` — soft delete (lixeira)
|
|
72
|
+
- `notes_trashed`, `note_restore`
|
|
73
|
+
- `annotations_search` — busca em cadernos, notas e fontes (`q` ≥ 2 caracteres)
|
|
74
|
+
|
|
58
75
|
## Publicar no npm (mantenedor)
|
|
59
76
|
|
|
60
77
|
1. Define o **nome** em `package.json` (`innov-mcp-tasks` ou `@scope/innov-mcp-tasks` se o nome simples estiver tomado).
|
|
@@ -67,6 +84,16 @@ npm test
|
|
|
67
84
|
npm publish --access public
|
|
68
85
|
```
|
|
69
86
|
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Se estiver deslogado para logar faça o comando
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm login
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
70
97
|
Para **scoped** (`@org/pkg`): a primeira vez costuma precisar de `--access public` se for pacote OSS.
|
|
71
98
|
|
|
72
99
|
## Testes
|
|
@@ -74,3 +101,4 @@ Para **scoped** (`@org/pkg`): a primeira vez costuma precisar de `--access publi
|
|
|
74
101
|
```bash
|
|
75
102
|
npm test
|
|
76
103
|
```
|
|
104
|
+
|
package/index.mjs
CHANGED
|
@@ -105,6 +105,23 @@ server.registerTool(
|
|
|
105
105
|
},
|
|
106
106
|
);
|
|
107
107
|
|
|
108
|
+
server.registerTool(
|
|
109
|
+
'projects_list',
|
|
110
|
+
{
|
|
111
|
+
description:
|
|
112
|
+
'Lista projetos visíveis ao utilizador do token (GET /projects). Inclui creator, tasks, team e allowedUsers.',
|
|
113
|
+
inputSchema: {},
|
|
114
|
+
},
|
|
115
|
+
async () => {
|
|
116
|
+
try {
|
|
117
|
+
const data = await apiFetch('/api/v1/projects');
|
|
118
|
+
return jsonText(data);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
108
125
|
server.registerTool(
|
|
109
126
|
'project_create',
|
|
110
127
|
{
|
|
@@ -248,5 +265,236 @@ server.registerTool(
|
|
|
248
265
|
},
|
|
249
266
|
);
|
|
250
267
|
|
|
268
|
+
function notesQueryString(filters) {
|
|
269
|
+
const params = new URLSearchParams();
|
|
270
|
+
if (filters.project_id != null) {
|
|
271
|
+
params.set('project_id', String(filters.project_id));
|
|
272
|
+
}
|
|
273
|
+
if (filters.note_type != null) {
|
|
274
|
+
params.set('note_type', filters.note_type);
|
|
275
|
+
}
|
|
276
|
+
if (filters.notebook_id != null) {
|
|
277
|
+
params.set('notebook_id', String(filters.notebook_id));
|
|
278
|
+
}
|
|
279
|
+
const qs = params.toString();
|
|
280
|
+
return qs ? `?${qs}` : '';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
server.registerTool(
|
|
284
|
+
'notes_list',
|
|
285
|
+
{
|
|
286
|
+
description:
|
|
287
|
+
'Lista anotações do utilizador do token (GET /notes). Filtros opcionais: project_id, note_type (personal|project), notebook_id.',
|
|
288
|
+
inputSchema: {
|
|
289
|
+
project_id: z.number().int().positive().optional(),
|
|
290
|
+
note_type: z.enum(['personal', 'project']).optional(),
|
|
291
|
+
notebook_id: z.number().int().positive().optional(),
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
async (args) => {
|
|
295
|
+
try {
|
|
296
|
+
const data = await apiFetch(`/api/v1/notes${notesQueryString(args)}`);
|
|
297
|
+
return jsonText(data);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
server.registerTool(
|
|
305
|
+
'notes_personal',
|
|
306
|
+
{
|
|
307
|
+
description: 'Anotações pessoais do utilizador do token (GET /notes/personal).',
|
|
308
|
+
inputSchema: {},
|
|
309
|
+
},
|
|
310
|
+
async () => {
|
|
311
|
+
try {
|
|
312
|
+
const data = await apiFetch('/api/v1/notes/personal');
|
|
313
|
+
return jsonText(data);
|
|
314
|
+
} catch (e) {
|
|
315
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
server.registerTool(
|
|
321
|
+
'notes_by_project',
|
|
322
|
+
{
|
|
323
|
+
description:
|
|
324
|
+
'Anotações de um projeto visível (GET /notes/project/{project_id}). Inclui user (criador).',
|
|
325
|
+
inputSchema: { project_id: z.number().int().positive() },
|
|
326
|
+
},
|
|
327
|
+
async (args) => {
|
|
328
|
+
try {
|
|
329
|
+
const data = await apiFetch(
|
|
330
|
+
`/api/v1/notes/project/${args.project_id}`,
|
|
331
|
+
);
|
|
332
|
+
return jsonText(data);
|
|
333
|
+
} catch (e) {
|
|
334
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
server.registerTool(
|
|
340
|
+
'note_get',
|
|
341
|
+
{
|
|
342
|
+
description: 'Obtém uma anotação por id (GET /notes/{id}). Só o autor.',
|
|
343
|
+
inputSchema: { note_id: z.number().int().positive() },
|
|
344
|
+
},
|
|
345
|
+
async (args) => {
|
|
346
|
+
try {
|
|
347
|
+
const data = await apiFetch(`/api/v1/notes/${args.note_id}`);
|
|
348
|
+
return jsonText(data);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
server.registerTool(
|
|
356
|
+
'note_create',
|
|
357
|
+
{
|
|
358
|
+
description:
|
|
359
|
+
'Cria anotação (POST /notes). Pessoal sem project_id; de projeto com note_type project + project_id; opcional notebook_id.',
|
|
360
|
+
inputSchema: {
|
|
361
|
+
content: z.string().min(1),
|
|
362
|
+
title: z.string().max(255).optional(),
|
|
363
|
+
note_type: z.enum(['personal', 'project']).optional(),
|
|
364
|
+
project_id: z.number().int().positive().optional(),
|
|
365
|
+
notebook_id: z.number().int().positive().optional(),
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
async (args) => {
|
|
369
|
+
try {
|
|
370
|
+
const body = {
|
|
371
|
+
content: args.content,
|
|
372
|
+
title: args.title ?? null,
|
|
373
|
+
note_type: args.note_type ?? null,
|
|
374
|
+
project_id: args.project_id ?? null,
|
|
375
|
+
notebook_id: args.notebook_id ?? null,
|
|
376
|
+
};
|
|
377
|
+
const data = await apiFetch('/api/v1/notes', {
|
|
378
|
+
method: 'POST',
|
|
379
|
+
body: JSON.stringify(body),
|
|
380
|
+
});
|
|
381
|
+
return jsonText(data);
|
|
382
|
+
} catch (e) {
|
|
383
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
server.registerTool(
|
|
389
|
+
'note_update',
|
|
390
|
+
{
|
|
391
|
+
description: 'Atualiza anotação (PUT /notes/{id}). Só o autor.',
|
|
392
|
+
inputSchema: {
|
|
393
|
+
note_id: z.number().int().positive(),
|
|
394
|
+
title: z.string().max(255).optional(),
|
|
395
|
+
content: z.string().optional(),
|
|
396
|
+
note_type: z.enum(['personal', 'project']).optional(),
|
|
397
|
+
project_id: z.number().int().positive().optional(),
|
|
398
|
+
notebook_id: z.number().int().positive().optional(),
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
async (args) => {
|
|
402
|
+
try {
|
|
403
|
+
const { note_id, ...fields } = args;
|
|
404
|
+
const body = Object.fromEntries(
|
|
405
|
+
Object.entries(fields).filter(([, v]) => v !== undefined),
|
|
406
|
+
);
|
|
407
|
+
const data = await apiFetch(`/api/v1/notes/${note_id}`, {
|
|
408
|
+
method: 'PUT',
|
|
409
|
+
body: JSON.stringify(body),
|
|
410
|
+
});
|
|
411
|
+
return jsonText(data);
|
|
412
|
+
} catch (e) {
|
|
413
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
server.registerTool(
|
|
419
|
+
'note_delete',
|
|
420
|
+
{
|
|
421
|
+
description:
|
|
422
|
+
'Remove anotação para a lixeira — soft delete (DELETE /notes/{id}). Só o autor.',
|
|
423
|
+
inputSchema: { note_id: z.number().int().positive() },
|
|
424
|
+
},
|
|
425
|
+
async (args) => {
|
|
426
|
+
try {
|
|
427
|
+
const data = await apiFetch(`/api/v1/notes/${args.note_id}`, {
|
|
428
|
+
method: 'DELETE',
|
|
429
|
+
});
|
|
430
|
+
return jsonText(data);
|
|
431
|
+
} catch (e) {
|
|
432
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
server.registerTool(
|
|
438
|
+
'notes_trashed',
|
|
439
|
+
{
|
|
440
|
+
description:
|
|
441
|
+
'Lista anotações na lixeira do utilizador (GET /notes/trashed). Filtros opcionais como notes_list.',
|
|
442
|
+
inputSchema: {
|
|
443
|
+
project_id: z.number().int().positive().optional(),
|
|
444
|
+
note_type: z.enum(['personal', 'project']).optional(),
|
|
445
|
+
notebook_id: z.number().int().positive().optional(),
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
async (args) => {
|
|
449
|
+
try {
|
|
450
|
+
const data = await apiFetch(
|
|
451
|
+
`/api/v1/notes/trashed${notesQueryString(args)}`,
|
|
452
|
+
);
|
|
453
|
+
return jsonText(data);
|
|
454
|
+
} catch (e) {
|
|
455
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
server.registerTool(
|
|
461
|
+
'note_restore',
|
|
462
|
+
{
|
|
463
|
+
description:
|
|
464
|
+
'Restaura anotação da lixeira (POST /notes/{id}/restore). Só o autor.',
|
|
465
|
+
inputSchema: { note_id: z.number().int().positive() },
|
|
466
|
+
},
|
|
467
|
+
async (args) => {
|
|
468
|
+
try {
|
|
469
|
+
const data = await apiFetch(
|
|
470
|
+
`/api/v1/notes/${args.note_id}/restore`,
|
|
471
|
+
{ method: 'POST' },
|
|
472
|
+
);
|
|
473
|
+
return jsonText(data);
|
|
474
|
+
} catch (e) {
|
|
475
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
server.registerTool(
|
|
481
|
+
'annotations_search',
|
|
482
|
+
{
|
|
483
|
+
description:
|
|
484
|
+
'Busca em cadernos, anotações e fontes (GET /annotations/search?q=). Mínimo 2 caracteres.',
|
|
485
|
+
inputSchema: { q: z.string().min(2) },
|
|
486
|
+
},
|
|
487
|
+
async (args) => {
|
|
488
|
+
try {
|
|
489
|
+
const data = await apiFetch(
|
|
490
|
+
`/api/v1/annotations/search?q=${encodeURIComponent(args.q)}`,
|
|
491
|
+
);
|
|
492
|
+
return jsonText(data);
|
|
493
|
+
} catch (e) {
|
|
494
|
+
return jsonError(e instanceof Error ? e.message : String(e));
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
);
|
|
498
|
+
|
|
251
499
|
const transport = new StdioServerTransport();
|
|
252
500
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "innov-mcp-tasks",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "MCP stdio —
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "MCP stdio — tarefas e anotações Innov (INNOV_API_BASE_URL + token Sanctum)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
7
7
|
"bin": {
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"mcp",
|
|
27
27
|
"modelcontextprotocol",
|
|
28
28
|
"tasks",
|
|
29
|
+
"notes",
|
|
30
|
+
"annotations",
|
|
29
31
|
"innov"
|
|
30
32
|
],
|
|
31
33
|
"author": "",
|