react-webmcp 0.1.1 → 0.2.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.
package/README.md CHANGED
@@ -128,8 +128,8 @@ Registers a single tool with `navigator.modelContext.registerTool()` on mount an
128
128
  | `name` | `string` | Unique tool identifier |
129
129
  | `description` | `string` | Human-readable description for agents |
130
130
  | `inputSchema` | `JSONSchema` | JSON Schema for input parameters |
131
- | `outputSchema` | `JSONSchema` | *(optional)* JSON Schema for output |
132
- | `annotations` | `ToolAnnotations` | *(optional)* Hints like `readOnlyHint` |
131
+ | `outputSchema` | `JSONSchema` | *(optional, library extension)* JSON Schema for output — not in browser WebIDL |
132
+ | `annotations` | `ToolAnnotations` | *(optional)* Hints; only `readOnlyHint` (`boolean`) is browser-native |
133
133
  | `execute` | `(input) => any` | Handler function called on invocation |
134
134
 
135
135
  #### `useWebMCPContext(config)`
@@ -209,7 +209,7 @@ Returns the `navigator.modelContext` object or `null`.
209
209
 
210
210
  ## Tool Annotations
211
211
 
212
- Annotations provide metadata hints to AI agents:
212
+ Annotations provide metadata hints to AI agents. Per the browser's WebIDL (`AnnotationsDict`), only `readOnlyHint` (`boolean`) is currently implemented in Chrome. The other fields are library-level extensions that may be used by higher-level agent frameworks:
213
213
 
214
214
  ```tsx
215
215
  useWebMCPTool({
package/dist/index.js CHANGED
@@ -23,10 +23,12 @@ function isWebMCPTestingAvailable() {
23
23
  return typeof window !== "undefined" && typeof window.navigator !== "undefined" && !!window.navigator.modelContextTesting;
24
24
  }
25
25
  function warnIfUnavailable(hookName) {
26
- if (!isWebMCPAvailable()) {
27
- console.warn(
28
- `[react-webmcp] ${hookName}: navigator.modelContext is not available. Ensure you are running Chrome 146+ with the "WebMCP for testing" flag enabled.`
29
- );
26
+ if (process.env.NODE_ENV !== "production") {
27
+ if (!isWebMCPAvailable()) {
28
+ console.warn(
29
+ `[react-webmcp] ${hookName}: navigator.modelContext is not available. Ensure you are running Chrome 146+ with the "WebMCP for testing" flag enabled.`
30
+ );
31
+ }
30
32
  }
31
33
  }
32
34
 
@@ -55,17 +57,26 @@ function useWebMCPTool(config) {
55
57
  name: config.name,
56
58
  description: config.description,
57
59
  inputSchema: config.inputSchema,
58
- ...config.outputSchema ? { outputSchema: config.outputSchema } : {},
59
- ...config.annotations ? { annotations: config.annotations } : {},
60
60
  execute: (input) => {
61
61
  return configRef.current.execute(input);
62
62
  }
63
63
  };
64
+ if (config.outputSchema) {
65
+ toolDef.outputSchema = config.outputSchema;
66
+ }
67
+ if (config.annotations) {
68
+ toolDef.annotations = config.annotations;
69
+ }
64
70
  try {
65
71
  mc.registerTool(toolDef);
66
72
  registeredNameRef.current = config.name;
67
73
  } catch (err) {
68
- console.error(`[react-webmcp] Failed to register tool "${config.name}":`, err);
74
+ if (process.env.NODE_ENV !== "production") {
75
+ console.error(
76
+ `[react-webmcp] Failed to register tool "${config.name}":`,
77
+ err
78
+ );
79
+ }
69
80
  }
70
81
  return () => {
71
82
  try {
@@ -91,16 +102,31 @@ function useWebMCPContext(config) {
91
102
  warnIfUnavailable("useWebMCPContext");
92
103
  return;
93
104
  }
94
- const stableTools = toolsRef.current.map((tool, idx) => ({
95
- ...tool,
96
- execute: (input) => {
97
- return toolsRef.current[idx].execute(input);
105
+ const stableTools = toolsRef.current.map((tool, idx) => {
106
+ const def = {
107
+ name: tool.name,
108
+ description: tool.description,
109
+ inputSchema: tool.inputSchema,
110
+ execute: (input) => {
111
+ return toolsRef.current[idx].execute(input);
112
+ }
113
+ };
114
+ if (tool.annotations) {
115
+ def.annotations = tool.annotations;
116
+ }
117
+ if (tool.outputSchema) {
118
+ def.outputSchema = tool.outputSchema;
98
119
  }
99
- }));
120
+ return def;
121
+ });
100
122
  try {
101
- mc.provideContext({ tools: stableTools });
123
+ mc.provideContext({
124
+ tools: stableTools
125
+ });
102
126
  } catch (err) {
103
- console.error("[react-webmcp] Failed to provide context:", err);
127
+ if (process.env.NODE_ENV !== "production") {
128
+ console.error("[react-webmcp] Failed to provide context:", err);
129
+ }
104
130
  }
105
131
  return () => {
106
132
  try {
package/dist/index.js.map CHANGED
@@ -1 +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.outputSchema ?? {})}::${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 // 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 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"]}
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. Only fires in development\n * builds so the warning is stripped from production bundles.\n */\nexport function warnIfUnavailable(hookName: string): void {\n if (process.env.NODE_ENV !== \"production\") {\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}\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: Record<string, unknown> = {\n name: config.name,\n description: config.description,\n inputSchema: config.inputSchema,\n execute: (input: Record<string, unknown>) => {\n return configRef.current.execute(input);\n },\n };\n if (config.outputSchema) {\n toolDef.outputSchema = config.outputSchema;\n }\n if (config.annotations) {\n toolDef.annotations = config.annotations;\n }\n\n try {\n mc.registerTool(toolDef as unknown as Parameters<typeof mc.registerTool>[0]);\n registeredNameRef.current = config.name;\n } catch (err) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(\n `[react-webmcp] Failed to register tool \"${config.name}\":`,\n err,\n );\n }\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.outputSchema ?? {})}::${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 // 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 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 const def: Record<string, unknown> = {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n execute: (input: Record<string, unknown>) => {\n return toolsRef.current[idx].execute(input);\n },\n };\n if (tool.annotations) {\n def.annotations = tool.annotations;\n }\n if (tool.outputSchema) {\n def.outputSchema = tool.outputSchema;\n }\n return def;\n });\n\n try {\n mc.provideContext({\n tools: stableTools as unknown as Parameters<typeof mc.provideContext>[0][\"tools\"],\n });\n } catch (err) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(\"[react-webmcp] Failed to provide context:\", err);\n }\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 CHANGED
@@ -17,10 +17,12 @@ function isWebMCPTestingAvailable() {
17
17
  return typeof window !== "undefined" && typeof window.navigator !== "undefined" && !!window.navigator.modelContextTesting;
18
18
  }
