# API MCP: bridge, relay e ferramentas

Referência do bridge do navegador, exportações Node do relay e das 18 ferramentas MCP publicadas pelo WU.

<span id="tres-interfaces-con-responsabilidades-distintas"></span>

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

## Três interfaces com responsabilidades diferentes

O WU integra um bridge no navegador e um servidor MCP no Node. O cliente de ferramentas fala JSON-RPC por stdio com o servidor; este usa um socket WebSocket autenticado para solicitar operações à página conectada.

| Interface | Ambiente | Acesso |
| --- | --- | --- |
| `wu.mcp` | Navegador | `connect`, `disconnect`, `isConnected` |
| `wu-framework/mcp-server` | Node | Configuração, listener, servidor e helpers de protocolo |
| Ferramentas `wu_*` | Cliente MCP | Catálogo publicado por `tools/list` |

Importar `wu-framework/mcp-server` não inicia um listener nem assume stdin/stdout. Executar `wu-mcp-server`, ou chamar `main(env)`, inicia o serviço. A [receita executável de IA/MCP](/pt/docs/framework/ai-recipes/) conecta todas as pontas com um navegador real e uma action local sem provedor LLM.

<span id="bridge-del-navegador"></span>

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

## Bridge do navegador

```js
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` e `sessionToken` vêm de uma integração confiável do host. O token não deve fazer parte de URLs, bundles públicos ou estado persistido no store.

| Método | Parâmetros | Retorno e ciclo de vida |
| --- | --- | --- |
| `connect(url?, options?)` | URL WebSocket; padrão `ws://localhost:19100`; token obrigatório, exceto com `{ insecure: true }` em loopback | `Promise<void>` de carregamento/início. Inicia o handshake após abrir o socket |
| `disconnect()` | Nenhum | `void`. Fecha o socket, interrompe reconexões e libera a inscrição e os interceptores do bridge |
| `isConnected()` | Nenhum | `boolean`. Verifica o socket aberto e o estado de autenticação conhecido pelo cliente |

O bridge 4.0 autentica o relay com um handshake HMAC mútuo v2 sem enviar o token. Atualize bridge e relay juntos: relays anteriores ao 4.0 não entendem v2; o novo relay mantém v1 para bridges antigos. A ausência de token falha com `WU_MCP_TOKEN_REQUIRED`. O modo local exige `{ insecure: true }` e `WU_MCP_INSECURE=1` no relay. Fora de loopback exige `wss://`; uma URL insegura falha com `WU_MCP_INSECURE_URL`.

O bridge evita abrir outra conexão se já estiver conectando ou conectado. Uma desconexão acidental pode iniciar reconexões; `disconnect()` cancela essa intenção. Fechar o MCP não desmonta automaticamente todas as apps nem cancela promises arbitrárias de handlers já iniciados.

<span id="configuración-del-servidor"></span>

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

## Configuração do servidor

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

