Назад до журналу
12 хв читання

WebMCP у React: Надання Стану Клієнта та Дій Форм Браузерним ШІ-Агентам

Перетворіть вебзастосунки на React 19 на адаптовані для агентів інтерфейси за допомогою стандарту W3C WebMCP, декларативних форм і хуків useWebMCPTool.

React.jsTypeScriptModel Context Protocol (MCP)LLM AgentsIn-Browser LLMs
WebMCP у React: Надання Стану Клієнта та Дій Форм Браузерним ШІ-Агентам

Під час інтеграції великих мовних моделей із зовнішніми системами протокол Model Context Protocol (MCP) став основним форматом для виклику інструментів. Проте переважна більшість наявних архітектур MCP ґрунтується на клієнт-серверній взаємодії: клієнт (як-от Claude Desktop чи плагін IDE) спілкується з фоновим процесом через stdio або Server-Sent Events (SSE) для опитування баз даних, виклику хмарних API чи навігації файловою системою.

Однак усередині веббраузера ця модель виявляє суттєві обмеження.

Сучасні вебзастосунки — це складні Single Page Applications (SPA) із розгалуженим внутрішнім станом. Ключові робочі дані — активні виділення на полотні (canvas), незафіксований ввід у формах, клієнтська пагінація, фільтри сортування та модальні діалоги — існують лише в пам'яті клієнта (стан компонентів React, сховища Zustand або DOM). Коли автономний браузерний агент або розширення намагається взаємодіяти із застосунком, йому доводиться покладатися на нестабільний парсинг візуального DOM, аналіз дерева доступності чи штучні кліки миші.

Нова специфікація WebMCP (Web Model Context Protocol), що розробляється спільнотою W3C Web Machine Learning і доступна експериментально в Chromium, долає цю проблему, інтегруючи Model Context Protocol безпосередньо у вкладку браузера.

Цей посібник демонструє архітектуру WebMCP, визначає його відмінності від серверного MCP і пояснює, як надати доступ до стану компонентів React 19 і дій форм браузерним агентам за допомогою декларативних HTML-атрибутів та ідіоматичних React-хуків.

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-модулях, класах Tailwind або ієрархії DOM призводять до збою автоматизованих CSS-селекторів.
  3. Надмірний контекст: Передавання всього дерева DOM чи зображень на кожному кроці споживає тисячі токенів і спричиняє мережеві затримки.
  4. Хиби синтетичних подій: Виклик штучних подій MouseEvent чи KeyboardEvent нерідко оминає синтетичну систему подій React, що веде до застарілих замикань (stale closures) або ігнорування перевірок.

WebMCP замінює евристичний перебір прямими структурованими RPC-викликами у середовищі виконання клієнта. Замість вгадування селекторів сторінка реєструє явні інструменти з чіткими JSON-схемами.

Порівняння 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-ключі Пісочниця браузера, Same-Origin Policy, підтвердження користувача

WebMCP не потребує мережевих сокетів або дочірніх процесів. Сам вебдокумент діє як реєстр інструментів завдяки інтерфейсу document.modelContext.


Архітектура Браузера: document.modelContext

У збірках 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: Замість окремого методу unregisterTool скасування реєстрації реалізовано через примітив AbortSignal. Коли сигнал переривається, браузер безпечно видаляє інструмент.

Патерн 1: Декларативний WebMCP із формами React 19

Найбільш доступний спосіб надати функціональність агентам — Декларативний API WebMCP. Він доповнює стандартні HTML-форми атрибутами для агентів:

  • toolname: Унікальне ім'я інструмента.
  • tooldescription: Стислий опис призначення та умов виклику інструмента.

У React 19 декларативні форми безпосередньо інтегруються з useActionState та клієнтськими/серверними діями.

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 та створює типізовану схему на основі імен та обмежень полів. Після запуску filter-orders браузер викликає обробник formAction у React.


Патерн 2: Імперативний хук useWebMCPTool у React

Якщо для стандартних сценаріїв вистачає декларативних форм, то складні інтерфейси потребують імперативної реєстрації. Агенту може знадобитися масштабувати полотно, здійснити вибірку з локальної таблиці в пам'яті або пройти кроки майстра налаштування.

Для реалізації цього в React створюється спеціальний хук, що розв'язує три завдання:

  1. Динамічний життєвий цикл: Інструменти реєструються під час монтування компонента й видаляються під час розмонтування.
  2. Доступ до актуального стану: Функція виконання має читати найновіші пропси й стан без постійної повторної реєстрації.
  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)

Надання клієнтських інструментів ШІ-агентам вимагає дотримання безпеки:

  • Агент може ненавмисно знищити дані у формі чи ініціювати платіж.
  • Атаки з ін'єкцією промптів (prompt injection) на сторонніх сайтах можуть намагатися активувати чутливі дії.

WebMCP передбачає два механізми захисту: позначку «лише для читання» (readOnlyHint) та бар'єри підтвердження користувачем.

1. Позначки лише для читання

Для інструментів вибірки завжди вказуйте annotations: { readOnlyHint: true }. Це повідомляє агенту й браузеру про відсутність побічних ефектів, завдяки чому модель може збирати відомості без частих запитів на дозвіл.

2. Патерн бар'єра підтвердження

У разі зміни стану або видалення даних проміс інструмента не повинен завершуватися миттєво. Замість цього компонент виводить діалогове вікно підтвердження. Проміс вирішується тільки тоді, коли користувач натискає «Підтвердити».

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 мілісекунд.
  • Жодні дані не залишають робочу станцію, що відповідає суворим стандартам конфіденційності.
  • Витрати на хмарні обчислення відсутні.

Виробнича Практика та Поступове Поліпшення

Доки специфікація WebMCP не буде фіналізована комітетами стандартів, у робочих проектах її слід впроваджувати як поступове поліпшення (progressive enhancement):

  1. Перевірка сумісності: Завжди перевіряйте наявність document.modelContext. Інтерфейс React має функціонувати коректно для людей незалежно від підтримки агентських API.
  2. Компактність схем: Локальні моделі мають обмежене вікно контексту (переважно від 4k до 8k токенів). Описи параметрів мають бути лаконічними без глибокої вкладеності.
  3. Атомарність оновлень: Інструменти мають завершувати свої проміси тільки після того, як React повністю зафіксував оновлення в DOM, аби запобігти конфліктам стану при послідовних викликах.

Поєднуючи форми React 19 та імперативні хуки WebMCP, розробники можуть адаптувати звичні вебзастосунки для продуктивної взаємодії зі штучним інтелектом.

Share this article