gesicomm-mcp 1.0.0 → 1.2.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.
- package/package.json +35 -33
- package/src/apiClient.js +100 -95
- package/src/server.js +196 -191
package/package.json
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "gesicomm-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "src/index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"gesicomm-mcp": "bin/cli.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"bin",
|
|
12
|
-
"src"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "gesicomm-mcp",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Conector MCP de Gesicomm para tiendas y productos",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"gesicomm-mcp": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src/index.js",
|
|
13
|
+
"src/apiClient.js",
|
|
14
|
+
"src/server.js"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"gesicomm",
|
|
23
|
+
"ecommerce",
|
|
24
|
+
"claude",
|
|
25
|
+
"codex"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "node bin/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
32
|
+
"dotenv": "^16.4.5",
|
|
33
|
+
"zod": "^3.25.76"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/apiClient.js
CHANGED
|
@@ -1,95 +1,100 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cliente HTTP para comunicarse con la API de Gesicomm.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class GesicommApiClient {
|
|
6
|
-
constructor({ apiUrl, token, tiendaId }) {
|
|
7
|
-
let url = (apiUrl || process.env.GESICOMM_API_URL || 'https://api.gesicomm.com/api').trim().replace(/\/+$/, '');
|
|
8
|
-
|
|
9
|
-
// Si el usuario configuró el dominio del frontend (gesicomm.com), redirigir automáticamente a api.gesicomm.com/api
|
|
10
|
-
if (url === 'https://gesicomm.com' || url === 'http://gesicomm.com' || url === 'https://gesicomm.com/api' || url === 'http://gesicomm.com/api') {
|
|
11
|
-
url = 'https://api.gesicomm.com/api';
|
|
12
|
-
} else if (url === 'https://www.gesicomm.com' || url === 'http://www.gesicomm.com' || url === 'https://www.gesicomm.com/api' || url === 'http://www.gesicomm.com/api') {
|
|
13
|
-
url = 'https://api.gesicomm.com/api';
|
|
14
|
-
} else if (!url.endsWith('/api') && !url.includes('localhost') && !url.includes('127.0.0.1')) {
|
|
15
|
-
url = `${url}/api`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
this.apiUrl = url;
|
|
19
|
-
this.token = token || process.env.GESICOMM_TOKEN || '';
|
|
20
|
-
this.tiendaId = tiendaId || process.env.GESICOMM_TIENDA_ID || null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async _request(path, options = {}) {
|
|
24
|
-
if (!this.token) {
|
|
25
|
-
throw new Error('Falta el token de autenticación de Gesicomm (GESICOMM_TOKEN). Generalo desde Gesicomm > Lienzo en blanco.');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const url = new URL(`${this.apiUrl}${path}`);
|
|
29
|
-
if (this.tiendaId && !url.searchParams.has('tienda_id')) {
|
|
30
|
-
url.searchParams.set('tienda_id', this.tiendaId);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const headers = {
|
|
34
|
-
'Authorization': `Bearer ${this.token}`,
|
|
35
|
-
'Content-Type': 'application/json',
|
|
36
|
-
...(options.headers || {}),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const res = await fetch(url.toString(), {
|
|
40
|
-
...options,
|
|
41
|
-
headers,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const rawText = await res.text();
|
|
45
|
-
let data;
|
|
46
|
-
try {
|
|
47
|
-
data = JSON.parse(rawText);
|
|
48
|
-
} catch {
|
|
49
|
-
if (rawText.includes('<!DOCTYPE') || rawText.includes('<html')) {
|
|
50
|
-
throw new Error(`El servidor en ${this.apiUrl} respondió con una página HTML en lugar de JSON. Si estás probando en desarrollo local, usá http://localhost:5000/api o asegurate de que el backend en producción tenga desplegados los endpoints /api/mcp.`);
|
|
51
|
-
}
|
|
52
|
-
throw new Error(`Respuesta inválida del servidor (código ${res.status}): ${rawText.substring(0, 150)}`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!res.ok) {
|
|
56
|
-
const msg = data.error || data.message || `Error en la solicitud a Gesicomm (${res.status})`;
|
|
57
|
-
const err = new Error(msg);
|
|
58
|
-
err.status = res.status;
|
|
59
|
-
err.detalles = data;
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return data;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async listarLandings() {
|
|
67
|
-
const data = await this._request('/mcp/landings');
|
|
68
|
-
return data.landings || [];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cliente HTTP para comunicarse con la API de Gesicomm.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class GesicommApiClient {
|
|
6
|
+
constructor({ apiUrl, token, tiendaId }) {
|
|
7
|
+
let url = (apiUrl || process.env.GESICOMM_API_URL || 'https://api.gesicomm.com/api').trim().replace(/\/+$/, '');
|
|
8
|
+
|
|
9
|
+
// Si el usuario configuró el dominio del frontend (gesicomm.com), redirigir automáticamente a api.gesicomm.com/api
|
|
10
|
+
if (url === 'https://gesicomm.com' || url === 'http://gesicomm.com' || url === 'https://gesicomm.com/api' || url === 'http://gesicomm.com/api') {
|
|
11
|
+
url = 'https://api.gesicomm.com/api';
|
|
12
|
+
} else if (url === 'https://www.gesicomm.com' || url === 'http://www.gesicomm.com' || url === 'https://www.gesicomm.com/api' || url === 'http://www.gesicomm.com/api') {
|
|
13
|
+
url = 'https://api.gesicomm.com/api';
|
|
14
|
+
} else if (!url.endsWith('/api') && !url.includes('localhost') && !url.includes('127.0.0.1')) {
|
|
15
|
+
url = `${url}/api`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.apiUrl = url;
|
|
19
|
+
this.token = token || process.env.GESICOMM_TOKEN || '';
|
|
20
|
+
this.tiendaId = tiendaId || process.env.GESICOMM_TIENDA_ID || null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async _request(path, options = {}) {
|
|
24
|
+
if (!this.token) {
|
|
25
|
+
throw new Error('Falta el token de autenticación de Gesicomm (GESICOMM_TOKEN). Generalo desde Gesicomm > Lienzo en blanco.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const url = new URL(`${this.apiUrl}${path}`);
|
|
29
|
+
if (this.tiendaId && !url.searchParams.has('tienda_id')) {
|
|
30
|
+
url.searchParams.set('tienda_id', this.tiendaId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const headers = {
|
|
34
|
+
'Authorization': `Bearer ${this.token}`,
|
|
35
|
+
...(options.body instanceof FormData ? {} : { 'Content-Type': 'application/json' }),
|
|
36
|
+
...(options.headers || {}),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const res = await fetch(url.toString(), {
|
|
40
|
+
...options,
|
|
41
|
+
headers,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const rawText = await res.text();
|
|
45
|
+
let data;
|
|
46
|
+
try {
|
|
47
|
+
data = JSON.parse(rawText);
|
|
48
|
+
} catch {
|
|
49
|
+
if (rawText.includes('<!DOCTYPE') || rawText.includes('<html')) {
|
|
50
|
+
throw new Error(`El servidor en ${this.apiUrl} respondió con una página HTML en lugar de JSON. Si estás probando en desarrollo local, usá http://localhost:5000/api o asegurate de que el backend en producción tenga desplegados los endpoints /api/mcp.`);
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Respuesta inválida del servidor (código ${res.status}): ${rawText.substring(0, 150)}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const msg = data.error || data.message || `Error en la solicitud a Gesicomm (${res.status})`;
|
|
57
|
+
const err = new Error(msg);
|
|
58
|
+
err.status = res.status;
|
|
59
|
+
err.detalles = data;
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async listarLandings() {
|
|
67
|
+
const data = await this._request('/mcp/landings');
|
|
68
|
+
return data.landings || [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async crearProducto(form) {
|
|
72
|
+
return this._request('/mcp/productos', { method: 'POST', body: form });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async obtenerContexto(landingId) {
|
|
76
|
+
const data = await this._request(`/mcp/landings/${landingId}/contexto`);
|
|
77
|
+
return data.contexto;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async obtenerCodigo(landingId, vista = 'inicio', contentId = null, seccionId = null) {
|
|
81
|
+
const query = new URLSearchParams({ vista });
|
|
82
|
+
if (seccionId) query.set('seccion_id', seccionId);
|
|
83
|
+
if (contentId) query.set('content_id', contentId);
|
|
84
|
+
return this._request(`/mcp/landings/${landingId}/codigo?${query.toString()}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async validarCodigo(landingId, html, vista = 'inicio') {
|
|
88
|
+
return this._request(`/mcp/landings/${landingId}/validar`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: JSON.stringify({ html, vista }),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async guardarCodigo(landingId, { vista = 'inicio', html, css, js, contentId = null, seccion_id, revision }) {
|
|
95
|
+
return this._request(`/mcp/landings/${landingId}/codigo`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
body: JSON.stringify({ vista, html, css, js, contentId, seccion_id, revision }),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/server.js
CHANGED
|
@@ -1,191 +1,196 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { GesicommApiClient } from './apiClient.js';
|
|
4
|
-
|
|
5
|
-
export function createGesicommServer(config = {}) {
|
|
6
|
-
const client = new GesicommApiClient(config);
|
|
7
|
-
const defaultLandingId = process.env.GESICOMM_LANDING_ID ? Number(process.env.GESICOMM_LANDING_ID) : null;
|
|
8
|
-
|
|
9
|
-
const server = new McpServer({
|
|
10
|
-
name: 'gesicomm-mcp',
|
|
11
|
-
version: '1.
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// Tool 1: Listar Landings
|
|
15
|
-
server.tool(
|
|
16
|
-
'gesicomm_listar_landings',
|
|
17
|
-
'Lista las landings activas y en borrador de la tienda en Gesicomm para conocer sus IDs y títulos.',
|
|
18
|
-
{},
|
|
19
|
-
async () => {
|
|
20
|
-
try {
|
|
21
|
-
const landings = await client.listarLandings();
|
|
22
|
-
return {
|
|
23
|
-
content: [{
|
|
24
|
-
type: 'text',
|
|
25
|
-
text: JSON.stringify(landings, null, 2),
|
|
26
|
-
}],
|
|
27
|
-
};
|
|
28
|
-
} catch (err) {
|
|
29
|
-
return {
|
|
30
|
-
isError: true,
|
|
31
|
-
content: [{ type: 'text', text: `Error al listar landings: ${err.message}` }],
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// Tool 2: Obtener Contexto Comercial Completo
|
|
38
|
-
server.tool(
|
|
39
|
-
'gesicomm_obtener_contexto_landing',
|
|
40
|
-
'Obtiene el contexto comercial completo de la landing: catálogo de productos con precios en Guaraníes (PYG), imágenes, variantes, ofertas (order bumps, packs), branding de la tienda y el contrato técnico de directivas obligatorias para que el carrito, los pedidos y el Píxel funcionen.',
|
|
41
|
-
{
|
|
42
|
-
landing_id: z.number().int().positive().optional().describe('ID numérico de la landing en Gesicomm (si se omite, usa GESICOMM_LANDING_ID)'),
|
|
43
|
-
},
|
|
44
|
-
async ({ landing_id }) => {
|
|
45
|
-
const id = landing_id || defaultLandingId;
|
|
46
|
-
if (!id) {
|
|
47
|
-
return {
|
|
48
|
-
isError: true,
|
|
49
|
-
content: [{ type: 'text', text: 'Debes proporcionar landing_id o configurar GESICOMM_LANDING_ID.' }],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const contexto = await client.obtenerContexto(id);
|
|
54
|
-
if (!contexto) {
|
|
55
|
-
throw new Error(`No se pudo obtener el contexto de la landing #${id}. Verificá que la landing exista y pertenezca a tu tienda.`);
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
content: [{
|
|
59
|
-
type: 'text',
|
|
60
|
-
text: JSON.stringify(contexto, null, 2),
|
|
61
|
-
}],
|
|
62
|
-
};
|
|
63
|
-
} catch (err) {
|
|
64
|
-
return {
|
|
65
|
-
isError: true,
|
|
66
|
-
content: [{ type: 'text', text: `Error al obtener contexto comercial de la landing #${id}: ${err.message}` }],
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Tool 3: Leer Código de la Landing
|
|
73
|
-
server.tool(
|
|
74
|
-
'gesicomm_leer_codigo_landing',
|
|
75
|
-
'Lee el código actual (HTML, CSS, JS) de una vista específica de la landing (inicio, producto, catalogo, checkout, legal) para poder inspeccionarlo, mejorarlo o modificarlo.',
|
|
76
|
-
{
|
|
77
|
-
landing_id: z.number().int().positive().optional().describe('ID numérico de la landing'),
|
|
78
|
-
vista: z.enum(['inicio', 'producto', 'catalogo', 'checkout', 'legal']).default('inicio').describe('Vista a consultar'),
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
'
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
'
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { GesicommApiClient } from './apiClient.js';
|
|
4
|
+
|
|
5
|
+
export function createGesicommServer(config = {}) {
|
|
6
|
+
const client = new GesicommApiClient(config);
|
|
7
|
+
const defaultLandingId = process.env.GESICOMM_LANDING_ID ? Number(process.env.GESICOMM_LANDING_ID) : null;
|
|
8
|
+
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: 'gesicomm-mcp',
|
|
11
|
+
version: '1.2.1',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Tool 1: Listar Landings
|
|
15
|
+
server.tool(
|
|
16
|
+
'gesicomm_listar_landings',
|
|
17
|
+
'Lista las landings activas y en borrador de la tienda en Gesicomm para conocer sus IDs y títulos.',
|
|
18
|
+
{},
|
|
19
|
+
async () => {
|
|
20
|
+
try {
|
|
21
|
+
const landings = await client.listarLandings();
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: JSON.stringify(landings, null, 2),
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return {
|
|
30
|
+
isError: true,
|
|
31
|
+
content: [{ type: 'text', text: `Error al listar landings: ${err.message}` }],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Tool 2: Obtener Contexto Comercial Completo
|
|
38
|
+
server.tool(
|
|
39
|
+
'gesicomm_obtener_contexto_landing',
|
|
40
|
+
'Obtiene el contexto comercial completo de la landing: catálogo de productos con precios en Guaraníes (PYG), imágenes, variantes, ofertas (order bumps, packs), branding de la tienda y el contrato técnico de directivas obligatorias para que el carrito, los pedidos y el Píxel funcionen.',
|
|
41
|
+
{
|
|
42
|
+
landing_id: z.number().int().positive().optional().describe('ID numérico de la landing en Gesicomm (si se omite, usa GESICOMM_LANDING_ID)'),
|
|
43
|
+
},
|
|
44
|
+
async ({ landing_id }) => {
|
|
45
|
+
const id = landing_id || defaultLandingId;
|
|
46
|
+
if (!id) {
|
|
47
|
+
return {
|
|
48
|
+
isError: true,
|
|
49
|
+
content: [{ type: 'text', text: 'Debes proporcionar landing_id o configurar GESICOMM_LANDING_ID.' }],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const contexto = await client.obtenerContexto(id);
|
|
54
|
+
if (!contexto) {
|
|
55
|
+
throw new Error(`No se pudo obtener el contexto de la landing #${id}. Verificá que la landing exista y pertenezca a tu tienda.`);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
content: [{
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: JSON.stringify(contexto, null, 2),
|
|
61
|
+
}],
|
|
62
|
+
};
|
|
63
|
+
} catch (err) {
|
|
64
|
+
return {
|
|
65
|
+
isError: true,
|
|
66
|
+
content: [{ type: 'text', text: `Error al obtener contexto comercial de la landing #${id}: ${err.message}` }],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Tool 3: Leer Código de la Landing
|
|
73
|
+
server.tool(
|
|
74
|
+
'gesicomm_leer_codigo_landing',
|
|
75
|
+
'Lee el código actual (HTML, CSS, JS) de una vista específica de la landing (inicio, producto, catalogo, categoria, checkout, legal) para poder inspeccionarlo, mejorarlo o modificarlo.',
|
|
76
|
+
{
|
|
77
|
+
landing_id: z.number().int().positive().optional().describe('ID numérico de la landing'),
|
|
78
|
+
vista: z.enum(['inicio', 'producto', 'catalogo', 'categoria', 'checkout', 'legal']).default('inicio').describe('Vista a consultar'),
|
|
79
|
+
seccion_id: z.string().regex(/^[a-zA-Z0-9_]+$/).optional().describe('Bloque de inicio a leer o editar (ej: anuncios). Omitir al leer para descubrir secciones.'),
|
|
80
|
+
content_id: z.string().optional().describe('Para fichas propias de un producto específico, el content_id (ej: "producto-15")'),
|
|
81
|
+
},
|
|
82
|
+
async ({ landing_id, vista, content_id, seccion_id }) => {
|
|
83
|
+
const id = landing_id || defaultLandingId;
|
|
84
|
+
if (!id) {
|
|
85
|
+
return {
|
|
86
|
+
isError: true,
|
|
87
|
+
content: [{ type: 'text', text: 'Debes proporcionar landing_id o configurar GESICOMM_LANDING_ID.' }],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const data = await client.obtenerCodigo(id, vista, content_id, seccion_id);
|
|
92
|
+
return {
|
|
93
|
+
content: [{
|
|
94
|
+
type: 'text',
|
|
95
|
+
text: JSON.stringify(data, null, 2),
|
|
96
|
+
}],
|
|
97
|
+
};
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return {
|
|
100
|
+
isError: true,
|
|
101
|
+
content: [{ type: 'text', text: `Error al leer código de la landing #${id}: ${err.message}` }],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Tool 4: Validar Motor Comercial
|
|
108
|
+
server.tool(
|
|
109
|
+
'gesicomm_validar_motor_comercial',
|
|
110
|
+
'Audita un fragmento o documento HTML antes de guardarlo, verificando que tenga los botones de compra (data-gesicomm-comprar/agregar), casillas de order bump, selector de variantes y binds de precios necesarios para procesar pedidos y activar el Píxel de Meta.',
|
|
111
|
+
{
|
|
112
|
+
html: z.string().describe('Código HTML a validar'),
|
|
113
|
+
vista: z.enum(['inicio', 'producto', 'catalogo', 'categoria', 'checkout', 'legal']).default('inicio').describe('Vista a validar'),
|
|
114
|
+
landing_id: z.number().int().positive().optional().describe('ID de la landing (opcional)'),
|
|
115
|
+
},
|
|
116
|
+
async ({ html, vista, landing_id }) => {
|
|
117
|
+
const id = landing_id || defaultLandingId;
|
|
118
|
+
try {
|
|
119
|
+
let res;
|
|
120
|
+
if (id) {
|
|
121
|
+
res = await client.validarCodigo(id, html, vista);
|
|
122
|
+
} else {
|
|
123
|
+
// Validación local básica si no hay id
|
|
124
|
+
const tieneComprar = /data-gesicomm-comprar/.test(html) || /data-gesicomm-agregar/.test(html);
|
|
125
|
+
res = {
|
|
126
|
+
valido: tieneComprar,
|
|
127
|
+
errores: tieneComprar ? [] : ['Falta botón de compra: El HTML debe contener data-gesicomm-comprar o data-gesicomm-agregar.'],
|
|
128
|
+
advertencias: [],
|
|
129
|
+
sugerencias: [],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
content: [{
|
|
134
|
+
type: 'text',
|
|
135
|
+
text: JSON.stringify(res, null, 2),
|
|
136
|
+
}],
|
|
137
|
+
};
|
|
138
|
+
} catch (err) {
|
|
139
|
+
return {
|
|
140
|
+
isError: true,
|
|
141
|
+
content: [{ type: 'text', text: `Error en validación: ${err.message}` }],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Tool 5: Guardar Código en Gesicomm
|
|
148
|
+
server.tool(
|
|
149
|
+
'gesicomm_guardar_codigo_landing',
|
|
150
|
+
'Guarda el código HTML/CSS/JS generado o modificado en la base de datos de Gesicomm. Valida automáticamente las directivas comerciales y sanitiza el código antes de persistirlo. Para cambios puntuales usá seccion_id y revision: conserva otros bloques y su drag and drop. Sincronizá el editor después de guardar.',
|
|
151
|
+
{
|
|
152
|
+
landing_id: z.number().int().positive().optional().describe('ID numérico de la landing'),
|
|
153
|
+
vista: z.enum(['inicio', 'producto', 'catalogo', 'categoria', 'checkout', 'legal']).default('inicio').describe('Vista a actualizar'),
|
|
154
|
+
revision: z.string().optional().describe('Revision devuelta por la lectura; obligatoria para editar una sección.'),
|
|
155
|
+
html: z.string().describe('HTML de la vista o raíz única del bloque indicado en seccion_id'),
|
|
156
|
+
css: z.string().optional().describe('Estilos CSS de la vista'),
|
|
157
|
+
js: z.string().optional().describe('JavaScript de la vista (opcional)'),
|
|
158
|
+
seccion_id: z.string().regex(/^[a-zA-Z0-9_]+$/).optional().describe('Bloque de inicio a leer o editar (ej: anuncios). Omitir al leer para descubrir secciones.'),
|
|
159
|
+
content_id: z.string().optional().describe('Content ID si es la ficha propia de un producto específico'),
|
|
160
|
+
},
|
|
161
|
+
async ({ landing_id, vista, html, css, js, content_id, seccion_id, revision }) => {
|
|
162
|
+
const id = landing_id || defaultLandingId;
|
|
163
|
+
if (!id) {
|
|
164
|
+
return {
|
|
165
|
+
isError: true,
|
|
166
|
+
content: [{ type: 'text', text: 'Debes proporcionar landing_id o configurar GESICOMM_LANDING_ID.' }],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const resultado = await client.guardarCodigo(id, {
|
|
171
|
+
vista,
|
|
172
|
+
html,
|
|
173
|
+
css,
|
|
174
|
+
js,
|
|
175
|
+
contentId: content_id,
|
|
176
|
+
seccion_id,
|
|
177
|
+
revision,
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
content: [{
|
|
181
|
+
type: 'text',
|
|
182
|
+
text: JSON.stringify(resultado, null, 2),
|
|
183
|
+
}],
|
|
184
|
+
};
|
|
185
|
+
} catch (err) {
|
|
186
|
+
const detalles = err.detalles?.errores ? `\nErrores encontrados:\n- ${err.detalles.errores.join('\n- ')}` : '';
|
|
187
|
+
return {
|
|
188
|
+
isError: true,
|
|
189
|
+
content: [{ type: 'text', text: `Error al guardar en Gesicomm: ${err.message}${detalles}` }],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return server;
|
|
196
|
+
}
|