# Acciones IA y MCP ejecutables

Registra y ejecuta acciones WU sin proveedor; conecta un relay MCP real y comprueba schema, permisos, autenticación, cancelación y limpieza.

<span class="section-anchor" id="section-1"></span>

## Una acción no necesita un proveedor LLM

`wu.ai.execute` invoca un handler registrado. Puedes usarlo desde una interfaz propia, un agente externo o una prueba sin configurar `provider`, hacer prompts ni introducir una API key. Los proveedores son necesarios para operaciones que conversan con modelos; el registro y la ejecución directa de acciones funcionan por separado.

La receta `examples/ai/local-actions.mjs` ejecuta una suma real y comprueba rechazos antes de que el handler realice su cálculo.

[Descarga el proyecto de recetas](/examples/wu-server-recipes.zip), extrae el ZIP y ejecuta `npm install` para obtener las dependencias. Incluye las mismas acciones utilizadas por los ejemplos Node y MCP, junto con sus pruebas.

```sh
node examples/ai/local-actions.mjs
node scripts/verify-server-examples.mjs
```

<span class="section-anchor" id="section-2"></span>

## Registrar, habilitar y ejecutar

```js
import wu from 'wu-framework';

const ai = await wu.aiReady();
ai.init({ permissions: {
  executeActions: true,
  readStore: false,
  writeStore: false,
  emitEvents: false,
  readDOM: false,
  modifyDOM: false,
  readDiagnostics: false,
  navigate: false,
  network: false,
} });

ai.action('sum_local', {
  description: 'Suma dos números sin efectos externos',
  parameters: {
    type: 'object',
    properties: { a: { type: 'number' }, b: { type: 'number' } },
    required: ['a', 'b'],
  },
  handler({ a, b }) {
    if (!Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(a + b)) {
      throw new Error('Se requieren números y resultado finitos');
    }
    return { sum: a + b };
  },
});

const outcome = await ai.execute('sum_local', { a: 2, b: 3 });
// { success: true, result: { sum: 5 } }
```

Espera `aiReady()` antes de operaciones que devuelven datos. `action` devuelve el objeto IA para encadenar registros. `execute` devuelve una promesa con `{ success, result? , reason? }`; un fallo de permiso o validación normalmente se representa con `success: false`, no lanzando una excepción al caller.

`ai.tools()` expone schemas del catálogo para clientes externos. Que una herramienta aparezca en esa lista no concede permiso para ejecutarla.

<span class="section-anchor" id="section-3"></span>

## Schema, permiso y reglas de dominio

La validación de parámetros de WU es deliberadamente pequeña: comprueba campos requeridos, tipos de propiedades de primer nivel —incluido `integer`— y `enum`. No es un validador completo de JSON Schema. Reglas como límites numéricos, `additionalProperties`, formatos y validación recursiva requieren validación del producto antes del efecto.

La receta prueba un campo ausente, un string donde se esperaba un número y un valor no finito. El primer y segundo caso los rechaza WU; el tercero lo rechaza el handler con una regla explícita. Un contador demuestra que sólo el caso permitido llegó al cálculo.

```js
ai.permissions.configure({ executeActions: false });
const result = await ai.execute('sum_local', { a: 2, b: 3 });
// { success: false, reason: 'Permission denied: executeActions' }
```

Una acción puede declarar `permissions: ['readStore']` o `permissionsWhen(params)`. Los flags requeridos se comprueban antes de la validación y del handler. La API suministrada como segundo argumento sólo ofrece las operaciones declaradas y habilitadas, por ejemplo `getState`, `setState`, `emit` o `fetch`.

Mantén separados estos controles:

| Control | Decide |
| --- | --- |
| Capabilities de la app WU | Acceso técnico a store, eventos y RPC a través de su facade |
| Flags IA | Si el subsistema puede ejecutar acciones, leer DOM, escribir estado o usar red |
| Autorización de dominio | Qué operación de negocio permite el servidor al actor autenticado |
| Capacidad del ejecutor | Si el backend o runner implementa cancelación, transacción u otra operación |

Los handlers son código confiable del host. Una closure puede acceder a objetos globales fuera de la API suministrada; el registro no es un sandbox para JavaScript hostil. Para acciones con efectos de backend, usa un servicio que revalide identidad, schema, scope y autorización. Estas recetas mantienen cálculos puros y no acceden a drivers privilegiados.

<span class="section-anchor" id="section-4"></span>

## Confirmación y cleanup

```js
const stop = wu.on('ai:tool:confirm', event => {
  // La receta de rechazo no concede ninguna aprobación.
  ai.rejectTool(event.data.callId);
});

try {
  const result = await ai.execute('sum_with_confirmation', { a: 2, b: 3 });
  // success: false; reason: 'User denied action'
} finally {
  stop();
  ai.destroy();
  await wu.destroy();
}
```

La acción `sum_with_confirmation` está registrada en `examples/ai/register-actions.mjs` con `confirm: true`. Una UI que obtenga una decisión afirmativa usa `confirmTool(callId)`; `rejectTool` resuelve la denegación. Sin decisión, la confirmación vence a los 30 segundos y deniega.

Resuelve o rechaza las confirmaciones pendientes antes de destruir el subsistema y elimina las suscripciones que instaló tu integración. `destroy` limpia recursos de IA; no cancela arbitrariamente promesas o efectos capturados por handlers propios. Tampoco convierte el historial en memoria en una auditoría durable.

<span class="section-anchor" id="section-5"></span>

## MCP: del cliente al navegador vivo

