WU / DOCSUNIVERSAL MICROFRONTENDS
EN

MCP API: bridge, relay, and tools

Reference for the browser bridge, relay Node exports, and the 18 MCP tools published by WU.

Three interfaces with different responsibilities

WU integrates a browser bridge and a Node MCP server. The tool client speaks JSON-RPC over stdio to the server; the server uses an authenticated WebSocket connection to request operations from the connected page.

Interface Environment Access
wu.mcp Browser connect, disconnect, isConnected
wu-framework/mcp-server Node Configuration, listener, server, and protocol helpers
wu_* tools MCP client Catalog published through tools/list

Importing wu-framework/mcp-server does not start a listener or take over stdin/stdout. Running wu-mcp-server, or calling main(env), does start the service. The runnable AI/MCP recipe connects every endpoint with a real browser and a local action without an LLM provider.

Browser bridge

import wu from 'wu-framework';

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

// Consulta de estado; connect no espera el resultado de autenticación.
const connected = wu.mcp.isConnected();

// Al retirar la integración:
wu.mcp.disconnect();

relayUrl and sessionToken come from a trusted host integration. The token must not appear in URLs, public bundles, or persisted store state.

Method Parameters Return and lifecycle
connect(url?, options?) WebSocket URL; default ws://localhost:19100; token required except with { insecure: true } on loopback Loading/startup Promise<void>. Begins the handshake after opening the socket
disconnect() None void. Closes the socket, stops reconnecting, and releases the bridge subscription and interceptors
isConnected() None boolean. Checks an open socket and authentication state known to the client

The 4.0 bridge authenticates the relay with a mutual HMAC v2 handshake without sending the token. Upgrade bridge and relay together: relays before 4.0 do not understand v2; the new relay retains v1 for older bridges. Missing tokens fail with WU_MCP_TOKEN_REQUIRED. Local mode requires { insecure: true } and WU_MCP_INSECURE=1 on the relay. Outside loopback, wss:// is required; an insecure URL fails with WU_MCP_INSECURE_URL.

The bridge avoids opening another connection while connecting or connected. Accidental disconnection may trigger reconnection; disconnect() cancels that intent. Closing MCP does not automatically unmount every app or cancel arbitrary promises from handlers already started.

Server configuration

import { loadConfig } from 'wu-framework/mcp-server';

const config = loadConfig(process.env);

loadConfig validates variables and returns an immutable object. It neither prints nor exposes the token to a client. Invalid values throw instead of silently falling back to defaults.

Variable Default Contract
WU_MCP_TOKEN Required UTF-8 string of 16–4096 bytes
WU_MCP_HOST 127.0.0.1 Listener host; no spaces or /
WU_MCP_PORT 19100 Integer 1–65535
WU_MCP_TIMEOUT 30000 Per-command bridge timeout, 50–300000 ms
WU_MCP_AUTH_TIMEOUT 5000 Authentication time, 50–60000 ms
WU_MCP_MAX_PAYLOAD 1048576 WebSocket limit, 1024–16777216 bytes
WU_MCP_ORIGINS HTTP(S) loopback Exact comma-separated origins, without a path
WU_MCP_INSECURE Disabled Only 1 permits omitting the token. If a token exists, it remains required

The result contains host, port, timeout, authTimeout, maxPayload, token, insecure, and allowedOrigins. The last property is a Set or null, not an array sent to the browser.

Origin is checked during the WebSocket HTTP upgrade. A missing or disallowed Origin receives 403. The authentication handshake then completes. The relay permits one active authenticated bridge at a time; knowing the token does not allow silently replacing an existing session.

Puente listener

import { Puente, loadConfig } from 'wu-framework/mcp-server';

const puente = new Puente(loadConfig(process.env));
await puente.ready();

// Estas consultas no requieren una página autenticada.
const address = puente.address();
const connected = puente.conectado();

// Cuando el host termine su sesión:
await puente.close();

Construction starts the listener. ready() resolves when it can listen and rejects bind failures; it does not wait for a page. You may inject { logger } as the constructor’s second argument; it must expose error(...args). Service logs must go to stderr to avoid corrupting the stdout protocol.

Method Return Failures or limits
ready() Promise<Puente> Rejects startup failure
address() Listener address Does not indicate whether a page is connected
conectado() boolean Instantaneous read; the session may close afterward
enviar(command, params = {}) Promise<unknown> Rejects without a bridge, on disconnection, timeout, or response error
close() Promise<void> Rejects pending requests, closes sockets and listener; does not restart the service

enviar receives the bridge command name, for example list_actions. Its corresponding external MCP name is wu_list_actions. Do not interchange these levels.

Each request keeps an identifier, timeout, and the socket that must respond. The relay removes the pending entry on completion or failure. close() releases pending requests and forcibly terminates sockets that have not closed after the shutdown grace period.

Constructing or starting the MCP server

import { main } from 'wu-framework/mcp-server';

// El entorno debe contener la configuración de autenticación.
await main(process.env);

