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.
Files changed (3) hide show
  1. package/README.md +38 -10
  2. package/index.mjs +248 -0
  3. 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
- | Variável | Descrição |
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` | Token pessoal Sanctum (ex.: **Perfil** → Tokens de API na app Innov) |
13
+ | `INNOV_API_TOKEN` | Token pessoal Sanctum (ex.: **Perfil** → Tokens de API na app Innov) |
14
+
13
15
 
14
- 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).
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: **`innov-mcp-tasks`** (se o nome estiver ocupado, publica como scoped, ex. `@tua-org/innov-mcp-tasks`, e ajusta os exemplos).
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 [`${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`).
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 [`.cursor/mcp.json`](../.cursor/mcp.json) com **dois** servidores que partilham o mesmo `mcp-tasks/.env`:
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
- | Servidor | Uso |
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` | Usa `npx -y innov-mcp-tasks` (testa o pacote publicado). |
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.2",
4
- "description": "MCP stdio — ferramentas de tarefas Innov (API a configurar via INNOV_API_BASE_URL + token Sanctum)",
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": "",