innov-mcp-tasks 1.0.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/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ # Copia para .env local (não commitar). Nenhum endereço vem no pacote npm — define tu.
2
+ INNOV_API_BASE_URL=
3
+ INNOV_API_TOKEN=
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # innov-mcp-tasks
2
+
3
+ Servidor [MCP](https://modelcontextprotocol.io) em **stdio** para ferramentas de tarefas na API Innov (`/api/v1/...`, Bearer Sanctum).
4
+
5
+ **Nenhuma URL de API vai no código publicado** — só o que definires em variáveis de ambiente ou num `.env` teu. Após `npm install` do pacote, o `postinstall` imprime um lembrete na consola.
6
+
7
+ ## Variáveis obrigatórias
8
+
9
+ | Variável | Descrição |
10
+ |----------|-----------|
11
+ | `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
+
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).
15
+
16
+ ## Cursor com pacote npm (`npx`)
17
+
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).
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "innov-tasks": {
24
+ "type": "stdio",
25
+ "command": "npx",
26
+ "args": ["-y", "innov-mcp-tasks"],
27
+ "env": {
28
+ "INNOV_API_BASE_URL": "https://tua-api.exemplo.com",
29
+ "INNOV_API_TOKEN": "cole_só_em_local_seguro_ou_usa_interpolação"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
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`).
37
+
38
+ Também podes apontar o binário instalado globalmente: `"command": "innov-mcp-tasks"` (após `npm install -g innov-mcp-tasks`).
39
+
40
+ ## Monorepo (desenvolvimento)
41
+
42
+ Na raiz do repo existe [`.cursor/mcp.json`](../.cursor/mcp.json) com **dois** servidores que partilham o mesmo `mcp-tasks/.env`:
43
+
44
+ | Servidor | Uso |
45
+ |----------|-----|
46
+ | `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). |
48
+
49
+ **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
+
51
+ ## Ferramentas
52
+
53
+ - `tasks_list` — opcional `project_id`
54
+ - `my_tasks` — tarefas do utilizador do token (endpoint “minhas” da API)
55
+ - `task_get`, `task_create`, `task_update_status`, `task_assign`
56
+
57
+ ## Publicar no npm (mantenedor)
58
+
59
+ 1. Define o **nome** em `package.json` (`innov-mcp-tasks` ou `@scope/innov-mcp-tasks` se o nome simples estiver tomado).
60
+ 2. Opcional: adiciona `"repository"` com URL real do Git antes do primeiro `publish`.
61
+ 3. Na pasta `mcp-tasks`:
62
+
63
+ ```bash
64
+ npm whoami
65
+ npm test
66
+ npm publish --access public
67
+ ```
68
+
69
+ Para **scoped** (`@org/pkg`): a primeira vez costuma precisar de `--access public` se for pacote OSS.
70
+
71
+ ## Testes
72
+
73
+ ```bash
74
+ npm test
75
+ ```
package/index.mjs ADDED
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP stdio — proxy para tarefas da API Innov (Sanctum Bearer).
4
+ */
5
+ import 'dotenv/config';
6
+ import { z } from 'zod';
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+
10
+ const base = (process.env.INNOV_API_BASE_URL || '').replace(/\/$/, '');
11
+ const token = (process.env.INNOV_API_TOKEN || '').trim();
12
+
13
+ function jsonText(data) {
14
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
15
+ }
16
+
17
+ function jsonError(message) {
18
+ return { isError: true, content: [{ type: 'text', text: message }] };
19
+ }
20
+
21
+ async function apiFetch(path, init = {}) {
22
+ if (!base || !token) {
23
+ throw new Error(
24
+ 'Defina INNOV_API_BASE_URL (URL base da API, sem /api/v1) e INNOV_API_TOKEN (Bearer Sanctum) no ambiente ou num .env. O pacote npm não inclui endereço da API.',
25
+ );
26
+ }
27
+ const url = `${base}${path.startsWith('/') ? path : `/${path}`}`;
28
+ const headers = {
29
+ Accept: 'application/json',
30
+ Authorization: `Bearer ${token}`,
31
+ ...init.headers,
32
+ };
33
+ if (init.body && !headers['Content-Type']) {
34
+ headers['Content-Type'] = 'application/json';
35
+ }
36
+ const res = await fetch(url, { ...init, headers });
37
+ const text = await res.text();
38
+ let data;
39
+ try {
40
+ data = text ? JSON.parse(text) : null;
41
+ } catch {
42
+ data = { _raw: text };
43
+ }
44
+ if (!res.ok) {
45
+ const msg = typeof data === 'object' && data && (data.message || data.error)
46
+ ? String(data.message || data.error)
47
+ : text.slice(0, 800);
48
+ throw new Error(`HTTP ${res.status}: ${msg}`);
49
+ }
50
+ return data;
51
+ }
52
+
53
+ const server = new McpServer({ name: 'innov-tasks', version: '1.0.0' });
54
+
55
+ server.registerTool(
56
+ 'tasks_list',
57
+ {
58
+ description: 'Lista tarefas. Com project_id usa GET /tasks?project_id=…; sem project_id usa GET /tasks/kanban.',
59
+ inputSchema: { project_id: z.number().int().positive().optional() },
60
+ },
61
+ async (args) => {
62
+ try {
63
+ const path =
64
+ args.project_id != null
65
+ ? `/api/v1/tasks?project_id=${encodeURIComponent(String(args.project_id))}`
66
+ : '/api/v1/tasks/kanban';
67
+ const data = await apiFetch(path);
68
+ return jsonText(data);
69
+ } catch (e) {
70
+ return jsonError(e instanceof Error ? e.message : String(e));
71
+ }
72
+ },
73
+ );
74
+
75
+ server.registerTool(
76
+ 'my_tasks',
77
+ {
78
+ description:
79
+ 'Minhas tarefas atribuídas a mim, do utilizador do token.',
80
+ inputSchema: {},
81
+ },
82
+ async () => {
83
+ try {
84
+ const data = await apiFetch('/api/v1/my-tasks');
85
+ return jsonText(data);
86
+ } catch (e) {
87
+ return jsonError(e instanceof Error ? e.message : String(e));
88
+ }
89
+ },
90
+ );
91
+
92
+ server.registerTool(
93
+ 'task_get',
94
+ {
95
+ description: 'Obtém uma tarefa por id (GET /tasks/{id}).',
96
+ inputSchema: { task_id: z.number().int().positive() },
97
+ },
98
+ async (args) => {
99
+ try {
100
+ const data = await apiFetch(`/api/v1/tasks/${args.task_id}`);
101
+ return jsonText(data);
102
+ } catch (e) {
103
+ return jsonError(e instanceof Error ? e.message : String(e));
104
+ }
105
+ },
106
+ );
107
+
108
+ server.registerTool(
109
+ 'task_create',
110
+ {
111
+ description: 'Cria tarefa (POST /tasks).',
112
+ inputSchema: {
113
+ title: z.string().min(1).max(255),
114
+ project_id: z.number().int().positive().optional(),
115
+ team_id: z.number().int().positive().optional(),
116
+ description: z.string().optional(),
117
+ assigned_to: z.number().int().positive().optional(),
118
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
119
+ },
120
+ },
121
+ async (args) => {
122
+ try {
123
+ const body = {
124
+ title: args.title,
125
+ project_id: args.project_id ?? null,
126
+ team_id: args.team_id ?? null,
127
+ description: args.description ?? null,
128
+ assigned_to: args.assigned_to ?? null,
129
+ priority: args.priority ?? 'medium',
130
+ };
131
+ const data = await apiFetch('/api/v1/tasks', {
132
+ method: 'POST',
133
+ body: JSON.stringify(body),
134
+ });
135
+ return jsonText(data);
136
+ } catch (e) {
137
+ return jsonError(e instanceof Error ? e.message : String(e));
138
+ }
139
+ },
140
+ );
141
+
142
+ server.registerTool(
143
+ 'task_update_status',
144
+ {
145
+ description: 'Atualiza apenas o status (PATCH /tasks/{id}/status).',
146
+ inputSchema: {
147
+ task_id: z.number().int().positive(),
148
+ status: z.enum(['pending', 'in_progress', 'in_review', 'completed', 'cancelled']),
149
+ },
150
+ },
151
+ async (args) => {
152
+ try {
153
+ const data = await apiFetch(`/api/v1/tasks/${args.task_id}/status`, {
154
+ method: 'PATCH',
155
+ body: JSON.stringify({ status: args.status }),
156
+ });
157
+ return jsonText(data);
158
+ } catch (e) {
159
+ return jsonError(e instanceof Error ? e.message : String(e));
160
+ }
161
+ },
162
+ );
163
+
164
+ server.registerTool(
165
+ 'task_assign',
166
+ {
167
+ description: 'Atribui responsável (POST /tasks/{id}/assign).',
168
+ inputSchema: {
169
+ task_id: z.number().int().positive(),
170
+ user_id: z.number().int().positive(),
171
+ },
172
+ },
173
+ async (args) => {
174
+ try {
175
+ const data = await apiFetch(`/api/v1/tasks/${args.task_id}/assign`, {
176
+ method: 'POST',
177
+ body: JSON.stringify({ user_id: args.user_id }),
178
+ });
179
+ return jsonText(data);
180
+ } catch (e) {
181
+ return jsonError(e instanceof Error ? e.message : String(e));
182
+ }
183
+ },
184
+ );
185
+
186
+ const transport = new StdioServerTransport();
187
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "innov-mcp-tasks",
3
+ "version": "1.0.0",
4
+ "description": "MCP stdio — ferramentas de tarefas Innov (API a configurar via INNOV_API_BASE_URL + token Sanctum)",
5
+ "type": "module",
6
+ "main": "index.mjs",
7
+ "bin": {
8
+ "innov-mcp-tasks": "./index.mjs"
9
+ },
10
+ "files": [
11
+ "index.mjs",
12
+ "README.md",
13
+ ".env.example",
14
+ "scripts/postinstall-notice.mjs"
15
+ ],
16
+ "scripts": {
17
+ "start": "node index.mjs",
18
+ "test": "node --check index.mjs",
19
+ "prepack": "node --check index.mjs",
20
+ "postinstall": "node ./scripts/postinstall-notice.mjs"
21
+ },
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "modelcontextprotocol",
28
+ "tasks",
29
+ "innov"
30
+ ],
31
+ "author": "",
32
+ "license": "ISC",
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.29.0",
35
+ "dotenv": "^17.4.2",
36
+ "zod": "^3.25.76"
37
+ }
38
+ }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Mensagem após npm install — não lê segredos nem imprime URLs reais.
4
+ */
5
+ const name = process.env.npm_package_name ?? 'innov-mcp-tasks';
6
+ // Evitar ruído em CI comum
7
+ if (process.env.CI === 'true' || process.env.CONTINUOUS_INTEGRATION === 'true') {
8
+ process.exit(0);
9
+ }
10
+ console.info(`
11
+ ┌─ ${name} ───────────────────────────────────────────────
12
+ │ Configure a API no **ambiente** (nada disso fica no pacote):
13
+ │ INNOV_API_BASE_URL — URL base, **sem** o sufixo /api/v1
14
+ │ INNOV_API_TOKEN — token Bearer Sanctum (ex.: Perfil → Tokens de API)
15
+
16
+ │ Cursor (mcp.json): use "env" ou "envFile" apontando para um .env teu.
17
+ │ Documentação: https://www.npmjs.com/package/${name}
18
+ └────────────────────────────────────────────────────────────
19
+ `);