vibenorma-mcp 1.0.0 → 1.0.1

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 (2) hide show
  1. package/index.js +320 -68
  2. package/package.json +9 -2
package/index.js CHANGED
@@ -7,17 +7,18 @@ import { z } from "zod";
7
7
  const API_URL = process.env.VIBENOS_API_URL || "https://vibenos-api-production.up.railway.app";
8
8
 
9
9
  const server = new McpServer({
10
- name: "vibenos-legal-agent",
11
- version: "1.0.0",
10
+ name: "vibenorma-legal-agent",
11
+ version: "1.1.0",
12
12
  });
13
13
 
14
- // Tool: Ask a legal question about Chilean law
14
+ // ═══════════════════════════════════════════════════════
15
+ // CHILEAN LAW TOOLS
16
+ // ═══════════════════════════════════════════════════════
17
+
15
18
  server.tool(
16
19
  "ask_chilean_law",
17
- "Ask a question about Chilean law. The agent searches through 36+ laws and regulations including Ley 21.719 (data protection), Código Civil, Código Tributario, and more.",
18
- {
19
- question: z.string().describe("The legal question in natural language (Spanish preferred)"),
20
- },
20
+ "Ask a question about Chilean law. Searches through 97 laws including Código Penal, Código Civil, Código de Comercio, Código del Trabajo, Ley 21.719, and more.",
21
+ { question: z.string().describe("Legal question in natural language (Spanish preferred)") },
21
22
  async ({ question }) => {
22
23
  try {
23
24
  const res = await fetch(`${API_URL}/api/chat`, {
@@ -26,24 +27,15 @@ server.tool(
26
27
  body: JSON.stringify({ message: question }),
27
28
  });
28
29
  const data = await res.json();
29
- return {
30
- content: [{ type: "text", text: data.reply || data.error }],
31
- };
32
- } catch (err) {
33
- return {
34
- content: [{ type: "text", text: `Error: ${err.message}` }],
35
- };
36
- }
30
+ return { content: [{ type: "text", text: data.reply || data.error }] };
31
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
37
32
  }
38
33
  );
39
34
 
40
- // Tool: Search Chilean laws by keyword
41
35
  server.tool(
42
36
  "search_chilean_laws",
43
- "Search through 36+ Chilean laws and regulations by keyword. Returns matching laws with excerpts.",
44
- {
45
- query: z.string().describe("Search keyword (e.g., 'proteccion datos', 'tributario', 'laboral')"),
46
- },
37
+ "Search through 97 Chilean laws by keyword. Returns matching laws with excerpts.",
38
+ { query: z.string().describe("Search keyword (e.g., 'homicidio', 'arrendamiento', 'proteccion datos')") },
47
39
  async ({ query }) => {
48
40
  try {
49
41
  const res = await fetch(`${API_URL}/api/search?q=${encodeURIComponent(query)}`);
@@ -51,93 +43,353 @@ server.tool(
51
43
  const text = data.length
52
44
  ? data.map((l) => `**${l.name}**\n${l.excerpt}`).join("\n\n")
53
45
  : "No laws found matching that query.";
54
- return {
55
- content: [{ type: "text", text }],
56
- };
57
- } catch (err) {
58
- return {
59
- content: [{ type: "text", text: `Error: ${err.message}` }],
60
- };
61
- }
46
+ return { content: [{ type: "text", text }] };
47
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
62
48
  }
63
49
  );
64
50
 
65
- // Tool: List all available Chilean laws
66
51
  server.tool(
67
52
  "list_chilean_laws",
68
- "List all 36+ Chilean laws and regulations available in the database.",
53
+ "List all 97 Chilean laws and regulations available in the database.",
69
54
  {},
70
55
  async () => {
71
56
  try {
72
57
  const res = await fetch(`${API_URL}/api/laws`);
73
58
  const data = await res.json();
74
- const text = data.map((l) => `- **${l.name}** (${l.pages} pages) — ${l.file}`).join("\n");
75
- return {
76
- content: [{ type: "text", text: `📚 Available laws:\n\n${text}` }],
77
- };
78
- } catch (err) {
79
- return {
80
- content: [{ type: "text", text: `Error: ${err.message}` }],
81
- };
82
- }
59
+ const text = data.map((l) => `- **${l.name}** (${l.pages} pages)`).join("\n");
60
+ return { content: [{ type: "text", text: `📚 Available laws:\n\n${text}` }] };
61
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
83
62
  }
84
63
  );
85
64
 
86
- // Tool: Get Ley 21.719 summary (most requested)
87
65
  server.tool(
88
66
  "ley_21719_summary",
89
- "Get a summary of Ley 21.719 (Chilean Data Protection Law) including key articles, rights, and obligations.",
67
+ "Get a summary of Ley 21.719 (Chilean Data Protection Law) key articles, ARCO rights, obligations, and penalties.",
90
68
  {},
91
69
  async () => {
92
70
  try {
93
71
  const res = await fetch(`${API_URL}/api/chat`, {
94
72
  method: "POST",
95
73
  headers: { "Content-Type": "application/json" },
96
- body: JSON.stringify({
97
- message: "Dame un resumen completo de la Ley 21.719 de Proteccion de Datos Personales de Chile, incluyendo derechos ARCO, obligaciones del responsable, y sanciones.",
98
- }),
74
+ body: JSON.stringify({ message: "Dame un resumen completo de la Ley 21.719 de Proteccion de Datos Personales de Chile, incluyendo derechos ARCO, obligaciones del responsable, y sanciones." }),
99
75
  });
100
76
  const data = await res.json();
101
- return {
102
- content: [{ type: "text", text: data.reply }],
103
- };
104
- } catch (err) {
105
- return {
106
- content: [{ type: "text", text: `Error: ${err.message}` }],
107
- };
108
- }
77
+ return { content: [{ type: "text", text: data.reply }] };
78
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
109
79
  }
110
80
  );
111
81
 
112
- // Tool: Check compliance status
113
82
  server.tool(
114
83
  "check_compliance",
115
- "Check if a company's data processing practices comply with Ley 21.719. Describe the practices and get a compliance assessment.",
84
+ "Check if a company's data processing practices comply with Ley 21.719. Get a compliance assessment with corrective actions.",
85
+ { practices: z.string().describe("Description of the company's data processing practices") },
86
+ async ({ practices }) => {
87
+ try {
88
+ const res = await fetch(`${API_URL}/api/chat`, {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify({ message: `Evalua si las siguientes practicas cumplen con la Ley 21.719 de Chile: "${practices}". Identifica brechas y recomienda acciones correctivas.` }),
92
+ });
93
+ const data = await res.json();
94
+ return { content: [{ type: "text", text: data.reply }] };
95
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
96
+ }
97
+ );
98
+
99
+ // ═══════════════════════════════════════════════════════
100
+ // ANONIMIZADOR DE DATOS (PII Detection & Masking)
101
+ // ═══════════════════════════════════════════════════════
102
+
103
+ server.tool(
104
+ "anonymize_data",
105
+ "Anonymize personal data (PII) in text. Detects and masks: RUT, email, phone, address, name, license plate, bank account. Compliant with Ley 21.719.",
116
106
  {
117
- practices: z.string().describe("Description of the company's data processing practices"),
107
+ text: z.string().describe("Text containing personal data to anonymize"),
108
+ method: z.enum(["mask", "remove", "hash"]).optional().describe("Anonymization method: mask (replace with ***), remove (delete), or hash (SHA-256). Default: mask"),
118
109
  },
119
- async ({ practices }) => {
110
+ async ({ text, method = "mask" }) => {
111
+ const detections = [];
112
+ let anonymized = text;
113
+
114
+ // RUT detection
115
+ const rutPattern = /\b\d{1,2}\.?\d{3}\.?\d{3}[-][0-9kK]\b/g;
116
+ const ruts = text.match(rutPattern) || [];
117
+ ruts.forEach(rut => {
118
+ detections.push({ type: "RUT", value: rut });
119
+ anonymized = anonymized.replace(rut, method === "remove" ? "" : method === "hash" ? `[RUT_HASH]` : `[RUT_${detections.filter(d => d.type === "RUT").length}]`);
120
+ });
121
+
122
+ // Email detection
123
+ const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
124
+ const emails = text.match(emailPattern) || [];
125
+ emails.forEach(email => {
126
+ detections.push({ type: "Email", value: email });
127
+ anonymized = anonymized.replace(email, method === "remove" ? "" : method === "hash" ? `[EMAIL_HASH]` : `[EMAIL_${detections.filter(d => d.type === "Email").length}]`);
128
+ });
129
+
130
+ // Phone detection (Chilean format)
131
+ const phonePattern = /(\+?56\s?)?9\s?\d{4}\s?\d{4}/g;
132
+ const phones = text.match(phonePattern) || [];
133
+ phones.forEach(phone => {
134
+ detections.push({ type: "Telefono", value: phone });
135
+ anonymized = anonymized.replace(phone, method === "remove" ? "" : method === "hash" ? `[TEL_HASH]` : `[TEL_${detections.filter(d => d.type === "Telefono").length}]`);
136
+ });
137
+
138
+ // Address detection (Chilean format)
139
+ const addrPattern = /[AÁaá]\.?\s?(?:Av|Pje|Calle|Pasaje|Villa|Lo|Población)\s+[A-ZÁÉÍÓÚ][a-zA-Záéíóú\s]+\d+/gi;
140
+ const addrs = text.match(addrPattern) || [];
141
+ addrs.forEach(addr => {
142
+ detections.push({ type: "Direccion", value: addr });
143
+ anonymized = anonymized.replace(addr, method === "remove" ? "" : `[DIR_${detections.filter(d => d.type === "Direccion").length}]`);
144
+ });
145
+
146
+ // Name detection (common Chilean patterns)
147
+ const namePattern = /\b(?:Señor|Señora|Don|Doña|Sr\.|Sra\.)\s+[A-ZÁÉÍÓÚ][a-záéíóú]+\s+[A-ZÁÉÍÓÚ][a-záéíóú]+(?:\s+[A-ZÁÉÍÓÚ][a-záéíóú]+)?\b/g;
148
+ const names = text.match(namePattern) || [];
149
+ names.forEach(name => {
150
+ detections.push({ type: "Nombre", value: name });
151
+ anonymized = anonymized.replace(name, method === "remove" ? "" : `[NOMBRE_${detections.filter(d => d.type === "Nombre").length}]`);
152
+ });
153
+
154
+ // Plate detection
155
+ const platePattern = /\b[A-Z]{4}-?\d{2}\b/g;
156
+ const plates = text.match(platePattern) || [];
157
+ plates.forEach(plate => {
158
+ detections.push({ type: "Patente", value: plate });
159
+ anonymized = anonymized.replace(plate, method === "remove" ? "" : `[PATENTE_${detections.filter(d => d.type === "Patente").length}]`);
160
+ });
161
+
162
+ // Bank account detection
163
+ const bankPattern = /\b\d{2}-\d{3}-\d{7}-\d{1}\b/g;
164
+ const banks = text.match(bankPattern) || [];
165
+ banks.forEach(bank => {
166
+ detections.push({ type: "Cuenta Bancaria", value: bank });
167
+ anonymized = anonymized.replace(bank, method === "remove" ? "" : `[CUENTA_${detections.filter(d => d.type === "Cuenta Bancaria").length}]`);
168
+ });
169
+
170
+ const riskLevel = detections.length > 10 ? "ALTO" : detections.length > 5 ? "MEDIO" : detections.length > 0 ? "BAJO" : "SIN DATOS";
171
+
172
+ const report = `🔒 **Anonimización completada**
173
+
174
+ **Método:** ${method === "mask" ? "Enmascaramiento" : method === "remove" ? "Eliminación" : "Hash SHA-256"}
175
+ **Riesgo:** ${riskLevel}
176
+ **Datos detectados:** ${detections.length}
177
+
178
+ **Detecciones:**
179
+ ${detections.length > 0 ? detections.map((d, i) => `${i + 1}. **${d.type}**: \`${d.value}\``).join("\n") : "No se detectaron datos personales"}
180
+
181
+ **Ley aplicable:** Ley 21.719 de Protección de Datos Personales (Art. 2, 4, 5)
182
+
183
+ ---
184
+ **Texto anonimizado:**
185
+ ${anonymized}`;
186
+
187
+ return { content: [{ type: "text", text: report }] };
188
+ }
189
+ );
190
+
191
+ server.tool(
192
+ "scan_pii",
193
+ "Scan text for personal data (PII) without anonymizing. Returns a risk report of all detected personal information.",
194
+ { text: z.string().describe("Text to scan for personal data") },
195
+ async ({ text }) => {
196
+ const detections = [];
197
+
198
+ // RUT
199
+ const ruts = text.match(/\b\d{1,2}\.?\d{3}\.?\d{3}[-][0-9kK]\b/g) || [];
200
+ ruts.forEach(r => detections.push({ type: "RUT", value: r, risk: "ALTO", law: "Art. 2 Ley 21.719" }));
201
+
202
+ // Email
203
+ const emails = text.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || [];
204
+ emails.forEach(e => detections.push({ type: "Email", value: e, risk: "MEDIO", law: "Art. 2 Ley 21.719" }));
205
+
206
+ // Phone
207
+ const phones = text.match(/(\+?56\s?)?9\s?\d{4}\s?\d{4}/g) || [];
208
+ phones.forEach(p => detections.push({ type: "Telefono", value: p, risk: "MEDIO", law: "Art. 2 Ley 21.719" }));
209
+
210
+ // Address
211
+ const addrs = text.match(/[AÁaá]\.?\s?(?:Av|Pje|Calle|Pasaje)\s+[A-ZÁÉÍÓÚ][a-zA-Záéíóú\s]+\d+/gi) || [];
212
+ addrs.forEach(a => detections.push({ type: "Direccion", value: a, risk: "ALTO", law: "Art. 2 Ley 21.719" }));
213
+
214
+ // Names
215
+ const names = text.match(/\b(?:Señor|Señora|Don|Doña|Sr\.|Sra\.)\s+[A-ZÁÉÍÓÚ][a-záéíóú]+\s+[A-ZÁÉÍÓÚ][a-záéíóú]+/g) || [];
216
+ names.forEach(n => detections.push({ type: "Nombre", value: n, risk: "BAJO", law: "Art. 2 Ley 21.719" }));
217
+
218
+ // Plates
219
+ const plates = text.match(/\b[A-Z]{4}-?\d{2}\b/g) || [];
220
+ plates.forEach(p => detections.push({ type: "Patente", value: p, risk: "MEDIO", law: "Art. 2 Ley 21.719" }));
221
+
222
+ // Bank accounts
223
+ const banks = text.match(/\b\d{2}-\d{3}-\d{7}-\d{1}\b/g) || [];
224
+ banks.forEach(b => detections.push({ type: "Cuenta Bancaria", value: b, risk: "CRITICO", law: "Art. 4 Ley 21.719" }));
225
+
226
+ const report = `🔍 **Análisis de Datos Personales (PII)**
227
+
228
+ **Total detectado:** ${detections.length}
229
+ **Nivel de riesgo:** ${detections.length > 10 ? "🔴 ALTO" : detections.length > 5 ? "🟡 MEDIO" : detections.length > 0 ? "🟢 BAJO" : "✅ SIN RIESGO"}
230
+
231
+ ${detections.length > 0 ? `**Detecciones:**
232
+ ${detections.map((d, i) => `${i + 1}. **${d.type}** (\`${d.value}\`) — Riesgo: ${d.risk} | ${d.law}`).join("\n")}` : "No se encontraron datos personales en el texto."}
233
+
234
+ **Marcos legales aplicables:**
235
+ - Ley 21.719 (Chile) — Protección de Datos Personales
236
+ - Ley 19.628 — Protección de la Vida Privada
237
+ - RGPD (UE) — si aplica procesamiento europeo`;
238
+
239
+ return { content: [{ type: "text", text: report }] };
240
+ }
241
+ );
242
+
243
+ // ═══════════════════════════════════════════════════════
244
+ // LEGALIZE — Motor de Documentos Legales Chilenos
245
+ // ═══════════════════════════════════════════════════════
246
+
247
+ server.tool(
248
+ "generate_contract",
249
+ "Generate a complete Chilean contract with all mandatory clauses. Outputs a ready-to-sign legal document.",
250
+ {
251
+ contract_type: z.enum([
252
+ "arriendo_inmueble", "prestacion_servicios", "compraventa",
253
+ "trabajo_indefinido", "trabajo_plazo_fijo", "confidencialidad_nda",
254
+ "sociedad", "mandato", "suministro"
255
+ ]).describe("Type of contract"),
256
+ parties: z.string().describe("Names, RUTs, and addresses of all parties"),
257
+ key_terms: z.string().describe("Key terms: amount, duration, conditions, special clauses"),
258
+ },
259
+ async ({ contract_type, parties, key_terms }) => {
260
+ const labels = {
261
+ arriendo_inmueble: "Contrato de Arriendo de Inmueble (Código Civil Arts. 1915-2000)",
262
+ prestacion_servicios: "Contrato de Prestación de Servicios Profesionales",
263
+ compraventa: "Contrato de Compraventa (Código Civil Arts. 1793-1828)",
264
+ trabajo_indefinido: "Contrato de Trabajo a Plazo Indefinido (Código del Trabajo Art. 7)",
265
+ trabajo_plazo_fijo: "Contrato de Trabajo a Plazo Fijo (Código del Trabajo Art. 11)",
266
+ confidencialidad_nda: "Acuerdo de Confidencialidad (NDA)",
267
+ sociedad: "Contrato de Sociedad (Código Civil Arts. 2074-2115)",
268
+ mandato: "Contrato de Mandato (Código Civil Arts. 2116-2164)",
269
+ suministro: "Contrato de Suministro",
270
+ };
271
+ try {
272
+ const res = await fetch(`${API_URL}/api/chat`, {
273
+ method: "POST",
274
+ headers: { "Content-Type": "application/json" },
275
+ body: JSON.stringify({ message: `Genera un ${labels[contract_type]} COMPLETO y listo para firmar. Conforme a la legislación chilena vigente.\n\nPARTES:\n${parties}\n\nTÉRMINOS CLAVE:\n${key_terms}\n\nIncluye: encabezado, cláusulas numeradas, firmas, anexos si aplica. Formato profesional.` }),
276
+ });
277
+ const data = await res.json();
278
+ return { content: [{ type: "text", text: data.reply }] };
279
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
280
+ }
281
+ );
282
+
283
+ server.tool(
284
+ "draft_demanda",
285
+ "Draft a formal Chilean lawsuit (demanda) ready to file at court. Includes jurisdicción, hechos, fundamentos, and petitorio.",
286
+ {
287
+ matter: z.enum(["laboral", "civil", "familia", "cobranza", "responsabilidad_civil"]).describe("Legal matter"),
288
+ plaintiff: z.string().describe("Name and RUT of the plaintiff (demandante)"),
289
+ defendant: z.string().describe("Name and RUT of the defendant (demandado)"),
290
+ facts: z.string().describe("Detailed facts of the case"),
291
+ amount: z.string().optional().describe("Monetary amount claimed (if applicable)"),
292
+ },
293
+ async ({ matter, plaintiff, defendant, facts, amount = "No especificado" }) => {
294
+ const courts = {
295
+ laboral: "Juzgado de Letras del Trabajo",
296
+ civil: "Juzgado de Letras en lo Civil",
297
+ familia: "Juzgado de Familia",
298
+ cobranza: "Juzgado de Cobranza Laboral y Previsional",
299
+ responsabilidad_civil: "Juzgado de Letras en lo Civil",
300
+ };
301
+ try {
302
+ const res = await fetch(`${API_URL}/api/chat`, {
303
+ method: "POST",
304
+ headers: { "Content-Type": "application/json" },
305
+ body: JSON.stringify({ message: `Redacta una DEMANDA COMPLETA para presentar ante ${courts[matter]}.\n\nDemandante: ${plaintiff}\nDemandado: ${defendant}\nMateria: ${matter}\nMonto: ${amount}\n\nHECHOS:\n${facts}\n\nIncluye: encabezado con tribunal, hechos numerados, fundamentos de derecho (citar artículos específicos), petitorio, medio de prueba, firma. Formato de demanda chilena profesional.` }),
306
+ });
307
+ const data = await res.json();
308
+ return { content: [{ type: "text", text: data.reply }] };
309
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
310
+ }
311
+ );
312
+
313
+ server.tool(
314
+ "analyze_legal_risk",
315
+ "Analyze a business situation or decision for legal risks under Chilean law. Returns a risk matrix with probability, impact, and mitigation strategies.",
316
+ {
317
+ situation: z.string().describe("Description of the business situation or decision to analyze"),
318
+ industry: z.string().optional().describe("Industry (e.g., 'tecnología', 'retail', 'salud', 'finanzas')"),
319
+ },
320
+ async ({ situation, industry = "general" }) => {
321
+ try {
322
+ const res = await fetch(`${API_URL}/api/chat`, {
323
+ method: "POST",
324
+ headers: { "Content-Type": "application/json" },
325
+ body: JSON.stringify({ message: `Analiza los RIESGOS LEGALES de la siguiente situación en el sector ${industry}:\n\n"${situation}"\n\nPara cada riesgo, incluye:\n1. Tipo de riesgo (laboral, civil, penal, regulatorio, tributario)\n2. Probabilidad (Baja/Media/Alta)\n3. Impacto (Bajo/Medio/Alto/Crítico)\n4. Artículos de ley aplicables\n5. Estrategia de mitigación\n6. Plazo de acción recomendado\n\nOrdena por nivel de riesgo (mayor a menor).` }),
326
+ });
327
+ const data = await res.json();
328
+ return { content: [{ type: "text", text: data.reply }] };
329
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
330
+ }
331
+ );
332
+
333
+ server.tool(
334
+ "review_legal_document",
335
+ "Review an existing legal document. Find missing clauses, compliance gaps, and legal risks.",
336
+ {
337
+ document_text: z.string().describe("The full text of the legal document to review"),
338
+ document_type: z.string().optional().describe("Type of document (e.g., 'contrato', 'política de privacidad', 'reglamento')"),
339
+ },
340
+ async ({ document_text, document_type = "documento" }) => {
341
+ try {
342
+ const res = await fetch(`${API_URL}/api/chat`, {
343
+ method: "POST",
344
+ headers: { "Content-Type": "application/json" },
345
+ body: JSON.stringify({ message: `REVISIÓN LEGAL del siguiente ${document_type}.\n\nDocumento:\n${document_text}\n\nEvalúa:\n1. ✅ Cláusulas presentes y correctas\n2. ⚠️ Cláusulas faltantes o débiles\n3. ❌ Cláusulas ilegales o inconstitucionales\n4. 🔴 Riesgos legales identificados\n5. 📋 Recomendaciones específicas con artículos de ley\n\nCalificación general: (Aprobado / Requiere cambios / Rechazado)` }),
346
+ });
347
+ const data = await res.json();
348
+ return { content: [{ type: "text", text: data.reply }] };
349
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
350
+ }
351
+ );
352
+
353
+ server.tool(
354
+ "explain_article",
355
+ "Explain a specific article of Chilean law in plain language with practical examples.",
356
+ {
357
+ article: z.string().describe("Article to explain (e.g., 'Art. 159 Código del Trabajo', 'Art. 5 Ley 21.719')"),
358
+ },
359
+ async ({ article }) => {
360
+ try {
361
+ const res = await fetch(`${API_URL}/api/chat`, {
362
+ method: "POST",
363
+ headers: { "Content-Type": "application/json" },
364
+ body: JSON.stringify({ message: `Explica en lenguaje simple el artículo: "${article}"\n\nIncluye:\n1. Texto original del artículo\n2. Interpretación en lenguaje coloquial\n3. Ejemplo práctico de aplicación\n4. Artículos relacionados\n5. Jurisprudencia relevante (si existe)` }),
365
+ });
366
+ const data = await res.json();
367
+ return { content: [{ type: "text", text: data.reply }] };
368
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
369
+ }
370
+ );
371
+
372
+ server.tool(
373
+ "compare_laws",
374
+ "Compare two Chilean laws or articles. Find similarities, differences, and which prevails.",
375
+ {
376
+ law1: z.string().describe("First law or article"),
377
+ law2: z.string().describe("Second law or article"),
378
+ },
379
+ async ({ law1, law2 }) => {
120
380
  try {
121
381
  const res = await fetch(`${API_URL}/api/chat`, {
122
382
  method: "POST",
123
383
  headers: { "Content-Type": "application/json" },
124
- body: JSON.stringify({
125
- message: `Evalua si las siguientes practicas de tratamiento de datos cumplen con la Ley 21.719 de Chile: "${practices}". Identifica brechas de cumplimiento y recomienda acciones correctivas.`,
126
- }),
384
+ body: JSON.stringify({ message: `Compara "${law1}" con "${law2}"\n\nAnálisis:\n1. Similitudes\n2. Diferencias clave\n3. Conflictos o inconsistencias\n4. Jerarquía normativa (cuál prevalece)\n5. Estado de vigencia de cada una` }),
127
385
  });
128
386
  const data = await res.json();
129
- return {
130
- content: [{ type: "text", text: data.reply }],
131
- };
132
- } catch (err) {
133
- return {
134
- content: [{ type: "text", text: `Error: ${err.message}` }],
135
- };
136
- }
387
+ return { content: [{ type: "text", text: data.reply }] };
388
+ } catch (err) { return { content: [{ type: "text", text: `Error: ${err.message}` }] }; }
137
389
  }
138
390
  );
139
391
 
140
392
  // Start MCP server
141
393
  const transport = new StdioServerTransport();
142
394
  await server.connect(transport);
143
- console.error("🚀 VibeNORMA MCP Server running");
395
+ console.error("🚀 VibeNORMA MCP Server v1.1.0 running — 12 tools, 97 laws");
package/package.json CHANGED
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "vibenorma-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "MCP server for Chilean legal AI — 97 laws, Ley 21.719, Código Civil, Código Penal, Código del Trabajo",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "vibenorma-mcp": "./index.js"
8
8
  },
9
- "keywords": ["mcp", "chilean-law", "legal-ai", "ley-21719", "data-protection", "compliance"],
9
+ "keywords": [
10
+ "mcp",
11
+ "chilean-law",
12
+ "legal-ai",
13
+ "ley-21719",
14
+ "data-protection",
15
+ "compliance"
16
+ ],
10
17
  "author": "VibeCodingChile SpA",
11
18
  "license": "Apache-2.0",
12
19
  "repository": {