react-webmcp 0.1.0 → 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({
@@ -217,9 +217,9 @@ useWebMCPTool({
217
217
  description: "Permanently delete the user account",
218
218
  inputSchema: { type: "object", properties: { confirm: { type: "boolean" } } },
219
219
  annotations: {
220
- readOnlyHint: "false",
221
- destructiveHint: "true",
222
- idempotentHint: "false",
220
+ readOnlyHint: false, // browser-native (boolean per WebIDL)
221
+ destructiveHint: true, // library extension (not in browser yet)
222
+ idempotentHint: false, // library extension (not in browser yet)
223
223
  },
224
224
  execute: ({ confirm }) => {
225
225
  if (!confirm) return "Deletion cancelled.";
package/dist/index.d.mts CHANGED
@@ -36,13 +36,22 @@ interface JSONSchema {
36
36
  items?: JSONSchemaProperty;
37
37
  }
38
38
  interface ToolAnnotations {
39
- /** Indicates the tool only reads data and does not modify state. */
40
- readOnlyHint?: "true" | "false";
41
- /** Indicates the tool performs a destructive/irreversible operation. */
42
- destructiveHint?: "true" | "false";
43
- /** Indicates the tool is idempotent (safe to retry). */
44
- idempotentHint?: "true" | "false";
45
- /** Indicates results can be cached. */
39
+ /** Indicates the tool only reads data and does not modify state (browser-native). */
40
+ readOnlyHint?: boolean;
41
+ /**
42
+ * Indicates the tool performs a destructive/irreversible operation.
43
+ * **Library extension** not yet implemented in the browser.
44
+ */
45
+ destructiveHint?: boolean;
46
+ /**
47
+ * Indicates the tool is idempotent (safe to retry).
48
+ * **Library extension** — not yet implemented in the browser.
49
+ */
50
+ idempotentHint?: boolean;
51
+ /**
52
+ * Indicates results can be cached.
53
+ * **Library extension** — not yet implemented in the browser.
54
+ */
46
55
  cache?: boolean;
47
56
  }
48
57
  interface ToolContentText {
@@ -61,7 +70,14 @@ interface WebMCPToolDefinition {
61
70
  description: string;
62
71
  /** JSON Schema describing the tool's input parameters. */
63
72
  inputSchema: JSONSchema | Record<string, never>;
64
- /** Optional JSON Schema describing the tool's output. */
73
+ /**
74
+ * Optional JSON Schema describing the tool's output.
75
+ *
76
+ * **Library extension** — `outputSchema` is NOT part of the browser's
77
+ * native `ToolRegistrationParams` WebIDL. It is silently ignored by
78
+ * `navigator.modelContext.registerTool()` but can be used by higher-level
79
+ * agent frameworks that inspect tool metadata.
80
+ */
65
81
  outputSchema?: JSONSchema | JSONSchemaProperty;
66
82
  /** Optional metadata hints for agents. */
67
83
  annotations?: ToolAnnotations;
@@ -75,7 +91,11 @@ interface UseWebMCPToolConfig {
75
91
  description: string;
76
92
  /** JSON Schema for the tool's input parameters. */
77
93
  inputSchema: JSONSchema | Record<string, never>;
78
- /** Optional JSON Schema for the tool's output. */
94
+ /**
95
+ * Optional JSON Schema for the tool's output.
96
+ *
97
+ * **Library extension** — not part of the browser's native WebIDL.
98
+ */
79
99
  outputSchema?: JSONSchema | JSONSchemaProperty;
80
100
  /** Optional metadata hints for agents. */
81
101
  annotations?: ToolAnnotations;
@@ -108,14 +128,35 @@ interface ModelContext {
108
128
  clearContext(): void;
109
129
  }
110
130
  interface ModelContextTesting {
131
+ /**
132
+ * Returns all registered tools.
133
+ *
134
+ * Per the browser's WebIDL, `inputSchema` is always a `DOMString`
135
+ * (the JSON-stringified schema), not a parsed object.
136
+ */
111
137
  listTools(): Array<{
112
138
  name: string;
113
139
  description: string;
114
- inputSchema: JSONSchema | string;
140
+ inputSchema: string;
115
141
  }>;
116
- executeTool(name: string, inputArgs: Record<string, unknown>): Promise<unknown>;
142
+ /**
143
+ * Execute a tool by name.
144
+ *
145
+ * Per the browser's WebIDL, `inputArguments` is a `DOMString`
146
+ * (a JSON-encoded string of the input object). Returns the
147
+ * JSON-stringified result, or `null` when the tool triggers a
148
+ * cross-document navigation (the result must then be retrieved
149
+ * via `getCrossDocumentScriptToolResult()`).
150
+ */
151
+ executeTool(toolName: string, inputArguments: string): Promise<string | null>;
152
+ /** Register a callback that fires whenever tools are added/removed/changed. */
117
153
  registerToolsChangedCallback(callback: () => void): void;
118
- getCrossDocumentScriptToolResult(): Promise<unknown>;
154
+ /**
155
+ * Retrieve the result of a tool execution that caused a
156
+ * cross-document navigation. Used by the Model Context Tool
157
+ * Inspector when `executeTool()` resolves with `null`.
158
+ */
159
+ getCrossDocumentScriptToolResult(): Promise<string>;
119
160
  }
120
161
  declare global {
121
162
  interface Navigator {
package/dist/index.d.ts CHANGED
@@ -36,13 +36,22 @@ interface JSONSchema {
36
36
  items?: JSONSchemaProperty;
37
37
  }
38
38
  interface ToolAnnotations {
39
- /** Indicates the tool only reads data and does not modify state. */
40
- readOnlyHint?: "true" | "false";
41
- /** Indicates the tool performs a destructive/irreversible operation. */
42
- destructiveHint?: "true" | "false";
43
- /** Indicates the tool is idempotent (safe to retry). */
44
- idempotentHint?: "true" | "false";
45
- /** Indicates results can be cached. */
39
+ /** Indicates the tool only reads data and does not modify state (browser-native). */
40
+ readOnlyHint?: boolean;
41
+ /**
42
+ * Indicates the tool performs a destructive/irreversible operation.
43
+ * **Library extension** not yet implemented in the browser.
44
+ */
45
+ destructiveHint?: boolean;
46
+ /**
47
+ * Indicates the tool is idempotent (safe to retry).
48
+ * **Library extension** — not yet implemented in the browser.
49
+ */
50
+ idempotentHint?: boolean;
51
+ /**
52
+ * Indicates results can be cached.
53
+ * **Library extension** — not yet implemented in the browser.
54
+ */
46
55
  cache?: boolean;
47
56
  }
48
57
  interface ToolContentText {
@@ -61,7 +70,14 @@ interface WebMCPToolDefinition {
61
70
  description: string;
62
71
  /** JSON Schema describing the tool's input parameters. */
63
72
  inputSchema: JSONSchema | Record<string, never>;
64
- /** Optional JSON Schema describing the tool's output. */
73
+ /**
74
+ * Optional JSON Schema describing the tool's output.
75
+ *
76
+ * **Library extension** — `outputSchema` is NOT part of the browser's
77
+ * native `ToolRegistrationParams` WebIDL. It is silently ignored by
78
+ * `navigator.modelContext.registerTool()` but can be used by higher-level
79
+ * agent frameworks that inspect tool metadata.
80
+ */
65
81
  outputSchema?: JSONSchema | JSONSchemaProperty;
66
82
  /** Optional metadata hints for agents. */
67
83
  annotations?: ToolAnnotations;
@@ -75,7 +91,11 @@ interface UseWebMCPToolConfig {
75
91
  description: string;
76
92
  /** JSON Schema for the tool's input parameters. */
77
93
  inputSchema: JSONSchema | Record<string, never>;
78
- /** Optional JSON Schema for the tool's output. */
94
+ /**
95
+ * Optional JSON Schema for the tool's output.
96
+ *
97
+ * **Library extension** — not part of the browser's native WebIDL.
98
+ */
79
99
  outputSchema?: JSONSchema | JSONSchemaProperty;
80
100
  /** Optional metadata hints for agents. */
81
101
  annotations?: ToolAnnotations;
@@ -108,14 +128,35 @@ interface ModelContext {
108
128
  clearContext(): void;
109
129
  }
110
130
  interface ModelContextTesting {
131
+ /**
132
+ * Returns all registered tools.
133
+ *
134
+ * Per the browser's WebIDL, `inputSchema` is always a `DOMString`
135
+ * (the JSON-stringified schema), not a parsed object.
136
+ */
111
137
  listTools(): Array<{
112
138
  name: string;
113
139
  description: string;
114
- inputSchema: JSONSchema | string;
140
+ inputSchema: string;
115
141
  }>;
116
- executeTool(name: string, inputArgs: Record<string, unknown>): Promise<unknown>;
142
+ /**
143
+ * Execute a tool by name.
144
+ *
145
+ * Per the browser's WebIDL, `inputArguments` is a `DOMString`
146
+ * (a JSON-encoded string of the input object). Returns the
147
+ * JSON-stringified result, or `null` when the tool triggers a
148
+ * cross-document navigation (the result must then be retrieved
149
+ * via `getCrossDocumentScriptToolResult()`).
150
+ */
151
+ executeTool(toolName: string, inputArguments: string): Promise<string | null>;
152
+ /** Register a callback that fires whenever tools are added/removed/changed. */
117
153
  registerToolsChangedCallback(callback: () => void): void;
118
- getCrossDocumentScriptToolResult(): Promise<unknown>;
154
+ /**
155
+ * Retrieve the result of a tool execution that caused a
156
+ * cross-document navigation. Used by the Model Context Tool
157
+ * Inspector when `executeTool()` resolves with `null`.
158
+ */
159
+ getCrossDocumentScriptToolResult(): Promise<string>;
119
160
  }
120
161
  declare global {
121
162
  interface Navigator {
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 {
@@ -78,34 +89,44 @@ function useWebMCPTool(config) {
78
89
  }
79
90
  function toolsFingerprint(tools) {
80
91
  return tools.map(
81
- (t) => `${t.name}::${t.description}::${JSON.stringify(t.inputSchema)}::${JSON.stringify(t.annotations ?? {})}`
92
+ (t) => `${t.name}::${t.description}::${JSON.stringify(t.inputSchema)}::${JSON.stringify(t.outputSchema ?? {})}::${JSON.stringify(t.annotations ?? {})}`
82
93
  ).join("|");
83
94
  }
84
95
  function useWebMCPContext(config) {
85
- const prevFingerprintRef = React2.useRef("");
86
96
  const toolsRef = React2.useRef(config.tools);
87
97
  toolsRef.current = config.tools;
88
98
  const fingerprint = toolsFingerprint(config.tools);
89
99
  React2.useEffect(() => {
90
- if (fingerprint === prevFingerprintRef.current) {
91
- return;
92
- }
93
- prevFingerprintRef.current = fingerprint;
94
100
  const mc = getModelContext();
95
101
  if (!mc) {
96
102
  warnIfUnavailable("useWebMCPContext");
97
103
  return;
98
104
  }
99
- const stableTools = toolsRef.current.map((tool, idx) => ({
100
- ...tool,
101
- execute: (input) => {
102
- 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;
103
116
  }
104
- }));
117
+ if (tool.outputSchema) {
118
+ def.outputSchema = tool.outputSchema;
119
+ }
120
+ return def;
121
+ });
105
122
  try {
106
- mc.provideContext({ tools: stableTools });
123
+ mc.provideContext({
124
+ tools: stableTools
125
+ });
107
126
  } catch (err) {
108
- 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
+ }
109
130
  }
110
131
  return () => {
111
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.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"]}
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 {
@@ -72,34 +83,44 @@ function useWebMCPTool(config) {
72
83
  }
73
84
  function toolsFingerprint(tools) {
74
85
  return tools.map(
75
- (t) => `${t.name}::${t.description}::${JSON.stringify(t.inputSchema)}::${JSON.stringify(t.annotations ?? {})}`
86
+ (t) => `${t.name}::${t.description}::${JSON.stringify(t.inputSchema)}::${JSON.stringify(t.outputSchema ?? {})}::${JSON.stringify(t.annotations ?? {})}`
76
87
  ).join("|");
77
88
  }
78
89
  function useWebMCPContext(config) {
79
- const prevFingerprintRef = useRef("");
80
90
  const toolsRef = useRef(config.tools);
81
91
  toolsRef.current = config.tools;
82
92
  const fingerprint = toolsFingerprint(config.tools);
83
93
  useEffect(() => {
84
- if (fingerprint === prevFingerprintRef.current) {
85
- return;
86
- }
87
- prevFingerprintRef.current = fingerprint;
88
94
  const mc = getModelContext();
89
95
  if (!mc) {
90
96
  warnIfUnavailable("useWebMCPContext");
91
97
  return;
92
98
  }
93
- const stableTools = toolsRef.current.map((tool, idx) => ({
94
- ...tool,
95
- execute: (input) => {
96
- 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;
97
110
  }
98
- }));
111
+ if (tool.outputSchema) {
112
+ def.outputSchema = tool.outputSchema;
113
+ }
114
+ return def;
115
+ });
99
116
  try {
100
- mc.provideContext({ tools: stableTools });
117
+ mc.provideContext({
118
+ tools: stableTools
119
+ });
101
120
  } catch (err) {
102
- 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
+ }
103
124
  }
104
125
  return () => {
105
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.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"]}
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.0",
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",