nexusflex-mcp 3.0.0 → 3.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/INSTALAR.md +97 -97
- package/LICENSE +21 -21
- package/README.md +52 -52
- package/api.mjs +135 -135
- package/device-auth.mjs +132 -132
- package/package.json +29 -29
- package/server.mjs +331 -315
package/INSTALAR.md
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
# MCP de Nexus Flex — Instalación (npx + autorización web)
|
|
2
|
-
|
|
3
|
-
Este paquete deja que un asistente de IA (**Claude Desktop** o **Claude Code**) opere
|
|
4
|
-
Nexus Flex **con tu cuenta**, en tu computadora, hablando con la API igual que la web.
|
|
5
|
-
|
|
6
|
-
> **Seguridad (aislamiento en 3 capas):** el MCP no puede hacer nada que tu usuario no
|
|
7
|
-
> pueda hacer desde la web. **1)** El backend gatea cada dato por rol y por nodo/cliente
|
|
8
|
-
> (el token hereda tu rol/permisos frescos en cada pedido). **2)** El MCP muestra SOLO
|
|
9
|
-
> los tools de tu rol. **3)** Nunca toca facturación/cobros/cuentas (bloqueado en el
|
|
10
|
-
> cliente **y** en el backend). Cada usuario ve **solo lo suyo**.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 1. Requisitos
|
|
15
|
-
- **Node.js 20+** — https://nodejs.org (LTS). Verificá con `node --version`.
|
|
16
|
-
- **Claude Desktop** (https://claude.ai/download) o Claude Code.
|
|
17
|
-
- Una cuenta de Nexus Flex (la misma de la web). **No** hace falta tu contraseña acá:
|
|
18
|
-
autorizás desde el navegador.
|
|
19
|
-
|
|
20
|
-
## 2. Autorizar (una sola vez)
|
|
21
|
-
En una terminal, corré:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npx -y nexusflex-mcp@latest login
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Se abre el navegador (o te muestra un link + un código de 8 letras). Entrás a Nexus Flex
|
|
28
|
-
—si no estás logueado, iniciás sesión— y hacés click en **Autorizar**. Listo: el token
|
|
29
|
-
queda guardado en tu equipo (`~/.nexusflex-mcp/token.json`).
|
|
30
|
-
|
|
31
|
-
## 3. Conectarlo a Claude Desktop
|
|
32
|
-
Editá el config de Claude Desktop:
|
|
33
|
-
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
34
|
-
- **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
35
|
-
|
|
36
|
-
Pegá esto (¡sin email ni contraseña!):
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
{
|
|
40
|
-
"mcpServers": {
|
|
41
|
-
"nexusflex": {
|
|
42
|
-
"command": "npx",
|
|
43
|
-
"args": ["-y", "nexusflex-mcp@latest"],
|
|
44
|
-
"env": {
|
|
45
|
-
"NEXUSFLEX_MCP_ALLOW_WRITE": "true",
|
|
46
|
-
"NEXUSFLEX_MCP_ALLOW_PRECIOS": "false"
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Guardá y **reiniciá Claude Desktop**. (Si nunca corriste el paso 2, la primera vez que
|
|
54
|
-
arranque el MCP te va a mostrar el link + código en sus logs para que autorices.)
|
|
55
|
-
|
|
56
|
-
- `NEXUSFLEX_MCP_ALLOW_WRITE` = `"true"` habilita **altas y ediciones** (clientes,
|
|
57
|
-
productos, y nodos si sos admin global). `"false"` = solo consulta.
|
|
58
|
-
- `NEXUSFLEX_MCP_ALLOW_PRECIOS` = `"true"` habilita **actualizar listas de precios**.
|
|
59
|
-
|
|
60
|
-
## 4. Qué ve cada usuario (según con qué cuenta autorices)
|
|
61
|
-
|
|
62
|
-
**CLIENTE (vendedor):** `mis_datos`, `mis_envios`, `mis_kpis`, `mi_stock`,
|
|
63
|
-
`mi_disponible` (disponible-para-vender por SKU), `mi_rentabilidad` (margen por SKU),
|
|
64
|
-
`mis_productos`, `mis_top_productos`. **No** ve datos del nodo ni de otros clientes.
|
|
65
|
-
|
|
66
|
-
**STAFF del nodo:** `clientes_del_nodo`, `precios_ver`, `kpi_nodo`, `stock_nodo`,
|
|
67
|
-
`productos_nodo`, `top_productos`, y —con escritura— `cliente_crear`, `cliente_editar`,
|
|
68
|
-
`producto_crear`, y —con precios— `precio_actualizar`. Todo scopeado a **su** nodo.
|
|
69
|
-
|
|
70
|
-
**Admin GLOBAL:** además `nodos_listar`, `kpi_red`, `nodo_crear`.
|
|
71
|
-
|
|
72
|
-
## 5. Ejemplos (lenguaje natural en Claude)
|
|
73
|
-
- *"¿Qué usuario y alcance tengo?"* → `mis_datos`.
|
|
74
|
-
- *"¿Cuánto tengo disponible para vender del SKU ABC?"* → `mi_disponible`.
|
|
75
|
-
- *"¿Qué productos me dejan más margen este mes?"* → `mi_rentabilidad`.
|
|
76
|
-
- *"Dame de alta a Distribuidora López con la lista B."* → `cliente_crear`.
|
|
77
|
-
- *"Actualizá la lista B: cercana 3800, media 4200."* → `precio_actualizar` (necesita ALLOW_PRECIOS).
|
|
78
|
-
|
|
79
|
-
## 6. Revocar el acceso
|
|
80
|
-
En la web de Nexus Flex, sección **🔌 Conexiones**, revocás cualquier asistente cuando
|
|
81
|
-
quieras. Local: `npx -y nexusflex-mcp@latest logout` borra el token de tu equipo.
|
|
82
|
-
|
|
83
|
-
## 7. Qué NO puede hacer — nunca
|
|
84
|
-
Facturar, registrar cobros/pagos, tocar cuentas corrientes, liquidaciones ni saldos.
|
|
85
|
-
Está bloqueado en el cliente **y** en el backend; aunque se lo pidas, lo rechaza.
|
|
86
|
-
|
|
87
|
-
## Alternativas de credenciales (avanzado / compatibilidad)
|
|
88
|
-
- `NEXUSFLEX_TOKEN` = un token ya emitido (MCP o JWT) → salta el device-flow.
|
|
89
|
-
- `NEXUSFLEX_EMAIL` + `NEXUSFLEX_PASSWORD` = login legado por contraseña.
|
|
90
|
-
- `NEXUSFLEX_API_URL` = base de la API (default `https://nexusflex.com.ar/api`).
|
|
91
|
-
- `NEXUSFLEX_MCP_NO_AUTH_PROMPT=true` = no abrir device-flow automáticamente (falla
|
|
92
|
-
pidiendo `login` explícito).
|
|
93
|
-
|
|
94
|
-
## Problemas comunes
|
|
95
|
-
- *"Sesión inválida"* (401) → corré `npx nexusflex-mcp login` de nuevo (te lo pudieron revocar).
|
|
96
|
-
- *"No tenés permiso"* (403) → tu usuario no tiene ese permiso (es correcto: el backend aísla).
|
|
97
|
-
- No aparecen las herramientas → cerrá Claude Desktop del todo y reabrí.
|
|
1
|
+
# MCP de Nexus Flex — Instalación (npx + autorización web)
|
|
2
|
+
|
|
3
|
+
Este paquete deja que un asistente de IA (**Claude Desktop** o **Claude Code**) opere
|
|
4
|
+
Nexus Flex **con tu cuenta**, en tu computadora, hablando con la API igual que la web.
|
|
5
|
+
|
|
6
|
+
> **Seguridad (aislamiento en 3 capas):** el MCP no puede hacer nada que tu usuario no
|
|
7
|
+
> pueda hacer desde la web. **1)** El backend gatea cada dato por rol y por nodo/cliente
|
|
8
|
+
> (el token hereda tu rol/permisos frescos en cada pedido). **2)** El MCP muestra SOLO
|
|
9
|
+
> los tools de tu rol. **3)** Nunca toca facturación/cobros/cuentas (bloqueado en el
|
|
10
|
+
> cliente **y** en el backend). Cada usuario ve **solo lo suyo**.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Requisitos
|
|
15
|
+
- **Node.js 20+** — https://nodejs.org (LTS). Verificá con `node --version`.
|
|
16
|
+
- **Claude Desktop** (https://claude.ai/download) o Claude Code.
|
|
17
|
+
- Una cuenta de Nexus Flex (la misma de la web). **No** hace falta tu contraseña acá:
|
|
18
|
+
autorizás desde el navegador.
|
|
19
|
+
|
|
20
|
+
## 2. Autorizar (una sola vez)
|
|
21
|
+
En una terminal, corré:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx -y nexusflex-mcp@latest login
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Se abre el navegador (o te muestra un link + un código de 8 letras). Entrás a Nexus Flex
|
|
28
|
+
—si no estás logueado, iniciás sesión— y hacés click en **Autorizar**. Listo: el token
|
|
29
|
+
queda guardado en tu equipo (`~/.nexusflex-mcp/token.json`).
|
|
30
|
+
|
|
31
|
+
## 3. Conectarlo a Claude Desktop
|
|
32
|
+
Editá el config de Claude Desktop:
|
|
33
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
34
|
+
- **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
35
|
+
|
|
36
|
+
Pegá esto (¡sin email ni contraseña!):
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"nexusflex": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["-y", "nexusflex-mcp@latest"],
|
|
44
|
+
"env": {
|
|
45
|
+
"NEXUSFLEX_MCP_ALLOW_WRITE": "true",
|
|
46
|
+
"NEXUSFLEX_MCP_ALLOW_PRECIOS": "false"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Guardá y **reiniciá Claude Desktop**. (Si nunca corriste el paso 2, la primera vez que
|
|
54
|
+
arranque el MCP te va a mostrar el link + código en sus logs para que autorices.)
|
|
55
|
+
|
|
56
|
+
- `NEXUSFLEX_MCP_ALLOW_WRITE` = `"true"` habilita **altas y ediciones** (clientes,
|
|
57
|
+
productos, y nodos si sos admin global). `"false"` = solo consulta.
|
|
58
|
+
- `NEXUSFLEX_MCP_ALLOW_PRECIOS` = `"true"` habilita **actualizar listas de precios**.
|
|
59
|
+
|
|
60
|
+
## 4. Qué ve cada usuario (según con qué cuenta autorices)
|
|
61
|
+
|
|
62
|
+
**CLIENTE (vendedor):** `mis_datos`, `mis_envios`, `mis_kpis`, `mi_stock`,
|
|
63
|
+
`mi_disponible` (disponible-para-vender por SKU), `mi_rentabilidad` (margen por SKU),
|
|
64
|
+
`mis_productos`, `mis_top_productos`. **No** ve datos del nodo ni de otros clientes.
|
|
65
|
+
|
|
66
|
+
**STAFF del nodo:** `clientes_del_nodo`, `precios_ver`, `kpi_nodo`, `stock_nodo`,
|
|
67
|
+
`productos_nodo`, `top_productos`, y —con escritura— `cliente_crear`, `cliente_editar`,
|
|
68
|
+
`producto_crear`, y —con precios— `precio_actualizar`. Todo scopeado a **su** nodo.
|
|
69
|
+
|
|
70
|
+
**Admin GLOBAL:** además `nodos_listar`, `kpi_red`, `nodo_crear`.
|
|
71
|
+
|
|
72
|
+
## 5. Ejemplos (lenguaje natural en Claude)
|
|
73
|
+
- *"¿Qué usuario y alcance tengo?"* → `mis_datos`.
|
|
74
|
+
- *"¿Cuánto tengo disponible para vender del SKU ABC?"* → `mi_disponible`.
|
|
75
|
+
- *"¿Qué productos me dejan más margen este mes?"* → `mi_rentabilidad`.
|
|
76
|
+
- *"Dame de alta a Distribuidora López con la lista B."* → `cliente_crear`.
|
|
77
|
+
- *"Actualizá la lista B: cercana 3800, media 4200."* → `precio_actualizar` (necesita ALLOW_PRECIOS).
|
|
78
|
+
|
|
79
|
+
## 6. Revocar el acceso
|
|
80
|
+
En la web de Nexus Flex, sección **🔌 Conexiones**, revocás cualquier asistente cuando
|
|
81
|
+
quieras. Local: `npx -y nexusflex-mcp@latest logout` borra el token de tu equipo.
|
|
82
|
+
|
|
83
|
+
## 7. Qué NO puede hacer — nunca
|
|
84
|
+
Facturar, registrar cobros/pagos, tocar cuentas corrientes, liquidaciones ni saldos.
|
|
85
|
+
Está bloqueado en el cliente **y** en el backend; aunque se lo pidas, lo rechaza.
|
|
86
|
+
|
|
87
|
+
## Alternativas de credenciales (avanzado / compatibilidad)
|
|
88
|
+
- `NEXUSFLEX_TOKEN` = un token ya emitido (MCP o JWT) → salta el device-flow.
|
|
89
|
+
- `NEXUSFLEX_EMAIL` + `NEXUSFLEX_PASSWORD` = login legado por contraseña.
|
|
90
|
+
- `NEXUSFLEX_API_URL` = base de la API (default `https://nexusflex.com.ar/api`).
|
|
91
|
+
- `NEXUSFLEX_MCP_NO_AUTH_PROMPT=true` = no abrir device-flow automáticamente (falla
|
|
92
|
+
pidiendo `login` explícito).
|
|
93
|
+
|
|
94
|
+
## Problemas comunes
|
|
95
|
+
- *"Sesión inválida"* (401) → corré `npx nexusflex-mcp login` de nuevo (te lo pudieron revocar).
|
|
96
|
+
- *"No tenés permiso"* (403) → tu usuario no tiene ese permiso (es correcto: el backend aísla).
|
|
97
|
+
- No aparecen las herramientas → cerrá Claude Desktop del todo y reabrí.
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Nexus Flex
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nexus Flex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
# nexusflex-mcp
|
|
2
|
-
|
|
3
|
-
Servidor **MCP** (Model Context Protocol) de [Nexus Flex](https://nexusflex.com.ar). Deja
|
|
4
|
-
que un asistente de IA (Claude Desktop / Claude Code) opere tu nodo o tu cuenta de
|
|
5
|
-
vendedor con lenguaje natural: altas de clientes, consulta de stock disponible,
|
|
6
|
-
rentabilidad por SKU, KPIs y precios. **Nunca toca dinero** (facturación, cobros,
|
|
7
|
-
cuentas, liquidaciones están bloqueados por diseño).
|
|
8
|
-
|
|
9
|
-
## Uso rápido
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# 1) Autorizá una vez (se abre el navegador):
|
|
13
|
-
npx -y nexusflex-mcp@latest login
|
|
14
|
-
|
|
15
|
-
# 2) En claude_desktop_config.json:
|
|
16
|
-
# "nexusflex": { "command": "npx", "args": ["-y", "nexusflex-mcp@latest"] }
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Guía completa en español: [INSTALAR.md](./INSTALAR.md).
|
|
20
|
-
|
|
21
|
-
## Cómo funciona (seguridad)
|
|
22
|
-
|
|
23
|
-
- **Login por device-flow (autorización web):** el MCP pide un código, lo autorizás con un
|
|
24
|
-
click desde la web (ya logueado), y recibe un **token de vida larga, revocable** guardado
|
|
25
|
-
localmente. No se pega email/contraseña.
|
|
26
|
-
- **Herencia de scope:** el token es un puntero opaco a tu usuario; el backend re-lee tu
|
|
27
|
-
rol/permisos/nodo **frescos en cada request**. El MCP no puede hacer nada que vos no
|
|
28
|
-
puedas desde la web. Revocar un permiso (o el token) en la app corta el acceso al instante.
|
|
29
|
-
- **Aislamiento en 3 capas:** gating del backend (por rol + nodo/cliente) · tools por rol ·
|
|
30
|
-
denylist de dinero (en el cliente **y** en el backend).
|
|
31
|
-
|
|
32
|
-
## Comandos
|
|
33
|
-
|
|
34
|
-
| Comando | Qué hace |
|
|
35
|
-
|---|---|
|
|
36
|
-
| `npx nexusflex-mcp` | Arranca el servidor MCP (stdio). Lo usa Claude Desktop. |
|
|
37
|
-
| `npx nexusflex-mcp login` | Autoriza por device-flow y guarda el token. |
|
|
38
|
-
| `npx nexusflex-mcp logout` | Borra el token local. |
|
|
39
|
-
|
|
40
|
-
## Variables de entorno
|
|
41
|
-
|
|
42
|
-
| Var | Default | Descripción |
|
|
43
|
-
|---|---|---|
|
|
44
|
-
| `NEXUSFLEX_API_URL` | `https://nexusflex.com.ar/api` | Base de la API. |
|
|
45
|
-
| `NEXUSFLEX_MCP_ALLOW_WRITE` | `false` | Habilita altas/ediciones (nunca dinero). |
|
|
46
|
-
| `NEXUSFLEX_MCP_ALLOW_PRECIOS` | `false` | Habilita editar listas de precios. |
|
|
47
|
-
| `NEXUSFLEX_TOKEN` | — | Token ya emitido (salta el device-flow). |
|
|
48
|
-
| `NEXUSFLEX_EMAIL` / `NEXUSFLEX_PASSWORD` | — | Login legado por contraseña. |
|
|
49
|
-
|
|
50
|
-
## Licencia
|
|
51
|
-
|
|
52
|
-
MIT.
|
|
1
|
+
# nexusflex-mcp
|
|
2
|
+
|
|
3
|
+
Servidor **MCP** (Model Context Protocol) de [Nexus Flex](https://nexusflex.com.ar). Deja
|
|
4
|
+
que un asistente de IA (Claude Desktop / Claude Code) opere tu nodo o tu cuenta de
|
|
5
|
+
vendedor con lenguaje natural: altas de clientes, consulta de stock disponible,
|
|
6
|
+
rentabilidad por SKU, KPIs y precios. **Nunca toca dinero** (facturación, cobros,
|
|
7
|
+
cuentas, liquidaciones están bloqueados por diseño).
|
|
8
|
+
|
|
9
|
+
## Uso rápido
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 1) Autorizá una vez (se abre el navegador):
|
|
13
|
+
npx -y nexusflex-mcp@latest login
|
|
14
|
+
|
|
15
|
+
# 2) En claude_desktop_config.json:
|
|
16
|
+
# "nexusflex": { "command": "npx", "args": ["-y", "nexusflex-mcp@latest"] }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Guía completa en español: [INSTALAR.md](./INSTALAR.md).
|
|
20
|
+
|
|
21
|
+
## Cómo funciona (seguridad)
|
|
22
|
+
|
|
23
|
+
- **Login por device-flow (autorización web):** el MCP pide un código, lo autorizás con un
|
|
24
|
+
click desde la web (ya logueado), y recibe un **token de vida larga, revocable** guardado
|
|
25
|
+
localmente. No se pega email/contraseña.
|
|
26
|
+
- **Herencia de scope:** el token es un puntero opaco a tu usuario; el backend re-lee tu
|
|
27
|
+
rol/permisos/nodo **frescos en cada request**. El MCP no puede hacer nada que vos no
|
|
28
|
+
puedas desde la web. Revocar un permiso (o el token) en la app corta el acceso al instante.
|
|
29
|
+
- **Aislamiento en 3 capas:** gating del backend (por rol + nodo/cliente) · tools por rol ·
|
|
30
|
+
denylist de dinero (en el cliente **y** en el backend).
|
|
31
|
+
|
|
32
|
+
## Comandos
|
|
33
|
+
|
|
34
|
+
| Comando | Qué hace |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `npx nexusflex-mcp` | Arranca el servidor MCP (stdio). Lo usa Claude Desktop. |
|
|
37
|
+
| `npx nexusflex-mcp login` | Autoriza por device-flow y guarda el token. |
|
|
38
|
+
| `npx nexusflex-mcp logout` | Borra el token local. |
|
|
39
|
+
|
|
40
|
+
## Variables de entorno
|
|
41
|
+
|
|
42
|
+
| Var | Default | Descripción |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `NEXUSFLEX_API_URL` | `https://nexusflex.com.ar/api` | Base de la API. |
|
|
45
|
+
| `NEXUSFLEX_MCP_ALLOW_WRITE` | `false` | Habilita altas/ediciones (nunca dinero). |
|
|
46
|
+
| `NEXUSFLEX_MCP_ALLOW_PRECIOS` | `false` | Habilita editar listas de precios. |
|
|
47
|
+
| `NEXUSFLEX_TOKEN` | — | Token ya emitido (salta el device-flow). |
|
|
48
|
+
| `NEXUSFLEX_EMAIL` / `NEXUSFLEX_PASSWORD` | — | Login legado por contraseña. |
|
|
49
|
+
|
|
50
|
+
## Licencia
|
|
51
|
+
|
|
52
|
+
MIT.
|
package/api.mjs
CHANGED
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// Cliente HTTP fino y autenticado contra la API REST de Nexus Flex.
|
|
3
|
-
//
|
|
4
|
-
// PRINCIPIO DE SEGURIDAD (no romper): el MCP NO habla con la base de datos ni
|
|
5
|
-
// firma tokens propios. Usa un TOKEN REAL (device-flow, o el JWT del login) y
|
|
6
|
-
// TODA llamada pasa por el middleware de permisos del backend (requireAuth/
|
|
7
|
-
// requirePermiso + scope por nodo). El aislamiento entre nodos y por rol se
|
|
8
|
-
// hereda 100% del backend: el MCP no puede hacer nada que ese usuario no pueda.
|
|
9
|
-
//
|
|
10
|
-
// Autenticación (en orden de preferencia):
|
|
11
|
-
// 1) NEXUSFLEX_TOKEN token explícito (MCP o JWT) por env
|
|
12
|
-
// 2) token guardado ~/.nexusflex-mcp/token.json (device-flow previo)
|
|
13
|
-
// 3) NEXUSFLEX_EMAIL/PASSWORD login legado (compatibilidad hacia atrás)
|
|
14
|
-
// 4) device-flow interactivo imprime URL + código, autorizás en el navegador
|
|
15
|
-
// ============================================================================
|
|
16
|
-
import { loadToken, saveToken, clearToken, runDeviceFlow } from "./device-auth.mjs";
|
|
17
|
-
|
|
18
|
-
const API_URL = (process.env.NEXUSFLEX_API_URL ?? "https://nexusflex.com.ar/api").replace(/\/+$/, "");
|
|
19
|
-
const EMAIL = process.env.NEXUSFLEX_EMAIL ?? "";
|
|
20
|
-
const PASSWORD = process.env.NEXUSFLEX_PASSWORD ?? "";
|
|
21
|
-
const NO_AUTH_PROMPT = /^(1|true|yes|si|sí)$/i.test(process.env.NEXUSFLEX_MCP_NO_AUTH_PROMPT ?? "");
|
|
22
|
-
let TOKEN = process.env.NEXUSFLEX_TOKEN ?? "";
|
|
23
|
-
let usandoDeviceFlow = false; // el token vino de un device-flow/guardado (no email/pass)
|
|
24
|
-
|
|
25
|
-
export { API_URL };
|
|
26
|
-
|
|
27
|
-
// Denylist de DEFENSA EN PROFUNDIDAD: aunque en el futuro alguien agregue un tool
|
|
28
|
-
// por error, el cliente NUNCA llama a endpoints que muevan dinero o expongan datos
|
|
29
|
-
// financieros. El backend además lo bloquea server-side para tokens de MCP (mcpMoneyGuard).
|
|
30
|
-
const RUTAS_PROHIBIDAS = [
|
|
31
|
-
/^\/liquidaciones/i, /^\/cuentas/i, /^\/cobros/i, /^\/afip/i, /^\/personal/i,
|
|
32
|
-
/^\/balance/i, /^\/reportes/i, /^\/programacion/i, /^\/recuento/i, /^\/whatsapp/i,
|
|
33
|
-
/^\/logisticas\/[^/]*\/cargo/i, /^\/logisticas\/saldos/i, /^\/logisticas\/facturar/i,
|
|
34
|
-
/^\/logisticas\/costo/i, /\/facturacion/i,
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
/** Timeout de red por llamada (ms). Un backend colgado no debe colgar el tool. */
|
|
38
|
-
const FETCH_TIMEOUT_MS = Number(process.env.NEXUSFLEX_MCP_TIMEOUT_MS) || 30000;
|
|
39
|
-
|
|
40
|
-
/** SIEMPRE loguear a stderr: stdout es el canal del transporte MCP (stdio). */
|
|
41
|
-
export function log(...args) {
|
|
42
|
-
console.error("[nexusflex-mcp]", ...args);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Aviso si se van a mandar credenciales por HTTP sin cifrar (login legado).
|
|
46
|
-
if (EMAIL && PASSWORD && !/^https:\/\//i.test(API_URL)) {
|
|
47
|
-
log("ADVERTENCIA: NEXUSFLEX_API_URL no es HTTPS y hay email/contraseña — viajarían sin cifrar.");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function guard(path) {
|
|
51
|
-
const clean = path.split("?")[0];
|
|
52
|
-
if (RUTAS_PROHIBIDAS.some((re) => re.test(clean))) {
|
|
53
|
-
throw new Error(`Ruta bloqueada por política del MCP (endpoints de dinero deshabilitados): ${clean}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function login() {
|
|
58
|
-
const r = await fetch(`${API_URL}/auth/login`, {
|
|
59
|
-
method: "POST",
|
|
60
|
-
headers: { "Content-Type": "application/json" },
|
|
61
|
-
body: JSON.stringify({ email: EMAIL, password: PASSWORD }),
|
|
62
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
63
|
-
});
|
|
64
|
-
if (!r.ok) throw new Error(`Login falló (${r.status}). Revisá email/contraseña del nodo.`);
|
|
65
|
-
const data = await r.json();
|
|
66
|
-
if (!data?.token) throw new Error("Login sin token en la respuesta.");
|
|
67
|
-
TOKEN = data.token;
|
|
68
|
-
usandoDeviceFlow = false;
|
|
69
|
-
log(`Sesión iniciada como ${data.usuario?.email} (rol ${data.usuario?.rol}, nodo ${data.usuario?.logisticaId ?? "global"}).`);
|
|
70
|
-
return TOKEN;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Corre el device-flow, guarda el token y lo deja listo para usar. */
|
|
74
|
-
async function autorizarPorDeviceFlow() {
|
|
75
|
-
const token = await runDeviceFlow(API_URL, { open: true, log });
|
|
76
|
-
saveToken(token, API_URL);
|
|
77
|
-
TOKEN = token;
|
|
78
|
-
usandoDeviceFlow = true;
|
|
79
|
-
return TOKEN;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Resuelve un token válido siguiendo el orden de preferencia. */
|
|
83
|
-
async function ensureToken() {
|
|
84
|
-
if (TOKEN) return TOKEN;
|
|
85
|
-
// 2) token guardado de un device-flow anterior (atado a esta API).
|
|
86
|
-
const guardado = loadToken(API_URL);
|
|
87
|
-
if (guardado) { TOKEN = guardado; usandoDeviceFlow = true; return TOKEN; }
|
|
88
|
-
// 3) login legado por email/contraseña (compatibilidad).
|
|
89
|
-
if (EMAIL && PASSWORD) return login();
|
|
90
|
-
// 4) device-flow interactivo (a menos que se deshabilite explícitamente).
|
|
91
|
-
if (NO_AUTH_PROMPT) {
|
|
92
|
-
throw new Error("No hay token. Ejecutá `npx nexusflex-mcp login` para autorizar, o definí NEXUSFLEX_TOKEN.");
|
|
93
|
-
}
|
|
94
|
-
return autorizarPorDeviceFlow();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Llamada autenticada a la API. Reintenta UNA vez si el token venció/se revocó. */
|
|
98
|
-
export async function api(method, path, body) {
|
|
99
|
-
guard(path);
|
|
100
|
-
let token = await ensureToken();
|
|
101
|
-
const doFetch = (t) =>
|
|
102
|
-
fetch(`${API_URL}${path}`, {
|
|
103
|
-
method,
|
|
104
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${t}` },
|
|
105
|
-
body: body != null ? JSON.stringify(body) : undefined,
|
|
106
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
let r = await doFetch(token);
|
|
110
|
-
if (r.status === 401) {
|
|
111
|
-
if (EMAIL && PASSWORD && !usandoDeviceFlow) {
|
|
112
|
-
// Token JWT vencido: re-login por credenciales.
|
|
113
|
-
log("401: token vencido, reintentando login…");
|
|
114
|
-
token = await login();
|
|
115
|
-
r = await doFetch(token);
|
|
116
|
-
} else if (usandoDeviceFlow && !NO_AUTH_PROMPT) {
|
|
117
|
-
// Token de MCP inválido/revocado: limpiamos y re-autorizamos por device-flow.
|
|
118
|
-
log("401: el token de MCP no es válido (revocado/vencido). Re-autorizando…");
|
|
119
|
-
clearToken();
|
|
120
|
-
TOKEN = "";
|
|
121
|
-
token = await autorizarPorDeviceFlow();
|
|
122
|
-
r = await doFetch(token);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const text = await r.text();
|
|
127
|
-
let data = null;
|
|
128
|
-
try {
|
|
129
|
-
data = text ? JSON.parse(text) : null;
|
|
130
|
-
} catch {
|
|
131
|
-
data = text;
|
|
132
|
-
}
|
|
133
|
-
log(`${method} ${path.split("?")[0]} -> ${r.status}`);
|
|
134
|
-
return { ok: r.ok, status: r.status, data };
|
|
135
|
-
}
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Cliente HTTP fino y autenticado contra la API REST de Nexus Flex.
|
|
3
|
+
//
|
|
4
|
+
// PRINCIPIO DE SEGURIDAD (no romper): el MCP NO habla con la base de datos ni
|
|
5
|
+
// firma tokens propios. Usa un TOKEN REAL (device-flow, o el JWT del login) y
|
|
6
|
+
// TODA llamada pasa por el middleware de permisos del backend (requireAuth/
|
|
7
|
+
// requirePermiso + scope por nodo). El aislamiento entre nodos y por rol se
|
|
8
|
+
// hereda 100% del backend: el MCP no puede hacer nada que ese usuario no pueda.
|
|
9
|
+
//
|
|
10
|
+
// Autenticación (en orden de preferencia):
|
|
11
|
+
// 1) NEXUSFLEX_TOKEN token explícito (MCP o JWT) por env
|
|
12
|
+
// 2) token guardado ~/.nexusflex-mcp/token.json (device-flow previo)
|
|
13
|
+
// 3) NEXUSFLEX_EMAIL/PASSWORD login legado (compatibilidad hacia atrás)
|
|
14
|
+
// 4) device-flow interactivo imprime URL + código, autorizás en el navegador
|
|
15
|
+
// ============================================================================
|
|
16
|
+
import { loadToken, saveToken, clearToken, runDeviceFlow } from "./device-auth.mjs";
|
|
17
|
+
|
|
18
|
+
const API_URL = (process.env.NEXUSFLEX_API_URL ?? "https://nexusflex.com.ar/api").replace(/\/+$/, "");
|
|
19
|
+
const EMAIL = process.env.NEXUSFLEX_EMAIL ?? "";
|
|
20
|
+
const PASSWORD = process.env.NEXUSFLEX_PASSWORD ?? "";
|
|
21
|
+
const NO_AUTH_PROMPT = /^(1|true|yes|si|sí)$/i.test(process.env.NEXUSFLEX_MCP_NO_AUTH_PROMPT ?? "");
|
|
22
|
+
let TOKEN = process.env.NEXUSFLEX_TOKEN ?? "";
|
|
23
|
+
let usandoDeviceFlow = false; // el token vino de un device-flow/guardado (no email/pass)
|
|
24
|
+
|
|
25
|
+
export { API_URL };
|
|
26
|
+
|
|
27
|
+
// Denylist de DEFENSA EN PROFUNDIDAD: aunque en el futuro alguien agregue un tool
|
|
28
|
+
// por error, el cliente NUNCA llama a endpoints que muevan dinero o expongan datos
|
|
29
|
+
// financieros. El backend además lo bloquea server-side para tokens de MCP (mcpMoneyGuard).
|
|
30
|
+
const RUTAS_PROHIBIDAS = [
|
|
31
|
+
/^\/liquidaciones/i, /^\/cuentas/i, /^\/cobros/i, /^\/afip/i, /^\/personal/i,
|
|
32
|
+
/^\/balance/i, /^\/reportes/i, /^\/programacion/i, /^\/recuento/i, /^\/whatsapp/i,
|
|
33
|
+
/^\/logisticas\/[^/]*\/cargo/i, /^\/logisticas\/saldos/i, /^\/logisticas\/facturar/i,
|
|
34
|
+
/^\/logisticas\/costo/i, /\/facturacion/i,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/** Timeout de red por llamada (ms). Un backend colgado no debe colgar el tool. */
|
|
38
|
+
const FETCH_TIMEOUT_MS = Number(process.env.NEXUSFLEX_MCP_TIMEOUT_MS) || 30000;
|
|
39
|
+
|
|
40
|
+
/** SIEMPRE loguear a stderr: stdout es el canal del transporte MCP (stdio). */
|
|
41
|
+
export function log(...args) {
|
|
42
|
+
console.error("[nexusflex-mcp]", ...args);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Aviso si se van a mandar credenciales por HTTP sin cifrar (login legado).
|
|
46
|
+
if (EMAIL && PASSWORD && !/^https:\/\//i.test(API_URL)) {
|
|
47
|
+
log("ADVERTENCIA: NEXUSFLEX_API_URL no es HTTPS y hay email/contraseña — viajarían sin cifrar.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function guard(path) {
|
|
51
|
+
const clean = path.split("?")[0];
|
|
52
|
+
if (RUTAS_PROHIBIDAS.some((re) => re.test(clean))) {
|
|
53
|
+
throw new Error(`Ruta bloqueada por política del MCP (endpoints de dinero deshabilitados): ${clean}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function login() {
|
|
58
|
+
const r = await fetch(`${API_URL}/auth/login`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { "Content-Type": "application/json" },
|
|
61
|
+
body: JSON.stringify({ email: EMAIL, password: PASSWORD }),
|
|
62
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
63
|
+
});
|
|
64
|
+
if (!r.ok) throw new Error(`Login falló (${r.status}). Revisá email/contraseña del nodo.`);
|
|
65
|
+
const data = await r.json();
|
|
66
|
+
if (!data?.token) throw new Error("Login sin token en la respuesta.");
|
|
67
|
+
TOKEN = data.token;
|
|
68
|
+
usandoDeviceFlow = false;
|
|
69
|
+
log(`Sesión iniciada como ${data.usuario?.email} (rol ${data.usuario?.rol}, nodo ${data.usuario?.logisticaId ?? "global"}).`);
|
|
70
|
+
return TOKEN;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Corre el device-flow, guarda el token y lo deja listo para usar. */
|
|
74
|
+
async function autorizarPorDeviceFlow() {
|
|
75
|
+
const token = await runDeviceFlow(API_URL, { open: true, log });
|
|
76
|
+
saveToken(token, API_URL);
|
|
77
|
+
TOKEN = token;
|
|
78
|
+
usandoDeviceFlow = true;
|
|
79
|
+
return TOKEN;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Resuelve un token válido siguiendo el orden de preferencia. */
|
|
83
|
+
async function ensureToken() {
|
|
84
|
+
if (TOKEN) return TOKEN;
|
|
85
|
+
// 2) token guardado de un device-flow anterior (atado a esta API).
|
|
86
|
+
const guardado = loadToken(API_URL);
|
|
87
|
+
if (guardado) { TOKEN = guardado; usandoDeviceFlow = true; return TOKEN; }
|
|
88
|
+
// 3) login legado por email/contraseña (compatibilidad).
|
|
89
|
+
if (EMAIL && PASSWORD) return login();
|
|
90
|
+
// 4) device-flow interactivo (a menos que se deshabilite explícitamente).
|
|
91
|
+
if (NO_AUTH_PROMPT) {
|
|
92
|
+
throw new Error("No hay token. Ejecutá `npx nexusflex-mcp login` para autorizar, o definí NEXUSFLEX_TOKEN.");
|
|
93
|
+
}
|
|
94
|
+
return autorizarPorDeviceFlow();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Llamada autenticada a la API. Reintenta UNA vez si el token venció/se revocó. */
|
|
98
|
+
export async function api(method, path, body) {
|
|
99
|
+
guard(path);
|
|
100
|
+
let token = await ensureToken();
|
|
101
|
+
const doFetch = (t) =>
|
|
102
|
+
fetch(`${API_URL}${path}`, {
|
|
103
|
+
method,
|
|
104
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${t}` },
|
|
105
|
+
body: body != null ? JSON.stringify(body) : undefined,
|
|
106
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
let r = await doFetch(token);
|
|
110
|
+
if (r.status === 401) {
|
|
111
|
+
if (EMAIL && PASSWORD && !usandoDeviceFlow) {
|
|
112
|
+
// Token JWT vencido: re-login por credenciales.
|
|
113
|
+
log("401: token vencido, reintentando login…");
|
|
114
|
+
token = await login();
|
|
115
|
+
r = await doFetch(token);
|
|
116
|
+
} else if (usandoDeviceFlow && !NO_AUTH_PROMPT) {
|
|
117
|
+
// Token de MCP inválido/revocado: limpiamos y re-autorizamos por device-flow.
|
|
118
|
+
log("401: el token de MCP no es válido (revocado/vencido). Re-autorizando…");
|
|
119
|
+
clearToken();
|
|
120
|
+
TOKEN = "";
|
|
121
|
+
token = await autorizarPorDeviceFlow();
|
|
122
|
+
r = await doFetch(token);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const text = await r.text();
|
|
127
|
+
let data = null;
|
|
128
|
+
try {
|
|
129
|
+
data = text ? JSON.parse(text) : null;
|
|
130
|
+
} catch {
|
|
131
|
+
data = text;
|
|
132
|
+
}
|
|
133
|
+
log(`${method} ${path.split("?")[0]} -> ${r.status}`);
|
|
134
|
+
return { ok: r.ok, status: r.status, data };
|
|
135
|
+
}
|