mcp-state-machine-test-framework 1.0.0 → 1.0.5
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/SMS_Protocol.md +8 -0
- package/agent_protocol.md +16 -16
- package/index.js +98 -1
- package/mcp_cli.js +52 -0
- package/new_project_demo/maps/default_map.json +20 -0
- package/new_project_demo/package-lock.json +4330 -0
- package/new_project_demo/package.json +16 -0
- package/package.json +3 -2
package/SMS_Protocol.md
CHANGED
|
@@ -37,5 +37,13 @@ Este documento registra el aprendizaje evolutivo sobre el uso del **State Machin
|
|
|
37
37
|
- **Tool Principal**: `execute_suite({ "name": "Nombre_Suite" })`.
|
|
38
38
|
- **Estructura Modular**: Suite -> Test Cases -> Steps -> Actions. Esta jerarquía garantiza que los reportes sean legibles y la evidencia se asocie correctamente a cada paso lógico.
|
|
39
39
|
|
|
40
|
+
## 🛠️ Herramientas de Gestión (Management Tools)
|
|
41
|
+
|
|
42
|
+
A partir de la versión 1.0.1, el servidor incluye herramientas para la construcción estructurada del framework:
|
|
43
|
+
|
|
44
|
+
- **`upsert_node`**: Garantiza la integridad del mapa de estados. Valida que cada nodo tenga sus transiciones correctamente definidas.
|
|
45
|
+
- **`save_test_case`**: Normaliza la creación de pasos de prueba, asegurando que la jerarquía Suite -> Case -> Step se mantenga para el generador de reportes.
|
|
46
|
+
- **`save_suite`**: Orquesta el ensamblaje final, permitiendo la inyección de hooks (`before/afterSuite`) que son críticos para la gestión de sesiones en la nube.
|
|
47
|
+
|
|
40
48
|
---
|
|
41
49
|
*Documento en evolución constante. Agregue nuevos aprendizajes técnicos aquí.*
|
package/agent_protocol.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# 🤖
|
|
1
|
+
# 🤖 Protocolo de Operación del Agente: SMS Framework
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Este protocolo es de **obligado cumplimiento** para cualquier agente que opere el MCP State Machine Test Framework.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
Act as a Senior Automation Architect. You are responsible for maintaining the integrity of multiple test environments (Web, Mobile, API).
|
|
5
|
+
## ⚖️ Reglas de Construcción de Pruebas
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
### 1. Gestión de Mapas y Estados
|
|
8
|
+
- **PROHIBIDO**: Crear o editar archivos en `/maps` usando herramientas genéricas de escritura de archivos.
|
|
9
|
+
- **MANDATORIO**: Usar la herramienta `upsert_node` para añadir o modificar nodos. Esto asegura que la estructura de transiciones y acciones sea válida para el motor de ejecución.
|
|
9
10
|
|
|
10
|
-
###
|
|
11
|
-
- **
|
|
12
|
-
- **Prefixing**: Although maps are separate files, continue using prefixes (`web_`, `mob_`) for absolute clarity in logs and reports.
|
|
13
|
-
- **Project Switching**: If you change from a Web task to a Mobile task, explicitly state: *"Switching context to [Map Name]"*.
|
|
11
|
+
### 2. Definición de Casos de Prueba
|
|
12
|
+
- **MANDATORIO**: Usar `save_test_case`. El agente debe definir los `steps` como un array de objetos, asegurando que cada paso tenga un nombre descriptivo para el reporte final.
|
|
14
13
|
|
|
15
|
-
###
|
|
16
|
-
- **
|
|
17
|
-
- **Mismatch Prevention**: Never mix nodes from different maps in the same Suite unless it is a documented Hybrid flow.
|
|
14
|
+
### 3. Ensamblaje de Suites
|
|
15
|
+
- **MANDATORIO**: Usar `save_suite`. El agente debe verificar que el `state_map` referenciado exista antes de guardar la suite.
|
|
18
16
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
## 🔄 Flujo de Trabajo Autónomo (Discovery-to-Code)
|
|
18
|
+
1. **Explorar**: Usar `wdio-mcp/get_elements` para identificar selectores.
|
|
19
|
+
2. **Registrar**: Inmediatamente usar `upsert_node` para añadir el hallazgo al mapa de estados.
|
|
20
|
+
3. **Validar**: Ejecutar un `save_test_case` rápido para confirmar que la nueva transición funciona.
|
|
21
|
+
4. **Reportar**: Usar `execute_suite` para generar la evidencia visual del nuevo flujo descubierto.
|
|
22
22
|
|
|
23
23
|
---
|
|
24
|
-
*
|
|
24
|
+
*V1.1 - Protocolo de Integridad Estructural*
|
package/index.js
CHANGED
|
@@ -326,10 +326,107 @@ export class MaquinaDeEstados {
|
|
|
326
326
|
|
|
327
327
|
const maquina = new MaquinaDeEstados();
|
|
328
328
|
const server = new McpServer({ name: "demo-state-machine", version: "11.1.0" });
|
|
329
|
-
server.tool("execute_suite", "Ejecución
|
|
329
|
+
server.tool("execute_suite", "Ejecución de Suite Completa", { name: z.string() }, async ({ name }) => {
|
|
330
330
|
await maquina.cargar();
|
|
331
331
|
const dir = await maquina.ejecutarSuite(name);
|
|
332
332
|
return { content: [{ type: "text", text: `Reporte: ${dir}` }] };
|
|
333
333
|
});
|
|
334
|
+
|
|
335
|
+
server.tool("upsert_node", "Añadir o actualizar un nodo en el mapa de estados", {
|
|
336
|
+
mapName: z.string(),
|
|
337
|
+
nodeName: z.string(),
|
|
338
|
+
nodeData: z.any()
|
|
339
|
+
}, async ({ mapName, nodeName, nodeData }) => {
|
|
340
|
+
const mapPath = path.join(__dirname, 'maps', mapName);
|
|
341
|
+
let map = { nodos: {} };
|
|
342
|
+
try {
|
|
343
|
+
const content = await fs.readFile(mapPath, 'utf8');
|
|
344
|
+
map = JSON.parse(content);
|
|
345
|
+
if (!map.nodos) map = { nodos: map };
|
|
346
|
+
} catch (e) {}
|
|
347
|
+
|
|
348
|
+
map.nodos[nodeName] = nodeData;
|
|
349
|
+
await fs.mkdir(path.dirname(mapPath), { recursive: true });
|
|
350
|
+
await fs.writeFile(mapPath, JSON.stringify(map, null, 2));
|
|
351
|
+
|
|
352
|
+
// Generar/Actualizar Diagrama Mermaid
|
|
353
|
+
try {
|
|
354
|
+
let mermaid = "graph TD\n";
|
|
355
|
+
for (const [name, node] of Object.entries(map.nodos)) {
|
|
356
|
+
if (node.transiciones) {
|
|
357
|
+
for (const [transName, trans] of Object.entries(node.transiciones)) {
|
|
358
|
+
mermaid += ` ${name} -- "${transName}" --> ${trans.destino}\n`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const mermaidPath = mapPath.replace('.json', '.md');
|
|
363
|
+
await fs.writeFile(mermaidPath, `# 🗺️ Diagrama de Estados: ${mapName}\n\n\`\`\`mermaid\n${mermaid}\`\`\``);
|
|
364
|
+
} catch (me) {
|
|
365
|
+
process.stderr.write(`[WARN] Error generando Mermaid: ${me.message}\n`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return { content: [{ type: "text", text: `Nodo '${nodeName}' actualizado. Diagrama visual regenerado en maps/${mapName.replace('.json', '.md')}` }] };
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
server.tool("inspect_framework", "Inspeccionar integridad y listar entidades del framework", {
|
|
372
|
+
filter: z.optional(z.string())
|
|
373
|
+
}, async () => {
|
|
374
|
+
const entities = { maps: [], test_cases: [], suites: [], health: [] };
|
|
375
|
+
const getFiles = async (dir) => {
|
|
376
|
+
try { return (await fs.readdir(path.join(__dirname, dir))).filter(f => f.endsWith('.json')); }
|
|
377
|
+
catch (e) { return []; }
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
entities.maps = await getFiles('maps');
|
|
381
|
+
entities.test_cases = await getFiles('test_cases');
|
|
382
|
+
entities.suites = await getFiles('suites');
|
|
383
|
+
|
|
384
|
+
// Validación de integridad simple
|
|
385
|
+
for (const suiteFile of entities.suites) {
|
|
386
|
+
try {
|
|
387
|
+
const suite = JSON.parse(await fs.readFile(path.join(__dirname, 'suites', suiteFile), 'utf8'));
|
|
388
|
+
if (!entities.maps.includes(suite.state_map)) {
|
|
389
|
+
entities.health.push(`❌ Suite '${suiteFile}' referencia a mapa inexistente: ${suite.state_map}`);
|
|
390
|
+
}
|
|
391
|
+
for (const tc of suite.tests) {
|
|
392
|
+
if (!entities.test_cases.includes(`${tc}.json`) && !entities.test_cases.includes(tc)) {
|
|
393
|
+
entities.health.push(`❌ Suite '${suiteFile}' referencia a test case inexistente: ${tc}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch (e) { entities.health.push(`❌ Error leyendo suite '${suiteFile}': ${e.message}`); }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (entities.health.length === 0) entities.health.push("✅ Estructura íntegra. Todos los vínculos son correctos.");
|
|
400
|
+
|
|
401
|
+
return { content: [{ type: "text", text: JSON.stringify(entities, null, 2) }] };
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
server.tool("save_test_case", "Crear o actualizar un caso de prueba", {
|
|
405
|
+
name: z.string(),
|
|
406
|
+
steps: z.array(z.any())
|
|
407
|
+
}, async ({ name, steps }) => {
|
|
408
|
+
const fileName = name.endsWith('.json') ? name : `${name}.json`;
|
|
409
|
+
const filePath = path.join(__dirname, 'test_cases', fileName);
|
|
410
|
+
const testCase = { name: name.replace('.json', ''), steps };
|
|
411
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
412
|
+
await fs.writeFile(filePath, JSON.stringify(testCase, null, 2));
|
|
413
|
+
return { content: [{ type: "text", text: `Caso de prueba '${name}' guardado correctamente.` }] };
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
server.tool("save_suite", "Crear o actualizar una suite de pruebas", {
|
|
417
|
+
name: z.string(),
|
|
418
|
+
state_map: z.string(),
|
|
419
|
+
tests: z.array(z.string()),
|
|
420
|
+
beforeSuite: z.optional(z.array(z.string())),
|
|
421
|
+
afterSuite: z.optional(z.array(z.string()))
|
|
422
|
+
}, async ({ name, state_map, tests, beforeSuite, afterSuite }) => {
|
|
423
|
+
const fileName = name.endsWith('.json') ? name : `${name}.json`;
|
|
424
|
+
const filePath = path.join(__dirname, 'suites', fileName);
|
|
425
|
+
const suite = { name: name.replace('.json', ''), state_map, tests, beforeSuite, afterSuite };
|
|
426
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
427
|
+
await fs.writeFile(filePath, JSON.stringify(suite, null, 2));
|
|
428
|
+
return { content: [{ type: "text", text: `Suite '${name}' guardada correctamente.` }] };
|
|
429
|
+
});
|
|
430
|
+
|
|
334
431
|
async function main() { await maquina.cargar(); const transport = new StdioServerTransport(); await server.connect(transport); }
|
|
335
432
|
main().catch(console.error);
|
package/mcp_cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
async function run() {
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
if (args.length < 2) {
|
|
13
|
+
console.error("Uso: mcp-cli <server-name> <tool-name> [json-args]");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [serverName, toolName, toolArgsRaw] = args;
|
|
18
|
+
const toolArgs = toolArgsRaw ? JSON.parse(toolArgsRaw) : {};
|
|
19
|
+
|
|
20
|
+
// Cargar config
|
|
21
|
+
let config;
|
|
22
|
+
try {
|
|
23
|
+
const configPath = path.join(process.cwd(), "mcp_config.json");
|
|
24
|
+
const content = await fs.readFile(configPath, "utf8");
|
|
25
|
+
config = JSON.parse(content).mcpServers[serverName];
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error(`❌ No se encontró la configuración para el servidor '${serverName}' en mcp_config.json`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.error(`🔌 Conectando a ${serverName}...`);
|
|
32
|
+
const transport = new StdioClientTransport({
|
|
33
|
+
command: config.command,
|
|
34
|
+
args: config.args || [],
|
|
35
|
+
env: { ...process.env, ...config.env }
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const client = new Client({ name: "mcp-cli-client", version: "1.0.0" }, { capabilities: {} });
|
|
39
|
+
await client.connect(transport);
|
|
40
|
+
|
|
41
|
+
console.error(`🚀 Ejecutando herramienta: ${toolName}`);
|
|
42
|
+
try {
|
|
43
|
+
const result = await client.callTool({ name: toolName, arguments: toolArgs });
|
|
44
|
+
console.log(JSON.stringify(result, null, 2));
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`❌ Error ejecutando herramienta: ${error.message}`);
|
|
47
|
+
} finally {
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
run().catch(console.error);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodos": {
|
|
3
|
+
"HOME": {
|
|
4
|
+
"transiciones": {
|
|
5
|
+
"IR_A_LOGIN": {
|
|
6
|
+
"destino": "LOGIN_PAGE",
|
|
7
|
+
"accion": "mcp:wdio-mcp/click_element { \"selector\": \"~Main Menu\" }"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"LOGIN_PAGE": {
|
|
12
|
+
"transiciones": {
|
|
13
|
+
"VOLVER": {
|
|
14
|
+
"destino": "HOME",
|
|
15
|
+
"accion": "mcp:wdio-mcp/click_element { \"selector\": \"~Back\" }"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|