WebMCP in React: Client-State und Formular-Aktionen für Browser-KI-Agenten bereitstellen
Verwandeln Sie React 19-Webanwendungen mit dem W3C WebMCP-Standard, deklarativen Formularattributen und useWebMCPTool-Hooks in agentenfähige Schnittstellen.

Wenn Software-Ingenieure große Sprachmodelle mit externen Systemen verknüpfen, hat sich das Model Context Protocol (MCP) rasch als Standardformat für Tool-Aufrufe etabliert. Fast alle bisherigen MCP-Architekturen basieren jedoch auf einem klassischen Client-Server-Modell: Ein Client (wie Claude Desktop oder ein IDE-Assistent) kommuniziert über stdio oder Server-Sent Events (SSE) mit Hintergrundprozessen, um Datenbanken abzufragen, Cloud-APIs auszuführen oder Dateisysteme zu durchsuchen.
Innerhalb des Webbrowsers stößt dieses Paradigma an seine Grenzen.
Moderne Webanwendungen sind komplexe Single-Page-Applications (SPAs) mit ausgeprägtem internem Zustand. Wichtige Zustandsdaten – wie aktive Canvas-Auswahlen, ungespeicherte Formulardaten, clientseitige Paginierung, Filterparameter oder interaktive Modalfenster – existieren ausschließlich im Arbeitsspeicher des Clients (React-Komponentenstatus, Zustand-Stores oder DOM). Wenn ein autonomer Browser-Agent oder eine Erweiterung mit einer Webanwendung interagieren möchte, bleibt meist nur der Rückgriff auf instabiles DOM-Scraping, das Parsen von Accessibility-Bäumen oder künstlich simulierte Maus-Events.
Die neue Spezifikation WebMCP (Web Model Context Protocol) – entwickelt in der W3C Web Machine Learning Community Group und als experimentelle Vorschau in Chromium integriert – schließt diese Lücke, indem sie das Model Context Protocol direkt in den Browser-Tab einbettet.
Dieser Leitfaden veranschaulicht die Funktionsweise von WebMCP, grenzt es von serverseitigem MCP ab und demonstriert, wie React 19-Zustände und Formularaktionen über deklarative HTML-Attribute und React-Hooks für Browser-Agenten bereitgestellt werden.
+-------------------------------------------------------------------------+
| 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 |
+-------------------------------------------------------------------------+Das Tool-Vakuum auf der Client-Seite
Um die Notwendigkeit von WebMCP zu verstehen, genügt ein Blick auf herkömmliche Versuche eines KI-Agenten, eine alltägliche Aktion auszuführen – etwa „Filtere die Tabelle nach Bestellungen über 500 $ und exportiere das Ergebnis“:
- Accessibility-Tree-Scraping: Der Agent liest den Accessibility-Baum aus oder analysiert Screenshots. Er muss die Filter-Schaltfläche aus Dutzenden verschachtelten
<div>- und<button>-Tags erraten. - Fragile Selektoren: Sobald das Entwicklungsteam CSS-Module, Tailwind-Klassen oder DOM-Strukturen ändert, schlagen automatisierte CSS-Selektoren fehl.
- Kontext-Overhead: Das Senden eines mehrere Megabyte großen DOM-Baums oder Bild-Snapshots bei jedem Einzelschritt verbraucht zehntausende Tokens und erzeugt erhebliche Netzwerklatenz.
- Probleme synthetischer Klicks: Das Auslösen von
MouseEvent- oderKeyboardEvent-Events umgeht häufig Reacts synthetisches Event-System, was zu veralteten Closures oder übergangenen Validierungen führen kann.
WebMCP ersetzt dieses fehleranfällige Vorgehen durch strukturierte, programmatische RPC-Aufrufe direkt im Ausführungskontext des Browsers. Anstelle unsicherer Selektoren registriert die Seite eindeutige Werkzeuge mit typisierten JSON-Schemas.
WebMCP im Vergleich zu Backend-MCP
Die Unterschiede zwischen serverseitigem MCP und WebMCP sind grundlegend:
| Dimension | Backend-MCP (Node.js / Python) | WebMCP (Nativ im Browser) |
|---|---|---|
| Ausführungskontext | Hintergrund-Dienst, Container, Serverless-Funktion | Ausführungs-Thread des aktiven Browser-Tabs |
| Transport | stdio, Server-Sent Events (SSE), WebSockets |
Direkter JavaScript-Funktionsaufruf |
| Zieldaten | Externe Datenbanken, Dateisysteme, Drittanbieter-APIs | React-State, Client-Router, lokaler Speicher, DOM |
| Unterstützte Primitive | Tools, Resources, Prompts | Ausschließlich Tools (gebunden an das aktive Dokument) |
| Sicherheitsgrenze | Prozess-Isolation, OS-Rechte, API-Keys | Browser-Sandbox, Same-Origin-Policy, Nutzerbestätigung |
WebMCP benötigt weder Netzwerk-Sockets noch Child-Prozesse. Das Webdokument selbst fungiert über die Schnittstelle document.modelContext als Registry.
Die Browser-Architektur: document.modelContext
In Chromium-Builds (ab Chromium 146 unter dem Flag #enable-webmcp-testing) stellt der Browser einen Tool-Broker direkt auf dem globalen document-Objekt bereit:
// 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;
}
}Zwei Kernprinzipien strukturieren diese Schnittstelle:
- Dokument-Gültigkeitsbereich: Werkzeuge sind an den Lebenszyklus des Dokuments gebunden. Verlässt der Nutzer die Seite oder schließt den Tab, wird die Registry verworfen.
- Abmeldung über AbortSignal: Statt einer separaten
unregisterTool-Methode wird der Lebenszyklus über standardisierteAbortSignal-Primitive gesteuert. Bricht das Signal ab, entfernt der Browser das Tool automatisch.
Muster 1: Deklaratives WebMCP mit React 19-Formularen
Der geradlinigste Weg zur Bereitstellung clientseitiger Funktionen ist die deklarative WebMCP-API. WebMCP erweitert herkömmliche HTML-Formulare um Attribute für Agenten:
toolname: Eindeutiger Name des Werkzeugs.tooldescription: Prägnante Beschreibung der Funktionalität und wann der Agent sie aufrufen sollte.
In React 19 arbeiten deklarative Formulare direkt mit useActionState und Client-/Server-Aktionen zusammen.
"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 > ${state.minAmount} in category "{state.category}"
</p>
)}
</form>
);
}Öffnet ein Agent die Seite, analysiert der Browser das DOM nach Formularen mit toolname-Attributen und generiert anhand der Feldnamen, Datentypen und Validierungsregeln dynamisch ein Tool-Schema. Ruft der Agent filter-orders auf, übergibt der Browser die Eingaben direkt an Reacts formAction.
Muster 2: Der imperative useWebMCPTool-Hook in React
Während deklarative Formulare für einfache Eingaben ausreichen, verlangen anspruchsvollere Anwendungen eine imperative Tool-Registrierung. Ein Agent muss eventuell eine Zoom-Ebene steuern, einen In-Memory-Index durchsuchen oder Navigationsschritte ausführen.
Für eine saubere React-Architektur erstellen wir einen benutzerdefinierten Hook, der drei zentrale Herausforderungen löst:
- Dynamischer Lebenszyklus: Werkzeuge müssen beim Mounten registriert und beim Unmounten entfernt werden.
- Aktueller Komponentenstatus: Der Callback muss auf die neuesten Props und States zugreifen können, ohne wiederholte Neuregistrierungen zu erzwingen.
- Ressourcenfreigabe via AbortSignal: Saubere Bereinigung bei SPA-Routenwechseln.
Hier ist die praxiserprobte Implementierung von useWebMCPTool:
"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]);
}Verwendung in einer realen Komponente
Das folgende Beispiel zeigt einen Dokumenten-Betrachter, in dem der KI-Agent Seiten wechseln und den aktuellen Textauszug abfragen kann:
"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>
);
}Muster 3: Schutzmechanismen und Nutzerbestätigung (Human-in-the-Loop)
Das direkte Bereitstellen clientseitiger Methoden für KI-Agenten birgt betriebliche und sicherheitstechnische Risiken:
- Ein Agent könnte unbeabsichtigt ungespeicherte Daten verwerfen oder kritische Transaktionen auslösen.
- Prompt-Injections in analysierten Webtexten könnten versuchen, sensible Werkzeuge unbefugt anzusteuern.
WebMCP begegnet diesen Risiken mit zwei Instrumenten: Read-Only-Hinweisen (readOnlyHint) und Interaktiven Bestätigungsbarrieren.
1. Read-Only-Hinweise
Reine Leseabfragen sollten stets mit annotations: { readOnlyHint: true } gekennzeichnet werden. Dies signalisiert dem Agenten und der Browser-Umgebung, dass der Aufruf nebenwirkungsfrei ist, wodurch das Modell Recherche-Schritte ohne wiederholte Sicherheitsabfragen bündeln kann.
2. Das Bestätigungs-Muster
Bei irreversiblen oder statusverändernden Aktionen sollte das Tool-Promise nicht unmittelbar aufgelöst werden. Stattdessen versetzt der Hook die React-Oberfläche in einen Bestätigungsmodus. Erst wenn die Anwenderin oder der Anwender im Dialog zustimmt, schließt das Promise erfolgreich ab.
"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>
)}
</>
);
}Muster 4: Autonomer Tab-Agent: WebMCP + Chrome Gemini Nano
Ein besonders leistungsfähiges Einsatzfeld von WebMCP entsteht im Verbund mit lokalen On-Device-Modellen. Über die Verknüpfung der W3C Prompt API (window.LanguageModel) mit document.modelContext lässt sich ein vollständig autarker In-Tab-Agent realisieren.
+-------------------------------------------------------------+
| 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) |
+-------------------------------------------------------------+// 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();
}
}Diese Ausführung erfolgt komplett auf der lokalen Hardware:
- Die Latenz bis zum ersten Token (TTFT) liegt unter 30 Millisekunden.
- Es fließen keine Daten an externe Server, was den Anforderungen von DSGVO und Bankenstandards entspricht.
- Es entstehen keine laufenden API-Kosten.
Produktionspraxis und Progressive Enhancement
Solange sich WebMCP im Standardisierungsprozess befindet, sollte die Technologie nach dem Prinzip der schrittweisen Verbesserung (Progressive Enhancement) eingesetzt werden:
- Feature-Erkennung: Prüfen Sie stets die Verfügbarkeit von
document.modelContext. Die React-Anwendung muss für menschliche Benutzer uneingeschränkt benutzbar bleiben, selbst wenn ein Browser keine KI-Schnittstellen unterstützt. - Kompakte Schemas: Lokale Gerätemodelle verfügen über kleinere Kontextfenster als Cloud-Modelle (meist 4k bis 8k Tokens). Parameterbeschreibungen sollten prägnant formuliert und tiefe Verschachtelungen vermieden werden.
- Atomare Zustandsübergänge: Achten Sie darauf, dass Tool-Aktionen erst dann zurückkehren, wenn React die DOM-Aktualisierungen abgeschlossen hat. Führt ein Agent zwei Werkzeuge nacheinander aus, muss der Folgeschritt stets auf dem frischen Zustand aufsetzen.
Durch die Kombination von React 19-Formularprimitiven mit imperativen WebMCP-Hooks wandeln Sie passive Benutzeroberflächen in strukturierte Aktionsräume für moderne KI-Agenten um.