usewebmcp 2.1.0 → 2.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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -109,6 +109,8 @@ export function CounterTool() {
|
|
|
109
109
|
- `state.executionCount`
|
|
110
110
|
- Returns `execute(input)` for manual in-app invocation and `reset()` for state reset.
|
|
111
111
|
|
|
112
|
+
Current Chrome Beta 147 returns `undefined` from `registerTool(...)`, but MCP-B wrappers still expose a deprecated compatibility handle. This hook prefers the returned handle when present and falls back to `unregisterTool(name)`.
|
|
113
|
+
|
|
112
114
|
Your tool implementation (`config.execute` or `config.handler`) can be synchronous or asynchronous.
|
|
113
115
|
|
|
114
116
|
## `config.execute` vs returned `execute(...)`
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/useWebMCP.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAmBA;;;;;;;AAK+B,KALnB,cAKmB,CAAA,CAAA,CAAA,GALC,CAKD,SAAA;EAAzB,SAAA,WAAA,EAAA;IACA,SAAA,KAAA,CAAA,EAAA,KAAA,MAAA;EAAM,CAAA;AAaZ,CAAA,GAlBI,KAkBQ,SAAW;EACC,SAAA,KAAA,EAAA,KAAA,EAAA;CAEpB,GApBE,CAoBF,GAnBE,MAmBF,CAAA,MAAA,EAAA,OAAA,CAAA,GAlBA,CAkBA,SAlBU,WAkBV,GAjBE,wBAiBF,CAjB2B,CAiB3B,CAAA,GAhBE,MAgBF,CAAA,MAAA,EAAA,OAAA,CAAA;;;;;;;;AAaJ;AAoCA;;;AAEwB,KAtDZ,WAsDY,CAAA,sBArDA,gBAqDA,GAAA,SAAA,GAAA,SAAA,EAAA,YAAA,OAAA,CAAA,GAnDpB,aAmDoB,SAAA,SAAA,GAlDpB,SAkDoB,GAjDpB,aAiDoB,SAjDE,gBAiDF,GAhDlB,eAgDkB,CAhDF,aAgDE,CAAA,GA/ClB,SA+CkB;;;;;;;;AAG8B,UAzCrC,kBAyCqC,CAAA,UAAA,OAAA,CAAA,CAAA;EAsD5C;;;;EAqCM,WAAA,EAAA,OAAA;EAwBC;;;;EA2BkB,UAAA,EA5KrB,OA4KqB,GAAA,IAAA;EAAZ;;;AASE;EAIF,KAAA,EAnLd,KAmLc,GAAA,IAAA;EAAkB;;;;EAO1B,cAAA,EAAA,MAAA;;;;;;;;;;;AA+Bf;AACuB,KAvMX,mBAuMW,CAAA,qBAtMA,eAsMA,GAtMkB,WAsMlB,EAAA,sBArMC,gBAqMD,GAAA,SAAA,GAAA,SAAA,CAAA,GAAA,CAAA,KAAA,EAnMd,cAmMc,CAnMC,YAmMD,CAAA,EAAA,GAlMlB,OAkMkB,CAlMV,WAkMU,CAlME,aAkMF,CAAA,CAAA,GAlMoB,WAkMpB,CAlMgC,aAkMhC,CAAA;;;;;;;;;;AAYvB;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/useWebMCP.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAmBA;;;;;;;AAK+B,KALnB,cAKmB,CAAA,CAAA,CAAA,GALC,CAKD,SAAA;EAAzB,SAAA,WAAA,EAAA;IACA,SAAA,KAAA,CAAA,EAAA,KAAA,MAAA;EAAM,CAAA;AAaZ,CAAA,GAlBI,KAkBQ,SAAW;EACC,SAAA,KAAA,EAAA,KAAA,EAAA;CAEpB,GApBE,CAoBF,GAnBE,MAmBF,CAAA,MAAA,EAAA,OAAA,CAAA,GAlBA,CAkBA,SAlBU,WAkBV,GAjBE,wBAiBF,CAjB2B,CAiB3B,CAAA,GAhBE,MAgBF,CAAA,MAAA,EAAA,OAAA,CAAA;;;;;;;;AAaJ;AAoCA;;;AAEwB,KAtDZ,WAsDY,CAAA,sBArDA,gBAqDA,GAAA,SAAA,GAAA,SAAA,EAAA,YAAA,OAAA,CAAA,GAnDpB,aAmDoB,SAAA,SAAA,GAlDpB,SAkDoB,GAjDpB,aAiDoB,SAjDE,gBAiDF,GAhDlB,eAgDkB,CAhDF,aAgDE,CAAA,GA/ClB,SA+CkB;;;;;;;;AAG8B,UAzCrC,kBAyCqC,CAAA,UAAA,OAAA,CAAA,CAAA;EAsD5C;;;;EAqCM,WAAA,EAAA,OAAA;EAwBC;;;;EA2BkB,UAAA,EA5KrB,OA4KqB,GAAA,IAAA;EAAZ;;;AASE;EAIF,KAAA,EAnLd,KAmLc,GAAA,IAAA;EAAkB;;;;EAO1B,cAAA,EAAA,MAAA;;;;;;;;;;;AA+Bf;AACuB,KAvMX,mBAuMW,CAAA,qBAtMA,eAsMA,GAtMkB,WAsMlB,EAAA,sBArMC,gBAqMD,GAAA,SAAA,GAAA,SAAA,CAAA,GAAA,CAAA,KAAA,EAnMd,cAmMc,CAnMC,YAmMD,CAAA,EAAA,GAlMlB,OAkMkB,CAlMV,WAkMU,CAlME,aAkMF,CAAA,CAAA,GAlMoB,WAkMpB,CAlMgC,aAkMhC,CAAA;;;;;;;;;;AAYvB;;;;;;;;;;;;ACvIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UDjBU,sCACa,kBAAkB,mCACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAmCR;;;;;;;;;;;;;;;;;;;;;;;iBAwBC;;;;;gBAMD;;;;;;;;;;;0BAYU,YAAY;;;;;;;;uBASf,YAAY;;;;;;;;oBASf;;KAGf,gDACkB,kBAAkB,mCACjB;;;;WAMT,oBAAoB,cAAc;;;;YAIjC,oBAAoB,cAAc;;;;;WAMnC,oBAAoB,cAAc;;;;YAIjC,oBAAoB,cAAc;;;;;;;;;;;;;;;;KAiBtC,kCACW,kBAAkB,mCACjB,4CACpB,iBAAiB,cAAc,iBACjC,2BAA2B,cAAc;;;;;;;;UAS1B,mCAAmC;;;;;SAK3C,mBAAmB,YAAY;;;;;;;;;+BAUT,QAAQ,YAAY;;;;;;;;;;AAzSnD;;;;;;;;;;;AAmBA;;;;;;;;;;AAgBA;AAoCA;;;;;;;;;;;;AAKsE;;;;;;;;;;;;;AAuJ7C;;;;;;;;;;;;;;;;;AA0CzB;;;;;;;;;;;AAaA;;;;;;;;;;;;ACvIA;;;;;;;;;;;;;;;;iBAAgB,+BACO,kBAAkB,mCACjB,kDAEd,aAAa,cAAc,uBAC5B,iBACN,aAAa"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";import{useCallback as e,useEffect as t,useLayoutEffect as n,useRef as r,useState as i}from"react";function a(e){return typeof e==`string`?e:JSON.stringify(e,null,2)}const o=new Map;function s(e){if(!e||typeof e!=`object`||Array.isArray(e))return null;try{let t=JSON.parse(JSON.stringify(e));return!t||typeof t!=`object`||Array.isArray(t)?null:t}catch{return null}}const c=typeof window<`u`?n:t;function l(){let e=globalThis.process?.env?.NODE_ENV;return e===void 0?!1:e!==`production`}function u(n,u){let{name:d,description:f,inputSchema:p,outputSchema:m,annotations:h,execute:g,handler:_,formatOutput:v=a,onSuccess:y,onError:b}=n,x=g??_;if(!x)throw TypeError(`[useWebMCP] Tool "${d}" must provide an implementation via config.execute or config.handler`);let[S,C]=i({isExecuting:!1,lastResult:null,error:null,executionCount:0}),w=r(x),T=r(y),E=r(b),D=r(v),O=r(!0),k=r(new Set),A=r({inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u});c(()=>{w.current=x,T.current=y,E.current=b,D.current=v},[x,y,b,v]),t(()=>(O.current=!0,()=>{O.current=!1}),[]),t(()=>{if(!l()){A.current={inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u};return}let e=(e,t)=>{k.current.has(e)||(console.warn(`[useWebMCP] ${t}`),k.current.add(e))},t=A.current;p&&t.inputSchema&&t.inputSchema!==p&&e(`inputSchema`,`Tool "${d}" inputSchema reference changed; memoize or define it outside the component to avoid re-registration.`),m&&t.outputSchema&&t.outputSchema!==m&&e(`outputSchema`,`Tool "${d}" outputSchema reference changed; memoize or define it outside the component to avoid re-registration.`),h&&t.annotations&&t.annotations!==h&&e(`annotations`,`Tool "${d}" annotations reference changed; memoize or define it outside the component to avoid re-registration.`),f!==t.description&&e(`description`,`Tool "${d}" description changed; this re-registers the tool. Memoize the description if it does not need to update.`),u?.some(e=>typeof e==`object`&&!!e||typeof e==`function`)&&e(`deps`,`Tool "${d}" deps contains non-primitive values; prefer primitives or memoize objects/functions to reduce re-registration.`),A.current={inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u}},[h,u,f,p,d,m]);let j=e(async e=>{C(e=>({...e,isExecuting:!0,error:null}));try{let t=await w.current(e);return O.current&&C(e=>({isExecuting:!1,lastResult:t,error:null,executionCount:e.executionCount+1})),T.current&&T.current(t,e),t}catch(t){let n=t instanceof Error?t:Error(String(t));throw O.current&&C(e=>({...e,isExecuting:!1,error:n})),E.current&&E.current(n,e),n}},[]),M=r(j);t(()=>{M.current=j},[j]);let N=e(e=>M.current(e),[]),P=e(()=>{C({isExecuting:!1,lastResult:null,error:null,executionCount:0})},[]);return t(()=>{if(typeof window>`u`||!window.navigator?.modelContext){console.warn(`[useWebMCP] window.navigator.modelContext is not available. Tool "${d}" will not be registered.`);return}let e=async e=>{try{let t=await M.current(e),n={content:[{type:`text`,text:D.current(t)}]};if(m){let e=s(t);if(!e)throw Error(`Tool "${d}" outputSchema requires the tool implementation to return a JSON object result`);n.structuredContent=e}return n}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}},t=Symbol(d),n=window.navigator.modelContext
|
|
1
|
+
"use client";import{useCallback as e,useEffect as t,useLayoutEffect as n,useRef as r,useState as i}from"react";function a(e){return typeof e==`string`?e:JSON.stringify(e,null,2)}const o=new Map;function s(e){if(!e||typeof e!=`object`||Array.isArray(e))return null;try{let t=JSON.parse(JSON.stringify(e));return!t||typeof t!=`object`||Array.isArray(t)?null:t}catch{return null}}const c=typeof window<`u`?n:t;function l(){let e=globalThis.process?.env?.NODE_ENV;return e===void 0?!1:e!==`production`}function u(n,u){let{name:d,description:f,inputSchema:p,outputSchema:m,annotations:h,execute:g,handler:_,formatOutput:v=a,onSuccess:y,onError:b}=n,x=g??_;if(!x)throw TypeError(`[useWebMCP] Tool "${d}" must provide an implementation via config.execute or config.handler`);let[S,C]=i({isExecuting:!1,lastResult:null,error:null,executionCount:0}),w=r(x),T=r(y),E=r(b),D=r(v),O=r(!0),k=r(new Set),A=r({inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u});c(()=>{w.current=x,T.current=y,E.current=b,D.current=v},[x,y,b,v]),t(()=>(O.current=!0,()=>{O.current=!1}),[]),t(()=>{if(!l()){A.current={inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u};return}let e=(e,t)=>{k.current.has(e)||(console.warn(`[useWebMCP] ${t}`),k.current.add(e))},t=A.current;p&&t.inputSchema&&t.inputSchema!==p&&e(`inputSchema`,`Tool "${d}" inputSchema reference changed; memoize or define it outside the component to avoid re-registration.`),m&&t.outputSchema&&t.outputSchema!==m&&e(`outputSchema`,`Tool "${d}" outputSchema reference changed; memoize or define it outside the component to avoid re-registration.`),h&&t.annotations&&t.annotations!==h&&e(`annotations`,`Tool "${d}" annotations reference changed; memoize or define it outside the component to avoid re-registration.`),f!==t.description&&e(`description`,`Tool "${d}" description changed; this re-registers the tool. Memoize the description if it does not need to update.`),u?.some(e=>typeof e==`object`&&!!e||typeof e==`function`)&&e(`deps`,`Tool "${d}" deps contains non-primitive values; prefer primitives or memoize objects/functions to reduce re-registration.`),A.current={inputSchema:p,outputSchema:m,annotations:h,description:f,deps:u}},[h,u,f,p,d,m]);let j=e(async e=>{C(e=>({...e,isExecuting:!0,error:null}));try{let t=await w.current(e);return O.current&&C(e=>({isExecuting:!1,lastResult:t,error:null,executionCount:e.executionCount+1})),T.current&&T.current(t,e),t}catch(t){let n=t instanceof Error?t:Error(String(t));throw O.current&&C(e=>({...e,isExecuting:!1,error:n})),E.current&&E.current(n,e),n}},[]),M=r(j);t(()=>{M.current=j},[j]);let N=e(e=>M.current(e),[]),P=e(()=>{C({isExecuting:!1,lastResult:null,error:null,executionCount:0})},[]);return t(()=>{if(typeof window>`u`||!window.navigator?.modelContext){console.warn(`[useWebMCP] window.navigator.modelContext is not available. Tool "${d}" will not be registered.`);return}let e=async e=>{try{let t=await M.current(e),n={content:[{type:`text`,text:D.current(t)}]};if(m){let e=s(t);if(!e)throw Error(`Tool "${d}" outputSchema requires the tool implementation to return a JSON object result`);n.structuredContent=e}return n}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}},t=Symbol(d),n=window.navigator.modelContext,r={name:d,description:f,...p&&{inputSchema:p},...m&&{outputSchema:m},...h&&{annotations:h},execute:e},i=n.registerTool(r);return o.set(d,t),()=>{if(o.get(d)===t){o.delete(d);try{if(i&&typeof i.unregister==`function`){i.unregister();return}n.unregisterTool(d)}catch(e){l()&&console.warn(`[useWebMCP] Failed to unregister tool "${d}" during cleanup:`,e)}}}},[d,f,p,m,h,...u??[]]),{state:S,execute:N,reset:P}}export{u as useWebMCP};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["response: CallToolResult"],"sources":["../src/useWebMCP.ts"],"sourcesContent":["import type { ToolInputSchema } from '@mcp-b/webmcp-polyfill';\nimport type {\n CallToolResult,\n InputSchema,\n JsonSchemaObject,\n ToolDescriptor,\n} from '@mcp-b/webmcp-types';\nimport type { DependencyList } from 'react';\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type {\n InferOutput,\n InferToolInput,\n ToolExecutionState,\n WebMCPConfig,\n WebMCPReturn,\n} from './types.js';\n\n/**\n * Default output formatter that converts values to formatted JSON strings.\n *\n * String values are returned as-is; all other types are serialized to\n * indented JSON for readability.\n *\n * @internal\n */\nfunction defaultFormatOutput(output: unknown): string {\n if (typeof output === 'string') {\n return output;\n }\n return JSON.stringify(output, null, 2);\n}\n\nconst TOOL_OWNER_BY_NAME = new Map<string, symbol>();\ntype StructuredContent = Exclude<CallToolResult['structuredContent'], undefined>;\n\nfunction toStructuredContent(value: unknown): StructuredContent | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return null;\n }\n\n try {\n const normalized = JSON.parse(JSON.stringify(value)) as unknown;\n if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) {\n return null;\n }\n return normalized as StructuredContent;\n } catch {\n return null;\n }\n}\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\nfunction isDev(): boolean {\n const env = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV;\n return env !== undefined ? env !== 'production' : false;\n}\n\n/**\n * React hook for registering and managing Model Context Protocol (MCP) tools.\n *\n * This hook handles the complete lifecycle of an MCP tool:\n * - Registers the tool with `window.navigator.modelContext`\n * - Manages execution state (loading, results, errors)\n * - Handles tool execution and lifecycle callbacks\n * - Automatically unregisters on component unmount\n * - Returns `structuredContent` when `outputSchema` is defined\n *\n * ## Output Schema (Recommended)\n *\n * Always define an `outputSchema` for your tools. This provides:\n * - **Type Safety**: Handler return type is inferred from the schema\n * - **MCP structuredContent**: AI models receive structured, typed data\n * - **Better AI Understanding**: Models can reason about your tool's output format\n *\n * ```tsx\n * useWebMCP({\n * name: 'get_user',\n * description: 'Get user by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { userId: { type: 'string' } },\n * required: ['userId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * id: { type: 'string' },\n * name: { type: 'string' },\n * email: { type: 'string' },\n * },\n * } as const,\n * execute: async ({ userId }) => {\n * const user = await fetchUser(userId);\n * return { id: user.id, name: user.name, email: user.email };\n * },\n * });\n * ```\n *\n * ## Re-render Optimization\n *\n * This hook is optimized to minimize unnecessary tool re-registrations:\n *\n * - **Ref-based callbacks**: `execute`/`handler`, `onSuccess`, `onError`, and `formatOutput`\n * are stored in refs, so changing these functions won't trigger re-registration.\n *\n * **IMPORTANT**: If `inputSchema`, `outputSchema`, or `annotations` are defined inline\n * or change on every render, the tool will re-register unnecessarily. To avoid this,\n * define them outside your component with `as const`:\n *\n * ```tsx\n * // Good: Static schema defined outside component\n * const OUTPUT_SCHEMA = {\n * type: 'object',\n * properties: { count: { type: 'number' } },\n * } as const;\n *\n * // Bad: Inline schema (creates new object every render)\n * useWebMCP({\n * outputSchema: { type: 'object', properties: { count: { type: 'number' } } } as const,\n * });\n * ```\n *\n * @template TInputSchema - JSON Schema defining input parameter types (use `as const` for inference)\n * @template TOutputSchema - JSON Schema object defining output structure (enables structuredContent)\n *\n * @param config - Configuration object for the tool\n * @param deps - Optional dependency array that triggers tool re-registration when values change.\n *\n * @returns Object containing execution state and control methods\n *\n * @public\n *\n * @example\n * Basic tool with outputSchema (recommended):\n * ```tsx\n * function PostActions() {\n * const likeTool = useWebMCP({\n * name: 'posts_like',\n * description: 'Like a post by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { postId: { type: 'string', description: 'The post ID' } },\n * required: ['postId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * success: { type: 'boolean' },\n * likeCount: { type: 'number' },\n * },\n * } as const,\n * execute: async ({ postId }) => {\n * const result = await api.posts.like(postId);\n * return { success: true, likeCount: result.likes };\n * },\n * });\n *\n * return <div>Likes: {likeTool.state.lastResult?.likeCount ?? 0}</div>;\n * }\n * ```\n */\nexport function useWebMCP<\n TInputSchema extends ToolInputSchema = InputSchema,\n TOutputSchema extends JsonSchemaObject | undefined = undefined,\n>(\n config: WebMCPConfig<TInputSchema, TOutputSchema>,\n deps?: DependencyList\n): WebMCPReturn<TOutputSchema> {\n type TOutput = InferOutput<TOutputSchema>;\n type TInput = InferToolInput<TInputSchema>;\n const {\n name,\n description,\n inputSchema,\n outputSchema,\n annotations,\n execute: configExecute,\n handler: legacyHandler,\n formatOutput = defaultFormatOutput,\n onSuccess,\n onError,\n } = config;\n const toolExecute = configExecute ?? legacyHandler;\n\n if (!toolExecute) {\n throw new TypeError(\n `[useWebMCP] Tool \"${name}\" must provide an implementation via config.execute or config.handler`\n );\n }\n\n const [state, setState] = useState<ToolExecutionState<TOutput>>({\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n });\n\n const toolExecuteRef = useRef(toolExecute);\n const onSuccessRef = useRef(onSuccess);\n const onErrorRef = useRef(onError);\n const formatOutputRef = useRef(formatOutput);\n const isMountedRef = useRef(true);\n const warnedRef = useRef(new Set<string>());\n const prevConfigRef = useRef({\n inputSchema,\n outputSchema,\n annotations,\n description,\n deps,\n });\n // Update refs when callbacks change (doesn't trigger re-registration)\n useIsomorphicLayoutEffect(() => {\n toolExecuteRef.current = toolExecute;\n onSuccessRef.current = onSuccess;\n onErrorRef.current = onError;\n formatOutputRef.current = formatOutput;\n }, [toolExecute, onSuccess, onError, formatOutput]);\n\n // Cleanup: mark component as unmounted\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n if (!isDev()) {\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n return;\n }\n\n const warnOnce = (key: string, message: string) => {\n if (warnedRef.current.has(key)) {\n return;\n }\n console.warn(`[useWebMCP] ${message}`);\n warnedRef.current.add(key);\n };\n\n const prev = prevConfigRef.current;\n\n if (inputSchema && prev.inputSchema && prev.inputSchema !== inputSchema) {\n warnOnce(\n 'inputSchema',\n `Tool \"${name}\" inputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (outputSchema && prev.outputSchema && prev.outputSchema !== outputSchema) {\n warnOnce(\n 'outputSchema',\n `Tool \"${name}\" outputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (annotations && prev.annotations && prev.annotations !== annotations) {\n warnOnce(\n 'annotations',\n `Tool \"${name}\" annotations reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (description !== prev.description) {\n warnOnce(\n 'description',\n `Tool \"${name}\" description changed; this re-registers the tool. Memoize the description if it does not need to update.`\n );\n }\n\n if (\n deps?.some(\n (value) => (typeof value === 'object' && value !== null) || typeof value === 'function'\n )\n ) {\n warnOnce(\n 'deps',\n `Tool \"${name}\" deps contains non-primitive values; prefer primitives or memoize objects/functions to reduce re-registration.`\n );\n }\n\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n }, [annotations, deps, description, inputSchema, name, outputSchema]);\n\n /**\n * Executes the configured tool implementation with input validation and state management.\n *\n * @param input - The input parameters to validate and pass to the tool implementation\n * @returns Promise resolving to the tool output\n * @throws Error if validation fails or the tool implementation throws\n */\n const execute = useCallback(async (input: unknown): Promise<TOutput> => {\n setState((prev) => ({\n ...prev,\n isExecuting: true,\n error: null,\n }));\n\n try {\n const result = await toolExecuteRef.current(input as TInput);\n\n // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n isExecuting: false,\n lastResult: result,\n error: null,\n executionCount: prev.executionCount + 1,\n }));\n }\n\n if (onSuccessRef.current) {\n onSuccessRef.current(result, input);\n }\n\n return result;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n ...prev,\n isExecuting: false,\n error: err,\n }));\n }\n\n if (onErrorRef.current) {\n onErrorRef.current(err, input);\n }\n\n throw err;\n }\n }, []);\n const executeRef = useRef(execute);\n\n useEffect(() => {\n executeRef.current = execute;\n }, [execute]);\n\n const stableExecute = useCallback(\n (input: unknown): Promise<TOutput> => executeRef.current(input),\n []\n );\n\n /**\n * Resets the execution state to initial values.\n */\n const reset = useCallback(() => {\n setState({\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n });\n }, []);\n\n useEffect(() => {\n if (typeof window === 'undefined' || !window.navigator?.modelContext) {\n console.warn(\n `[useWebMCP] window.navigator.modelContext is not available. Tool \"${name}\" will not be registered.`\n );\n return;\n }\n\n /**\n * Handles MCP tool execution by running the tool implementation and formatting the response.\n *\n * @param input - The input parameters from the MCP client\n * @returns CallToolResult with text content and optional structuredContent\n */\n const mcpHandler = async (input: unknown): Promise<CallToolResult> => {\n try {\n const result = await executeRef.current(input);\n const formattedOutput = formatOutputRef.current(result);\n\n const response: CallToolResult = {\n content: [\n {\n type: 'text',\n text: formattedOutput,\n },\n ],\n };\n\n if (outputSchema) {\n const structuredContent = toStructuredContent(result);\n if (!structuredContent) {\n throw new Error(\n `Tool \"${name}\" outputSchema requires the tool implementation to return a JSON object result`\n );\n }\n response.structuredContent = structuredContent;\n }\n\n return response;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n };\n\n const ownerToken = Symbol(name);\n const modelContext = window.navigator.modelContext;\n\n (modelContext.registerTool as (tool: ToolDescriptor) => void)({\n name,\n description,\n ...(inputSchema && { inputSchema: inputSchema as InputSchema }),\n ...(outputSchema && { outputSchema: outputSchema as InputSchema }),\n ...(annotations && { annotations }),\n execute: mcpHandler,\n });\n TOOL_OWNER_BY_NAME.set(name, ownerToken);\n\n return () => {\n const currentOwner = TOOL_OWNER_BY_NAME.get(name);\n if (currentOwner !== ownerToken) {\n return;\n }\n\n TOOL_OWNER_BY_NAME.delete(name);\n try {\n modelContext.unregisterTool(name);\n } catch (error) {\n if (isDev()) {\n console.warn(`[useWebMCP] Failed to unregister tool \"${name}\" during cleanup:`, error);\n }\n }\n };\n // Spread operator in dependencies: Allows users to provide additional dependencies\n // via the `deps` parameter. While unconventional, this pattern is intentional to support\n // dynamic dependency injection. The spread is safe because deps is validated and warned\n // about non-primitive values earlier in this hook.\n }, [name, description, inputSchema, outputSchema, annotations, ...(deps ?? [])]);\n\n return {\n state,\n execute: stableExecute,\n reset,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["response: CallToolResult","toolDescriptor: ToolDescriptor"],"sources":["../src/useWebMCP.ts"],"sourcesContent":["import type { ToolInputSchema } from '@mcp-b/webmcp-polyfill';\nimport type {\n CallToolResult,\n InputSchema,\n JsonSchemaObject,\n ToolDescriptor,\n} from '@mcp-b/webmcp-types';\nimport type { DependencyList } from 'react';\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type {\n InferOutput,\n InferToolInput,\n ToolExecutionState,\n WebMCPConfig,\n WebMCPReturn,\n} from './types.js';\n\n/**\n * Default output formatter that converts values to formatted JSON strings.\n *\n * String values are returned as-is; all other types are serialized to\n * indented JSON for readability.\n *\n * @internal\n */\nfunction defaultFormatOutput(output: unknown): string {\n if (typeof output === 'string') {\n return output;\n }\n return JSON.stringify(output, null, 2);\n}\n\nconst TOOL_OWNER_BY_NAME = new Map<string, symbol>();\ntype StructuredContent = Exclude<CallToolResult['structuredContent'], undefined>;\ntype CompatModelContext = Navigator['modelContext'] & {\n registerTool: (tool: ToolDescriptor) => { unregister: () => void } | undefined;\n unregisterTool: (name: string) => void;\n};\n\nfunction toStructuredContent(value: unknown): StructuredContent | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return null;\n }\n\n try {\n const normalized = JSON.parse(JSON.stringify(value)) as unknown;\n if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) {\n return null;\n }\n return normalized as StructuredContent;\n } catch {\n return null;\n }\n}\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\nfunction isDev(): boolean {\n const env = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV;\n return env !== undefined ? env !== 'production' : false;\n}\n\n/**\n * React hook for registering and managing Model Context Protocol (MCP) tools.\n *\n * This hook handles the complete lifecycle of an MCP tool:\n * - Registers the tool with `window.navigator.modelContext`\n * - Manages execution state (loading, results, errors)\n * - Handles tool execution and lifecycle callbacks\n * - Automatically unregisters on component unmount\n * - Returns `structuredContent` when `outputSchema` is defined\n *\n * ## Output Schema (Recommended)\n *\n * Always define an `outputSchema` for your tools. This provides:\n * - **Type Safety**: Handler return type is inferred from the schema\n * - **MCP structuredContent**: AI models receive structured, typed data\n * - **Better AI Understanding**: Models can reason about your tool's output format\n *\n * ```tsx\n * useWebMCP({\n * name: 'get_user',\n * description: 'Get user by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { userId: { type: 'string' } },\n * required: ['userId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * id: { type: 'string' },\n * name: { type: 'string' },\n * email: { type: 'string' },\n * },\n * } as const,\n * execute: async ({ userId }) => {\n * const user = await fetchUser(userId);\n * return { id: user.id, name: user.name, email: user.email };\n * },\n * });\n * ```\n *\n * ## Re-render Optimization\n *\n * This hook is optimized to minimize unnecessary tool re-registrations:\n *\n * - **Ref-based callbacks**: `execute`/`handler`, `onSuccess`, `onError`, and `formatOutput`\n * are stored in refs, so changing these functions won't trigger re-registration.\n *\n * **IMPORTANT**: If `inputSchema`, `outputSchema`, or `annotations` are defined inline\n * or change on every render, the tool will re-register unnecessarily. To avoid this,\n * define them outside your component with `as const`:\n *\n * ```tsx\n * // Good: Static schema defined outside component\n * const OUTPUT_SCHEMA = {\n * type: 'object',\n * properties: { count: { type: 'number' } },\n * } as const;\n *\n * // Bad: Inline schema (creates new object every render)\n * useWebMCP({\n * outputSchema: { type: 'object', properties: { count: { type: 'number' } } } as const,\n * });\n * ```\n *\n * @template TInputSchema - JSON Schema defining input parameter types (use `as const` for inference)\n * @template TOutputSchema - JSON Schema object defining output structure (enables structuredContent)\n *\n * @param config - Configuration object for the tool\n * @param deps - Optional dependency array that triggers tool re-registration when values change.\n *\n * @returns Object containing execution state and control methods\n *\n * @public\n *\n * @example\n * Basic tool with outputSchema (recommended):\n * ```tsx\n * function PostActions() {\n * const likeTool = useWebMCP({\n * name: 'posts_like',\n * description: 'Like a post by ID',\n * inputSchema: {\n * type: 'object',\n * properties: { postId: { type: 'string', description: 'The post ID' } },\n * required: ['postId'],\n * } as const,\n * outputSchema: {\n * type: 'object',\n * properties: {\n * success: { type: 'boolean' },\n * likeCount: { type: 'number' },\n * },\n * } as const,\n * execute: async ({ postId }) => {\n * const result = await api.posts.like(postId);\n * return { success: true, likeCount: result.likes };\n * },\n * });\n *\n * return <div>Likes: {likeTool.state.lastResult?.likeCount ?? 0}</div>;\n * }\n * ```\n */\nexport function useWebMCP<\n TInputSchema extends ToolInputSchema = InputSchema,\n TOutputSchema extends JsonSchemaObject | undefined = undefined,\n>(\n config: WebMCPConfig<TInputSchema, TOutputSchema>,\n deps?: DependencyList\n): WebMCPReturn<TOutputSchema> {\n type TOutput = InferOutput<TOutputSchema>;\n type TInput = InferToolInput<TInputSchema>;\n const {\n name,\n description,\n inputSchema,\n outputSchema,\n annotations,\n execute: configExecute,\n handler: legacyHandler,\n formatOutput = defaultFormatOutput,\n onSuccess,\n onError,\n } = config;\n const toolExecute = configExecute ?? legacyHandler;\n\n if (!toolExecute) {\n throw new TypeError(\n `[useWebMCP] Tool \"${name}\" must provide an implementation via config.execute or config.handler`\n );\n }\n\n const [state, setState] = useState<ToolExecutionState<TOutput>>({\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n });\n\n const toolExecuteRef = useRef(toolExecute);\n const onSuccessRef = useRef(onSuccess);\n const onErrorRef = useRef(onError);\n const formatOutputRef = useRef(formatOutput);\n const isMountedRef = useRef(true);\n const warnedRef = useRef(new Set<string>());\n const prevConfigRef = useRef({\n inputSchema,\n outputSchema,\n annotations,\n description,\n deps,\n });\n // Update refs when callbacks change (doesn't trigger re-registration)\n useIsomorphicLayoutEffect(() => {\n toolExecuteRef.current = toolExecute;\n onSuccessRef.current = onSuccess;\n onErrorRef.current = onError;\n formatOutputRef.current = formatOutput;\n }, [toolExecute, onSuccess, onError, formatOutput]);\n\n // Cleanup: mark component as unmounted\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n if (!isDev()) {\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n return;\n }\n\n const warnOnce = (key: string, message: string) => {\n if (warnedRef.current.has(key)) {\n return;\n }\n console.warn(`[useWebMCP] ${message}`);\n warnedRef.current.add(key);\n };\n\n const prev = prevConfigRef.current;\n\n if (inputSchema && prev.inputSchema && prev.inputSchema !== inputSchema) {\n warnOnce(\n 'inputSchema',\n `Tool \"${name}\" inputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (outputSchema && prev.outputSchema && prev.outputSchema !== outputSchema) {\n warnOnce(\n 'outputSchema',\n `Tool \"${name}\" outputSchema reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (annotations && prev.annotations && prev.annotations !== annotations) {\n warnOnce(\n 'annotations',\n `Tool \"${name}\" annotations reference changed; memoize or define it outside the component to avoid re-registration.`\n );\n }\n\n if (description !== prev.description) {\n warnOnce(\n 'description',\n `Tool \"${name}\" description changed; this re-registers the tool. Memoize the description if it does not need to update.`\n );\n }\n\n if (\n deps?.some(\n (value) => (typeof value === 'object' && value !== null) || typeof value === 'function'\n )\n ) {\n warnOnce(\n 'deps',\n `Tool \"${name}\" deps contains non-primitive values; prefer primitives or memoize objects/functions to reduce re-registration.`\n );\n }\n\n prevConfigRef.current = { inputSchema, outputSchema, annotations, description, deps };\n }, [annotations, deps, description, inputSchema, name, outputSchema]);\n\n /**\n * Executes the configured tool implementation with input validation and state management.\n *\n * @param input - The input parameters to validate and pass to the tool implementation\n * @returns Promise resolving to the tool output\n * @throws Error if validation fails or the tool implementation throws\n */\n const execute = useCallback(async (input: unknown): Promise<TOutput> => {\n setState((prev) => ({\n ...prev,\n isExecuting: true,\n error: null,\n }));\n\n try {\n const result = await toolExecuteRef.current(input as TInput);\n\n // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n isExecuting: false,\n lastResult: result,\n error: null,\n executionCount: prev.executionCount + 1,\n }));\n }\n\n if (onSuccessRef.current) {\n onSuccessRef.current(result, input);\n }\n\n return result;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Only update state if component is still mounted\n if (isMountedRef.current) {\n setState((prev) => ({\n ...prev,\n isExecuting: false,\n error: err,\n }));\n }\n\n if (onErrorRef.current) {\n onErrorRef.current(err, input);\n }\n\n throw err;\n }\n }, []);\n const executeRef = useRef(execute);\n\n useEffect(() => {\n executeRef.current = execute;\n }, [execute]);\n\n const stableExecute = useCallback(\n (input: unknown): Promise<TOutput> => executeRef.current(input),\n []\n );\n\n /**\n * Resets the execution state to initial values.\n */\n const reset = useCallback(() => {\n setState({\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n });\n }, []);\n\n useEffect(() => {\n if (typeof window === 'undefined' || !window.navigator?.modelContext) {\n console.warn(\n `[useWebMCP] window.navigator.modelContext is not available. Tool \"${name}\" will not be registered.`\n );\n return;\n }\n\n /**\n * Handles MCP tool execution by running the tool implementation and formatting the response.\n *\n * @param input - The input parameters from the MCP client\n * @returns CallToolResult with text content and optional structuredContent\n */\n const mcpHandler = async (input: unknown): Promise<CallToolResult> => {\n try {\n const result = await executeRef.current(input);\n const formattedOutput = formatOutputRef.current(result);\n\n const response: CallToolResult = {\n content: [\n {\n type: 'text',\n text: formattedOutput,\n },\n ],\n };\n\n if (outputSchema) {\n const structuredContent = toStructuredContent(result);\n if (!structuredContent) {\n throw new Error(\n `Tool \"${name}\" outputSchema requires the tool implementation to return a JSON object result`\n );\n }\n response.structuredContent = structuredContent;\n }\n\n return response;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n return {\n content: [\n {\n type: 'text',\n text: `Error: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n };\n\n const ownerToken = Symbol(name);\n const modelContext = window.navigator.modelContext as CompatModelContext;\n const toolDescriptor: ToolDescriptor = {\n name,\n description,\n ...(inputSchema && { inputSchema: inputSchema as InputSchema }),\n ...(outputSchema && { outputSchema: outputSchema as InputSchema }),\n ...(annotations && { annotations }),\n execute: mcpHandler,\n };\n\n const registration = modelContext.registerTool(toolDescriptor);\n TOOL_OWNER_BY_NAME.set(name, ownerToken);\n\n return () => {\n const currentOwner = TOOL_OWNER_BY_NAME.get(name);\n if (currentOwner !== ownerToken) {\n return;\n }\n\n TOOL_OWNER_BY_NAME.delete(name);\n try {\n if (registration && typeof registration.unregister === 'function') {\n registration.unregister();\n return;\n }\n\n modelContext.unregisterTool(name);\n } catch (error) {\n if (isDev()) {\n console.warn(`[useWebMCP] Failed to unregister tool \"${name}\" during cleanup:`, error);\n }\n }\n };\n // Spread operator in dependencies: Allows users to provide additional dependencies\n // via the `deps` parameter. While unconventional, this pattern is intentional to support\n // dynamic dependency injection. The spread is safe because deps is validated and warned\n // about non-primitive values earlier in this hook.\n }, [name, description, inputSchema, outputSchema, annotations, ...(deps ?? [])]);\n\n return {\n state,\n execute: stableExecute,\n reset,\n };\n}\n"],"mappings":"+GAyBA,SAAS,EAAoB,EAAyB,CAIpD,OAHI,OAAO,GAAW,SACb,EAEF,KAAK,UAAU,EAAQ,KAAM,EAAE,CAGxC,MAAM,EAAqB,IAAI,IAO/B,SAAS,EAAoB,EAA0C,CACrE,GAAI,CAAC,GAAS,OAAO,GAAU,UAAY,MAAM,QAAQ,EAAM,CAC7D,OAAO,KAGT,GAAI,CACF,IAAM,EAAa,KAAK,MAAM,KAAK,UAAU,EAAM,CAAC,CAIpD,MAHI,CAAC,GAAc,OAAO,GAAe,UAAY,MAAM,QAAQ,EAAW,CACrE,KAEF,OACD,CACN,OAAO,MAIX,MAAM,EAA4B,OAAO,OAAW,IAAc,EAAkB,EAEpF,SAAS,GAAiB,CACxB,IAAM,EAAO,WAA6D,SAAS,KAAK,SACxF,OAAO,IAAQ,IAAA,GAAmC,GAAvB,IAAQ,aA2GrC,SAAgB,EAId,EACA,EAC6B,CAG7B,GAAM,CACJ,OACA,cACA,cACA,eACA,cACA,QAAS,EACT,QAAS,EACT,eAAe,EACf,YACA,WACE,EACE,EAAc,GAAiB,EAErC,GAAI,CAAC,EACH,MAAU,UACR,qBAAqB,EAAK,uEAC3B,CAGH,GAAM,CAAC,EAAO,GAAY,EAAsC,CAC9D,YAAa,GACb,WAAY,KACZ,MAAO,KACP,eAAgB,EACjB,CAAC,CAEI,EAAiB,EAAO,EAAY,CACpC,EAAe,EAAO,EAAU,CAChC,EAAa,EAAO,EAAQ,CAC5B,EAAkB,EAAO,EAAa,CACtC,EAAe,EAAO,GAAK,CAC3B,EAAY,EAAO,IAAI,IAAc,CACrC,EAAgB,EAAO,CAC3B,cACA,eACA,cACA,cACA,OACD,CAAC,CAEF,MAAgC,CAC9B,EAAe,QAAU,EACzB,EAAa,QAAU,EACvB,EAAW,QAAU,EACrB,EAAgB,QAAU,GACzB,CAAC,EAAa,EAAW,EAAS,EAAa,CAAC,CAGnD,OACE,EAAa,QAAU,OACV,CACX,EAAa,QAAU,KAExB,EAAE,CAAC,CAEN,MAAgB,CACd,GAAI,CAAC,GAAO,CAAE,CACZ,EAAc,QAAU,CAAE,cAAa,eAAc,cAAa,cAAa,OAAM,CACrF,OAGF,IAAM,GAAY,EAAa,IAAoB,CAC7C,EAAU,QAAQ,IAAI,EAAI,GAG9B,QAAQ,KAAK,eAAe,IAAU,CACtC,EAAU,QAAQ,IAAI,EAAI,GAGtB,EAAO,EAAc,QAEvB,GAAe,EAAK,aAAe,EAAK,cAAgB,GAC1D,EACE,cACA,SAAS,EAAK,uGACf,CAGC,GAAgB,EAAK,cAAgB,EAAK,eAAiB,GAC7D,EACE,eACA,SAAS,EAAK,wGACf,CAGC,GAAe,EAAK,aAAe,EAAK,cAAgB,GAC1D,EACE,cACA,SAAS,EAAK,uGACf,CAGC,IAAgB,EAAK,aACvB,EACE,cACA,SAAS,EAAK,2GACf,CAID,GAAM,KACH,GAAW,OAAO,GAAU,YAAY,GAAmB,OAAO,GAAU,WAC9E,EAED,EACE,OACA,SAAS,EAAK,iHACf,CAGH,EAAc,QAAU,CAAE,cAAa,eAAc,cAAa,cAAa,OAAM,EACpF,CAAC,EAAa,EAAM,EAAa,EAAa,EAAM,EAAa,CAAC,CASrE,IAAM,EAAU,EAAY,KAAO,IAAqC,CACtE,EAAU,IAAU,CAClB,GAAG,EACH,YAAa,GACb,MAAO,KACR,EAAE,CAEH,GAAI,CACF,IAAM,EAAS,MAAM,EAAe,QAAQ,EAAgB,CAgB5D,OAbI,EAAa,SACf,EAAU,IAAU,CAClB,YAAa,GACb,WAAY,EACZ,MAAO,KACP,eAAgB,EAAK,eAAiB,EACvC,EAAE,CAGD,EAAa,SACf,EAAa,QAAQ,EAAQ,EAAM,CAG9B,QACA,EAAO,CACd,IAAM,EAAM,aAAiB,MAAQ,EAAY,MAAM,OAAO,EAAM,CAAC,CAerE,MAZI,EAAa,SACf,EAAU,IAAU,CAClB,GAAG,EACH,YAAa,GACb,MAAO,EACR,EAAE,CAGD,EAAW,SACb,EAAW,QAAQ,EAAK,EAAM,CAG1B,IAEP,EAAE,CAAC,CACA,EAAa,EAAO,EAAQ,CAElC,MAAgB,CACd,EAAW,QAAU,GACpB,CAAC,EAAQ,CAAC,CAEb,IAAM,EAAgB,EACnB,GAAqC,EAAW,QAAQ,EAAM,CAC/D,EAAE,CACH,CAKK,EAAQ,MAAkB,CAC9B,EAAS,CACP,YAAa,GACb,WAAY,KACZ,MAAO,KACP,eAAgB,EACjB,CAAC,EACD,EAAE,CAAC,CAgGN,OA9FA,MAAgB,CACd,GAAI,OAAO,OAAW,KAAe,CAAC,OAAO,WAAW,aAAc,CACpE,QAAQ,KACN,qEAAqE,EAAK,2BAC3E,CACD,OASF,IAAM,EAAa,KAAO,IAA4C,CACpE,GAAI,CACF,IAAM,EAAS,MAAM,EAAW,QAAQ,EAAM,CAGxCA,EAA2B,CAC/B,QAAS,CACP,CACE,KAAM,OACN,KANkB,EAAgB,QAAQ,EAAO,CAOlD,CACF,CACF,CAED,GAAI,EAAc,CAChB,IAAM,EAAoB,EAAoB,EAAO,CACrD,GAAI,CAAC,EACH,MAAU,MACR,SAAS,EAAK,gFACf,CAEH,EAAS,kBAAoB,EAG/B,OAAO,QACA,EAAO,CAGd,MAAO,CACL,QAAS,CACP,CACE,KAAM,OACN,KAAM,UANS,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,GAOtE,CACF,CACD,QAAS,GACV,GAIC,EAAa,OAAO,EAAK,CACzB,EAAe,OAAO,UAAU,aAChCC,EAAiC,CACrC,OACA,cACA,GAAI,GAAe,CAAe,cAA4B,CAC9D,GAAI,GAAgB,CAAgB,eAA6B,CACjE,GAAI,GAAe,CAAE,cAAa,CAClC,QAAS,EACV,CAEK,EAAe,EAAa,aAAa,EAAe,CAG9D,OAFA,EAAmB,IAAI,EAAM,EAAW,KAE3B,CACU,KAAmB,IAAI,EAAK,GAC5B,EAIrB,GAAmB,OAAO,EAAK,CAC/B,GAAI,CACF,GAAI,GAAgB,OAAO,EAAa,YAAe,WAAY,CACjE,EAAa,YAAY,CACzB,OAGF,EAAa,eAAe,EAAK,OAC1B,EAAO,CACV,GAAO,EACT,QAAQ,KAAK,0CAA0C,EAAK,mBAAoB,EAAM,KAQ3F,CAAC,EAAM,EAAa,EAAa,EAAc,EAAa,GAAI,GAAQ,EAAE,CAAE,CAAC,CAEzE,CACL,QACA,QAAS,EACT,QACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usewebmcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Standalone React hooks for strict core WebMCP tool registration with navigator.modelContext",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@mcp-b/webmcp-polyfill": "2.
|
|
41
|
-
"@mcp-b/webmcp-types": "2.
|
|
40
|
+
"@mcp-b/webmcp-polyfill": "2.2.0",
|
|
41
|
+
"@mcp-b/webmcp-types": "2.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "22.17.2",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"typescript": "^5.8.3",
|
|
54
54
|
"vitest": "^4.0.18",
|
|
55
55
|
"vitest-browser-react": "^2.0.4",
|
|
56
|
-
"@mcp-b/global": "2.
|
|
56
|
+
"@mcp-b/global": "2.2.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|