const config = loadConfig(process.env);
```

`loadConfig` valida as variáveis e retorna um objeto imutável. Não imprime nem expõe o token a um cliente. Valores inválidos lançam um erro em vez de recorrer silenciosamente ao padrão.

| Variável | Padrão | Contrato |
| --- | --- | --- |
| `WU_MCP_TOKEN` | Obrigatório | String de 16–4096 bytes UTF-8 |
| `WU_MCP_HOST` | `127.0.0.1` | Host do listener; sem espaços nem `/` |
| `WU_MCP_PORT` | `19100` | Inteiro 1–65535 |
| `WU_MCP_TIMEOUT` | `30000` | Timeout por comando ao bridge, 50–300000 ms |
| `WU_MCP_AUTH_TIMEOUT` | `5000` | Tempo para autenticar, 50–60000 ms |
| `WU_MCP_MAX_PAYLOAD` | `1048576` | Limite WebSocket, 1024–16777216 bytes |
| `WU_MCP_ORIGINS` | HTTP(S) loopback | Lista de origens exatas separadas por vírgulas, sem caminho |
| `WU_MCP_INSECURE` | Desativado | Apenas `1` permite omitir o token. Se houver um token, ele continua obrigatório |

O resultado contém `host`, `port`, `timeout`, `authTimeout`, `maxPayload`, `token`, `insecure` e `allowedOrigins`. Esta última propriedade é um `Set` ou `null`, não um array enviado ao navegador.

Origin é verificado durante o upgrade HTTP do WebSocket. Um Origin ausente ou não permitido recebe 403. Depois, o handshake de autenticação é concluído. O relay permite um bridge autenticado ativo por vez; conhecer o token não permite substituir silenciosamente uma sessão já ativa.

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

## Listener Puente

```js
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();
```

A construção inicia o listener. `ready()` resolve quando pode escutar e rejeita em caso de falha de bind; não espera uma página. Você pode injetar `{ logger }` como segundo argumento do construtor; ele deve expor `error(...args)`. Os logs do serviço devem ir para stderr para não corromper o protocolo de stdout.

| Método | Retorno | Falhas ou limites |
| --- | --- | --- |
| `ready()` | `Promise<Puente>` | Rejeita se a inicialização falhar |
| `address()` | Endereço do listener | Não indica se há uma página conectada |
| `conectado()` | `boolean` | Leitura instantânea; a sessão pode fechar depois |
| `enviar(command, params = {})` | `Promise<unknown>` | Rejeita sem bridge, por desconexão, timeout ou erro de resposta |
| `close()` | `Promise<void>` | Rejeita pendências, fecha sockets e listener; não reinicia o serviço |

`enviar` recebe o nome do comando do bridge, por exemplo `list_actions`. O nome MCP externo correspondente é `wu_list_actions`. Não confunda os dois níveis.

Cada solicitação mantém um identificador, um timeout e o socket que deve responder. O relay remove a entrada pendente ao concluir ou falhar. `close()` libera as pendências e força o encerramento de sockets que não fecharam após o prazo de fechamento.

<span id="construir-o-iniciar-el-servidor-mcp"></span>

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

## Construir ou iniciar o servidor MCP

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

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

`main(env)` cria o listener, aguarda sua inicialização, registra ferramentas e conecta o transporte stdio. Instala o tratamento de SIGINT/SIGTERM para fechar servidor e ponte. Resolve a inicialização e não retorna um objeto para gerenciar a sessão. Em um cliente externo, o executável equivalente é `wu-mcp-server`.

Para um host que administra explicitamente seu ciclo de vida:

| Exportação | Contrato |
| --- | --- |
| `createMcpServer(puente, config)` | Retorna um servidor com `connect(transport)` e `close()`; registra as 18 ferramentas |
| `createCommandRunner(puente)` | Retorna uma função `(commandName, params?) => Promise<WuMcpToolResult>` que adapta a resposta do bridge |

O host fornece um transporte MCP compatível a `connect`. Ao fechar uma integração embarcada, feche tanto o servidor MCP quanto `Puente`; um não substitui o ciclo de vida do outro. A receita para download usa o executável empacotado e um cliente stdio mínimo, sem precisar importar classes privadas do SDK incluído no bundle.

<span id="catálogo-de-herramientas"></span>

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

## Catálogo de ferramentas

Este é o catálogo registrado pelo servidor. Os parâmetros das ferramentas passam pelos schemas do relay; as permissões de execução são verificadas novamente no bridge ou em `wu.ai.execute`.

| Ferramenta | Argumentos | Resultado principal | Permissão da página |
| --- | --- | --- | --- |
| `wu_conexion` | `{}` | `{ puerto, host, requiere_token, app_conectada }` | Não precisa de página conectada |
| `wu_list_actions` | `{}` | `{ actions, count }` | Bridge autenticado; listar não concede execução |
| `wu_execute_action` | `{ action, params? }` | `{ action, success, result?, reason? }` | `executeActions` e permissões da action |
| `wu_status` | `{}` | Framework, apps, chaves raiz, quantidade de ações/eventos | Bridge autenticado; chaves raiz exigem `readStore` |
| `wu_list_apps` | `{}` | Descritores e estado das apps | Bridge autenticado |
| `wu_list_events` | `{ limit? }` | Eventos recentes com dados sensíveis ocultados | `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 proibidos |
| `wu_navigate` | `{ route }` | `{ navigated: route }` | `emitEvents`; `writeStore` para atualizar `currentPath` |
| `wu_snapshot` | `{ appName? }` | App, árvore acessível e timestamp | `readDOM` |
| `wu_screenshot` | `{ selector?, quality? }` | Captura DOM, metadados ou erro | `readDOM` |
| `wu_click` | `{ selector?, text? }` | Elemento ativado ou erro | `modifyDOM` |
| `wu_type` | `{ selector, text, clear?, submit? }` | Resultado da escrita ou erro | `modifyDOM` |
| `wu_console` | `{ level?, limit? }` | Entradas do console com dados sensíveis ocultados | `readDiagnostics` |
| `wu_network` | `{ method?, status?, limit? }` | Metadados de rede filtrados | `readDiagnostics` |
| `wu_mount_app` | `{ appName, container? }` | App montada | `modifyDOM` e o contrato normal de montagem |
| `wu_unmount_app` | `{ appName }` | App desmontada | `modifyDOM` |

