# 可运行的 AI actions 与 MCP

无需 provider 即可注册和执行 WU actions；连接真实 MCP relay，检查 schema、权限、身份验证、取消和清理。

<span id="una-acción-no-necesita-un-proveedor-llm"></span>

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

## Action 不需要 LLM provider

`wu.ai.execute` 调用已注册的处理函数。可以从自己的界面、外部代理或测试中使用，无需配置 `provider`、编写 prompt 或输入 API key。只有与模型对话的操作需要 provider；action 注册和直接执行彼此独立。

`examples/ai/local-actions.mjs` 示例执行真实求和，并确认拒绝发生在处理函数开始计算之前。

[下载示例项目](/examples/wu-server-recipes.zip)，解压 ZIP 并运行 `npm install` 安装依赖。其中包含 Node 和 MCP 示例使用的相同 actions 及测试。

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

<span id="registrar-habilitar-y-ejecutar"></span>

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

## 注册、启用与执行

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

执行返回数据的操作前等待 `aiReady()`。`action` 返回 AI 对象，可链式注册。`execute` 返回包含 `{ success, result? , reason? }` 的 Promise；权限或验证失败通常以 `success: false` 表示，不向调用方抛异常。

`ai.tools()` 向外部客户端暴露目录 schema。工具出现在列表中，不代表获得执行权限。

<span id="schema-permiso-y-reglas-de-dominio"></span>

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

## Schema、权限与领域规则

WU 的参数验证刻意保持简单：检查必填字段、第一层属性类型（包括 `integer`）和 `enum`，并非完整的 JSON Schema 验证器。数值边界、`additionalProperties`、格式及递归验证等规则，需要产品在产生副作用前自行验证。

示例测试缺少字段、数字位置传入字符串，以及非有限值。前两种由 WU 拒绝，第三种由处理函数的显式规则拒绝。计数器证明只有获准的情况进入计算。

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

Action 可以声明 `permissions: ['readStore']` 或 `permissionsWhen(params)`。所需标志在参数验证和处理函数前检查。第二个参数提供的 API 只包含已声明且已启用的操作，例如 `getState`、`setState`、`emit` 或 `fetch`。

请区分以下控制：

| 控制 | 决定内容 |
| --- | --- |
| WU 应用 capabilities | 通过自身 facade 对 store、事件和 RPC 的技术访问 |
| AI 标志 | 子系统能否执行 actions、读取 DOM、写状态或使用网络 |
| 领域授权 | 服务器允许已认证主体执行哪些业务操作 |
| 执行器能力 | 后端或 runner 是否实现取消、事务等操作 |

处理函数是可信宿主代码。闭包可以访问所提供 API 之外的全局对象，注册机制不是针对恶意 JavaScript 的沙箱。涉及后端副作用的 actions 应使用重新验证身份、schema、scope 和授权的服务。这些示例保持纯计算，不访问特权驱动。

<span id="confirmación-y-cleanup"></span>

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

## 确认与清理

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

`sum_with_confirmation` 在 `examples/ai/register-actions.mjs` 中以 `confirm: true` 注册。界面获得肯定决定后调用 `confirmTool(callId)`；`rejectTool` 完成拒绝。如果没有决定，确认在 30 秒后超时并拒绝。

销毁子系统前，应完成或拒绝待处理确认，并移除集成安装的订阅。`destroy` 清理 AI 资源，不会任意取消自定义处理函数捕获的 Promise 或副作用，也不会把内存历史转换为持久审计。

<span id="mcp-del-cliente-al-navegador-vivo"></span>

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

## MCP：从客户端到运行中的浏览器

软件包中的 MCP 服务器是 relay。客户端通过 stdio 使用 MCP/JSON-RPC，relay 经 WebSocket 向已加载 WU 的页面桥接发送命令。Action 在该页面中执行，使用其 AI 权限。

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

运行完整示例：

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

示例通过 `wu-framework/mcp-server` export 启动打包服务器，从本地监听器提供已安装的 UMD bundle，并打开无头 Chromium。Windows 使用 Edge，其他系统使用 Playwright Chromium。缺少 Playwright 浏览器时，运行 `npx playwright install chromium` 安装二进制。可以通过 `WU_BROWSER_CHANNEL` 选择已安装的渠道。

