siag-mcp 2.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.
Files changed (3) hide show
  1. package/README.md +91 -0
  2. package/index.js +317 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # SIAG MCP Client
2
+
3
+ Cliente MCP para **Claude Desktop** que conecta con el servidor centralizado SIAG en n8n.
4
+ Soporta múltiples empresas en una sola instalación.
5
+
6
+ ```
7
+ Claude Desktop ──stdio──▶ siag-mcp-client ──HTTPS POST──▶ n8n webhook ──▶ SIAG API
8
+ ```
9
+
10
+ ---
11
+
12
+ ## Instalación rápida (Windows — usuarios no técnicos)
13
+
14
+ 1. Asegúrate de tener [Node.js 18+](https://nodejs.org) instalado
15
+ 2. Abre **PowerShell** y ejecuta:
16
+
17
+ ```powershell
18
+ Set-ExecutionPolicy Bypass -Scope Process -Force
19
+ irm https://gitlab.com/sgsst/siag-mcp-client/-/raw/main/install.ps1 | iex
20
+ ```
21
+
22
+ 3. El instalador te pedirá el nombre y API Key de cada empresa
23
+ 4. Reinicia Claude Desktop
24
+
25
+ ---
26
+
27
+ ## Configuración manual
28
+
29
+ Edita `%APPDATA%\Claude\claude_desktop_config.json`:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "siag": {
35
+ "command": "npx",
36
+ "args": ["-y", "@sgsst/siag-mcp"],
37
+ "env": {
38
+ "SIAG_API_KEY_EMPRESA1": "API-tu-key-empresa1",
39
+ "SIAG_API_KEY_EMPRESA2": "API-tu-key-empresa2"
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ Reinicia Claude Desktop.
47
+
48
+ ---
49
+
50
+ ## Variables de entorno
51
+
52
+ | Variable | Empresa detectada | Ejemplo |
53
+ |---|---|---|
54
+ | `SIAG_API_KEY` | `default` (legacy) | `SIAG_API_KEY=API-xxx` |
55
+ | `SIAG_API_KEY_<NOMBRE>` | El nombre en minúsculas | `SIAG_API_KEY_ACME=API-yyy` → empresa `acme` |
56
+
57
+ Puedes configurar una o varias empresas. Si hay más de una, Claude pedirá
58
+ el parámetro `empresa` en cada consulta.
59
+
60
+ ---
61
+
62
+ ## Herramientas disponibles
63
+
64
+ | Herramienta | Descripción | Parámetro `empresa` |
65
+ |---|---|---|
66
+ | `listar_empresas` | Lista las empresas configuradas | No aplica |
67
+ | `listar_incapacidades` | Lista con filtros opcionales | Requerido si hay varias |
68
+ | `obtener_incapacidad` | Detalle por ID | Requerido si hay varias |
69
+ | `listar_ausentismo` | Lista con filtros opcionales | Requerido si hay varias |
70
+ | `obtener_ausentismo` | Detalle por ID | Requerido si hay varias |
71
+ | `verificar_conexion` | Diagnóstico de conectividad | Requerido si hay varias |
72
+
73
+ ---
74
+
75
+ ## Ejemplos de uso en Claude
76
+
77
+ - *"¿Qué empresas tengo configuradas?"*
78
+ - *"Lista las incapacidades de la empresa acme del empleado 12345678 en 2025"*
79
+ - *"¿Cuántos días de ausentismo tuvo el área de producción en constructora este mes?"*
80
+ - *"Muéstrame el detalle de la incapacidad con ID 450 en empresa1"*
81
+ - *"Verifica si SIAG está respondiendo para acme"*
82
+
83
+ ---
84
+
85
+ ## Publicación
86
+
87
+ ```bash
88
+ npm publish --access public
89
+ ```
90
+
91
+ > Requiere cuenta en npm y haber hecho `npm login` previamente.
package/index.js ADDED
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SIAG MCP Client
5
+ * ───────────────
6
+ * Cliente MCP para Claude Desktop que se comunica con el
7
+ * MCP Server centralizado en n8n (webhook /siag-mcp).
8
+ *
9
+ * Configuración en variables de entorno (o .env):
10
+ * SIAG_API_KEY → API key legacy (empresa "default")
11
+ * SIAG_API_KEY_<EMPRESA> → API key por empresa (ej: SIAG_API_KEY_ACME)
12
+ * N8N_WEBHOOK_URL → URL del webhook n8n (opcional)
13
+ */
14
+
15
+ const fs = require("fs");
16
+ const path = require("path");
17
+ const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
18
+ const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
19
+ const { z } = require("zod");
20
+ const axios = require("axios");
21
+
22
+ // Cargar variables de entorno manualmente desde .env sin usar console
23
+ const envPath = path.join(__dirname, ".env");
24
+ if (fs.existsSync(envPath)) {
25
+ const envContent = fs.readFileSync(envPath, "utf-8");
26
+ envContent.split("\n").forEach((line) => {
27
+ const trimmed = line.trim();
28
+ if (trimmed && !trimmed.startsWith("#")) {
29
+ const [key, ...valueParts] = trimmed.split("=");
30
+ if (key) process.env[key.trim()] = valueParts.join("=").trim();
31
+ }
32
+ });
33
+ }
34
+
35
+ // ─── Configuración multi-empresa ──────────────────────────────────────────────
36
+
37
+ const WEBHOOK_URL = process.env.N8N_WEBHOOK_URL || "https://automate.sgsst.co/webhook/siag-mcp";
38
+
39
+ /**
40
+ * Construye el mapa de empresas desde variables de entorno.
41
+ * Soporta:
42
+ * SIAG_API_KEY → empresa "default" (compatibilidad legacy)
43
+ * SIAG_API_KEY_<EMPRESA> → empresa con nombre (ej: SIAG_API_KEY_ACME → "acme")
44
+ */
45
+ function buildCompanyMap() {
46
+ const map = {};
47
+ const PREFIX = "SIAG_API_KEY_";
48
+
49
+ // Legacy: clave sin sufijo
50
+ if (process.env.SIAG_API_KEY) {
51
+ map["default"] = process.env.SIAG_API_KEY;
52
+ }
53
+
54
+ // Prefijadas: SIAG_API_KEY_<EMPRESA>
55
+ for (const [key, value] of Object.entries(process.env)) {
56
+ if (key.startsWith(PREFIX) && value) {
57
+ const empresa = key.slice(PREFIX.length).toLowerCase();
58
+ map[empresa] = value;
59
+ }
60
+ }
61
+
62
+ return map;
63
+ }
64
+
65
+ const COMPANIES = buildCompanyMap();
66
+ const COMPANY_NAMES = Object.keys(COMPANIES);
67
+ const IS_MULTI = COMPANY_NAMES.length > 1;
68
+
69
+ if (COMPANY_NAMES.length === 0) {
70
+ console.error(
71
+ "❌ ERROR: No se encontró ninguna API key de SIAG.\n" +
72
+ " Configura SIAG_API_KEY o SIAG_API_KEY_<EMPRESA>=... en el entorno."
73
+ );
74
+ process.exit(1);
75
+ }
76
+
77
+ /**
78
+ * Resuelve la API key para la empresa indicada.
79
+ * - Si hay 1 sola empresa: el parámetro empresa es ignorado.
80
+ * - Si hay varias: empresa es obligatorio y debe coincidir.
81
+ */
82
+ function resolveApiKey(empresa) {
83
+ if (!IS_MULTI) {
84
+ return { apiKey: COMPANIES[COMPANY_NAMES[0]], resolvedName: COMPANY_NAMES[0] };
85
+ }
86
+ if (!empresa) {
87
+ return {
88
+ error:
89
+ `Se configuraron varias empresas (${COMPANY_NAMES.join(", ")}). ` +
90
+ "Debes indicar el parámetro 'empresa'. Usa listar_empresas para ver las disponibles."
91
+ };
92
+ }
93
+ const normalized = empresa.toLowerCase();
94
+ const apiKey = COMPANIES[normalized];
95
+ if (!apiKey) {
96
+ return {
97
+ error: `Empresa '${empresa}' no encontrada. Disponibles: ${COMPANY_NAMES.join(", ")}`
98
+ };
99
+ }
100
+ return { apiKey, resolvedName: normalized };
101
+ }
102
+
103
+ // ─── Llamada al MCP Server (n8n) ──────────────────────────────────────────────
104
+
105
+ async function callServer(apiKey, tool, params = {}) {
106
+ const { data } = await axios.post(
107
+ WEBHOOK_URL,
108
+ { api_key: apiKey, tool, params },
109
+ { headers: { "Content-Type": "application/json" }, timeout: 30000 }
110
+ );
111
+ return data;
112
+ }
113
+
114
+ function ok(text) {
115
+ return { content: [{ type: "text", text: typeof text === "string" ? text : JSON.stringify(text, null, 2) }] };
116
+ }
117
+
118
+ function err(msg) {
119
+ return { content: [{ type: "text", text: `❌ ${msg}` }], isError: true };
120
+ }
121
+
122
+ // ─── Servidor MCP ─────────────────────────────────────────────────────────────
123
+
124
+ const server = new McpServer(
125
+ { name: "siag-client", version: "2.0.0" },
126
+ {
127
+ capabilities: { tools: {} },
128
+ instructions: [
129
+ "Cliente MCP de SIAG (SGSST) — solo lectura.",
130
+ "Consulta incapacidades laborales y ausentismo de personas.",
131
+ IS_MULTI
132
+ ? `Empresas configuradas: ${COMPANY_NAMES.join(", ")}. Usa el parámetro 'empresa' en cada llamada.`
133
+ : `Empresa activa: ${COMPANY_NAMES[0]}.`,
134
+ "Las llamadas se enrutan al servidor centralizado en n8n.",
135
+ ].join(" "),
136
+ }
137
+ );
138
+
139
+ // ─── Helper Zod reutilizable para el campo empresa ────────────────────────────
140
+
141
+ const empresaField = IS_MULTI
142
+ ? z.string().describe(
143
+ `Nombre de la empresa a consultar. Opciones: ${COMPANY_NAMES.join(", ")}`
144
+ )
145
+ : z.string().optional().describe(
146
+ `Empresa (opcional — única configurada: ${COMPANY_NAMES[0]})`
147
+ );
148
+
149
+ // ══════════════════════════════════════════════════════════════════════════════
150
+ // EMPRESAS
151
+ // ══════════════════════════════════════════════════════════════════════════════
152
+
153
+ server.tool(
154
+ "listar_empresas",
155
+ "Lista las empresas configuradas en este cliente SIAG MCP.",
156
+ {},
157
+ async () => {
158
+ const PREFIX = "SIAG_API_KEY_";
159
+ const list = COMPANY_NAMES.map((name) => ({
160
+ empresa: name,
161
+ variable: name === "default" ? "SIAG_API_KEY" : `${PREFIX}${name.toUpperCase()}`,
162
+ }));
163
+ return ok({ empresas: list, total: list.length });
164
+ }
165
+ );
166
+
167
+ // ══════════════════════════════════════════════════════════════════════════════
168
+ // INCAPACIDAD LABORAL
169
+ // ══════════════════════════════════════════════════════════════════════════════
170
+
171
+ server.tool(
172
+ "listar_incapacidades",
173
+ "Lista las incapacidades laborales registradas en SIAG. Todos los filtros son opcionales.",
174
+ {
175
+ empresa: empresaField,
176
+ documento_empleado: z.string().optional()
177
+ .describe("Número de documento del empleado"),
178
+ fecha_inicio: z.string().optional()
179
+ .describe("Fecha inicio del rango a consultar (YYYY-MM-DD)"),
180
+ fecha_fin: z.string().optional()
181
+ .describe("Fecha fin del rango a consultar (YYYY-MM-DD)"),
182
+ tipo_incapacidad: z.string().optional()
183
+ .describe("Tipo: enfermedad_general, accidente_trabajo, enfermedad_laboral, maternidad, paternidad"),
184
+ estado: z.string().optional()
185
+ .describe("Estado: activa, cerrada, pendiente"),
186
+ pagina: z.number().int().positive().optional()
187
+ .describe("Número de página (default: 1)"),
188
+ limite: z.number().int().positive().max(100).optional()
189
+ .describe("Registros por página (default: 20, máx: 100)"),
190
+ },
191
+ async ({ empresa, ...rest }) => {
192
+ const resolved = resolveApiKey(empresa);
193
+ if (resolved.error) return err(resolved.error);
194
+ try {
195
+ const result = await callServer(resolved.apiKey, "listar_incapacidades", rest);
196
+ return ok(result);
197
+ } catch (e) {
198
+ return err(`listar_incapacidades: ${e.message}`);
199
+ }
200
+ }
201
+ );
202
+
203
+ server.tool(
204
+ "obtener_incapacidad",
205
+ "Obtiene el detalle completo de una incapacidad laboral por su ID.",
206
+ {
207
+ empresa: empresaField,
208
+ id: z.string().describe("ID único de la incapacidad laboral"),
209
+ },
210
+ async ({ empresa, id }) => {
211
+ const resolved = resolveApiKey(empresa);
212
+ if (resolved.error) return err(resolved.error);
213
+ try {
214
+ const result = await callServer(resolved.apiKey, "obtener_incapacidad", { id });
215
+ return ok(result);
216
+ } catch (e) {
217
+ return err(`obtener_incapacidad [${id}]: ${e.message}`);
218
+ }
219
+ }
220
+ );
221
+
222
+ // ══════════════════════════════════════════════════════════════════════════════
223
+ // AUSENTISMO PERSONA
224
+ // ══════════════════════════════════════════════════════════════════════════════
225
+
226
+ server.tool(
227
+ "listar_ausentismo",
228
+ "Lista los registros de ausentismo de personas en SIAG. Todos los filtros son opcionales.",
229
+ {
230
+ empresa: empresaField,
231
+ documento_empleado: z.string().optional()
232
+ .describe("Número de documento del empleado"),
233
+ fecha_inicio: z.string().optional()
234
+ .describe("Fecha inicio del período (YYYY-MM-DD)"),
235
+ fecha_fin: z.string().optional()
236
+ .describe("Fecha fin del período (YYYY-MM-DD)"),
237
+ tipo_ausentismo: z.string().optional()
238
+ .describe("Tipo: incapacidad, calamidad, vacaciones, licencia, otro"),
239
+ area: z.string().optional()
240
+ .describe("Área o departamento"),
241
+ cargo: z.string().optional()
242
+ .describe("Cargo del empleado"),
243
+ pagina: z.number().int().positive().optional()
244
+ .describe("Número de página (default: 1)"),
245
+ limite: z.number().int().positive().max(100).optional()
246
+ .describe("Registros por página (default: 20, máx: 100)"),
247
+ },
248
+ async ({ empresa, ...rest }) => {
249
+ const resolved = resolveApiKey(empresa);
250
+ if (resolved.error) return err(resolved.error);
251
+ try {
252
+ const result = await callServer(resolved.apiKey, "listar_ausentismo", rest);
253
+ return ok(result);
254
+ } catch (e) {
255
+ return err(`listar_ausentismo: ${e.message}`);
256
+ }
257
+ }
258
+ );
259
+
260
+ server.tool(
261
+ "obtener_ausentismo",
262
+ "Obtiene el detalle completo de un registro de ausentismo por su ID.",
263
+ {
264
+ empresa: empresaField,
265
+ id: z.string().describe("ID único del registro de ausentismo"),
266
+ },
267
+ async ({ empresa, id }) => {
268
+ const resolved = resolveApiKey(empresa);
269
+ if (resolved.error) return err(resolved.error);
270
+ try {
271
+ const result = await callServer(resolved.apiKey, "obtener_ausentismo", { id });
272
+ return ok(result);
273
+ } catch (e) {
274
+ return err(`obtener_ausentismo [${id}]: ${e.message}`);
275
+ }
276
+ }
277
+ );
278
+
279
+ // ══════════════════════════════════════════════════════════════════════════════
280
+ // DIAGNÓSTICO
281
+ // ══════════════════════════════════════════════════════════════════════════════
282
+
283
+ server.tool(
284
+ "verificar_conexion",
285
+ "Verifica que la conexión con el servidor SIAG esté funcionando correctamente.",
286
+ {
287
+ empresa: empresaField,
288
+ },
289
+ async ({ empresa }) => {
290
+ const resolved = resolveApiKey(empresa);
291
+ if (resolved.error) return err(resolved.error);
292
+ try {
293
+ const result = await callServer(resolved.apiKey, "verificar_conexion");
294
+ return ok(result);
295
+ } catch (e) {
296
+ return err(`verificar_conexion: ${e.message}`);
297
+ }
298
+ }
299
+ );
300
+
301
+ // ─── Arranque ─────────────────────────────────────────────────────────────────
302
+
303
+ async function main() {
304
+ const transport = new StdioServerTransport();
305
+ await server.connect(transport);
306
+ console.error(
307
+ `[SIAG MCP] Servidor iniciado — ` +
308
+ (IS_MULTI
309
+ ? `${COMPANY_NAMES.length} empresas: ${COMPANY_NAMES.join(", ")}`
310
+ : `empresa: ${COMPANY_NAMES[0]}`)
311
+ );
312
+ }
313
+
314
+ main().catch((e) => {
315
+ console.error("[SIAG MCP] Error fatal:", e);
316
+ process.exit(1);
317
+ });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "siag-mcp",
3
+ "version": "2.0.0",
4
+ "description": "Cliente MCP de SIAG para Claude Desktop — soporte multi-empresa",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "siag-mcp": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "start": "node index.js"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "siag",
19
+ "sgsst",
20
+ "claude",
21
+ "ausentismo",
22
+ "incapacidades"
23
+ ],
24
+ "author": "SGSST <soporte@sgsst.co>",
25
+ "license": "ISC",
26
+ "engines": {
27
+ "node": ">=18.14.1"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://gitlab.com/ANSWERCPI9999/siag-mcp-client.git"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.27.1",
35
+ "axios": "^1.13.6",
36
+ "zod": "^4.3.6"
37
+ }
38
+ }