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 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
- # 🤖 SMS Agent Protocol V12.3 (Multi-Project Edition)
1
+ # 🤖 Protocolo de Operación del Agente: SMS Framework
2
2
 
3
- This document defines the rules for AI Agents. The framework now supports multiple isolated state maps.
3
+ Este protocolo es de **obligado cumplimiento** para cualquier agente que opere el MCP State Machine Test Framework.
4
4
 
5
- ## 🎯 Your Mission
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
- ## 🏗️ Multi-Project Laws
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
- ### 1. Map Isolation
11
- - **Context Awareness**: Before modeling or creating nodes, you MUST identify which map you are working on (e.g., `maps/web_map.json` or `maps/mobile_map.json`).
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
- ### 2. Pre-flight Integrity
16
- - **Validation First**: Before suggesting a suite execution, verify that the `state_map` property is defined in the Suite JSON and that the file exists in the `maps/` directory.
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
- ## 📡 Technical Governance
20
- - **Orchestrator Integrity**: `index.js` V12.3 includes a validation layer. If an "Integrity Check Failed" error occurs, analyze the mismatch between the Suite and the Map file.
21
- - **Path Standard**: All state maps MUST reside in the `maps/` directory.
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
- *The SMS framework is now a multi-tenant orchestration engine. Handle each project with its own context.*
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 External Data", { name: z.string() }, async ({ name }) => {
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
+ }