19
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
- );
20
+ if (process.env.NODE_ENV !== "production") {
21
+ if (!isWebMCPAvailable()) {
22
+ console.warn(
23
+ `[react-webmcp] ${hookName}: navigator.modelContext is not available. Ensure you are running Chrome 146+ with the "WebMCP for testing" flag enabled.`
24
+ );
25
+ }
24
26
  }
25
27
  }
26
28
 
@@ -49,17 +51,26 @@ function useWebMCPTool(config) {
49
51
  name: config.name,
50
52
  description: config.description,
51
53
  inputSchema: config.inputSchema,
52
- ...config.outputSchema ? { outputSchema: config.outputSchema } : {},
53
- ...config.annotations ? { annotations: config.annotations } : {},
54
54
  execute: (input) => {
55
55
  return configRef.current.execute(input);
56
56
  }
57
57
  };
58
+ if (config.outputSchema) {
59
+ toolDef.outputSchema = config.outputSchema;
60
+ }
61
+ if (config.annotations) {
62
+ toolDef.annotations = config.annotations;
63
+ }
58
64
  try {
59
65
  mc.registerTool(toolDef);
60
66
  registeredNameRef.current = config.name;
61
67
  } catch (err) {
62
- console.error(`[react-webmcp] Failed to register tool "${config.name}":`, err);
68
+ if (process.env.NODE_ENV !== "production") {
69
+ console.error(
70
+ `[react-webmcp] Failed to register tool "${config.name}":`,
71
+ err
72
+ );
73
+ }
63
74
  }
