返回日志
8 分钟阅读

React 中的 WebMCP:向浏览器 AI 智能体开放客户端状态与表单操作

借助 W3C WebMCP 标准、声明式表单属性与 useWebMCPTool 钩子,将 React 19 Web 应用程序转变为智能体友好的可操作界面。

React.jsTypeScriptModel Context Protocol (MCP)LLM AgentsIn-Browser LLMs
React 中的 WebMCP:向浏览器 AI 智能体开放客户端状态与表单操作

在将大型语言模型与外部系统进行集成的过程中,Model Context Protocol (MCP) 已迅速成为工具调用的行业标准通信格式。然而,目前绝大多数 MCP 架构都建立在传统的客户端-服务器模式之上:客户端(例如 Claude Desktop 或 IDE 插件)通过 stdio 或 Server-Sent Events (SSE) 与后台进程通信,用以查询数据库、调用云端 API 或检查文件目录树。

然而,在网页浏览器内部,这种架构遭遇了结构性瓶颈。

现代 Web 应用大多是功能丰富、拥有复杂状态的单页应用(SPA)。诸如 Canvas 上的活跃选中区域、未保存的表单输入项、客户端本地分页、排序筛选参数以及各类交互式模态弹窗等核心状态,完全保存在客户端内存(React 组件状态、Zustand Store 或 DOM)中。当自主浏览器智能体或浏览器插件尝试与 Web 应用交互时,不得不退回到脆弱的视觉 DOM 抓取、无障碍辅助树解析或合成鼠标模拟点击。

正在 W3C Web 机器学习社区组内制定、并在 Chromium 中提供实验性支持的 WebMCP (Web Model Context Protocol) 规范,直接将 Model Context Protocol 引入浏览器标签页,从而解决了这一难题。

本指南深入剖析 WebMCP 的工作机制及其与服务端 MCP 的区别,并详细演示如何通过声明式 HTML 属性与定制的 React Hook,将 React 19 组件状态与表单操作直接暴露给浏览器智能体。

CODE
+-------------------------------------------------------------------------+
| Browser Tab (Main Thread / React 19 Context)                            |
|                                                                         |
|  [ React Component State ] <---> [ useWebMCPTool Hook ]                 |
|            |                                |                           |
|            v                                v                           |
|  [ <form toolname="..." /> ]      [ document.modelContext ]             |
|            |                      (In-Memory Tool Registry)             |
|            +--------------------------------+                           |
+---------------------------------------------|---------------------------+
                                              | Local In-Tab Dispatch
+---------------------------------------------v---------------------------+
| In-Browser Agent Context (Chrome Assistant / Extension / Gemini Nano)   |
|                                                                         |
|  1. Inspects active tab tools via document.modelContext.listTools()     |
|  2. Invokes tool with structured JSON arguments                         |
|  3. React action dispatches state transition -> UI updates instantly    |
+-------------------------------------------------------------------------+

客户端工具的生态真空

为了看清 WebMCP 的必要性,不妨观察自主智能体当前是如何执行一项常见任务的——例如“筛选表格以展示金额大于 500 美元的订单并导出”:

  1. 无障碍辅助树抓取:智能体读取辅助功能树或截取屏幕快照。它必须在数十个相互嵌套的 <div><button> 标签中推测筛选按钮的位置。
  2. 选择器脆弱易碎:前端团队一旦微调 CSS Modules、Tailwind 类名或 DOM 节点层级,自动化的 CSS 选择器便会立即失效。
  3. 上下文开销沉重:在每一步交互中向 LLM 发送完整的 DOM 树或图像快照,会消耗数万个 Token 并带来明显的网络延迟。
  4. 合成点击的潜在隐患:派发合成的 MouseEventKeyboardEvent 往往会绕过 React 的合成事件系统,从而引发闭包过期(stale closures)或跳过内置校验。

WebMCP 以运行在客户端主执行线程内的结构化、程序化 RPC 调用彻底取代了盲目推测。网页不再依赖外部猜测,而是主动注册带有明确 JSON Schema 类型的操作工具。

WebMCP 与后端 MCP 的对比

后端 MCP 与 WebMCP 之间的差异是本质性的:

维度 后端 MCP (Node.js / Python) WebMCP (浏览器原生)
执行上下文 后台守护进程、容器、Serverless 函数 当前处于活动状态的浏览器标签页执行线程
传输协议 stdio、Server-Sent Events (SSE)、WebSockets 直接的 JavaScript 函数引用
目标数据 远程数据库、磁盘文件系统、第三方 API React 状态、客户端路由、Local Storage、DOM
支持的原语 Tools、Resources、Prompts 仅支持 Tools(严格限定在活动文档内)
安全边界 进程隔离、操作系统权限、API 密钥 浏览器沙箱、同源策略 (SOP)、用户交互确认

