react-webmcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/modelContext.ts","../src/hooks/useWebMCPTool.ts","../src/hooks/useWebMCPContext.ts","../src/hooks/useToolEvent.ts","../src/components/WebMCPForm.tsx","../src/components/WebMCPInput.tsx","../src/components/WebMCPSelect.tsx","../src/components/WebMCPTextarea.tsx","../src/context.tsx"],"names":["useRef","useEffect","useCallback","jsx","React","createContext","useMemo","useContext"],"mappings":"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","file":"index.js","sourcesContent":["import type { ModelContext } from \"../types\";\n\n/**\n * Returns the navigator.modelContext API if available, or null.\n */\nexport function getModelContext(): ModelContext | null {\n if (\n typeof window !== \"undefined\" &&\n typeof window.navigator !== \"undefined\" &&\n window.navigator.modelContext\n ) {\n return window.navigator.modelContext;\n }\n return null;\n}\n\n/**\n * Returns true if the WebMCP API (navigator.modelContext) is available\n * in the current browsing context.\n */\nexport function isWebMCPAvailable(): boolean {\n return getModelContext() !== null;\n}\n\n/**\n * Returns true if the WebMCP testing API (navigator.modelContextTesting)\n * is available. This is the API used by the Model Context Tool Inspector\n * extension and requires the \"WebMCP for testing\" Chrome flag.\n */\nexport function isWebMCPTestingAvailable(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof window.navigator !== \"undefined\" &&\n !!window.navigator.modelContextTesting\n );\n}\n\n/**\n * Logs a warning when WebMCP is not available. Useful during development\n * to remind developers to enable the Chrome flag.\n */\nexport function warnIfUnavailable(hookName: string): void {\n if (!isWebMCPAvailable()) {\n console.warn(\n `[react-webmcp] ${hookName}: navigator.modelContext is not available. ` +\n `Ensure you are running Chrome 146+ with the \"WebMCP for testing\" flag enabled.`,\n );\n }\n}\n","import { useEffect, useRef } from \"react\";\nimport type { UseWebMCPToolConfig } from \"../types\";\nimport { getModelContext, warnIfUnavailable } from \"../utils/modelContext\";\n\n/**\n * Produces a stable fingerprint for a single tool definition so we can\n * detect meaningful changes without being tricked by new object references\n * created on every render (e.g. inline schema literals).\n */\nfunction toolFingerprint(config: UseWebMCPToolConfig): string {\n return `${config.name}::${config.description}::${JSON.stringify(config.inputSchema)}::${JSON.stringify(config.outputSchema ?? {})}::${JSON.stringify(config.annotations ?? {})}`;\n}\n\n/**\n * Register a single WebMCP tool via the imperative API.\n *\n * The tool is registered with `navigator.modelContext.registerTool()` when\n * the component mounts and unregistered with `unregisterTool()` on unmount.\n * If the tool definition changes (name, description, schemas, or\n * annotations), the previous tool is unregistered and the new one is\n * registered.\n *\n * Object/array props like `inputSchema` and `annotations` are compared by\n * value (serialised fingerprint), so passing inline literals on every render\n * will **not** cause unnecessary re-registration.\n *\n * The `execute` callback is always called through a ref, so it does not\n * need to be memoised by the consumer.\n *\n * @example\n * ```tsx\n * useWebMCPTool({\n * name: \"searchFlights\",\n * description: \"Search for flights with the given parameters.\",\n * inputSchema: {\n * type: \"object\",\n * properties: {\n * origin: { type: \"string\", description: \"Origin IATA code\" },\n * destination: { type: \"string\", description: \"Destination IATA code\" },\n * },\n * required: [\"origin\", \"destination\"],\n * },\n * execute: async ({ origin, destination }) => {\n * const results = await api.searchFlights(origin, destination);\n * return { content: [{ type: \"text\", text: JSON.stringify(results) }] };\n * },\n * });\n * ```\n */\nexport function useWebMCPTool(config: UseWebMCPToolConfig): void {\n const registeredNameRef = useRef<string | null>(null);\n const configRef = useRef(config);\n configRef.current = config;\n\n // Derive a stable fingerprint from the definition values.\n const fingerprint = toolFingerprint(config);\n\n useEffect(() => {\n const mc = getModelContext();\n if (!mc) {\n warnIfUnavailable(\"useWebMCPTool\");\n return;\n }\n\n // Unregister the previous tool if the name changed\n if (registeredNameRef.current && registeredNameRef.current !== config.name) {\n try {\n mc.unregisterTool(registeredNameRef.current);\n } catch {\n // Tool may have already been unregistered\n }\n }\n\n // Build the tool definition matching the navigator.modelContext shape.\n // The execute function is always routed through configRef so callers\n // never need to memoise their handler.\n const toolDef = {\n name: config.name,\n description: config.description,\n inputSchema: config.inputSchema,\n ...(config.outputSchema ? { outputSchema: config.outputSchema } : {}),\n ...(config.annotations ? { annotations: config.annotations } : {}),\n execute: (input: Record<string, unknown>) => {\n return configRef.current.execute(input);\n },\n };\n\n try {\n mc.registerTool(toolDef);\n registeredNameRef.current = config.name;\n } catch (err) {\n console.error(`[react-webmcp] Failed to register tool \"${config.name}\":`, err);\n }\n\n return () => {\n try {\n mc.unregisterTool(config.name);\n } catch {\n // Tool may have already been unregistered externally\n }\n registeredNameRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- fingerprint\n // captures the serialised value of all definition fields; config.name\n // is included so the cleanup closure captures the correct name.\n }, [fingerprint, config.name]);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { WebMCPToolDefinition } from \"../types\";\nimport { getModelContext, warnIfUnavailable } from \"../utils/modelContext\";\n\n/**\n * Produces a stable fingerprint string from a tools array so we can detect\n * meaningful changes without being tricked by new array references.\n * Compares tool names, descriptions, and serialised input schemas.\n */\nfunction toolsFingerprint(tools: WebMCPToolDefinition[]): string {\n return tools\n .map(\n (t) =>\n `${t.name}::${t.description}::${JSON.stringify(t.inputSchema)}::${JSON.stringify(t.annotations ?? {})}`,\n )\n .join(\"|\");\n}\n\n/**\n * Register multiple WebMCP tools at once using `provideContext()`.\n *\n * Unlike `useWebMCPTool` which manages a single tool, `useWebMCPContext`\n * replaces the entire set of registered tools. This is useful when the\n * application state changes significantly and you want to expose a\n * completely different set of tools.\n *\n * On unmount, all tools are cleared via `clearContext()`.\n *\n * The hook performs a deep comparison of tool definitions (name, description,\n * inputSchema, annotations) so that passing a new array reference on every\n * render does **not** cause unnecessary re-registration.\n *\n * @example\n * ```tsx\n * useWebMCPContext({\n * tools: [\n * {\n * name: \"addTodo\",\n * description: \"Add a new item to the todo list\",\n * inputSchema: { type: \"object\", properties: { text: { type: \"string\" } } },\n * execute: ({ text }) => ({ content: [{ type: \"text\", text: `Added: ${text}` }] }),\n * },\n * {\n * name: \"markComplete\",\n * description: \"Mark a todo item as complete\",\n * inputSchema: { type: \"object\", properties: { id: { type: \"string\" } } },\n * execute: ({ id }) => ({ content: [{ type: \"text\", text: `Completed: ${id}` }] }),\n * },\n * ],\n * });\n * ```\n */\nexport function useWebMCPContext(config: {\n tools: WebMCPToolDefinition[];\n}): void {\n const prevFingerprintRef = useRef<string>(\"\");\n // Keep a ref to the latest tools so the execute callbacks always close\n // over current handlers without triggering the effect.\n const toolsRef = useRef(config.tools);\n toolsRef.current = config.tools;\n\n const fingerprint = toolsFingerprint(config.tools);\n\n useEffect(() => {\n // Skip re-registration if the tool definitions haven't actually changed.\n if (fingerprint === prevFingerprintRef.current) {\n return;\n }\n prevFingerprintRef.current = fingerprint;\n\n const mc = getModelContext();\n if (!mc) {\n warnIfUnavailable(\"useWebMCPContext\");\n return;\n }\n\n // Wrap execute functions so they always call through the latest ref,\n // allowing callers to pass inline arrow functions without triggering\n // the effect.\n const stableTools = toolsRef.current.map((tool, idx) => ({\n ...tool,\n execute: (input: Record<string, unknown>) => {\n return toolsRef.current[idx].execute(input);\n },\n }));\n\n try {\n mc.provideContext({ tools: stableTools });\n } catch (err) {\n console.error(\"[react-webmcp] Failed to provide context:\", err);\n }\n\n return () => {\n try {\n mc.clearContext();\n } catch {\n // Context may have already been cleared\n }\n };\n }, [fingerprint]);\n}\n","import { useEffect } from \"react\";\n\n/**\n * Listen for WebMCP tool lifecycle events on the window.\n *\n * The browser fires `toolactivated` when an AI agent invokes a declarative\n * tool (form fields are pre-filled) and `toolcancel` when the agent or\n * user cancels the operation.\n *\n * @param event - The event name: \"toolactivated\" or \"toolcancel\"\n * @param callback - Called with the tool name when the event fires\n * @param toolNameFilter - Optional: only fire for a specific tool name\n *\n * @example\n * ```tsx\n * useToolEvent(\"toolactivated\", (toolName) => {\n * console.log(`Agent activated tool: ${toolName}`);\n * validateForm();\n * }, \"book_table\");\n * ```\n */\nexport function useToolEvent(\n event: \"toolactivated\" | \"toolcancel\",\n callback: (toolName: string) => void,\n toolNameFilter?: string,\n): void {\n useEffect(() => {\n const handler = (e: Event) => {\n const toolName = (e as CustomEvent & { toolName?: string }).toolName ??\n (e as CustomEvent).detail?.toolName;\n if (!toolName) return;\n if (toolNameFilter && toolName !== toolNameFilter) return;\n callback(toolName);\n };\n\n window.addEventListener(event, handler);\n return () => {\n window.removeEventListener(event, handler);\n };\n }, [event, callback, toolNameFilter]);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\nimport type { WebMCPFormSubmitEvent } from \"../types\";\n\nexport interface WebMCPFormProps\n extends Omit<React.FormHTMLAttributes<HTMLFormElement>, \"onSubmit\"> {\n /** The tool name exposed to AI agents. Maps to the `toolname` HTML attribute. */\n toolName: string;\n /** Description of what this tool does. Maps to `tooldescription`. */\n toolDescription: string;\n /** If true, the form auto-submits when filled by an agent. Maps to `toolautosubmit`. */\n toolAutoSubmit?: boolean;\n /**\n * Submit handler that receives the enhanced SubmitEvent with\n * `agentInvoked` and `respondWith` properties.\n */\n onSubmit?: (event: WebMCPFormSubmitEvent) => void;\n /** Called when a tool activation event fires for this form's tool. */\n onToolActivated?: (toolName: string) => void;\n /** Called when a tool cancel event fires for this form's tool. */\n onToolCancel?: (toolName: string) => void;\n children: React.ReactNode;\n}\n\n/**\n * A React wrapper for the WebMCP declarative API.\n *\n * Renders a `<form>` element with the appropriate WebMCP HTML attributes\n * (`toolname`, `tooldescription`, `toolautosubmit`) so the browser\n * automatically registers it as a WebMCP tool.\n *\n * @example\n * ```tsx\n * <WebMCPForm\n * toolName=\"book_table\"\n * toolDescription=\"Book a table at the restaurant\"\n * onSubmit={(e) => {\n * e.preventDefault();\n * if (e.agentInvoked) {\n * e.respondWith(Promise.resolve(\"Booking confirmed!\"));\n * }\n * }}\n * >\n * <WebMCPInput name=\"name\" label=\"Full Name\" />\n * <button type=\"submit\">Book</button>\n * </WebMCPForm>\n * ```\n */\nexport function WebMCPForm({\n toolName,\n toolDescription,\n toolAutoSubmit,\n onSubmit,\n onToolActivated,\n onToolCancel,\n children,\n ...rest\n}: WebMCPFormProps) {\n const formRef = useRef<HTMLFormElement>(null);\n\n // Listen for toolactivated and toolcancel events\n useEffect(() => {\n const handleActivated = (e: Event) => {\n const name =\n (e as CustomEvent & { toolName?: string }).toolName ??\n (e as CustomEvent).detail?.toolName;\n if (name === toolName && onToolActivated) {\n onToolActivated(name);\n }\n };\n\n const handleCancel = (e: Event) => {\n const name =\n (e as CustomEvent & { toolName?: string }).toolName ??\n (e as CustomEvent).detail?.toolName;\n if (name === toolName && onToolCancel) {\n onToolCancel(name);\n }\n };\n\n window.addEventListener(\"toolactivated\", handleActivated);\n window.addEventListener(\"toolcancel\", handleCancel);\n\n return () => {\n window.removeEventListener(\"toolactivated\", handleActivated);\n window.removeEventListener(\"toolcancel\", handleCancel);\n };\n }, [toolName, onToolActivated, onToolCancel]);\n\n const handleSubmit = useCallback(\n (e: React.FormEvent<HTMLFormElement>) => {\n if (onSubmit) {\n onSubmit(e.nativeEvent as unknown as WebMCPFormSubmitEvent);\n }\n },\n [onSubmit],\n );\n\n // Build the HTML attributes. React doesn't recognize toolname etc.,\n // so we spread them via a plain object cast.\n const webmcpAttrs: Record<string, string | boolean> = {\n toolname: toolName,\n tooldescription: toolDescription,\n };\n if (toolAutoSubmit) {\n webmcpAttrs.toolautosubmit = \"\";\n }\n\n return (\n <form\n ref={formRef}\n onSubmit={handleSubmit}\n {...webmcpAttrs}\n {...rest}\n >\n {children}\n </form>\n );\n}\n","import React from \"react\";\n\nexport interface WebMCPInputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {\n /** Maps to the `toolparamtitle` attribute (overrides the JSON Schema property key). */\n toolParamTitle?: string;\n /** Maps to the `toolparamdescription` attribute (describes this parameter to agents). */\n toolParamDescription?: string;\n}\n\n/**\n * An `<input>` element enhanced with WebMCP declarative attributes.\n *\n * Use inside a `<WebMCPForm>` to annotate individual form fields\n * for AI agents.\n *\n * @example\n * ```tsx\n * <WebMCPInput\n * type=\"text\"\n * name=\"name\"\n * toolParamDescription=\"Customer's full name (min 2 chars)\"\n * required\n * minLength={2}\n * />\n * ```\n */\nexport const WebMCPInput = React.forwardRef<HTMLInputElement, WebMCPInputProps>(\n ({ toolParamTitle, toolParamDescription, ...rest }, ref) => {\n const webmcpAttrs: Record<string, string> = {};\n if (toolParamTitle) {\n webmcpAttrs.toolparamtitle = toolParamTitle;\n }\n if (toolParamDescription) {\n webmcpAttrs.toolparamdescription = toolParamDescription;\n }\n\n return <input ref={ref} {...webmcpAttrs} {...rest} />;\n },\n);\n\nWebMCPInput.displayName = \"WebMCPInput\";\n","import React from \"react\";\n\nexport interface WebMCPSelectProps\n extends React.SelectHTMLAttributes<HTMLSelectElement> {\n /** Maps to the `toolparamtitle` attribute (overrides the JSON Schema property key). */\n toolParamTitle?: string;\n /** Maps to the `toolparamdescription` attribute (describes this parameter to agents). */\n toolParamDescription?: string;\n children: React.ReactNode;\n}\n\n/**\n * A `<select>` element enhanced with WebMCP declarative attributes.\n *\n * Use inside a `<WebMCPForm>` to annotate select inputs for AI agents.\n * The `<option>` values and text are automatically mapped to the tool's\n * JSON Schema `enum` / `oneOf` definitions by the browser.\n *\n * @example\n * ```tsx\n * <WebMCPSelect\n * name=\"seating\"\n * toolParamDescription=\"Preferred seating area\"\n * >\n * <option value=\"Main Dining\">Main Dining Room</option>\n * <option value=\"Terrace\">Terrace (Outdoor)</option>\n * </WebMCPSelect>\n * ```\n */\nexport const WebMCPSelect = React.forwardRef<\n HTMLSelectElement,\n WebMCPSelectProps\n>(({ toolParamTitle, toolParamDescription, children, ...rest }, ref) => {\n const webmcpAttrs: Record<string, string> = {};\n if (toolParamTitle) {\n webmcpAttrs.toolparamtitle = toolParamTitle;\n }\n if (toolParamDescription) {\n webmcpAttrs.toolparamdescription = toolParamDescription;\n }\n\n return (\n <select ref={ref} {...webmcpAttrs} {...rest}>\n {children}\n </select>\n );\n});\n\nWebMCPSelect.displayName = \"WebMCPSelect\";\n","import React from \"react\";\n\nexport interface WebMCPTextareaProps\n extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {\n /** Maps to the `toolparamtitle` attribute (overrides the JSON Schema property key). */\n toolParamTitle?: string;\n /** Maps to the `toolparamdescription` attribute (describes this parameter to agents). */\n toolParamDescription?: string;\n}\n\n/**\n * A `<textarea>` element enhanced with WebMCP declarative attributes.\n *\n * Use inside a `<WebMCPForm>` to annotate textarea inputs for AI agents.\n *\n * @example\n * ```tsx\n * <WebMCPTextarea\n * name=\"requests\"\n * rows={3}\n * toolParamDescription=\"Special requests (allergies, occasions, etc.)\"\n * />\n * ```\n */\nexport const WebMCPTextarea = React.forwardRef<\n HTMLTextAreaElement,\n WebMCPTextareaProps\n>(({ toolParamTitle, toolParamDescription, ...rest }, ref) => {\n const webmcpAttrs: Record<string, string> = {};\n if (toolParamTitle) {\n webmcpAttrs.toolparamtitle = toolParamTitle;\n }\n if (toolParamDescription) {\n webmcpAttrs.toolparamdescription = toolParamDescription;\n }\n\n return <textarea ref={ref} {...webmcpAttrs} {...rest} />;\n});\n\nWebMCPTextarea.displayName = \"WebMCPTextarea\";\n","import React, { createContext, useContext, useMemo } from \"react\";\nimport { isWebMCPAvailable, isWebMCPTestingAvailable } from \"./utils/modelContext\";\n\ninterface WebMCPContextValue {\n /** Whether navigator.modelContext is available in this browser. */\n available: boolean;\n /** Whether navigator.modelContextTesting is available (inspector API). */\n testingAvailable: boolean;\n}\n\nconst WebMCPReactContext = createContext<WebMCPContextValue>({\n available: false,\n testingAvailable: false,\n});\n\n/**\n * Provides WebMCP availability information to the component tree.\n *\n * Wrap your application (or a subtree) with `<WebMCPProvider>` to let\n * child components check WebMCP availability via the `useWebMCPStatus` hook.\n *\n * @example\n * ```tsx\n * function App() {\n * return (\n * <WebMCPProvider>\n * <MyComponent />\n * </WebMCPProvider>\n * );\n * }\n * ```\n */\nexport function WebMCPProvider({ children }: { children: React.ReactNode }) {\n const value = useMemo<WebMCPContextValue>(\n () => ({\n available: isWebMCPAvailable(),\n testingAvailable: isWebMCPTestingAvailable(),\n }),\n [],\n );\n\n return (\n <WebMCPReactContext.Provider value={value}>\n {children}\n </WebMCPReactContext.Provider>\n );\n}\n\n/**\n * Returns the current WebMCP availability status.\n *\n * Must be used within a `<WebMCPProvider>`.\n *\n * @example\n * ```tsx\n * function StatusBadge() {\n * const { available } = useWebMCPStatus();\n * return <span>{available ? \"WebMCP Ready\" : \"WebMCP Not Available\"}</span>;\n * }\n * ```\n */\nexport function useWebMCPStatus(): WebMCPContextValue {\n return useContext(WebMCPReactContext);\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,238 @@
1
+ import React2, { createContext, useRef, useEffect, useCallback, useMemo, useContext } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/hooks/useWebMCPTool.ts
5
+
6
+ // src/utils/modelContext.ts
7
+ function getModelContext() {
8
+ if (typeof window !== "undefined" && typeof window.navigator !== "undefined" && window.navigator.modelContext) {
9
+ return window.navigator.modelContext;
10
+ }
11
+ return null;
12
+ }
13
+ function isWebMCPAvailable() {
14
+ return getModelContext() !== null;
15
+ }
16
+ function isWebMCPTestingAvailable() {
17
+ return typeof window !== "undefined" && typeof window.navigator !== "undefined" && !!window.navigator.modelContextTesting;
18
+ }
19
+ function warnIfUnavailable(hookName) {
20
+ if (!isWebMCPAvailable()) {
21
+ console.warn(
22
+ `[react-webmcp] ${hookName}: navigator.modelContext is not available. Ensure you are running Chrome 146+ with the "WebMCP for testing" flag enabled.`
23
+ );
24
+ }
25
+ }
26
+
27
+ // src/hooks/useWebMCPTool.ts
28
+ function toolFingerprint(config) {
29
+ return `${config.name}::${config.description}::${JSON.stringify(config.inputSchema)}::${JSON.stringify(config.outputSchema ?? {})}::${JSON.stringify(config.annotations ?? {})}`;
30
+ }
31
+ function useWebMCPTool(config) {
32
+ const registeredNameRef = useRef(null);
33
+ const configRef = useRef(config);
34
+ configRef.current = config;
35
+ const fingerprint = toolFingerprint(config);
36
+ useEffect(() => {
37
+ const mc = getModelContext();
38
+ if (!mc) {
39
+ warnIfUnavailable("useWebMCPTool");
40
+ return;
41
+ }
42
+ if (registeredNameRef.current && registeredNameRef.current !== config.name) {
43
+ try {
44
+ mc.unregisterTool(registeredNameRef.current);
45
+ } catch {
46
+ }
47
+ }
48
+ const toolDef = {
49
+ name: config.name,
50
+ description: config.description,
51
+ inputSchema: config.inputSchema,
52
+ ...config.outputSchema ? { outputSchema: config.outputSchema } : {},
53
+ ...config.annotations ? { annotations: config.annotations } : {},
54
+ execute: (input) => {
55
+ return configRef.current.execute(input);
56
+ }
57
+ };
58
+ try {
59
+ mc.registerTool(toolDef);
60
+ registeredNameRef.current = config.name;
61
+ } catch (err) {
62
+ console.error(`[react-webmcp] Failed to register tool "${config.name}":`, err);
63
+ }
64
+ return () => {
65
+ try {
66
+ mc.unregisterTool(config.name);
67
+ } catch {
68
+ }
69
+ registeredNameRef.current = null;
70
+ };
71
+ }, [fingerprint, config.name]);
72
+ }
73
+ function toolsFingerprint(tools) {
74
+ return tools.map(
75
+ (t) => `${t.name}::${t.description}::${JSON.stringify(t.inputSchema)}::${JSON.stringify(t.annotations ?? {})}`
76
+ ).join("|");
77
+ }
78
+ function useWebMCPContext(config) {
79
+ const prevFingerprintRef = useRef("");
80
+ const toolsRef = useRef(config.tools);
81
+ toolsRef.current = config.tools;
82
+ const fingerprint = toolsFingerprint(config.tools);
83
+ useEffect(() => {
84
+ if (fingerprint === prevFingerprintRef.current) {
85
+ return;
86
+ }
87
+ prevFingerprintRef.current = fingerprint;
88
+ const mc = getModelContext();
89
+ if (!mc) {
90
+ warnIfUnavailable("useWebMCPContext");
91
+ return;
92
+ }
93
+ const stableTools = toolsRef.current.map((tool, idx) => ({
94
+ ...tool,
95
+ execute: (input) => {
96
+ return toolsRef.current[idx].execute(input);
97
+ }
98
+ }));
99
+ try {
100
+ mc.provideContext({ tools: stableTools });
101
+ } catch (err) {
102
+ console.error("[react-webmcp] Failed to provide context:", err);
103
+ }
104
+ return () => {
105
+ try {
106
+ mc.clearContext();
107
+ } catch {
108
+ }
109
+ };
110
+ }, [fingerprint]);
111
+ }
112
+ function useToolEvent(event, callback, toolNameFilter) {
113
+ useEffect(() => {
114
+ const handler = (e) => {
115
+ const toolName = e.toolName ?? e.detail?.toolName;
116
+ if (!toolName) return;
117
+ if (toolNameFilter && toolName !== toolNameFilter) return;
118
+ callback(toolName);
119
+ };
120
+ window.addEventListener(event, handler);
121
+ return () => {
122
+ window.removeEventListener(event, handler);
123
+ };
124
+ }, [event, callback, toolNameFilter]);
125
+ }
126
+ function WebMCPForm({
127
+ toolName,
128
+ toolDescription,
129
+ toolAutoSubmit,
130
+ onSubmit,
131
+ onToolActivated,
132
+ onToolCancel,
133
+ children,
134
+ ...rest
135
+ }) {
136
+ const formRef = useRef(null);
137
+ useEffect(() => {
138
+ const handleActivated = (e) => {
139
+ const name = e.toolName ?? e.detail?.toolName;
140
+ if (name === toolName && onToolActivated) {
141
+ onToolActivated(name);
142
+ }
143
+ };
144
+ const handleCancel = (e) => {
145
+ const name = e.toolName ?? e.detail?.toolName;
146
+ if (name === toolName && onToolCancel) {
147
+ onToolCancel(name);
148
+ }
149
+ };
150
+ window.addEventListener("toolactivated", handleActivated);
151
+ window.addEventListener("toolcancel", handleCancel);
152
+ return () => {
153
+ window.removeEventListener("toolactivated", handleActivated);
154
+ window.removeEventListener("toolcancel", handleCancel);
155
+ };
156
+ }, [toolName, onToolActivated, onToolCancel]);
157
+ const handleSubmit = useCallback(
158
+ (e) => {
159
+ if (onSubmit) {
160
+ onSubmit(e.nativeEvent);
161
+ }
162
+ },
163
+ [onSubmit]
164
+ );
165
+ const webmcpAttrs = {
166
+ toolname: toolName,
167
+ tooldescription: toolDescription
168
+ };
169
+ if (toolAutoSubmit) {
170
+ webmcpAttrs.toolautosubmit = "";
171
+ }
172
+ return /* @__PURE__ */ jsx(
173
+ "form",
174
+ {
175
+ ref: formRef,
176
+ onSubmit: handleSubmit,
177
+ ...webmcpAttrs,
178
+ ...rest,
179
+ children
180
+ }
181
+ );
182
+ }
183
+ var WebMCPInput = React2.forwardRef(
184
+ ({ toolParamTitle, toolParamDescription, ...rest }, ref) => {
185
+ const webmcpAttrs = {};
186
+ if (toolParamTitle) {
187
+ webmcpAttrs.toolparamtitle = toolParamTitle;
188
+ }
189
+ if (toolParamDescription) {
190
+ webmcpAttrs.toolparamdescription = toolParamDescription;
191
+ }
192
+ return /* @__PURE__ */ jsx("input", { ref, ...webmcpAttrs, ...rest });
193
+ }
194
+ );
195
+ WebMCPInput.displayName = "WebMCPInput";
196
+ var WebMCPSelect = React2.forwardRef(({ toolParamTitle, toolParamDescription, children, ...rest }, ref) => {
197
+ const webmcpAttrs = {};
198
+ if (toolParamTitle) {
199
+ webmcpAttrs.toolparamtitle = toolParamTitle;
200
+ }
201
+ if (toolParamDescription) {
202
+ webmcpAttrs.toolparamdescription = toolParamDescription;
203
+ }
204
+ return /* @__PURE__ */ jsx("select", { ref, ...webmcpAttrs, ...rest, children });
205
+ });
206
+ WebMCPSelect.displayName = "WebMCPSelect";
207
+ var WebMCPTextarea = React2.forwardRef(({ toolParamTitle, toolParamDescription, ...rest }, ref) => {
208
+ const webmcpAttrs = {};
209
+ if (toolParamTitle) {
210
+ webmcpAttrs.toolparamtitle = toolParamTitle;
211
+ }
212
+ if (toolParamDescription) {
213
+ webmcpAttrs.toolparamdescription = toolParamDescription;
214
+ }
215
+ return /* @__PURE__ */ jsx("textarea", { ref, ...webmcpAttrs, ...rest });
216
+ });
217
+ WebMCPTextarea.displayName = "WebMCPTextarea";
218
+ var WebMCPReactContext = createContext({
219
+ available: false,
220
+ testingAvailable: false
221
+ });
222
+ function WebMCPProvider({ children }) {
223
+ const value = useMemo(
224
+ () => ({
225
+ available: isWebMCPAvailable(),
226
+ testingAvailable: isWebMCPTestingAvailable()
227
+ }),
228
+ []
229
+ );
230
+ return /* @__PURE__ */ jsx(WebMCPReactContext.Provider, { value, children });
231
+ }
232
+ function useWebMCPStatus() {
233
+ return useContext(WebMCPReactContext);
234
+ }
235
+
236
+ export { WebMCPForm, WebMCPInput, WebMCPProvider, WebMCPSelect, WebMCPTextarea, getModelContext, isWebMCPAvailable, isWebMCPTestingAvailable, useToolEvent, useWebMCPContext, useWebMCPStatus, useWebMCPTool };
237
+ //# sourceMappingURL=index.mjs.map
238
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/modelContext.ts","../src/hooks/useWebMCPTool.ts","../src/hooks/useWebMCPContext.ts","../src/hooks/useToolEvent.ts","../src/components/WebMCPForm.tsx","../src/components/WebMCPInput.tsx","../src/components/WebMCPSelect.tsx","../src/components/WebMCPTextarea.tsx","../src/context.tsx"],"names":["useRef","useEffect","React","jsx"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ModelContext } from \"../types\";\n\n/**\n * Returns the navigator.modelContext API if available, or null.\n */\nexport function getModelContext(): ModelContext | null {\n if (\n typeof window !== \"undefined\" &&\n typeof window.navigator !== \"undefined\" &&\n window.navigator.modelContext\n ) {\n return window.navigator.modelContext;\n }\n return null;\n}\n\n/**\n * Returns true if the WebMCP API (navigator.modelContext) is available\n * in the current browsing context.\n */\nexport function isWebMCPAvailable(): boolean {\n return getModelContext() !== null;\n}\n\n/**\n * Returns true if the WebMCP testing API (navigator.modelContextTesting)\n * is available. This is the API used by the Model Context Tool Inspector\n * extension and requires the \"WebMCP for testing\" Chrome flag.\n */\nexport function isWebMCPTestingAvailable(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof window.navigator !== \"undefined\" &&\n !!window.navigator.modelContextTesting\n );\n}\n\n/**\n * Logs a warning when WebMCP is not available. Useful during development\n * to remind developers to enable the Chrome flag.\n */\nexport function warnIfUnavailable(hookName: string): void {\n if (!isWebMCPAvailable()) {\n console.warn(\n `[react-webmcp] ${hookName}: navigator.modelContext is not available. ` +\n `Ensure you are running Chrome 146+ with the \"WebMCP for testing\" flag enabled.`,\n );\n }\n}\n","import { useEffect, useRef } from \"react\";\nimport type { UseWebMCPToolConfig } from \"../types\";\nimport { getModelContext, warnIfUnavailable } from \"../utils/modelContext\";\n\n/**\n * Produces a stable fingerprint for a single tool definition so we can\n * detect meaningful changes without being tricked by new object references\n * created on every render (e.g. inline schema literals).\n */\nfunction toolFingerprint(config: UseWebMCPToolConfig): string {\n return `${config.name}::${config.description}::${JSON.stringify(config.inputSchema)}::${JSON.stringify(config.outputSchema ?? {})}::${JSON.stringify(config.annotations ?? {})}`;\n}\n\n/**\n * Register a single WebMCP tool via the imperative API.\n *\n * The tool is registered with `navigator.modelContext.registerTool()` when\n * the component mounts and unregistered with `unregisterTool()` on unmount.\n * If the tool definition changes (name, description, schemas, or\n * annotations), the previous tool is unregistered and the new one is\n * registered.\n *\n * Object/array props like `inputSchema` and `annotations` are compared by\n * value (serialised fingerprint), so passing inline literals on every render\n * will **not** cause unnecessary re-registration.\n *\n * The `execute` callback is always called through a ref, so it does not\n * need to be memoised by the consumer.\n *\n * @example\n * ```tsx\n * useWebMCPTool({\n * name: \"searchFlights\",\n * description: \"Search for flights with the given parameters.\",\n * inputSchema: {\n * type: \"object\",\n * properties: {\n * origin: { type: \"string\", description: \"Origin IATA code\" },\n * destination: { type: \"string\", description: \"Destination IATA code\" },\n * },\n * required: [\"origin\", \"destination\"],\n * },\n * execute: async ({ origin, destination }) => {\n * const results = await api.searchFlights(origin, destination);\n * return { content: [{ type: \"text\", text: JSON.stringify(results) }] };\n * },\n * });\n * ```\n */\nexport function useWebMCPTool(config: UseWebMCPToolConfig): void {\n const registeredNameRef = useRef<string | null>(null);\n const configRef = useRef(config);\n configRef.current = config;\n\n // Derive a stable fingerprint from the definition values.\n const fingerprint = toolFingerprint(config);\n\n useEffect(() => {\n const mc = getModelContext();\n if (!mc) {\n warnIfUnavailable(\"useWebMCPTool\");\n return;\n }\n\n // Unregister the previous tool if the name changed\n if (registeredNameRef.current && registeredNameRef.current !== config.name) {\n try {\n mc.unregisterTool(registeredNameRef.current);\n } catch {\n // Tool may have already been unregistered\n }\n }\n\n // Build the tool definition matching the navigator.modelContext shape.\n // The execute function is always routed through configRef so callers\n // never need to memoise their handler.\n const toolDef = {\n name: config.name,\n description: config.description,\n inputSchema: config.inputSchema,\n ...(config.outputSchema ? { outputSchema: config.outputSchema } : {}),\n ...(config.annotations ? { annotations: config.annotations } : {}),\n execute: (input: Record<string, unknown>) => {\n return configRef.current.execute(input);\n },\n };\n\n try {\n mc.registerTool(toolDef);\n registeredNameRef.current = config.name;\n } catch (err) {\n console.error(`[react-webmcp] Failed to register tool \"${config.name}\":`, err);\n }\n\n return () => {\n try {\n mc.unregisterTool(config.name);\n } catch {\n // Tool may have already been unregistered externally\n }\n registeredNameRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- fingerprint\n // captures the serialised value of all definition fields; config.name\n // is included so the cleanup closure captures the correct name.\n }, [fingerprint, config.name]);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { WebMCPToolDefinition } from \"../types\";\nimport { getModelContext, warnIfUnavailable } from \"../utils/modelContext\";\n\n/**\n * Produces a stable fingerprint string from a tools array so we can detect\n * meaningful changes without being tricked by new array references.\n * Compares tool names, descriptions, and serialised input schemas.\n */\nfunction toolsFingerprint(tools: WebMCPToolDefinition[]): string {\n return tools\n .map(\n (t) =>\n `${t.name}::${t.description}::${JSON.stringify(t.inputSchema)}::${JSON.stringify(t.annotations ?? {})}`,\n )\n .join(\"|\");\n}\n\n/**\n * Register multiple WebMCP tools at once using `provideContext()`.\n *\n * Unlike `useWebMCPTool` which manages a single tool, `useWebMCPContext`\n * replaces the entire set of registered tools. This is useful when the\n * application state changes significantly and you want to expose a\n * completely different set of tools.\n *\n * On unmount, all tools are cleared via `clearContext()`.\n *\n * The hook performs a deep comparison of tool definitions (name, description,\n * inputSchema, annotations) so that passing a new array reference on every\n * render does **not** cause unnecessary re-registration.\n *\n * @example\n * ```tsx\n * useWebMCPContext({\n * tools: [\n * {\n * name: \"addTodo\",\n * description: \"Add a new item to the todo list\",\n * inputSchema: { type: \"object\", properties: { text: { type: \"string\" } } },\n * execute: ({ text }) => ({ content: [{ type: \"text\", text: `Added: ${text}` }] }),\n * },\n * {\n * name: \"markComplete\",\n * description: \"Mark a todo item as complete\",\n * inputSchema: { type: \"object\", properties: { id: { type: \"string\" } } },\n * execute: ({ id }) => ({ content: [{ type: \"text\", text: `Completed: ${id}` }] }),\n * },\n * ],\n * });\n * ```\n */\nexport function useWebMCPContext(config: {\n tools: WebMCPToolDefinition[];\n}): void {\n const prevFingerprintRef = useRef<string>(\"\");\n // Keep a ref to the latest tools so the execute callbacks always close\n // over current handlers without triggering the effect.\n const toolsRef = useRef(config.tools);\n toolsRef.current = config.tools;\n\n const fingerprint = toolsFingerprint(config.tools);\n\n useEffect(() => {\n // Skip re-registration if the tool definitions haven't actually changed.\n if (fingerprint === prevFingerprintRef.current) {\n return;\n }\n prevFingerprintRef.current = fingerprint;\n\n const mc = getModelContext();\n if (!mc) {\n warnIfUnavailable(\"useWebMCPContext\");\n return;\n }\n\n // Wrap execute functions so they always call through the latest ref,\n // allowing callers to pass inline arrow functions without triggering\n // the effect.\n const stableTools = toolsRef.current.map((tool, idx) => ({\n ...tool,\n execute: (input: Record<string, unknown>) => {\n return toolsRef.current[idx].execute(input);\n },\n }));\n\n try {\n mc.provideContext({ tools: stableTools });\n } catch (err) {\n console.error(\"[react-webmcp] Failed to provide context:\", err);\n }\n\n return () => {\n try {\n mc.clearContext();\n } catch {\n // Context may have already been cleared\n }\n };\n }, [fingerprint]);\n}\n","import { useEffect } from \"react\";\n\n/**\n * Listen for WebMCP tool lifecycle events on the window.\n *\n * The browser fires `toolactivated` when an AI agent invokes a declarative\n * tool (form fields are pre-filled) and `toolcancel` when the agent or\n * user cancels the operation.\n *\n * @param event - The event name: \"toolactivated\" or \"toolcancel\"\n * @param callback - Called with the tool name when the event fires\n * @param toolNameFilter - Optional: only fire for a specific tool name\n *\n * @example\n * ```tsx\n * useToolEvent(\"toolactivated\", (toolName) => {\n * console.log(`Agent activated tool: ${toolName}`);\n * validateForm();\n * }, \"book_table\");\n * ```\n */\nexport function useToolEvent(\n event: \"toolactivated\" | \"toolcancel\",\n callback: (toolName: string) => void,\n toolNameFilter?: string,\n): void {\n useEffect(() => {\n const handler = (e: Event) => {\n const toolName = (e as CustomEvent & { toolName?: string }).toolName ??\n (e as CustomEvent).detail?.toolName;\n if (!toolName) return;\n if (toolNameFilter && toolName !== toolNameFilter) return;\n callback(toolName);\n };\n\n window.addEventListener(event, handler);\n return () => {\n window.removeEventListener(event, handler);\n };\n }, [event, callback, toolNameFilter]);\n}\n","import React, { useCallback, useEffect, useRef } from \"react\";\nimport type { WebMCPFormSubmitEvent } from \"../types\";\n\nexport interface WebMCPFormProps\n extends Omit<React.FormHTMLAttributes<HTMLFormElement>, \"onSubmit\"> {\n /** The tool name exposed to AI agents. Maps to the `toolname` HTML attribute. */\n toolName: string;\n /** Description of what this tool does. Maps to `tooldescription`. */\n toolDescription: string;\n /** If true, the form auto-submits when filled by an agent. Maps to `toolautosubmit`. */\n toolAutoSubmit?: boolean;\n /**\n * Submit handler that receives the enhanced SubmitEvent with\n * `agentInvoked` and `respondWith` properties.\n */\n onSubmit?: (event: WebMCPFormSubmitEvent) => void;\n /** Called when a tool activation event fires for this form's tool. */\n onToolActivated?: (toolName: string) => void;\n /** Called when a tool cancel event fires for this form's tool. */\n onToolCancel?: (toolName: string) => void;\n children: React.ReactNode;\n}\n\n/**\n * A React wrapper for the WebMCP declarative API.\n *\n * Renders a `<form>` element with the appropriate WebMCP HTML attributes\n * (`toolname`, `tooldescription`, `toolautosubmit`) so the browser\n * automatically registers it as a WebMCP tool.\n *\n * @example\n * ```tsx\n * <WebMCPForm\n * toolName=\"book_table\"\n * toolDescription=\"Book a table at the restaurant\"\n * onSubmit={(e) => {\n * e.preventDefault();\n * if (e.agentInvoked) {\n * e.respondWith(Promise.resolve(\"Booking confirmed!\"));\n * }\n * }}\n * >\n * <WebMCPInput name=\"name\" label=\"Full Name\" />\n * <button type=\"submit\">Book</button>\n * </WebMCPForm>\n * ```\n */\nexport function WebMCPForm({\n toolName,\n toolDescription,\n toolAutoSubmit,\n onSubmit,\n onToolActivated,\n onToolCancel,\n children,\n ...rest\n}: WebMCPFormProps) {\n const formRef = useRef<HTMLFormElement>(null);\n\n // Listen for toolactivated and toolcancel events\n useEffect(() => {\n const handleActivated = (e: Event) => {\n const name =\n (e as CustomEvent & { toolName?: string }).toolName ??\n (e as CustomEvent).detail?.toolName;\n if (name === toolName && onToolActivated) {\n onToolActivated(name);\n }\n };\n\n const handleCancel = (e: Event) => {\n const name =\n (e as CustomEvent & { toolName?: string }).toolName ??\n (e as CustomEvent).detail?.toolName;\n if (name === toolName && onToolCancel) {\n onToolCancel(name);\n }\n };\n\n window.addEventListener(\"toolactivated\", handleActivated);\n window.addEventListener(\"toolcancel\", handleCancel);\n\n return () => {\n window.removeEventListener(\"toolactivated\", handleActivated);\n window.removeEventListener(\"toolcancel\", handleCancel);\n };\n }, [toolName, onToolActivated, onToolCancel]);\n\n const handleSubmit = useCallback(\n (e: React.FormEvent<HTMLFormElement>) => {\n if (onSubmit) {\n onSubmit(e.nativeEvent as unknown as WebMCPFormSubmitEvent);\n }\n },\n [onSubmit],\n );\n\n // Build the HTML attributes. React doesn't recognize toolname etc.,\n // so we spread them via a plain object cast.\n const webmcpAttrs: Record<string, string | boolean> = {\n toolname: toolName,\n tooldescription: toolDescription,\n };\n if (toolAutoSubmit) {\n webmcpAttrs.toolautosubmit = \"\";\n }\n\n return (\n <form\n ref={formRef}\n onSubmit={handleSubmit}\n {...webmcpAttrs}\n {...rest}\n >\n {children}\n </form>\n );\n}\n","import React from \"react\";\n\nexport interface WebMCPInputProps\n extends React.InputHTMLAttributes<HTMLInputElement> {\n /** Maps to the `toolparamtitle` attribute (overrides the JSON Schema property key). */\n toolParamTitle?: string;\n /** Maps to the `toolparamdescription` attribute (describes this parameter to agents). */\n toolParamDescription?: string;\n}\n\n/**\n * An `<input>` element enhanced with WebMCP declarative attributes.\n *\n * Use inside a `<WebMCPForm>` to annotate individual form fields\n * for AI agents.\n *\n * @example\n * ```tsx\n * <WebMCPInput\n * type=\"text\"\n * name=\"name\"\n * toolParamDescription=\"Customer's full name (min 2 chars)\"\n * required\n * minLength={2}\n * />\n * ```\n */\nexport const WebMCPInput = React.forwardRef<HTMLInputElement, WebMCPInputProps>(\n ({ toolParamTitle, toolParamDescription, ...rest }, ref) => {\n const webmcpAttrs: Record<string, string> = {};\n if (toolParamTitle) {\n webmcpAttrs.toolparamtitle = toolParamTitle;\n }\n if (toolParamDescription) {\n webmcpAttrs.toolparamdescription = toolParamDescription;\n }\n\n return <input ref={ref} {...webmcpAttrs} {...rest} />;\n },\n);\n\nWebMCPInput.displayName = \"WebMCPInput\";\n","import React from \"react\";\n\nexport interface WebMCPSelectProps\n extends React.SelectHTMLAttributes<HTMLSelectElement> {\n /** Maps to the `toolparamtitle` attribute (overrides the JSON Schema property key). */\n toolParamTitle?: string;\n /** Maps to the `toolparamdescription` attribute (describes this parameter to agents). */\n toolParamDescription?: string;\n children: React.ReactNode;\n}\n\n/**\n * A `<select>` element enhanced with WebMCP declarative attributes.\n *\n * Use inside a `<WebMCPForm>` to annotate select inputs for AI agents.\n * The `<option>` values and text are automatically mapped to the tool's\n * JSON Schema `enum` / `oneOf` definitions by the browser.\n *\n * @example\n * ```tsx\n * <WebMCPSelect\n * name=\"seating\"\n * toolParamDescription=\"Preferred seating area\"\n * >\n * <option value=\"Main Dining\">Main Dining Room</option>\n * <option value=\"Terrace\">Terrace (Outdoor)</option>\n * </WebMCPSelect>\n * ```\n */\nexport const WebMCPSelect = React.forwardRef<\n HTMLSelectElement,\n WebMCPSelectProps\n>(({ toolParamTitle, toolParamDescription, children, ...rest }, ref) => {\n const webmcpAttrs: Record<string, string> = {};\n if (toolParamTitle) {\n webmcpAttrs.toolparamtitle = toolParamTitle;\n }\n if (toolParamDescription) {\n webmcpAttrs.toolparamdescription = toolParamDescription;\n }\n\n return (\n <select ref={ref} {...webmcpAttrs} {...rest}>\n {children}\n </select>\n );\n});\n\nWebMCPSelect.displayName = \"WebMCPSelect\";\n","import React from \"react\";\n\nexport interface WebMCPTextareaProps\n extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {\n /** Maps to the `toolparamtitle` attribute (overrides the JSON Schema property key). */\n toolParamTitle?: string;\n /** Maps to the `toolparamdescription` attribute (describes this parameter to agents). */\n toolParamDescription?: string;\n}\n\n/**\n * A `<textarea>` element enhanced with WebMCP declarative attributes.\n *\n * Use inside a `<WebMCPForm>` to annotate textarea inputs for AI agents.\n *\n * @example\n * ```tsx\n * <WebMCPTextarea\n * name=\"requests\"\n * rows={3}\n * toolParamDescription=\"Special requests (allergies, occasions, etc.)\"\n * />\n * ```\n */\nexport const WebMCPTextarea = React.forwardRef<\n HTMLTextAreaElement,\n WebMCPTextareaProps\n>(({ toolParamTitle, toolParamDescription, ...rest }, ref) => {\n const webmcpAttrs: Record<string, string> = {};\n if (toolParamTitle) {\n webmcpAttrs.toolparamtitle = toolParamTitle;\n }\n if (toolParamDescription) {\n webmcpAttrs.toolparamdescription = toolParamDescription;\n }\n\n return <textarea ref={ref} {...webmcpAttrs} {...rest} />;\n});\n\nWebMCPTextarea.displayName = \"WebMCPTextarea\";\n","import React, { createContext, useContext, useMemo } from \"react\";\nimport { isWebMCPAvailable, isWebMCPTestingAvailable } from \"./utils/modelContext\";\n\ninterface WebMCPContextValue {\n /** Whether navigator.modelContext is available in this browser. */\n available: boolean;\n /** Whether navigator.modelContextTesting is available (inspector API). */\n testingAvailable: boolean;\n}\n\nconst WebMCPReactContext = createContext<WebMCPContextValue>({\n available: false,\n testingAvailable: false,\n});\n\n/**\n * Provides WebMCP availability information to the component tree.\n *\n * Wrap your application (or a subtree) with `<WebMCPProvider>` to let\n * child components check WebMCP availability via the `useWebMCPStatus` hook.\n *\n * @example\n * ```tsx\n * function App() {\n * return (\n * <WebMCPProvider>\n * <MyComponent />\n * </WebMCPProvider>\n * );\n * }\n * ```\n */\nexport function WebMCPProvider({ children }: { children: React.ReactNode }) {\n const value = useMemo<WebMCPContextValue>(\n () => ({\n available: isWebMCPAvailable(),\n testingAvailable: isWebMCPTestingAvailable(),\n }),\n [],\n );\n\n return (\n <WebMCPReactContext.Provider value={value}>\n {children}\n </WebMCPReactContext.Provider>\n );\n}\n\n/**\n * Returns the current WebMCP availability status.\n *\n * Must be used within a `<WebMCPProvider>`.\n *\n * @example\n * ```tsx\n * function StatusBadge() {\n * const { available } = useWebMCPStatus();\n * return <span>{available ? \"WebMCP Ready\" : \"WebMCP Not Available\"}</span>;\n * }\n * ```\n */\nexport function useWebMCPStatus(): WebMCPContextValue {\n return useContext(WebMCPReactContext);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "react-webmcp",
3
+ "version": "0.1.0",
4
+ "description": "React hooks and components for the WebMCP standard — expose structured tools for AI agents on your website",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "sideEffects": false,
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "lint": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0",
29
+ "react-dom": ">=18.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "^19.0.0",
33
+ "@types/react-dom": "^19.0.0",
34
+ "react": "^19.0.0",
35
+ "react-dom": "^19.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.8.0"
38
+ },
39
+ "keywords": [
40
+ "webmcp",
41
+ "react",
42
+ "ai-agents",
43
+ "model-context-protocol",
44
+ "mcp",
45
+ "navigator-modelContext",
46
+ "chrome",
47
+ "web-standard"
48
+ ],
49
+ "author": "tech-sumit",
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/tech-sumit/react-webmcp.git"
54
+ },
55
+ "homepage": "https://github.com/tech-sumit/react-webmcp#readme",
56
+ "bugs": {
57
+ "url": "https://github.com/tech-sumit/react-webmcp/issues"
58
+ }
59
+ }