API MCP: bridge, relay y herramientas
Referencia del bridge del navegador, exports Node del relay y las 18 herramientas MCP publicadas por WU.
Tres interfaces con responsabilidades distintas
WU integra un bridge en el navegador y un servidor MCP en Node. El cliente de herramientas habla JSON-RPC por stdio con el servidor; éste utiliza un socket WebSocket autenticado para pedir operaciones a la página conectada.
| Interfaz | Entorno | Acceso |
|---|---|---|
wu.mcp |
Navegador | connect, disconnect, isConnected |
wu-framework/mcp-server |
Node | Configuración, listener, servidor y helpers de protocolo |
Herramientas wu_* |
Cliente MCP | Catálogo publicado mediante tools/list |
Importar wu-framework/mcp-server no inicia un listener ni toma stdin/stdout. Ejecutar wu-mcp-server, o llamar main(env), sí inicia el servicio. La receta ejecutable de IA/MCP conecta todos los extremos con un navegador real y una action local sin proveedor LLM.
Bridge del navegador
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 y sessionToken provienen de una integración confiable del host. El token no debe formar parte de URLs, bundles públicos ni estado persistido en el store.
| Método | Parámetros | Retorno y ciclo de vida |
|---|---|---|
connect(url?, options?) |
URL WebSocket; default ws://localhost:19100; token obligatorio salvo { insecure: true } en loopback |
Promise<void> de carga/inicio. Inicia el handshake después de abrir el socket |
disconnect() |
Ninguno | void. Cierra el socket, detiene reconexiones y libera suscripción e interceptores del bridge |
isConnected() |
Ninguno | boolean. Comprueba socket abierto y estado de autenticación conocido por el cliente |
El bridge 4.0 autentica al relay mediante un handshake HMAC mutuo v2 sin enviar el token. Actualiza bridge y relay juntos: un relay anterior a 4.0 no entiende v2; el relay nuevo conserva v1 para bridges antiguos. Sin token falla con WU_MCP_TOKEN_REQUIRED. El modo local exige { insecure: true } y WU_MCP_INSECURE=1 en el relay. Fuera de loopback exige wss://; una URL insegura falla con WU_MCP_INSECURE_URL.
El bridge evita abrir otra conexión si ya está conectando o conectado. Una desconexión accidental puede iniciar reconexiones; disconnect() cancela esa intención. Cerrar MCP no desmonta automáticamente todas las apps ni cancela promesas arbitrarias de handlers ya iniciados.
Configuración del servidor
import { loadConfig } from 'wu-framework/mcp-server';
const config = loadConfig(process.env);
loadConfig valida las variables y devuelve un objeto inmutable. No imprime ni expone el token a un cliente. Los valores inválidos lanzan un error en lugar de caer silenciosamente al default.
| Variable | Default | Contrato |
|---|---|---|
WU_MCP_TOKEN |
Obligatorio | String de 16–4096 bytes UTF-8 |
WU_MCP_HOST |
127.0.0.1 |
Host del listener; sin espacios ni / |
WU_MCP_PORT |
19100 |
Entero 1–65535 |
WU_MCP_TIMEOUT |
30000 |
Timeout por comando al bridge, 50–300000 ms |
WU_MCP_AUTH_TIMEOUT |
5000 |
Tiempo para autenticarse, 50–60000 ms |
WU_MCP_MAX_PAYLOAD |
1048576 |
Límite WebSocket, 1024–16777216 bytes |
WU_MCP_ORIGINS |
HTTP(S) loopback | Lista de origins exactos separados por comas, sin ruta |
WU_MCP_INSECURE |
Desactivado | Sólo 1 permite omitir token. Si hay un token, éste sigue siendo requerido |
El resultado contiene host, port, timeout, authTimeout, maxPayload, token, insecure y allowedOrigins. Esta última propiedad es un Set o null, no un array que se envíe al navegador.
La comprobación de Origin sucede en el upgrade HTTP de WebSocket. Un Origin ausente o no permitido recibe 403. Después se completa el handshake de autenticación. El relay admite un bridge autenticado activo a la vez; conocer el token no permite reemplazar silenciosamente una sesión que ya está activa.
Listener Puente
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();
La construcción inicia el listener. ready() resuelve cuando puede escuchar y rechaza ante un fallo de bind; no espera una página. Puedes inyectar { logger } como segundo argumento del constructor; debe exponer error(...args). Los logs del servicio deben ir a stderr para no corromper el protocolo de stdout.
| Método | Retorno | Fallos o límites |
|---|---|---|
ready() |
Promise<Puente> |
Rechaza si falla el arranque |
address() |
Dirección del listener | No indica si hay una página conectada |
conectado() |
boolean |
Lectura instantánea; la sesión puede cerrarse después |
enviar(command, params = {}) |
Promise<unknown> |
Rechaza sin bridge, por desconexión, timeout o error de respuesta |
close() |
Promise<void> |
Rechaza pendientes, cierra sockets y listener; no vuelve a iniciar el servicio |
enviar recibe el nombre del comando del bridge, por ejemplo list_actions. El nombre MCP externo correspondiente es wu_list_actions. No intercambies ambos niveles.
Cada request mantiene un identificador, un timeout y el socket que debe responder. El relay retira la entrada pendiente al completarse o fallar. close() libera pendientes y fuerza la terminación de sockets que no hayan cerrado tras el margen de cierre.
Construir o iniciar el servidor MCP
import { main } from 'wu-framework/mcp-server';
// El entorno debe contener la configuración de autenticación.
await main(process.env);
main(env) crea el listener, espera su arranque, registra herramientas y conecta el transporte stdio. Instala manejo de SIGINT/SIGTERM para cerrar servidor y puente. Resuelve el arranque y no devuelve un objeto para gestionar la sesión. Desde un cliente externo, el ejecutable equivalente es wu-mcp-server.
Para un host que administra explícitamente su lifecycle:
| Export | Contrato |
|---|---|
createMcpServer(puente, config) |
Devuelve un servidor con connect(transport) y close(); registra las 18 herramientas |
createCommandRunner(puente) |
Devuelve una función (commandName, params?) => Promise<WuMcpToolResult> que adapta la respuesta del bridge |
El host suministra un transporte MCP compatible a connect. Al cerrar una integración embebida, cierra tanto el servidor MCP como Puente; uno no reemplaza el lifecycle del otro. La receta descargable utiliza el ejecutable empaquetado y un cliente stdio mínimo, sin necesitar importar clases privadas del SDK incluido en el bundle.
Catálogo de herramientas
Este es el catálogo registrado por el servidor. Los parámetros de herramientas pasan por los schemas del relay; los permisos de ejecución se comprueban de nuevo en el bridge o en wu.ai.execute.
| Herramienta | Argumentos | Resultado principal | Permiso de página |
|---|---|---|---|
wu_conexion |
{} |
{ puerto, host, requiere_token, app_conectada } |
No necesita página conectada |
wu_list_actions |
{} |
{ actions, count } |
Bridge autenticado; listar no concede ejecución |
wu_execute_action |
{ action, params? } |
{ action, success, result?, reason? } |
executeActions y permisos de la action |
wu_status |
{} |
Framework, apps, claves raíz, cantidad de acciones/eventos | Bridge autenticado; las claves raíz requieren readStore |
wu_list_apps |
{} |
Descriptores y estado de apps | Bridge autenticado |
wu_list_events |
{ limit? } |
Eventos recientes redactados | 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; namespaces internos prohibidos |
wu_navigate |
{ route } |
{ navigated: route } |
emitEvents; writeStore para actualizar currentPath |
wu_snapshot |
{ appName? } |
App, árbol accesible y timestamp | readDOM |
wu_screenshot |
{ selector?, quality? } |
Captura DOM, metadatos o error | readDOM |
wu_click |
{ selector?, text? } |
Elemento activado o error | modifyDOM |
wu_type |
{ selector, text, clear?, submit? } |
Resultado de escritura o error | modifyDOM |
wu_console |
{ level?, limit? } |
Entradas de consola redactadas | readDiagnostics |
wu_network |
{ method?, status?, limit? } |
Metadatos de red filtrados | readDiagnostics |
wu_mount_app |
{ appName, container? } |
App montada | modifyDOM y el contrato normal de montaje |
wu_unmount_app |
{ appName } |
App desmontada | modifyDOM |
Detalles de parámetros:
limitde eventos: entero de 1–200, default 20. El ring contiene como máximo 200 entradas.limitde consola y red: entero de 1–500, default 50. Consola aceptalevel: 'all' | 'log' | 'warn' | 'error', defaultall; red aceptastatusentre 100 y 599.quality: 0–1, default 0.8 del bridge. El helper usa captura DOM/foreignObject y PNG por defecto; esa calidad no modifica la compresión PNG ni garantiza fidelidad de captura privilegiada.clearysubmit:falsepor defecto. Escribir puede emitir eventos del control;submit: truepuede activar el envío de un formulario.event: string de 1–200 caracteres. Los prefijoswu:,system:yapp:son rechazados en el relay y el bridge.pathvacío en lectura selecciona la raíz; escritura exige un path no vacío. Los datos diagnósticos pasan por redacción antes de salir del bridge.
wu_navigate emite shell:navigate: el shell debe manejar ese evento. El resultado no demuestra por sí solo que se haya cambiado la página y el gate utilizado es emitEvents, no el flag IA navigate. wu_status devuelve storeKeys: [] cuando readStore está desactivado.
JSON-RPC y resultados negativos
Después de inicializar la sesión MCP, una llamada a una action tiene esta forma:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "wu_execute_action",
"arguments": { "action": "sum_local", "params": { "a": 2, "b": 3 } }
}
}
El servidor entrega contenido textual en content[0].text. La función createCommandRunner marca isError cuando el bridge devuelve un campo error, o cuando falla el transporte. Un resultado de acción con success: false puede permanecer dentro de un sobre isError: false.
Comprueba la capa MCP y la capa de la action por separado. No interpretes un HTTP correcto, un socket abierto o un sobre sin isError como prueba de que el handler obtuvo autorización y terminó con éxito.
tools/list enumera herramientas de la versión instalada. wu_list_actions enumera otro catálogo: los handlers que la página registró en el subsistema IA. Para ejecutar una action hacen falta ambos registros y los permisos adecuados.
Helpers de autenticación y nombres
| Export | Firma | Comportamiento |
|---|---|---|
isOriginAllowed |
(origin, allowedOrigins = null) => boolean |
Con null, permite únicamente origins HTTP(S) exactos de loopback |
tokenMatches |
(candidate, expectedDigest) => boolean |
Compara el SHA-256 del candidato con un digest esperado de 32 bytes usando timingSafeEqual |
isReservedEvent |
(eventName) => boolean |
Detecta los tres namespaces internos reservados |
tokenMatches espera un digest, no el token en texto como segundo argumento. Un digest de tamaño incorrecto puede provocar un error de comparación. Estos helpers no abren sockets ni conceden permisos de dominio.
Cancelación, seguridad y pruebas
El protocolo de esta implementación no propaga una cancelación MCP hasta el handler de wu.ai.execute. La receta cancela una espera y observa que el cálculo que ya había comenzado continúa. disconnect y close liberan transporte y pendientes, pero no implementan compensación de efectos.
Usa acciones propias acotadas y un servicio autenticado para operaciones privilegiadas. Los flags IA del browser son controles técnicos de esa página; no sustituyen autorización de negocio, idempotencia ni un registro durable de operaciones.
node examples/mcp/local-relay.mjs
node scripts/verify-server-examples.mjs --mcp
La prueba incluye handshake, token incorrecto, herramientas/acciones reales, suma, schema inválido, permisos denegados, evento reservado, cancelación observable y cleanup. El token se genera en memoria, todos los listeners son locales y no se requiere proveedor LLM. Puedes descargar las recetas completas o revisar acciones IA y MCP ejecutables.