WU / DOCSUNIVERSAL MICROFRONTENDS
EN

Runnable AI actions and MCP

Register and execute WU actions without a provider; connect a real MCP relay and check schema, permissions, authentication, cancellation, and cleanup.

An action does not need an LLM provider

wu.ai.execute invokes a registered handler. You can use it from your own interface, an external agent, or a test without configuring provider, writing prompts, or supplying an API key. Providers are needed for operations that converse with models; action registration and direct execution work separately.

The examples/ai/local-actions.mjs recipe performs a real sum and checks rejections before the handler calculates it.

Download the recipes project, extract the ZIP, and run npm install for dependencies. It includes the same actions used by the Node and MCP examples, along with their tests.

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

Register, enable, and execute

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 } }

Await aiReady() before operations returning data. action returns the AI object for chained registrations. execute returns a promise containing { success, result? , reason? }; a permission or validation failure is normally represented by success: false, rather than throwing an exception to the caller.

ai.tools() exposes catalog schemas for external clients. A tool appearing in that list does not grant permission to execute it.

Schema, permissions, and domain rules

WU parameter validation is deliberately small: it checks required fields, top-level property types—including integer—and enum. It is not a complete JSON Schema validator. Rules such as numeric bounds, additionalProperties, formats, and recursive validation require product validation before effects occur.

The recipe tests a missing field, a string where a number was expected, and a non-finite value. WU rejects the first two cases; the handler rejects the third using an explicit rule. A counter demonstrates that only the allowed case reached the calculation.

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

An action can declare permissions: ['readStore'] or permissionsWhen(params). Required flags are checked before validation and the handler. The API supplied as the second argument offers only declared and enabled operations, such as getState, setState, emit, or fetch.

Keep these controls separate:

Control Decides
WU app capabilities Technical access to store, events, and RPC through its facade
AI flags Whether the subsystem may execute actions, read DOM, write state, or use the network
Domain authorization Which business operation the server permits for the authenticated actor
Executor capability Whether the backend or runner implements cancellation, transactions, or another operation

Handlers are trusted host code. A closure can access global objects outside the supplied API; registration is not a sandbox for hostile JavaScript. For actions with backend effects, use a service that revalidates identity, schema, scope, and authorization. These recipes keep calculations pure and do not access privileged drivers.

Confirmation and cleanup

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();
}

The sum_with_confirmation action is registered in examples/ai/register-actions.mjs with confirm: true. A UI obtaining an affirmative decision uses confirmTool(callId); rejectTool resolves denial. Without a decision, confirmation expires after 30 seconds and denies.

Resolve or reject pending confirmations before destroying the subsystem, and remove subscriptions installed by your integration. destroy cleans AI resources; it does not arbitrarily cancel promises or effects captured by your own handlers. It also does not turn in-memory history into a durable audit.

MCP: from the client to the live browser

The package’s MCP server is a relay. The client speaks MCP/JSON-RPC over stdio; the relay sends commands over WebSocket to the bridge on a page with WU loaded. The action executes on that page under its AI permissions.

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

Run the complete recipe:

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

The recipe starts the packaged server through the wu-framework/mcp-server export, serves the installed UMD bundle from a local listener, and opens headless Chromium. It uses Edge on Windows and Playwright Chromium on other systems. If the Playwright browser is missing, install its binaries with npx playwright install chromium. You can choose an installed channel through WU_BROWSER_CHANNEL.

It does not require an installed external MCP client: examples/mcp/stdio-client.mjs implements only the JSON-RPC operations needed to initialize, list tools, invoke, and cancel a wait. For general integration, use an MCP client handling the full protocol.

Connecting your own client

The package installs the wu-mcp-server executable. Configure its environment from the host launching that process. Do not place the token in a URL or publish it in frontend code.

Variable Behavior
WU_MCP_TOKEN Required unless insecure mode is explicitly enabled; 16–4096 bytes
WU_MCP_HOST 127.0.0.1 by default
WU_MCP_PORT 19100; accepts 1–65535
WU_MCP_ORIGINS Exact comma-separated HTTP(S) origins; no paths
WU_MCP_TIMEOUT Bridge response wait: 30000 ms default, range 50–300000
WU_MCP_AUTH_TIMEOUT Authentication wait: 5000 ms default, range 50–60000
WU_MCP_MAX_PAYLOAD 1048576 bytes default, range 1024–16777216
WU_MCP_INSECURE Only 1 allows omitting the token; the recipe keeps authentication mandatory

Without WU_MCP_ORIGINS, HTTP(S) loopback pages are accepted; a WebSocket client without Origin fails that check. The recipe restricts the relay to the exact origin of the server it just created.

On the page, a trusted integration supplies the ephemeral token to the bridge:

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

The promise loads the module and starts connecting; it does not guarantee the handshake has finished. Check wu.mcp.isConnected() and query wu_conexion from the MCP client to observe both ends. The relay supports one active authenticated bridge at a time.

Tools and reading results

Tool Main arguments
wu_conexion {}; relay state even without a connected page
wu_list_actions {}; page action catalog
wu_execute_action { action, params? }
wu_get_state { path? }; requires readStore
wu_set_state { path, value }; requires writeStore
wu_emit_event { event, data? }; rejects wu:, system:, and app: namespaces

The relay also registers tools for WU state, apps, events, navigation, DOM, diagnostics, and mounting. tools/list returns the exact catalog for the installed version. Its permissions remain enforced inside the page.

There are two result levels: the MCP envelope and the action result. A denied action may travel in an isError: false envelope while containing success: false in the JSON of content[0].text. Check both:

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);

This snippet uses the recipe’s minimal client. Another SDK’s callTool signature may differ.

Observable cancellation

The recipe sends notifications/cancelled when canceling the wait for an MCP request. It then checks that the bounded_sum handler, already started in the browser, completes its fixed delay and calculation. The relay does not pass an AbortSignal to wu.ai.execute.

Canceling a wait therefore does not prove work stopped. For long-running operations with effects, design a service with an operation identifier, status/cancel authorization, and cooperative executor cancellation. WASM/WASI can host portable logic, but the host must still implement those contracts and network transport. The WU-connected service shows a different case: it connects the RPC signal to effective Worker termination.

When finished, call wu.mcp.disconnect() to stop reconnecting and release its interceptors. The test checks that fetch and console.log recover their previous references, the relay no longer sees a connected app, and created processes close. See extensions and AI and security for runtime limits.

BUILD WU WITH US

Found a bug? Have an idea?

Your experience improves WU. Tell us what went wrong or help support its development.