Atgal į žurnalą
12 min. skaitymo

WebMCP sistemoje React: kliento būsenos ir formų veiksmų pateikimas naršyklės AI agentams

Paverskite React 19 saityno programas agentams pritaikytomis erdvėmis naudodami W3C WebMCP standartą, deklaratyvias formas ir useWebMCPTool kabliukus.

React.jsTypeScriptModel Context Protocol (MCP)LLM AgentsIn-Browser LLMs
WebMCP sistemoje React: kliento būsenos ir formų veiksmų pateikimas naršyklės AI agentams

Kai programinės įrangos inžinieriai jungia kalbos modelius su išorinėmis sistemomis, Model Context Protocol (MCP) tapo standartiniu formatu įrankių iškvietimui. Vis dėlto beveik visos dabartinės MCP sistemos remiasi kliento-serverio modeliu: klientas (pvz., Claude Desktop ar IDE asistentas) bendrauja su fono procesu per stdio arba Server-Sent Events (SSE), kad atliktų užklausas duomenų bazėse, kviestų debesų API ar naršytų failų struktūrose.

Tačiau naršyklės viduje šis modelis susiduria su esminiais apribojimais.

Šiuolaikinės saityno programos yra sudėtingos vieno puslapio programos (SPA), valdančios didelį vietinės būsenos kiekį. Svarbi informacija (aktyvūs drobės canvas pažymėjimai, neįrašyti formų laukai, vietinis puslapiavimas, rikiavimo filtrai ar atviri modaliniai langai) egzistuoja tik kliento operatyviojoje atmintyje (React būsenoje, Zustand saugyklose ar DOM). Kai autonominis naršyklės agentas ar plėtinys bando atlikti veiksmus tokioje programoje, jam tenka remtis nepatikimu vizualiu DOM nuskaitymu (scraping), prieinamumo medžio analize ar sintetiniais pelės paspaudimais.

Besiformuojanti WebMCP (Web Model Context Protocol) specifikacija, kuriama W3C Web Machine Learning Community Group ir aprašyta W3C WebMCP Draft Community Group Report (bandoma Chromium), išsprendžia šią problemą, perkeldama Model Context Protocol tiesiai į naršyklės skirtuką.

Šiame gide nagrinėjama, kaip veikia WebMCP, kuo jis skiriasi nuo serverio MCP ir kaip pateikti React 19 komponentų būseną bei formų veiksmus naršyklės agentams pasitelkiant deklaratyvius HTML atributus ir React kabliukus (hooks).

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    |
+-------------------------------------------------------------------------+

Kliento pusės įrankių vakuumas

Kad suprastume WebMCP prasmę, pažvelkime, kaip autonominis agentas šiandien atlieka paprastą užduotį, pavyzdžiui, „filtruoti lentelę pagal užsakymus, viršijančius 500 $, ir juos eksportuoti“:

  1. Prieinamumo medžio nuskaitymas: Agentas skaito prieinamumo medį arba fiksuoja ekrano kopiją. Jis bando atspėti filtro mygtuką tarp dešimčių įterptų <div> ir <button> elementų.
  2. Selektorių trapumas: Jei komanda atnaujina CSS modulius, Tailwind klases ar DOM medžio struktūrą, automatizuoti CSS selektoriai iškart nustoja veikti.
  3. Konteksto perteklius: Kiekviename žingsnyje siunčiant visą DOM medį ar vaizdus į LLM, išeikvojama dešimtys tūkstančių prieigos žetonų (tokens) ir sukeliama pastebima delsos problema.
  4. Sintetinių paspaudimų bėdos: MouseEvent ar KeyboardEvent įvykių siuntimas dažnai apeina React sintetinių įvykių tvarkykles, todėl atsiranda pasenusių uždarymų (stale closures) ir neįvykdomos validacijos.

WebMCP pakeičia šiuos apytikslius metodus struktūrizuotais, programiniais RPC iškvietimais pačiame kliento vykdymo sraute. Puslapis registruoja konkrečius įrankius su griežtomis JSON schemomis.

WebMCP ir serverio MCP palyginimas

Skirtumas tarp serverio MCP ir WebMCP yra esminis:

Aspektas Serverio MCP (Node.js / Python) WebMCP (Naršyklės lygmeniu)
Vykdymo aplinka Foninis procesas, konteineris, serverless funkcija Aktyvaus naršyklės skirtuko vykdymo gija
Transportas stdio, Server-Sent Events (SSE), WebSockets Tiesioginė JavaScript funkcijos nuoroda
Tiksliniai duomenys Nutolusios duomenų bazės, failų sistemos, debesų API React būsena, maršrutizatorius, vietinė saugykla, DOM
Palaikomi elementai Tools, Resources, Prompts Tik Tools (apribota aktyviu dokumentu)
Saugumo riba Procesų izoliacija, OS teisės, API raktai Naršyklės smėliadėžė, Same-Origin Policy, vartotojo patvirtinimas

WebMCP nenaudoja tinklo lizdų ar šalutinių procesų. Pats saityno dokumentas tampa įrankių registru per document.modelContext sąsają.


Naršyklės architektūra: document.modelContext