无需安装外部 MCP 客户端：`examples/mcp/stdio-client.mjs` 只实现初始化、列出工具、调用和取消等待所需的 JSON-RPC 操作。通用集成请使用支持完整协议的 MCP 客户端。

<span id="conectar-tu-propio-cliente"></span>

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

## 连接自己的客户端

软件包安装 `wu-mcp-server` 可执行文件。请由启动进程的宿主配置环境。不要把 token 放在 URL 中，也不要发布到前端代码。

| 变量 | 行为 |
| --- | --- |
| `WU_MCP_TOKEN` | 除非显式启用不安全模式，否则必填；16 至 4096 字节 |
| `WU_MCP_HOST` | 默认 `127.0.0.1` |
| `WU_MCP_PORT` | `19100`；支持 1–65535 |
| `WU_MCP_ORIGINS` | 逗号分隔的精确 HTTP(S) origin，不含路径 |
| `WU_MCP_TIMEOUT` | 桥接响应等待：默认 30000 ms，范围 50–300000 |
| `WU_MCP_AUTH_TIMEOUT` | 身份验证等待：默认 5000 ms，范围 50–60000 |
| `WU_MCP_MAX_PAYLOAD` | 默认 1048576 字节，范围 1024–16777216 |
| `WU_MCP_INSECURE` | 仅 `1` 允许省略 token；示例保持强制身份验证 |

未设置 `WU_MCP_ORIGINS` 时，接受 HTTP(S) 回环页面；没有 Origin 的 WebSocket 客户端无法通过检查。示例将 relay 限制为刚创建服务器的精确 origin。

在页面中，可信集成将短期 token 交给桥接：

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

Promise 加载模块并开始连接，不保证握手已完成。检查 `wu.mcp.isConnected()`，并从 MCP 客户端查询 `wu_conexion`，观察两端状态。relay 同时只允许一个已认证的活动桥接。

<span id="herramientas-y-lectura-de-resultados"></span>

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

## 工具与结果读取

| 工具 | 主要参数 |
| --- | --- |
| `wu_conexion` | `{}`；即使没有连接页面，也返回 relay 状态 |
| `wu_list_actions` | `{}`；页面的 action 目录 |
| `wu_execute_action` | `{ action, params? }` |
| `wu_get_state` | `{ path? }`；需要 `readStore` |
| `wu_set_state` | `{ path, value }`；需要 `writeStore` |
| `wu_emit_event` | `{ event, data? }`；拒绝 `wu:`、`system:` 和 `app:` 命名空间 |

relay 还注册 WU 状态、应用、事件、导航、DOM、诊断和挂载工具。`tools/list` 返回已安装版本的精确目录，权限仍在页面内生效。

结果有两层：MCP envelope 和 action 结果。被拒绝的 action 可能放在 `isError: false` 的 envelope 中，而 `content[0].text` 内的 JSON 包含 `success: false`。两层都要检查：

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

此片段使用示例的最小客户端。其他 SDK 的 `callTool` 签名可能不同。

<span id="cancelación-que-puedes-observar"></span>

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

## 可观察的取消行为

取消 MCP request 等待时，示例发送 `notifications/cancelled`，随后验证已在浏览器启动的 `bounded_sum` 处理函数仍完成固定延迟和计算。relay 不会向 `wu.ai.execute` 传递 `AbortSignal`。

因此，取消等待不能证明工作已经停止。对于有副作用的长操作，应设计带操作标识符、`status`/`cancel` 授权及执行器协作取消的服务。WASM/WASI 可以承载可移植逻辑，但宿主仍需实现这些契约和网络传输。[使用 WU 连接的服务](/zh-cn/docs/guides/browser-backend/)展示另一种情况：把 RPC signal 连接到 Worker 的实际终止。

完成后调用 `wu.mcp.disconnect()`，停止重连并释放拦截器。测试确认 `fetch` 和 `console.log` 恢复原先引用、relay 不再看到连接应用，并关闭创建的进程。运行时边界请参阅[扩展与 AI](/zh-cn/docs/framework/advanced/)和[安全](/zh-cn/docs/framework/security/)。