64
75
  return () => {
65
76
  try {
@@ -85,16 +96,31 @@ function useWebMCPContext(config) {
85
96
  warnIfUnavailable("useWebMCPContext");
86
97
  return;
87
98
  }
88
- const stableTools = toolsRef.current.map((tool, idx) => ({
89
- ...tool,
90
- execute: (input) => {
91
- return toolsRef.current[idx].execute(input);
99
+ const stableTools = toolsRef.current.map((tool, idx) => {
100
+ const def = {
101
+ name: tool.name,
102
+ description: tool.description,
103
+ inputSchema: tool.inputSchema,
104
+ execute: (input) => {
105
+ return toolsRef.current[idx].execute(input);
106
+ }
107
+ };
108
+ if (tool.annotations) {
109
+ def.annotations = tool.annotations;
110
+ }
111
+ if (tool.outputSchema) {
112
+ def.outputSchema = tool.outputSchema;
92
113
  }
93
- }));
114
+ return def;
115
+ });
94
116
  try {
95
- mc.provideContext({ tools: stableTools });
117
+ mc.provideContext({
118
+ tools: stableTools
119
+ });
96
120
  } catch (err) {
97
- console.error("[react-webmcp] Failed to provide context:", err);
121
+ if (process.env.NODE_ENV !== "production") {
122
+ console.error("[react-webmcp] Failed to provide context:", err);
123
+ }
98
124
  }
99
125
  return () => {
100
126
  try {
@@ -1 +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.outputSchema ?? {})}::${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 // 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 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"]}
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. Only fires in development\n * builds so the warning is stripped from production bundles.\n */\nexport function warnIfUnavailable(hookName: string): void {\n if (process.env.NODE_ENV !== \"production\") {\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}\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: Record<string, unknown> = {\n name: config.name,\n description: config.description,\n inputSchema: config.inputSchema,\n execute: (input: Record<string, unknown>) => {\n return configRef.current.execute(input);\n },\n };\n if (config.outputSchema) {\n toolDef.outputSchema = config.outputSchema;\n }\n if (config.annotations) {\n toolDef.annotations = config.annotations;\n }\n\n try {\n mc.registerTool(toolDef as unknown as Parameters<typeof mc.registerTool>[0]);\n registeredNameRef.current = config.name;\n } catch (err) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(\n `[react-webmcp] Failed to register tool \"${config.name}\":`,\n err,\n );\n }\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.outputSchema ?? {})}::${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 // 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 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 const def: Record<string, unknown> = {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n execute: (input: Record<string, unknown>) => {\n return toolsRef.current[idx].execute(input);\n },\n };\n if (tool.annotations) {\n def.annotations = tool.annotations;\n }\n if (tool.outputSchema) {\n def.outputSchema = tool.outputSchema;\n }\n return def;\n });\n\n try {\n mc.provideContext({\n tools: stableTools as unknown as Parameters<typeof mc.provideContext>[0][\"tools\"],\n });\n } catch (err) {\n if (process.env.NODE_ENV !== \"production\") {\n console.error(\"[react-webmcp] Failed to provide context:\", err);\n }\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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-webmcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "React hooks and components for the WebMCP standard — expose structured tools for AI agents on your website",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -22,6 +22,8 @@
22
22
  "build": "tsup",
23
23
  "dev": "tsup --watch",
24
24
  "lint": "tsc --noEmit",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
25
27
  "prepublishOnly": "npm run build"
26
28
  },
27
29
  "peerDependencies": {
@@ -29,12 +31,17 @@
29
31
  "react-dom": ">=18.0.0"
30
32
  },
31
33
  "devDependencies": {
34
+ "@testing-library/jest-dom": "^6.9.1",
35
+ "@testing-library/react": "^16.3.2",
36
+ "@types/node": "^25.2.3",
32
37
  "@types/react": "^19.0.0",
33
38
  "@types/react-dom": "^19.0.0",
39
+ "jsdom": "^28.1.0",
34
40
  "react": "^19.0.0",
35
41
  "react-dom": "^19.0.0",
36
42
  "tsup": "^8.0.0",
37
- "typescript": "^5.8.0"
43
+ "typescript": "^5.8.0",
44
+ "vitest": "^4.0.18"
38
45
  },
39
46
  "keywords": [
40
47
  "webmcp",