Chromium naršyklėse (nuo Chromium 146 versijos su vėliavėle #enable-webmcp-testing) suteikiamas įrankių tarpininkas, pasiekiamas per visuotinį document objektą:

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

Šią sąsają apibrėžia dvi taisyklės:

  1. Dokumento galiojimo sritis: Įrankiai yra tiesiogiai susieti su dokumento gyvavimo ciklu. Kai naudotojas išeina iš puslapio ar užveria skirtuką, registras sunaikinamas.
  2. Išregistravimas per AbortSignal: Užuot pateikus atskirą unregisterTool metodą, įrankių gyvavimo ciklas valdomas per standartinį AbortSignal. Nutraukus signalą, naršyklė tvarkingai pašalina įrankį.

1 modelis: Deklaratyvus WebMCP su React 19 formomis

Paprasčiausias būdas suteikti galimybes naršyklės agentams: naudoti deklaratyvią WebMCP API. Ji papildo įprastas HTML formas specialiais atributais:

  • toolname: Unikalus įrankio pavadinimas.
  • tooldescription: Glaustas įrankio paskirties paaiškinimas, nurodantis agentui, kada jį kviesti.

Sistemoje React 19 deklaratyvios formos sklandžiai jungiasi su useActionState ir serverio bei kliento veiksmais.

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

Agentui atidarius puslapį, naršyklė peržiūri DOM medį, aptinka formas su toolname atributu ir sukuria parametrų schemą pagal laukų pavadinimus bei apribojimus. Agentui iškvietus filter-orders, naršyklė perduoda vykdymą tiesiai į React formAction.


2 modelis: Imperatyvus useWebMCPTool kabliukas sistemoje React

Nors deklaratyvios formos tinka įprastoms įvestims, sudėtingose programose būtina imperatyvi įrankių registracija. Pavyzdžiui, agentui gali tekti pakeisti drobės mastelį, atlikti paiešką atmintyje esančioje lentelėje ar pereiti kelis vedlio žingsnius.

Kad tai integruotume pagal React principus, sukuriame specialų kabliuką, atitinkantį tris reikalavimus:

  1. Dinaminis gyvavimo ciklas: Įrankiai turi būti užregistruoti prijungus komponentą ir pašalinti jį atjungus.
  2. Prieiga prie naujausios būsenos: Funkcija turi pasiekti naujausius rekvizitus (props) ir būseną, neišprovokuodama pakartotinių registracijų.
  3. Išvalymas su AbortSignal: Tvarkingas išteklių atlaisvinimas keičiantis maršrutams.

Štai išbaigtas useWebMCPTool kabliuko kodas:

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]);
}

Praktinis panaudojimas komponente

Žemiau pateiktas interaktyvus dokumentų peržiūros komponentas, kuriame agentas gali versti puslapius ir perskaityti pažymėtą tekstą:

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 modelis: Saugumo užkardos ir patvirtinimas su žmogaus įsikišimu

Atveriant kliento funkcijas AI agentams, būtina valdyti rizikas:

  • Agentas gali netyčia ištrinti neįrašytus duomenis arba inicijuoti nepageidaujamą mokėjimą.
  • Svetainėje esantys raginimų injekcijos (prompt injection) bandymai gali siekti atlikti jautrius veiksmus.

WebMCP siūlo du būdus šioms rizikoms mažinti: tik skaitymo žymą (readOnlyHint) ir patvirtinimo užkardas su žmogaus dalyvavimu.

1. Tik skaitymo žymos

Užklausų įrankiams visada nurodykite annotations: { readOnlyHint: true }. Tai praneša agentui ir naršyklei, kad veiksmas neturi šalutinio poveikio būsenai, todėl modelis gali rinkti informaciją be pakartotinių patvirtinimo prašymų.

2. Patvirtinimo užkardos modelis

Destruktyviems ar svarbiems veiksmams nereikėtų iškart patvirtinti įrankio pažado (promise). Vietoje to komponentas parodo patvirtinimo langą naudotojui. Įrankio pažadas išsprendžiamas tik tada, kai naudotojas spusteli „Patvirtinti“.

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 modelis: Autonominis vietinis ciklas: WebMCP + Chrome Gemini Nano

Vienas įspūdingiausių WebMCP pritaikymo būdų apima darbą su vietiniais įrenginio kalbos modeliais. Sujungus W3C Prompt API (window.LanguageModel) su document.modelContext, gaunamas visiškai vietinis, minimalios delsos autonominis agentas naršyklės skirtuke.

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

Šis procesas vyksta vien tik įrenginio techninėje įrangoje:

  • Laikas iki pirmojo žetono (TTFT) siekia mažiau nei 30 milisekundžių.
  • Jokie duomenys nepalieka naudotojo kompiuterio, užtikrinant BDAR reikalavimų laikymąsi.
  • Nėra jokių debesų infrastruktūros išlaidų.

Gamybos realijos ir laipsniškas tobulinimas

Kol WebMCP specifikacija tobulinama standartų komitetuose, komercinėse programose ją verta taikyti pagal laipsniško tobulinimo (progressive enhancement) principą:

  1. Galimybių tikrinimas: Visada patikrinkite document.modelContext buvimą. React programa turi veikti be priekaištų įprastiems naudotojams net ir naršyklėse be agentų palaikymo.
  2. Kompaktiškos schemos: Įrenginiuose veikiantys modeliai turi ribotą konteksto langą (dažniausiai nuo 4k iki 8k žetonų). Parametrų aprašymai turi būti trumpi ir aiškūs.
  3. Atominiai būsenos atnaujinimai: Užtikrinkite, kad įrankių pažadai būtų išsprendžiami tik po to, kai React užbaigia DOM atnaujinimą, taip išvengiant konkurencinių situacijų (race conditions).

Sujungus React 19 formų priemones su imperatyviais WebMCP kabliukais, pasyvios saityno sąsajos tampa paruoštos autonominių AI agentų veiksmams.

Share this article