El servidor MCP del paquete es un relay. El cliente habla MCP/JSON-RPC por stdio; el relay envía comandos por WebSocket al bridge de una página que tiene WU cargado. La acción se ejecuta en esa página, con sus permisos IA.

```text
Cliente MCP → stdio → wu-mcp-server → WebSocket → wu.mcp → wu.ai.execute
```

Ejecuta la receta completa:

```sh
node examples/mcp/local-relay.mjs
node scripts/verify-server-examples.mjs --mcp
```

La receta inicia el servidor empaquetado mediante el export `wu-framework/mcp-server`, sirve el bundle UMD instalado desde un listener local y abre Chromium headless. En Windows usa Edge; en otros sistemas utiliza Chromium de Playwright. Si falta el navegador de Playwright, instala sus binarios con `npx playwright install chromium`. Puedes elegir un canal instalado mediante `WU_BROWSER_CHANNEL`.

No necesita un cliente MCP externo instalado: `examples/mcp/stdio-client.mjs` implementa únicamente las operaciones JSON-RPC necesarias para inicializar, listar herramientas, invocar y cancelar una espera. Para una integración general utiliza un cliente MCP que gestione el protocolo completo.

<span class="section-anchor" id="section-6"></span>

## Conectar tu propio cliente

El paquete instala el ejecutable `wu-mcp-server`. Configura su entorno desde el host que lanza ese proceso. No pongas el token en una URL ni lo publiques en código del frontend.

| Variable | Comportamiento |
| --- | --- |
| `WU_MCP_TOKEN` | Obligatorio salvo opt-in inseguro; entre 16 y 4096 bytes |
| `WU_MCP_HOST` | `127.0.0.1` por defecto |
| `WU_MCP_PORT` | `19100`; admite 1–65535 |
| `WU_MCP_ORIGINS` | Origins HTTP(S) exactos separados por comas; sin rutas |
| `WU_MCP_TIMEOUT` | Espera de respuesta del bridge: 30000 ms por defecto, rango 50–300000 |
| `WU_MCP_AUTH_TIMEOUT` | Espera de autenticación: 5000 ms por defecto, rango 50–60000 |
| `WU_MCP_MAX_PAYLOAD` | 1048576 bytes por defecto, rango 1024–16777216 |
| `WU_MCP_INSECURE` | Sólo `1` permite omitir token; la receta mantiene autenticación obligatoria |

Sin `WU_MCP_ORIGINS`, se aceptan páginas HTTP(S) de loopback; un cliente WebSocket sin Origin no pasa esa comprobación. La receta restringe el relay al origin exacto del servidor que acaba de crear.

En la página, una integración confiable entrega el token efímero al bridge:

```js
await wu.mcp.connect(relayUrl, { token: sessionToken });
```

La promesa carga el módulo y comienza la conexión; no garantiza que el handshake ya haya terminado. Comprueba `wu.mcp.isConnected()` y consulta `wu_conexion` desde el cliente MCP para observar ambos extremos. El relay admite un bridge autenticado activo a la vez.

<span class="section-anchor" id="section-7"></span>

## Herramientas y lectura de resultados

| Herramienta | Argumentos principales |
| --- | --- |
| `wu_conexion` | `{}`; estado del relay aunque no haya página conectada |
| `wu_list_actions` | `{}`; catálogo de acciones de la página |
| `wu_execute_action` | `{ action, params? }` |
| `wu_get_state` | `{ path? }`; requiere `readStore` |
| `wu_set_state` | `{ path, value }`; requiere `writeStore` |
| `wu_emit_event` | `{ event, data? }`; rechaza namespaces `wu:`, `system:` y `app:` |

El relay también registra herramientas de estado de WU, apps, eventos, navegación, DOM, diagnóstico y montaje. `tools/list` devuelve el catálogo exacto de la versión instalada. Sus permisos continúan aplicándose dentro de la página.

Hay dos niveles de resultado: el sobre MCP y el resultado de la acción. Una acción denegada puede viajar en un sobre `isError: false` y contener `success: false` en el JSON de `content[0].text`. Comprueba ambos:

```js
const response = await client.callTool('wu_execute_action', {
  action: 'sum_local', params: { a: 2, b: 3 },
});
if (response.isError) throw new Error(response.content[0].text);
const outcome = JSON.parse(response.content[0].text);
if (!outcome.success) throw new Error(outcome.reason);
console.log(outcome.result);
```

Ese fragmento utiliza el cliente mínimo de la receta. La firma de `callTool` de otro SDK puede ser diferente.

<span class="section-anchor" id="section-8"></span>

## Cancelación que puedes observar

La receta envía `notifications/cancelled` cuando cancela la espera de un request MCP. Comprueba después que el handler `bounded_sum`, ya iniciado en el navegador, termina su demora fija y su cálculo. El relay no transmite un `AbortSignal` a `wu.ai.execute`.

Por tanto, cancelar una espera no demuestra que el trabajo se haya detenido. Para operaciones largas con efectos, diseña un servicio con identificador de operación, autorización de `status`/`cancel` y cancelación cooperativa del ejecutor. WASM/WASI puede alojar lógica portable, pero el host sigue necesitando implementar esos contratos y el transporte de red. El [servicio conectado con WU](/es/docs/guides/browser-backend/) muestra un caso diferente: conecta el signal RPC con la terminación efectiva de su Worker.

Al terminar, llama `wu.mcp.disconnect()` para detener reconexiones y liberar sus interceptores. La prueba comprueba que `fetch` y `console.log` recuperan sus referencias previas, que el relay ya no ve una app conectada y que se cierran los procesos creados. Consulta [extensiones e IA](/es/docs/framework/advanced/) y [seguridad](/es/docs/framework/security/) para los límites del runtime.