WebMCP 无需网络套接字或子进程。网页文档本身通过 document.modelContext 接口充当工具注册表。


浏览器架构:document.modelContext

在 Chromium 环境中(从 Chromium 146 开始在 #enable-webmcp-testing 标志后启用),浏览器在全局 document 对象上挂载了工具调度接口:

TYPESCRIPT
// Core interface for browser-native WebMCP
interface ModelContextTool {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>;
  execute: (input: Record<string, unknown>) => Promise<Record<string, unknown>>;
  annotations?: {
    readOnlyHint?: boolean;
  };
}

interface ModelContext {
  registerTool(tool: ModelContextTool, options?: { signal?: AbortSignal }): void;
  listTools(): Promise<ModelContextTool[]>;
}

declare global {
  interface Document {
    modelContext?: ModelContext;
  }
}

两大架构准则规范了该接口的行为:

  1. 文档生命周期限定:工具直接绑定至文档生命周期。用户跳转到新页面或关闭标签页时,注册表将自动销毁。
  2. 通过 AbortSignal 注销:WebMCP 并未设计单独的 unregisterTool 方法,而是统一借助 AbortSignal 控制工具生命周期。一旦中止信号触发,浏览器便会自动清理注销该工具。

模式 1:基于 React 19 表单的声明式 WebMCP

向浏览器智能体开放能力最直接的方式是使用 WebMCP 声明式 API。WebMCP 为标准 HTML 表单扩展了智能体注解:

  • toolname:工具的唯一标识符。
  • tooldescription:工具功能及其适用场景的简明说明。

在 React 19 中,声明式表单与 useActionState 及 Server/Client Actions 结合紧密。

TSX
"use client";

import React, { useActionState } from "react";

interface FilterState {
  minAmount: number;
  category: string;
  status: "idle" | "applied";
}

async function applyFilterAction(
  prevState: FilterState,
  formData: FormData
): Promise<FilterState> {
  const minAmount = Number(formData.get("minAmount") || 0);
  const category = String(formData.get("category") || "all");

  // Perform client-side filter computation or query
  return {
    minAmount,
    category,
    status: "applied",
  };
}

export function AgenticOrderFilter() {
  const [state, formAction, isPending] = useActionState(applyFilterAction, {
    minAmount: 0,
    category: "all",
    status: "idle",
  });

  return (
    <form
      action={formAction}
      // WebMCP Declarative Tool Annotations
      toolname="filter-orders"
      tooldescription="Filters the current order ledger by minimum dollar amount and product category."
      className="filter-form"
    >
      <label htmlFor="minAmount">Minimum Amount ($)</label>
      <input
        id="minAmount"
        name="minAmount"
        type="number"
        defaultValue={state.minAmount}
        required
      />

      <label htmlFor="category">Category</label>
      <select id="category" name="category" defaultValue={state.category}>
        <option value="all">All Categories</option>
        <option value="hardware">Hardware</option>
        <option value="software">Software</option>
      </select>

      <button type="submit" disabled={isPending}>
        {isPending ? "Filtering..." : "Apply Filter"}
      </button>

      {state.status === "applied" && (
        <p className="status-text">
          Showing orders &gt; ${state.minAmount} in category &quot;{state.category}&quot;
        </p>
      )}
    </form>
  );
}

当智能体访问网页时,浏览器会扫描 DOM 中带有 toolname 属性的表单,并根据输入框的名称、类型与验证约束动态构建工具 Schema。智能体调用 filter-orders 时,浏览器会直接将输入分发到 React 的 formAction 中。


模式 2:React 中的命令式 useWebMCPTool 钩子

声明式表单适合常规数据录入,但在复杂的前端场景中,必须使用命令式工具注册。例如,智能体可能需要调整画布缩放比例、查询内存中的数据集或控制多步骤流程面板。

为了让这一过程符合 React 的状态哲学,我们封装了一个自定义 Hook,精准解决以下三项工程需求:

  1. 动态生命周期:组件挂载时注册工具,卸载时立即注销。
  2. 获取最新状态闭包:执行器能够读取最新的 Props 和 State,而无需频繁重新注册。
  3. AbortSignal 自动释放:在 SPA 路由切换时平稳释放资源。

以下是 useWebMCPTool 的完整工程实现:

TYPESCRIPT
"use client";

import { useEffect, useRef } from "react";

export interface ToolDefinition<TInput = Record<string, unknown>, TOutput = Record<string, unknown>> {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>;
  execute: (input: TInput) => Promise<TOutput>;
  readOnlyHint?: boolean;
}

/**
 * Registers an imperative tool with document.modelContext, ensuring
 * safe teardown with AbortSignal and fresh closure references.
 */
export function useWebMCPTool<TInput = Record<string, unknown>, TOutput = Record<string, unknown>>(
  tool: ToolDefinition<TInput, TOutput>,
  enabled: boolean = true
) {
  // Store the latest executor in a ref to avoid re-registering on every state change
  const executeRef = useRef(tool.execute);
  useEffect(() => {
    executeRef.current = tool.execute;
  });

  useEffect(() => {
    // Feature detection: Check for browser WebMCP support
    if (typeof document === "undefined" || !document.modelContext || !enabled) {
      return;
    }

    const abortController = new AbortController();

    try {
      document.modelContext.registerTool(
        {
          name: tool.name,
          description: tool.description,
          inputSchema: tool.inputSchema,
          execute: async (args: Record<string, unknown>) => {
            return await executeRef.current(args as TInput);
          },
          annotations: tool.readOnlyHint ? { readOnlyHint: true } : undefined,
        },
        { signal: abortController.signal }
      );
    } catch (err) {
      console.warn(`[WebMCP] Failed to register tool "${tool.name}":`, err);
    }

    // Teardown: AbortSignal unregisters the tool automatically
    return () => {
      abortController.abort();
    };
  }, [tool.name, tool.description, tool.readOnlyHint, enabled]);
}

实际组件中的应用范例

下例展示了一个交互式文档查看器,智能体可以通过该工具翻页并读取当前选中的文本片段:

TSX
"use client";

import React, { useState, useCallback } from "react";
import { useWebMCPTool } from "./useWebMCPTool";

interface DocumentViewerProps {
  totalPages: number;
  documentTitle: string;
}

export function DocumentViewer({ totalPages, documentTitle }: DocumentViewerProps) {
  const [currentPage, setCurrentPage] = useState<number>(1);
  const [selection, setSelection] = useState<string>("");

  // Tool 1: Jump to a specific page (Mutating action)
  useWebMCPTool({
    name: "navigate-document-page",
    description: "Navigates the interactive PDF viewer to a specific page number.",
    inputSchema: {
      type: "object",
      properties: {
        pageNumber: {
          type: "integer",
          minimum: 1,
          maximum: totalPages,
          description: "Target page index to display",
        },
      },
      required: ["pageNumber"],
    },
    execute: async (args: { pageNumber: number }) => {
      if (args.pageNumber < 1 || args.pageNumber > totalPages) {
        return {
          success: false,
          error: `Page ${args.pageNumber} out of bounds (1-${totalPages}).`,
        };
      }
      setCurrentPage(args.pageNumber);
      return {
        success: true,
        activePage: args.pageNumber,
        documentTitle,
      };
    },
  });

  // Tool 2: Read current selection (Read-only query)
  useWebMCPTool({
    name: "get-active-selection",
    description: "Retrieves the currently highlighted text snippet in the viewer.",
    inputSchema: {
      type: "object",
      properties: {},
    },
    readOnlyHint: true,
    execute: async () => {
      return {
        hasSelection: selection.length > 0,
        text: selection,
        pageNumber: currentPage,
      };
    },
  });

  return (
    <div className="viewer-container">
      <header>
        <h3>{documentTitle}</h3>
        <span>Page {currentPage} of {totalPages}</span>
      </header>
      <main
        onMouseUp={() => {
          const selectedText = window.getSelection()?.toString() || "";
          setSelection(selectedText);
        }}
        className="document-canvas"
      >
        <p>Displaying page content for page {currentPage}...</p>
      </main>
    </div>
  );
}

模式 3:安全防护栏与人机协同确认机制 (Human-in-the-Loop)

直接将前端操作暴露给 AI 智能体存在明确的安全边界需求:

  • 智能体可能会误触发破坏性操作(例如清空未提交的表单内容或提交支付操作)。
  • 恶意网页内容可能包含 Prompt 注入代码,试图诱导智能体触发高危前端动作。

WebMCP 为此提供了两道防线:只读提示 (readOnlyHint)交互式人机确认屏障

1. 只读提示

针对仅用于信息查询的工具,务必添加 annotations: { readOnlyHint: true }。这可以告知智能体和浏览器当前调用不存在副作用,允许模型连续编排多步数据收集,而无需频繁弹出确认阻断。

2. 人机确认屏障模式

对于涉及关键状态变更或破坏性的操作,不应立即 Resolve 工具的 Promise。相反,Hook 会触发 React 状态展示确认模态框。只有当用户亲自点击“确认”后,Promise 才会继续决议。

TSX
"use client";

import React, { useState, useRef } from "react";
import { useWebMCPTool } from "./useWebMCPTool";

interface PendingAction {
  id: string;
  description: string;
  resolve: (value: { approved: boolean }) => void;
}

export function DestructiveActionShield() {
  const [pendingAction, setPendingAction] = useState<PendingAction | null>(null);

  useWebMCPTool({
    name: "delete-active-workspace",
    description: "Permanently deletes the current active workspace. Requires user confirmation.",
    inputSchema: {
      type: "object",
      properties: {
        reason: { type: "string", description: "Reason for deletion" },
      },
      required: ["reason"],
    },
    execute: async (input: { reason: string }) => {
      // Pause tool execution and wait for manual user approval
      return new Promise((resolve) => {
        setPendingAction({
          id: crypto.randomUUID(),
          description: `Delete workspace: "${input.reason}"`,
          resolve,
        });
      });
    },
  });

  return (
    <>
      {pendingAction && (
        <aside className="confirmation-modal" role="alertdialog">
          <h4>Agent Action Confirmation</h4>
          <p>An AI assistant requested permission to:</p>
          <blockquote>{pendingAction.description}</blockquote>
          <div className="button-row">
            <button
              type="button"
              onClick={() => {
                pendingAction.resolve({ approved: false });
                setPendingAction(null);
              }}
            >
              Reject
            </button>
            <button
              type="button"
              className="danger-btn"
              onClick={() => {
                pendingAction.resolve({ approved: true });
                setPendingAction(null);
              }}
            >
              Confirm Deletion
            </button>
          </div>
        </aside>
      )}
    </>
  );
}

模式 4:端侧闭环自主智能体:WebMCP + Chrome Gemini Nano

WebMCP 的核心优势之一在于与端侧大语言模型的深度配合。通过将 W3C Prompt API (window.LanguageModel) 与 document.modelContext 相互打通,即可在当前标签页内构建出零延迟的完全自主闭环。

CODE
+-------------------------------------------------------------+
| Browser Tab Memory Space                                    |
|                                                             |
|  [ User Goal ] ---> [ window.LanguageModel Session ]        |
|                               |                             |
|                               v                             |
|                   [ Tool Call JSON Output ]                 |
|                               |                             |
|                               v                             |
|               [ document.modelContext.execute ]             |
|                               |                             |
|                               v                             |
|                 [ React State Mutates DOM ]                 |
|                               |                             |
|                               v                             |
|             (Observation Result fed back to model)          |
+-------------------------------------------------------------+
TYPESCRIPT
// Client-side agent loop executing in the browser tab
export async function runLocalTabAgent(userPrompt: string): Promise<string> {
  // 1. Verify availability of on-device LLM and WebMCP
  if (!("ai" in window) || !document.modelContext) {
    throw new Error("On-device AI or WebMCP is not supported in this browser.");
  }

  // 2. Discover available tools exposed by mounted React components
  const availableTools = await document.modelContext.listTools();
  
  // Format tool descriptions for system prompt
  const toolDeclarations = availableTools.map((t) => ({
    name: t.name,
    description: t.description,
    parameters: t.inputSchema,
  }));

  // 3. Instantiate local session with Gemini Nano
  const session = await (window as unknown as {
    ai: {
      languageModel: {
        create: (options: { systemPrompt: string }) => Promise<{
          prompt: (msg: string) => Promise<string>;
          destroy: () => void;
        }>;
      };
    };
  }).ai.languageModel.create({
    systemPrompt: `You are an in-tab browser assistant. You can control the active page using tools: ${JSON.stringify(
      toolDeclarations
    )}. Output JSON tool invocations as {"tool": string, "args": object}.`,
  });

  try {
    const response = await session.prompt(userPrompt);
    
    // Parse structured tool call
    const action = JSON.parse(response);
    const targetTool = availableTools.find((t) => t.name === action.tool);

    if (targetTool) {
      const result = await targetTool.execute(action.args);
      return `Tool executed successfully: ${JSON.stringify(result)}`;
    }

    return response;
  } finally {
    session.destroy();
  }
}

整个执行过程完全在设备本地硬件上完成:

  • 首字延迟(TTFT)低至 30 毫秒以内。
  • 绝无任何数据离开用户设备,符合严苛的隐私安全法规。
  • 无任何持续性的云端推理算力支出。

生产实践与渐进式增强 (Progressive Enhancement)

在 WebMCP 标准逐步走向成熟并正式定稿前,生产环境应用应当将其作为渐进式增强功能来引入:

  1. 特性检测:始终对 document.modelContext 进行兼容性防护。无论浏览器是否支持智能体接口,React 应用都必须确保人类用户的正常使用体验。
  2. 精简 Schema 设计:端侧模型通常拥有相对紧凑的上下文窗口(一般在 4k 到 8k Token)。参数描述应力求清晰简炼,避免层级过深的复杂 JSON 嵌套。
  3. 保证状态原子更新:确保工具回调必须在 React 彻底完成 DOM 状态应用之后再予以 Resolve,以杜绝智能体连续调用多个工具时发生的数据竞争风险。

结合 React 19 的表单基础能力与命令式 WebMCP Hook,开发团队能够将原本静态被动的网页应用转变为供新一代 AI 智能体直接操作的交互界面。

Share this article