React में WebMCP: ब्राउज़र AI एजेंट्स के लिए क्लाइंट-साइड स्टेट और फॉर्म एक्शन्स को उपलब्ध कराना
W3C WebMCP मानक, डिक्लेरेटिव फॉर्म एट्रिब्यूट्स और useWebMCPTool हुक्स की सहायता से React 19 वेब ऍप्लिकेशन्स को एजेंट-फ्रेंडली सतह में बदलें।

जब सॉफ्टवेयर इंजीनियर्स बड़े भाषा मॉडल्स (LLMs) को बाहरी प्रणालियों से जोड़ते हैं, तो मॉडल कॉन्टेक्स्ट प्रोटोकॉल (MCP) टूल इनवोकेशन के लिए एक मानक वायर फॉर्मेट बन चुका है। हालाँकि, अधिकांश मौजूदा MCP आर्किटेक्चर क्लाइंट-सर्वर मॉडल पर निर्भर करते हैं: जहाँ एक क्लाइंट (जैसे Claude Desktop या IDE असिस्टेंट) डेटाबेस क्वेरी करने, क्लाउड APIs चलाने या फ़ाइल सिस्टम देखने के लिए stdio या Server-Sent Events (SSE) के ज़रिए बैकग्राउंड प्रोसेस से संपर्क करता है।
लेकिन वेब ब्राउज़र के भीतर यह मॉडल पूरी तरह फिट नहीं बैठता।
आधुनिक वेब ऍप्लिकेशन्स समृद्ध और स्टेटफुल सिंगल-पेज ऍप्लिकेशन्स (SPAs) हैं। महत्वपूर्ण स्टेट—जैसे कैनवस पर सक्रिय चयन (selections), फॉर्म में अभी टाइप किया गया अनसेव्ड डेटा, क्लाइंट-साइड पेजिनेशन, सॉर्टिंग फ़िल्टर्स और इंटरैक्टिव मोडल डायलॉग्स—पूरी तरह से क्लाइंट की मेमोरी (React कंपोनेंट स्टेट, Zustand स्टोर्स या DOM) में रहती है। जब कोई ऑटोनॉमस ब्राउज़र एजेंट या एक्सटेंशन ऐसी ऍप्लिकेशन के साथ काम करने की कोशिश करता है, तो उसे विजुअल DOM स्क्रैपिंग, एक्सेसिबिलिटी ट्री पार्सिंग या नकली माउस क्लिक्स पर निर्भर होना पड़ता है, जो बेहद अस्थिर और धीमे होते हैं।
W3C वेब मशीन लर्निंग कम्युनिटी ग्रुप में विकसित और Chromium में प्रीव्यू के रूप में उपलब्ध WebMCP (Web Model Context Protocol) स्पेसिफिकेशन इस समस्या को हल करती है। यह मॉडल कॉन्टेक्स्ट प्रोटोकॉल को सीधे ब्राउज़र टैब के भीतर ले आती है।
यह गाइड समझाती है कि WebMCP कैसे काम करता है, यह बैकएंड MCP से किस प्रकार भिन्न है, और डिक्लेरेटिव HTML एट्रिब्यूट्स तथा React हुक्स का उपयोग करके React 19 कंपोनेंट स्टेट और फॉर्म एक्शन्स को ब्राउज़र एजेंट्स के समक्ष कैसे प्रस्तुत किया जाए।
+-------------------------------------------------------------------------+
| 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 से अधिक के ऑर्डर्स के लिए फ़िल्टर करें और उन्हें एक्सपोर्ट करें":
- एक्सेसिबिलिटी ट्री स्क्रैपिंग: एजेंट एक्सेसिबिलिटी ट्री को पढ़ता है या स्क्रीनशॉट कैप्चर करता है। उसे दर्जनों नेस्टेड
<div>और<button>एलिमेंट्स में से फ़िल्टर बटन ढूंढने का अनुमान लगाना पड़ता है। - सेलेक्टर्स की अस्थिरता: यदि इंजीनियरिंग टीम CSS मॉड्यूल्स, Tailwind क्लासेस या DOM संरचना को बदलती है, तो ऑटोमेटेड CSS सेलेक्टर्स तुरंत विफल हो जाते हैं।
- कॉन्टेक्स्ट ओवरहेड: हर चरण पर पूरे DOM ट्री या स्क्रीनशॉट को LLM तक भेजने में हज़ारों टोकन्स खर्च होते हैं और नेटवर्क लेटेंसी बढ़ जाती है।
- सिंथेटिक क्लिक्स की समस्या:
MouseEventयाKeyboardEventट्रिगर करना अक्सर React के सिंथेटिक इवेंट हैंडलर्स को बायपास कर देता है, जिससे बासी क्लोज़र्स (stale closures) या छूटे हुए वैलिडेशन्स की समस्याएँ होती हैं।
WebMCP इन समस्याओं को दूर करके सीधे क्लाइंट के भीतर स्ट्रक्चर्ड और प्रोग्रामैटिक RPC कॉल की सुविधा देता है। सेलेक्टर्स का अनुमान लगाने के बजाय, वेबपेज टाइप किए गए JSON स्कीमा के साथ स्पष्ट टूल्स रजिस्टर करता है।
WebMCP बनाम बैकएंड MCP
बैकएंड MCP और WebMCP के बीच का अंतर महत्वपूर्ण है:
| आयाम | बैकएंड MCP (Node.js / Python) | WebMCP (ब्राउज़र नेटिव) |
|---|---|---|
| एक्ज़ीक्यूशन कॉन्टेक्स्ट | बैकग्राउंड डीमन, कंटेनर, सर्वरलेस फंक्शन | एक्टिव ब्राउज़र टैब का एक्ज़ीक्यूशन थ्रेड |
| ट्रांसपोर्ट | stdio, Server-Sent Events (SSE), WebSockets |
डायरेक्ट जावास्क्रिप्ट फ़ंक्शन रेफरेंस |
| लक्षित डेटा | रिमोट डेटाबेस, फ़ाइल सिस्टम, थर्ड-पार्टी APIs | React स्टेट, क्लाइंट-साइड राउटर, लोकल स्टोरेज, DOM |
| समर्थित प्रिमिटिव्स | Tools, Resources, Prompts | केवल Tools (सक्रिय डॉक्यूमेंट तक सीमित) |
| सुरक्षा सीमा | प्रोसेस आइसोलेशन, OS अनुमतियाँ, API कुंजियाँ | ब्राउज़र सैंडबॉक्स, Same-Origin Policy, यूज़र कन्फर्मेशन |
WebMCP किसी नेटवर्क सॉकेट या चाइल्ड प्रोसेस का उपयोग नहीं करता। वेब डॉक्यूमेंट स्वयं document.modelContext इंटरफ़ेस के माध्यम से टूल रजिस्ट्री का कार्य करता है।
ब्राउज़र आर्किटेक्चर: document.modelContext
Chromium में (Chromium 146+ में #enable-webmcp-testing फ्लैग के साथ), ब्राउज़र वैश्विक document ऑब्जेक्ट पर एक इन-मेमोरी टूल ब्रोकर प्रदान करता है:
// 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;
}
}इस इंटरफ़ेस के दो प्रमुख नियम हैं:
- डॉक्यूमेंट स्कोपिंग: टूल्स डॉक्यूमेंट के लाइफसाइकिल से बंधे होते हैं। जब उपयोगकर्ता किसी दूसरे पेज पर जाता है या टैब बंद करता है, तो रजिस्ट्री समाप्त हो जाती है।
- AbortSignal अनरजिस्ट्रेशन: अलग से
unregisterToolविधि देने के बजाय, टूल का जीवनकाल मानकAbortSignalसे नियंत्रित होता है। सिग्नल एबॉर्ट होते ही ब्राउज़र टूल को हटा देता है।
पैटर्न 1: React 19 फॉर्म्स के साथ डिक्लेरेटिव WebMCP
ब्राउज़र एजेंट्स के लिए फ़ंक्शनैलिटी उपलब्ध कराने का सबसे सरल तरीका डिक्लेरेटिव WebMCP API है। WebMCP साधारण HTML फॉर्म्स में एजेंट एनोटेशन्स जोड़ता है:
toolname: टूल का विशिष्ट नाम।tooldescription: टूल के कार्य का संक्षिप्त विवरण कि एजेंट को इसे कब कॉल करना चाहिए।
React 19 में, डिक्लेरेटिव फॉर्म्स सीधे useActionState और सर्वर/क्लाइंट एक्शन्स के साथ एकीकृत होते हैं।
"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>
);
}जब कोई एजेंट पेज पर आता है, तो ब्राउज़र DOM को स्कैन करके toolname वाले फॉर्म्स की पहचान करता है और इनपुट फ़ील्ड्स के आधार पर ऑटोमैटिक टूल स्कीमा बनाता है। जब एजेंट filter-orders चलाता है, तो ब्राउज़र सीधे React के formAction को कॉल करता है।
पैटर्न 2: React में इम्परेटिव useWebMCPTool हुक
जहाँ डिक्लेरेटिव फॉर्म्स साधारण इनपुट के लिए उपयुक्त हैं, वहीं जटिल ऍप्लिकेशन्स को इम्परेटिव टूल रजिस्ट्रेशन की आवश्यकता होती है। उदाहरण के लिए, किसी एजेंट को कैनवस ज़ूम स्तर बदलना हो, मेमोरी में मौजूद टेबल को खोजना हो, या मल्टी-स्टेप विज़ार्ड में आगे बढ़ना हो।
React में इसे सुचारू रूप से लागू करने के लिए, हम एक कस्टम हुक बनाते हैं जो तीन मुख्य बिंदुओं का ध्यान रखता है:
- डायनेमिक लाइफसाइकिल: कंपोनेंट माउंट होने पर टूल रजिस्टर हो और अनमाउंट होने पर हट जाए।
- ताज़ा क्लोज़र एक्सेस: टूल एग्जीक्यूटर को बिना बार-बार री-रजिस्टर किए हमेशा ताज़ा प्रॉप्स और स्टेट मिलें।
- AbortSignal क्लीनअप: SPA राउट बदलते समय सुरक्षित रूप से अनरजिस्टर होना।
यहाँ 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]);
}वास्तविक कंपोनेंट में उपयोग का उदाहरण
नीचे एक इंटरैक्टिव डॉक्यूमेंट व्यूअर का उदाहरण है, जहाँ एजेंट पेजों को नेविगेट कर सकता है और चयनित टेक्स्ट पढ़ सकता है:
"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: सुरक्षा और ह्यूमन-इन-द-लूप कन्फर्मेशन
क्लाइंट-साइड फंक्शन्स को AI एजेंट्स के समक्ष प्रस्तुत करते समय सुरक्षा का विशेष ध्यान रखना आवश्यक है:
- कोई एजेंट किसी हानिकारक कार्य (जैसे डेटा डिलीट करना या अनपेक्षित भुगतान करना) को गलती से ट्रिगर न कर दे।
- वेबपेज में मौजूद प्रॉम्प्ट इंजेक्शन से संवेदनशील टूल्स का दुरुपयोग न हो।
WebMCP इसके लिए दो प्रमुख साधन प्रदान करता है: रीड-ओनली संकेत (readOnlyHint) और कन्फर्मेशन बैरियर।
1. रीड-ओनली संकेत
केवल डेटा पढ़ने वाले टूल्स के लिए हमेशा annotations: { readOnlyHint: true } जोड़ें। इससे एजेंट और ब्राउज़र को पता चलता है कि इस टूल के चलने से कोई साइड-इफेक्ट नहीं होगा, जिससे मॉडल बिना बार-बार उपयोगकर्ता से पूछे सूचनाएं एकत्र कर सकता है।
2. कन्फर्मेशन बैरियर पैटर्न
महत्वपूर्ण या संवेदनशील एक्शन्स के लिए टूल प्रॉमिस को तुरंत पूरा न करें। इसके बजाय React स्टेट में एक मोडल डायलॉग प्रदर्शित करें। जब यूज़र "पुष्टि करें" बटन दबाए, तभी प्रॉमिस रिज़ॉल्व होना चाहिए।
"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 के साथ जोड़कर ब्राउज़र टैब के भीतर ही शून्य-लेटेंसी वाला लोकल एजेंट बनाया जा सकता है।
+-------------------------------------------------------------+
| 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();
}
}यह संपूर्ण एक्ज़ीक्यूशन डिवाइस के अपने सिलिकॉन पर चलता है:
- पहले टोकन की लेटेंसी (TTFT) 30 मिलीसेकंड से भी कम होती है।
- कोई भी डेटा उपयोगकर्ता के डिवाइस से बाहर नहीं जाता, जो कड़े डेटा गोपनीयता नियमों का पालन करता है।
- क्लाउड इंफरेंस का कोई खर्च नहीं आता।
प्रोडक्शन वास्तविकताएँ और प्रोग्रेसिव एन्हांसमेंट
जब तक WebMCP मानक अंतिम रूप से स्वीकृत नहीं हो जाता, तब तक इसे प्रोग्रेसिव एन्हांसमेंट के रूप में अपनाना चाहिए:
- फ़ीचर डिटेक्शन: हमेशा
document.modelContextकी उपस्थिति की जांच करें। यदि ब्राउज़र में एजेंट API न भी हो, तब भी सामान्य उपयोगकर्ताओं के लिए React ऍप्लिकेशन सुचारू रूप से कार्य करती रहनी चाहिए। - संक्षिप्त स्कीमा: ऑन-डिवाइस मॉडल्स की कॉन्टेक्स्ट विंडो सीमित (4k से 8k टोकन) होती है। इसलिए टूल और पैरामीटर विवरण हमेशा संक्षिप्त और स्पष्ट रखें।
- एटॉमिक स्टेट अपडेट्स: सुनिश्चित करें कि टूल का प्रॉमिस तभी रिज़ॉल्व हो जब React ने DOM में स्टेट अपडेट पूरा कर लिया हो, ताकि रेस कंडीशन्स से बचा जा सके।
React 19 के फॉर्म प्रिमिटिव्स को WebMCP हुक्स के साथ जोड़कर, डेवलपर्स अपने यूज़र इंटरफ़ेस को आधुनिक AI एजेंट्स के लिए पूरी तरह तैयार कर सकते हैं।