main(env) creates the listener, awaits startup, registers tools, and connects stdio transport. It installs SIGINT/SIGTERM handlers to close the server and bridge. It resolves startup and does not return a session-management object. From an external client, the equivalent executable is wu-mcp-server.

For a host explicitly managing its lifecycle:

Export Contract
createMcpServer(puente, config) Returns a server with connect(transport) and close(); registers the 18 tools
createCommandRunner(puente) Returns a (commandName, params?) => Promise<WuMcpToolResult> function adapting the bridge response

The host supplies compatible MCP transport to connect. When closing an embedded integration, close both the MCP server and Puente; neither replaces the other’s lifecycle. The downloadable recipe uses the packaged executable and a minimal stdio client without importing private classes from the bundled SDK.

Tool catalog

This is the server’s registered catalog. Tool parameters pass through relay schemas; execution permissions are checked again in the bridge or wu.ai.execute.

Tool Arguments Main result Page permission
wu_conexion {} { puerto, host, requiere_token, app_conectada } Does not need a connected page
wu_list_actions {} { actions, count } Authenticated bridge; listing grants no execution permission
wu_execute_action { action, params? } { action, success, result?, reason? } executeActions and action permissions
wu_status {} Framework, apps, root keys, action/event count Authenticated bridge; root keys require readStore
wu_list_apps {} App descriptors and state Authenticated bridge
wu_list_events { limit? } Recent redacted events readDiagnostics
wu_get_state { path? } { path, value } readStore
wu_set_state { path, value } { path, value, updated: true } writeStore
wu_emit_event { event, data? } { emitted, data } emitEvents; internal namespaces prohibited
wu_navigate { route } { navigated: route } emitEvents; writeStore to update currentPath
wu_snapshot { appName? } App, accessible tree, and timestamp readDOM
wu_screenshot { selector?, quality? } DOM capture, metadata, or error readDOM
wu_click { selector?, text? } Activated element or error modifyDOM
wu_type { selector, text, clear?, submit? } Write result or error modifyDOM
wu_console { level?, limit? } Redacted console entries readDiagnostics
wu_network { method?, status?, limit? } Filtered network metadata readDiagnostics
wu_mount_app { appName, container? } App mounted modifyDOM and the normal mounting contract
wu_unmount_app { appName } App unmounted modifyDOM

Parameter details:

  • Event limit: integer 1–200, default 20. The ring holds at most 200 entries.
  • Console and network limit: integer 1–500, default 50. Console accepts level: 'all' | 'log' | 'warn' | 'error', default all; network accepts status from 100 to 599.
  • quality: 0–1, bridge default 0.8. The helper uses DOM/foreignObject capture and PNG by default; this quality does not change PNG compression or guarantee privileged-capture fidelity.
  • clear and submit: false by default. Typing may emit control events; submit: true may trigger form submission.
  • event: string of 1–200 characters. The wu:, system:, and app: prefixes are rejected in the relay and bridge.
  • An empty read path selects the root; writing requires a nonempty path. Diagnostic data is redacted before leaving the bridge.

wu_navigate emits shell:navigate: the shell must handle that event. The result alone does not prove the page changed, and the gate used is emitEvents, not the AI navigate flag. wu_status returns storeKeys: [] when readStore is disabled.

JSON-RPC and negative results

After initializing the MCP session, an action call has this form:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "wu_execute_action",
    "arguments": { "action": "sum_local", "params": { "a": 2, "b": 3 } }
  }
}

The server delivers text in content[0].text. createCommandRunner sets isError when the bridge returns an error field or transport fails. An action result with success: false may remain inside an isError: false envelope.

Check the MCP and action layers separately. Do not interpret successful HTTP, an open socket, or an envelope without isError as proof that the handler was authorized and completed successfully.

tools/list lists tools in the installed version. wu_list_actions lists a different catalog: handlers the page registered in the AI subsystem. Executing an action requires both registrations and appropriate permissions.

Authentication and naming helpers

Export Signature Behavior
isOriginAllowed (origin, allowedOrigins = null) => boolean With null, allows only exact HTTP(S) loopback origins
tokenMatches (candidate, expectedDigest) => boolean Compares the candidate’s SHA-256 with an expected 32-byte digest using timingSafeEqual
isReservedEvent (eventName) => boolean Detects the three reserved internal namespaces

tokenMatches expects a digest, not a plaintext token, as its second argument. An incorrectly sized digest may cause a comparison error. These helpers open no sockets and grant no domain permissions.

Cancellation, security, and tests

This implementation’s protocol does not propagate MCP cancellation to the wu.ai.execute handler. The recipe cancels a wait and observes that the calculation already started continues. disconnect and close release transport and pending requests, but do not compensate effects.

Use bounded custom actions and an authenticated service for privileged operations. Browser AI flags are technical controls for that page; they do not replace business authorization, idempotency, or durable operation records.

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

The test includes handshake, wrong token, real tools/actions, summation, invalid schema, denied permissions, reserved events, observable cancellation, and cleanup. The token is generated in memory, all listeners are local, and no LLM provider is required. You can download the complete recipes or review runnable AI actions and MCP.

BUILD WU WITH US

Found a bug? Have an idea?

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