@zap-studio/webmcp-react 1.0.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/use-web-mcp-tool.d.ts +52 -0
- package/dist/use-web-mcp-tool.d.ts.map +1 -0
- package/dist/use-web-mcp-tool.js +69 -0
- package/dist/use-web-mcp-tool.js.map +1 -0
- package/package.json +62 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.0.0]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- First release. `useWebMCPTool(tool, deps?)` registers `tool` with `@zap-studio/webmcp`'s `registerTool` on mount, and unregisters it on unmount or when `deps` changes. `deps` defaults to `[]` and follows `useEffect`'s dependency-array semantics.
|
|
12
|
+
- Registration failures (most commonly an unsupported browser) are caught and surfaced through the hook's returned `error`, instead of being thrown.
|
|
13
|
+
- Guards against unmount-before-registration-resolves races: a tool that finishes registering after the component has already unmounted is unregistered immediately instead of leaking.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexandre Trotel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @zap-studio/webmcp-react
|
|
2
|
+
|
|
3
|
+
React bindings for [`@zap-studio/webmcp`](https://www.npmjs.com/package/@zap-studio/webmcp): a `useWebMCPTool` hook that registers a tool with the native WebMCP API for the lifetime of a component.
|
|
4
|
+
|
|
5
|
+
Full documentation: [zapstudio.dev/webmcp/react](https://www.zapstudio.dev/webmcp/react)
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @zap-studio/webmcp-react @zap-studio/webmcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Import
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { useWebMCPTool } from "@zap-studio/webmcp-react";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { useWebMCPTool } from "@zap-studio/webmcp-react";
|
|
23
|
+
|
|
24
|
+
function LikeButton({ postId }: { postId: string }) {
|
|
25
|
+
useWebMCPTool(
|
|
26
|
+
{
|
|
27
|
+
name: "posts_like",
|
|
28
|
+
description: "Like a post by ID",
|
|
29
|
+
execute: async ({ id }: { id: string }) => ({ liked: await likePost(id) }),
|
|
30
|
+
},
|
|
31
|
+
[postId],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return <button onClick={() => likePost(postId)}>Like</button>;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Registration runs in `useEffect`, so it happens after mount, in the browser only — safe under Next.js and TanStack Start server rendering, where the hook simply registers nothing.
|
|
39
|
+
|
|
40
|
+
## Changing Tools
|
|
41
|
+
|
|
42
|
+
Pass `deps` to control when the tool re-registers — this works exactly like `useEffect`'s dependency array. With the default `[]`, the tool registers once, on mount, using the first render's `tool`:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
useWebMCPTool(
|
|
46
|
+
{
|
|
47
|
+
name: "posts_like",
|
|
48
|
+
description: "Like a post by ID",
|
|
49
|
+
execute: async ({ id }: { id: string }) => ({ liked: await likePost(id) }),
|
|
50
|
+
},
|
|
51
|
+
[],
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Pass the values `tool` depends on to re-register when they change — for example, when a tool's `execute` closes over a prop:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
useWebMCPTool(
|
|
59
|
+
{
|
|
60
|
+
name: "posts_like",
|
|
61
|
+
description: "Like a post by ID",
|
|
62
|
+
execute: async () => ({ liked: await likePost(postId) }),
|
|
63
|
+
},
|
|
64
|
+
[postId],
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Unmount / Cleanup
|
|
69
|
+
|
|
70
|
+
The tool unregisters automatically when the component unmounts, or right before it re-registers on a `deps` change — no manual cleanup needed. If registration is still pending when the component unmounts, `useWebMCPTool` unregisters it as soon as it resolves instead of leaking a dangling tool.
|
|
71
|
+
|
|
72
|
+
## Handling Errors
|
|
73
|
+
|
|
74
|
+
Registration failures — most commonly an unsupported browser — are caught internally and surfaced through the returned `error`, not thrown, so a missing agent-callable tool never crashes the component tree:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
function LikeButton({ postId }: { postId: string }) {
|
|
78
|
+
const { error } = useWebMCPTool(
|
|
79
|
+
{
|
|
80
|
+
name: "posts_like",
|
|
81
|
+
description: "Like a post by ID",
|
|
82
|
+
execute: async ({ id }: { id: string }) => ({ liked: await likePost(id) }),
|
|
83
|
+
},
|
|
84
|
+
[postId],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<button onClick={() => likePost(postId)}>Like{error ? " (agent tool unavailable)" : ""}</button>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## See Also
|
|
94
|
+
|
|
95
|
+
- [`@zap-studio/webmcp`](https://www.npmjs.com/package/@zap-studio/webmcp) — the framework-agnostic core this hook wraps
|
|
96
|
+
- [Getting Started](/webmcp/getting-started) — `defineTool`, `registerTool`, and SSR safety
|
|
97
|
+
- [Tool Registry](/webmcp/registry) — batch-register a route's tools outside of React
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ModelContextTool } from "@zap-studio/webmcp";
|
|
2
|
+
import { DependencyList } from "react";
|
|
3
|
+
//#region src/use-web-mcp-tool.d.ts
|
|
4
|
+
/** The shape returned by `useWebMCPTool`. */
|
|
5
|
+
interface UseWebMCPToolResult {
|
|
6
|
+
/**
|
|
7
|
+
* Set when registration fails — most commonly because the browser doesn't
|
|
8
|
+
* support WebMCP, or `tool` failed validation. `null` while pending or
|
|
9
|
+
* once registration succeeds.
|
|
10
|
+
*/
|
|
11
|
+
error: Error | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Registers `tool` with the native WebMCP API on mount, and unregisters it
|
|
15
|
+
* on unmount. Re-registers whenever `deps` changes — this works just like
|
|
16
|
+
* `useEffect`'s dependency array, so `tool` only needs a stable reference if
|
|
17
|
+
* `deps` does not change between renders.
|
|
18
|
+
*
|
|
19
|
+
* Registration failures (most commonly an unsupported browser) are caught
|
|
20
|
+
* and surfaced through the returned `error`, not thrown — a missing
|
|
21
|
+
* agent-callable tool shouldn't crash the component tree.
|
|
22
|
+
*
|
|
23
|
+
* @param tool - The tool to register.
|
|
24
|
+
* @param deps - Re-registers `tool` when these change. Defaults to `[]`
|
|
25
|
+
* (register once, on mount).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import { useWebMCPTool } from "@zap-studio/webmcp-react";
|
|
30
|
+
*
|
|
31
|
+
* function LikeButton({ postId }: { postId: string }) {
|
|
32
|
+
* const { error } = useWebMCPTool(
|
|
33
|
+
* {
|
|
34
|
+
* name: "posts_like",
|
|
35
|
+
* description: "Like a post by ID",
|
|
36
|
+
* execute: async ({ id }: { id: string }) => ({ liked: await likePost(id) }),
|
|
37
|
+
* },
|
|
38
|
+
* [postId],
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* return (
|
|
42
|
+
* <button onClick={() => likePost(postId)}>
|
|
43
|
+
* Like{error ? " (agent tool unavailable)" : ""}
|
|
44
|
+
* </button>
|
|
45
|
+
* );
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare const useWebMCPTool: <TInput = unknown>(tool: ModelContextTool<TInput>, deps?: DependencyList) => UseWebMCPToolResult;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { UseWebMCPToolResult, useWebMCPTool };
|
|
52
|
+
//# sourceMappingURL=use-web-mcp-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-web-mcp-tool.d.ts","names":[],"sources":["../src/use-web-mcp-tool.ts"],"mappings":";;;;UAciB;;;;;;EAMf,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAuCI,gBAAiB,kBAC5B,MAAM,iBAAiB,SACvB,OAAM,mBACL"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { registerTool } from "@zap-studio/webmcp";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
//#region src/use-web-mcp-tool.ts
|
|
4
|
+
/**
|
|
5
|
+
* Registers `tool` with the native WebMCP API on mount, and unregisters it
|
|
6
|
+
* on unmount. Re-registers whenever `deps` changes — this works just like
|
|
7
|
+
* `useEffect`'s dependency array, so `tool` only needs a stable reference if
|
|
8
|
+
* `deps` does not change between renders.
|
|
9
|
+
*
|
|
10
|
+
* Registration failures (most commonly an unsupported browser) are caught
|
|
11
|
+
* and surfaced through the returned `error`, not thrown — a missing
|
|
12
|
+
* agent-callable tool shouldn't crash the component tree.
|
|
13
|
+
*
|
|
14
|
+
* @param tool - The tool to register.
|
|
15
|
+
* @param deps - Re-registers `tool` when these change. Defaults to `[]`
|
|
16
|
+
* (register once, on mount).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { useWebMCPTool } from "@zap-studio/webmcp-react";
|
|
21
|
+
*
|
|
22
|
+
* function LikeButton({ postId }: { postId: string }) {
|
|
23
|
+
* const { error } = useWebMCPTool(
|
|
24
|
+
* {
|
|
25
|
+
* name: "posts_like",
|
|
26
|
+
* description: "Like a post by ID",
|
|
27
|
+
* execute: async ({ id }: { id: string }) => ({ liked: await likePost(id) }),
|
|
28
|
+
* },
|
|
29
|
+
* [postId],
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* return (
|
|
33
|
+
* <button onClick={() => likePost(postId)}>
|
|
34
|
+
* Like{error ? " (agent tool unavailable)" : ""}
|
|
35
|
+
* </button>
|
|
36
|
+
* );
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
const useWebMCPTool = (tool, deps = []) => {
|
|
41
|
+
const [error, setError] = useState(null);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
let cleanup = () => {};
|
|
45
|
+
setError(null);
|
|
46
|
+
const run = async () => {
|
|
47
|
+
try {
|
|
48
|
+
const unregisterTool = await registerTool(tool);
|
|
49
|
+
if (cancelled) {
|
|
50
|
+
unregisterTool();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
cleanup = unregisterTool;
|
|
54
|
+
} catch (caught) {
|
|
55
|
+
if (!cancelled) setError(caught instanceof Error ? caught : new Error(String(caught)));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
run();
|
|
59
|
+
return () => {
|
|
60
|
+
cancelled = true;
|
|
61
|
+
cleanup();
|
|
62
|
+
};
|
|
63
|
+
}, deps);
|
|
64
|
+
return { error };
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
67
|
+
export { useWebMCPTool };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=use-web-mcp-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-web-mcp-tool.js","names":[],"sources":["../src/use-web-mcp-tool.ts"],"sourcesContent":["/**\n * `useWebMCPTool`: registers a tool with the native WebMCP API for as long\n * as the calling component is mounted.\n *\n * @module @zap-studio/webmcp-react/use-web-mcp-tool\n */\n\nimport type { ModelContextTool } from \"@zap-studio/webmcp\";\nimport type { DependencyList } from \"react\";\n\nimport { registerTool } from \"@zap-studio/webmcp\";\nimport { useEffect, useState } from \"react\";\n\n/** The shape returned by `useWebMCPTool`. */\nexport interface UseWebMCPToolResult {\n /**\n * Set when registration fails — most commonly because the browser doesn't\n * support WebMCP, or `tool` failed validation. `null` while pending or\n * once registration succeeds.\n */\n error: Error | null;\n}\n\n/**\n * Registers `tool` with the native WebMCP API on mount, and unregisters it\n * on unmount. Re-registers whenever `deps` changes — this works just like\n * `useEffect`'s dependency array, so `tool` only needs a stable reference if\n * `deps` does not change between renders.\n *\n * Registration failures (most commonly an unsupported browser) are caught\n * and surfaced through the returned `error`, not thrown — a missing\n * agent-callable tool shouldn't crash the component tree.\n *\n * @param tool - The tool to register.\n * @param deps - Re-registers `tool` when these change. Defaults to `[]`\n * (register once, on mount).\n *\n * @example\n * ```tsx\n * import { useWebMCPTool } from \"@zap-studio/webmcp-react\";\n *\n * function LikeButton({ postId }: { postId: string }) {\n * const { error } = useWebMCPTool(\n * {\n * name: \"posts_like\",\n * description: \"Like a post by ID\",\n * execute: async ({ id }: { id: string }) => ({ liked: await likePost(id) }),\n * },\n * [postId],\n * );\n *\n * return (\n * <button onClick={() => likePost(postId)}>\n * Like{error ? \" (agent tool unavailable)\" : \"\"}\n * </button>\n * );\n * }\n * ```\n */\nexport const useWebMCPTool = <TInput = unknown>(\n tool: ModelContextTool<TInput>,\n deps: DependencyList = [],\n): UseWebMCPToolResult => {\n const [error, setError] = useState<Error | null>(null);\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- `deps` comes from the caller, like the dependency array of useEffect. This hook cannot know what is inside it.\n useEffect(() => {\n let cancelled = false;\n let cleanup = (): void => {};\n setError(null);\n\n const run = async () => {\n try {\n const unregisterTool = await registerTool(tool);\n if (cancelled) {\n unregisterTool();\n return;\n }\n cleanup = unregisterTool;\n } catch (caught) {\n if (!cancelled) {\n setError(caught instanceof Error ? caught : new Error(String(caught)));\n }\n }\n };\n\n void run();\n\n return () => {\n cancelled = true;\n cleanup();\n };\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- same pass-through `deps` as above; the rule cannot verify a non-literal dependency list.\n }, deps);\n\n return { error };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,MAAa,iBACX,MACA,OAAuB,CAAC,MACA;CACxB,MAAM,CAAC,OAAO,YAAY,SAAuB,IAAI;CAGrD,gBAAgB;EACd,IAAI,YAAY;EAChB,IAAI,gBAAsB,CAAC;EAC3B,SAAS,IAAI;EAEb,MAAM,MAAM,YAAY;GACtB,IAAI;IACF,MAAM,iBAAiB,MAAM,aAAa,IAAI;IAC9C,IAAI,WAAW;KACb,eAAe;KACf;IACF;IACA,UAAU;GACZ,SAAS,QAAQ;IACf,IAAI,CAAC,WACH,SAAS,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;GAEzE;EACF;EAEA,IAAS;EAET,aAAa;GACX,YAAY;GACZ,QAAQ;EACV;CAEF,GAAG,IAAI;CAEP,OAAO,EAAE,MAAM;AACjB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zap-studio/webmcp-react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "React bindings for @zap-studio/webmcp: a useWebMCPTool hook that registers a tool with the native WebMCP API for the lifetime of a component.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"ai",
|
|
8
|
+
"ai-agent",
|
|
9
|
+
"mcp",
|
|
10
|
+
"model-context-protocol",
|
|
11
|
+
"react",
|
|
12
|
+
"react-hooks",
|
|
13
|
+
"tools",
|
|
14
|
+
"typescript",
|
|
15
|
+
"webmcp"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://www.zapstudio.dev/webmcp/react",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/zap-studio/monorepo.git",
|
|
22
|
+
"directory": "packages/webmcp-react"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./dist/index.js",
|
|
35
|
+
"./use-web-mcp-tool": "./dist/use-web-mcp-tool.js",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@testing-library/dom": "^10.4.1",
|
|
43
|
+
"@testing-library/react": "^16.3.3",
|
|
44
|
+
"@types/react": "^19.2.18",
|
|
45
|
+
"@types/react-dom": "^19.2.5",
|
|
46
|
+
"@zap-studio/typescript": "0.0.0",
|
|
47
|
+
"@zap-studio/webmcp": "1.0.0",
|
|
48
|
+
"react": "^19.2.8",
|
|
49
|
+
"react-dom": "^19.2.8",
|
|
50
|
+
"tsdown": "^0.22.14",
|
|
51
|
+
"typescript": "^7.0.2",
|
|
52
|
+
"vitest": "^4.1.11"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@zap-studio/webmcp": "^1.0.0",
|
|
56
|
+
"react": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {}
|
|
62
|
+
}
|