Detalhes dos parâmetros:

- `limit` de eventos: inteiro de 1–200, padrão 20. O buffer circular contém no máximo 200 entradas.
- `limit` de console e rede: inteiro de 1–500, padrão 50. O console aceita `level: 'all' | 'log' | 'warn' | 'error'`, padrão `all`; a rede aceita `status` entre 100 e 599.
- `quality`: 0–1, padrão 0.8 do bridge. O helper usa captura DOM/foreignObject e PNG por padrão; essa qualidade não modifica a compressão PNG nem garante a fidelidade de uma captura privilegiada.
- `clear` e `submit`: `false` por padrão. Escrever pode emitir eventos do controle; `submit: true` pode acionar o envio de um formulário.
- `event`: string de 1–200 caracteres. Os prefixos `wu:`, `system:` e `app:` são rejeitados no relay e no bridge.
- `path` vazio na leitura seleciona a raiz; a escrita exige um path não vazio. Os dados de diagnóstico passam pela ocultação de dados sensíveis antes de sair do bridge.

`wu_navigate` emite `shell:navigate`: o shell deve tratar esse evento. O resultado isolado não prova que a página mudou, e o gate usado é `emitEvents`, não o flag IA `navigate`. `wu_status` retorna `storeKeys: []` quando `readStore` está desativado.

<span id="json-rpc-y-resultados-negativos"></span>

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

## JSON-RPC e resultados negativos

Após inicializar a sessão MCP, uma chamada a uma action tem esta forma:

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

O servidor entrega conteúdo textual em `content[0].text`. A função `createCommandRunner` marca `isError` quando o bridge retorna um campo `error`, ou quando o transporte falha. Um resultado de ação com `success: false` pode permanecer dentro de um envelope `isError: false`.

Verifique a camada MCP e a camada da action separadamente. Não interprete um HTTP correto, um socket aberto ou um envelope sem `isError` como prova de que o handler obteve autorização e terminou com sucesso.

`tools/list` lista as ferramentas da versão instalada. `wu_list_actions` lista outro catálogo: os handlers que a página registrou no subsistema de IA. Para executar uma action, ambos os registros e as permissões adequadas são necessários.

<span id="helpers-de-autenticación-y-nombres"></span>

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

## Helpers de autenticação e nomes

| Exportação | Assinatura | Comportamento |
| --- | --- | --- |
| `isOriginAllowed` | `(origin, allowedOrigins = null) => boolean` | Com `null`, permite apenas origens HTTP(S) exatas de loopback |
| `tokenMatches` | `(candidate, expectedDigest) => boolean` | Compara o SHA-256 do candidato com um digest esperado de 32 bytes usando `timingSafeEqual` |
| `isReservedEvent` | `(eventName) => boolean` | Detecta os três namespaces internos reservados |

`tokenMatches` espera um digest, não o token em texto como segundo argumento. Um digest de tamanho incorreto pode causar um erro de comparação. Esses helpers não abrem sockets nem concedem permissões de domínio.

<span id="cancelación-seguridad-y-pruebas"></span>

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

## Cancelamento, segurança e testes

O protocolo desta implementação não propaga um cancelamento MCP até o handler de `wu.ai.execute`. A receita cancela uma espera e observa que o cálculo já iniciado continua. `disconnect` e `close` liberam transporte e pendências, mas não implementam compensação de efeitos.

Use ações próprias limitadas e um serviço autenticado para operações privilegiadas. As flags de IA do navegador são controles técnicos dessa página; não substituem autorização de negócio, idempotência nem um registro durável de operações.

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

O teste inclui handshake, token incorreto, ferramentas e ações reais, soma, schema inválido, permissões negadas, evento reservado, cancelamento observável e limpeza. O token é gerado em memória, todos os listeners são locais e não é necessário um provedor LLM. Você pode [baixar as receitas completas](/examples/wu-server-recipes.zip) ou revisar [ações de IA e MCP executáveis](/pt/docs/framework/ai-recipes/).