usewebmcp 5.0.3 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Use `usewebmcp` when an app only needs standard tool registration. Use
|
|
|
11
11
|
pnpm add usewebmcp react
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
The hook expects `document.modelContext` to exist before
|
|
14
|
+
The hook expects `document.modelContext` to exist before it is enabled. Provide it through
|
|
15
15
|
native browser support, `@mcp-b/webmcp-polyfill`, or `@mcp-b/global`. Older
|
|
16
16
|
`navigator.modelContext` runtimes remain a fallback.
|
|
17
17
|
|
|
@@ -64,6 +64,7 @@ const { state, execute, reset } = useWebMCP(config, deps?);
|
|
|
64
64
|
`config` contains:
|
|
65
65
|
|
|
66
66
|
- `name` and `description`
|
|
67
|
+
- optional `enabled`, which defaults to `true`
|
|
67
68
|
- optional `inputSchema`, `outputSchema`, and `annotations`
|
|
68
69
|
- `execute(input)`, which may return synchronously or asynchronously
|
|
69
70
|
|
|
@@ -71,6 +72,16 @@ const { state, execute, reset } = useWebMCP(config, deps?);
|
|
|
71
72
|
`execute(input)` invokes the same implementation locally. `reset()` clears observed state; it does
|
|
72
73
|
not cancel work already running.
|
|
73
74
|
|
|
75
|
+
## Conditional registration
|
|
76
|
+
|
|
77
|
+
Pass `enabled: false` to keep the tool unregistered. Changing it to `true` registers the latest
|
|
78
|
+
committed configuration; changing it back to `false` unregisters the tool. Call the hook
|
|
79
|
+
unconditionally and pass your condition as `enabled`.
|
|
80
|
+
|
|
81
|
+
Disabling preserves execution state and the local `execute` and `reset` controls. It does not
|
|
82
|
+
cancel your `execute` implementation. An in-flight MCP request may still be rejected by the
|
|
83
|
+
runtime when its registration is removed. `enabled` controls exposure, not authorization.
|
|
84
|
+
|
|
74
85
|
## Tool responses
|
|
75
86
|
|
|
76
87
|
Raw return values use the same normalization as the MCP-B runtime:
|
|
@@ -86,11 +97,11 @@ for output inference and compatible runtimes.
|
|
|
86
97
|
|
|
87
98
|
## Re-registration
|
|
88
99
|
|
|
89
|
-
|
|
100
|
+
While enabled, the tool re-registers when `name`, `description`, or a value in `deps` changes. Implementations,
|
|
90
101
|
schemas, and annotations use their latest committed values without reference-driven churn. Include
|
|
91
102
|
a primitive schema or annotation revision in `deps` when registered metadata must change.
|
|
92
103
|
|
|
93
|
-
Registration is aborted on unmount. If the runtime is missing
|
|
104
|
+
Registration is aborted on unmount or when disabled. If the runtime is missing while enabled, the hook warns and skips
|
|
94
105
|
registration; initialize the runtime first rather than polling once per hook.
|
|
95
106
|
|
|
96
107
|
## Exports
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ interface WebMCPConfig<TInputSchema extends ToolInputSchema = InputSchema, TOutp
|
|
|
32
32
|
name: string;
|
|
33
33
|
/** Description shown to MCP clients. */
|
|
34
34
|
description: string;
|
|
35
|
+
/** Whether to register with the runtime. Defaults to `true`. */
|
|
36
|
+
enabled?: boolean;
|
|
35
37
|
/** JSON Schema literal or Standard JSON Schema for the tool input. */
|
|
36
38
|
inputSchema?: TInputSchema;
|
|
37
39
|
/** JSON Schema used for output inference and structured content. */
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";import{normalizeInputSchema as e,normalizeToolResponse as t}from"@mcp-b/webmcp-polyfill/schema";import{useCallback as n,useEffect as r,useLayoutEffect as i,useRef as a,useState as o}from"react";const s={isExecuting:!1,lastResult:null,error:null,executionCount:0},c=typeof window>`u`?r:i;function l(i,l){let{name:u,description:d}=i,[
|
|
1
|
+
"use client";import{normalizeInputSchema as e,normalizeToolResponse as t}from"@mcp-b/webmcp-polyfill/schema";import{useCallback as n,useEffect as r,useLayoutEffect as i,useRef as a,useState as o}from"react";const s={isExecuting:!1,lastResult:null,error:null,executionCount:0},c=typeof window>`u`?r:i;function l(i,l){let{name:u,description:d,enabled:f=!0}=i,[p,m]=o(s),h=a(i),g=a(0);c(()=>{h.current=i});let _=n(async e=>{g.current+=1,m(e=>e.isExecuting&&e.error===null?e:{...e,isExecuting:!0,error:null});let n,r=h.current;try{if(n=await r.execute(e),r.outputSchema&&t(n).structuredContent===void 0)throw Error(`Tool "${r.name}" outputSchema requires execute to return a JSON-serializable result`)}catch(e){--g.current;let t=e instanceof Error?e:Error(String(e));throw m(e=>({...e,isExecuting:g.current>0,error:t})),t}return--g.current,m(e=>({isExecuting:g.current>0,lastResult:n,error:null,executionCount:e.executionCount+1})),n},[]),v=n(()=>{let e=g.current>0;m(t=>t.isExecuting===e&&t.lastResult===null&&t.error===null&&t.executionCount===0?t:{...s,isExecuting:e})},[]);return r(()=>{if(!f)return;let n=typeof document>`u`?void 0:document.modelContext??(typeof navigator>`u`?void 0:navigator.modelContext);if(!n){console.warn(`[useWebMCP] document.modelContext is not available. Tool "${u}" will not be registered.`);return}let r=h.current,i=r.outputSchema,a=new AbortController,o={name:u,description:d,inputSchema:e(r.inputSchema).inputSchema,...i&&{outputSchema:i},...r.annotations&&{annotations:r.annotations},execute:async e=>{try{let n=t(await _(e));if(i&&n.structuredContent===void 0)throw Error(`Tool "${u}" outputSchema requires the tool implementation to return a JSON-serializable result`);return n}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:String(e)}`}],isError:!0}}}};try{let e=n.registerTool(o,{signal:a.signal});Promise.resolve(e).catch(e=>{a.signal.aborted||(a.abort(),console.warn(`[useWebMCP] registerTool("${u}") rejected:`,e))})}catch(e){a.abort(),console.warn(`[useWebMCP] registerTool("${u}") rejected:`,e);return}return()=>a.abort()},[u,d,f,...l??[]]),{state:p,execute:_,reset:v}}export{l as useWebMCP};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/useWebMCP.ts"],"sourcesContent":["import {\n normalizeInputSchema,\n normalizeToolResponse,\n type ToolInputSchema,\n} from '@mcp-b/webmcp-polyfill/schema';\nimport type { InputSchema, JsonSchemaForInference } 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\nconst INITIAL_STATE = {\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n};\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\n/** Registers a React-owned tool with the current WebMCP model context. */\nexport function useWebMCP<\n TInputSchema extends ToolInputSchema = InputSchema,\n TOutputSchema extends JsonSchemaForInference | undefined = undefined,\n>(\n config: WebMCPConfig<TInputSchema, TOutputSchema>,\n deps?: DependencyList\n): WebMCPReturn<TOutputSchema, TInputSchema> {\n type TOutput = InferOutput<TOutputSchema>;\n type TInput = InferToolInput<TInputSchema>;\n const { name, description } = config;\n const [state, setState] = useState<ToolExecutionState<TOutput>>(INITIAL_STATE);\n const committedConfigRef = useRef(config);\n const pendingExecutionsRef = useRef(0);\n\n // MCP calls can arrive after commit but before passive effects. A layout effect\n // publishes only committed renders; the server fallback avoids the SSR warning.\n useIsomorphicLayoutEffect(() => {\n committedConfigRef.current = config;\n });\n\n const run = useCallback(async (input: TInput): Promise<TOutput> => {\n pendingExecutionsRef.current += 1;\n setState((previous)
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/useWebMCP.ts"],"sourcesContent":["import {\n normalizeInputSchema,\n normalizeToolResponse,\n type ToolInputSchema,\n} from '@mcp-b/webmcp-polyfill/schema';\nimport type { InputSchema, JsonSchemaForInference } 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\nconst INITIAL_STATE = {\n isExecuting: false,\n lastResult: null,\n error: null,\n executionCount: 0,\n};\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\n/** Registers a React-owned tool with the current WebMCP model context. */\nexport function useWebMCP<\n TInputSchema extends ToolInputSchema = InputSchema,\n TOutputSchema extends JsonSchemaForInference | undefined = undefined,\n>(\n config: WebMCPConfig<TInputSchema, TOutputSchema>,\n deps?: DependencyList\n): WebMCPReturn<TOutputSchema, TInputSchema> {\n type TOutput = InferOutput<TOutputSchema>;\n type TInput = InferToolInput<TInputSchema>;\n const { name, description, enabled = true } = config;\n const [state, setState] = useState<ToolExecutionState<TOutput>>(INITIAL_STATE);\n const committedConfigRef = useRef(config);\n const pendingExecutionsRef = useRef(0);\n\n // MCP calls can arrive after commit but before passive effects. A layout effect\n // publishes only committed renders; the server fallback avoids the SSR warning.\n useIsomorphicLayoutEffect(() => {\n committedConfigRef.current = config;\n });\n\n const run = useCallback(async (input: TInput): Promise<TOutput> => {\n pendingExecutionsRef.current += 1;\n setState((previous) =>\n previous.isExecuting && previous.error === null\n ? previous\n : { ...previous, isExecuting: true, error: null }\n );\n\n let result: TOutput;\n const executionConfig = committedConfigRef.current;\n try {\n result = await executionConfig.execute(input);\n if (\n executionConfig.outputSchema &&\n normalizeToolResponse(result).structuredContent === undefined\n ) {\n throw new Error(\n `Tool \"${executionConfig.name}\" outputSchema requires execute to return a JSON-serializable result`\n );\n }\n } catch (error) {\n pendingExecutionsRef.current -= 1;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n setState((previous) => ({\n ...previous,\n isExecuting: pendingExecutionsRef.current > 0,\n error: normalizedError,\n }));\n throw normalizedError;\n }\n\n pendingExecutionsRef.current -= 1;\n setState((previous) => ({\n isExecuting: pendingExecutionsRef.current > 0,\n lastResult: result,\n error: null,\n executionCount: previous.executionCount + 1,\n }));\n return result;\n }, []);\n\n const reset = useCallback(() => {\n const isExecuting = pendingExecutionsRef.current > 0;\n setState((previous) =>\n previous.isExecuting === isExecuting &&\n previous.lastResult === null &&\n previous.error === null &&\n previous.executionCount === 0\n ? previous\n : { ...INITIAL_STATE, isExecuting }\n );\n }, []);\n\n useEffect(() => {\n if (!enabled) return;\n\n const modelContext =\n typeof document === 'undefined'\n ? undefined\n : (document.modelContext ??\n (typeof navigator === 'undefined' ? undefined : navigator.modelContext));\n if (!modelContext) {\n console.warn(\n `[useWebMCP] document.modelContext is not available. Tool \"${name}\" will not be registered.`\n );\n return;\n }\n\n const registeredConfig = committedConfigRef.current;\n const registeredOutputSchema = registeredConfig.outputSchema;\n const controller = new AbortController();\n const tool = {\n name,\n description,\n inputSchema: normalizeInputSchema(registeredConfig.inputSchema).inputSchema,\n ...(registeredOutputSchema && { outputSchema: registeredOutputSchema }),\n ...(registeredConfig.annotations && { annotations: registeredConfig.annotations }),\n execute: async (input: TInput) => {\n try {\n const response = normalizeToolResponse(await run(input));\n if (registeredOutputSchema && response.structuredContent === undefined) {\n throw new Error(\n `Tool \"${name}\" outputSchema requires the tool implementation to return a JSON-serializable result`\n );\n }\n return response;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n };\n }\n },\n };\n\n try {\n const registration = modelContext.registerTool(tool, { signal: controller.signal });\n void Promise.resolve(registration).catch((error: unknown) => {\n if (!controller.signal.aborted) {\n controller.abort();\n console.warn(`[useWebMCP] registerTool(\"${name}\") rejected:`, error);\n }\n });\n } catch (error) {\n controller.abort();\n console.warn(`[useWebMCP] registerTool(\"${name}\") rejected:`, error);\n return;\n }\n\n return () => controller.abort();\n // `deps` lets callers explicitly opt descriptor values into re-registration.\n // oxlint-disable-next-line react-doctor/exhaustive-deps -- Public API deliberately forwards caller deps.\n }, [name, description, enabled, ...(deps ?? [])]);\n\n return { state, execute: run, reset };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usewebmcp",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Standalone React hooks for strict core WebMCP tool registration with document.modelContext",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -43,22 +43,22 @@
|
|
|
43
43
|
"registry": "https://registry.npmjs.org/"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@mcp-b/webmcp-polyfill": "5.0
|
|
47
|
-
"@mcp-b/webmcp-types": "5.0
|
|
46
|
+
"@mcp-b/webmcp-polyfill": "5.1.0",
|
|
47
|
+
"@mcp-b/webmcp-types": "5.1.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@types/node": "22.
|
|
51
|
-
"@types/react": "^19.2.
|
|
52
|
-
"@vitest/coverage-v8": "4.1.
|
|
53
|
-
"playwright": "^1.
|
|
54
|
-
"react": "^19.
|
|
55
|
-
"react-dom": "^19.
|
|
50
|
+
"@types/node": "22.20.1",
|
|
51
|
+
"@types/react": "^19.2.18",
|
|
52
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
53
|
+
"playwright": "^1.62.1",
|
|
54
|
+
"react": "^19.2.8",
|
|
55
|
+
"react-dom": "^19.2.8",
|
|
56
56
|
"typescript": "^5.8.3",
|
|
57
|
-
"vite-plus": "0.1.
|
|
58
|
-
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.
|
|
59
|
-
"vitest-browser-react": "^2.0
|
|
57
|
+
"vite-plus": "0.1.24",
|
|
58
|
+
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.24",
|
|
59
|
+
"vitest-browser-react": "^2.2.0",
|
|
60
60
|
"zod": "4.4.3",
|
|
61
|
-
"@mcp-b/global": "5.0
|
|
61
|
+
"@mcp-b/global": "5.1.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"react": "^18.0.0 || ^19.0.0"
|