@tooluminati/react 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jitterbox
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.
@@ -0,0 +1,11 @@
1
+ import type { PolicyContext, WebMcpRegistry } from '@tooluminati/core';
2
+ import type { WebMcpPolicySet } from '@tooluminati/policies';
3
+ export interface WebMcpReactContextValue {
4
+ registry: WebMcpRegistry;
5
+ enabled: boolean;
6
+ policies?: WebMcpPolicySet;
7
+ policyContext?: PolicyContext | undefined;
8
+ namespace?: string | undefined;
9
+ }
10
+ export declare const WebMcpContext: import("react").Context<WebMcpReactContextValue | null>;
11
+ //# sourceMappingURL=WebMcpContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebMcpContext.d.ts","sourceRoot":"","sources":["../src/WebMcpContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,eAAO,MAAM,aAAa,yDAEzB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { createContext } from 'react';
2
+ export const WebMcpContext = createContext(null);
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type PolicyContext, type WebMcpRegistryOptions, type WebMcpToolDescriptor } from '@tooluminati/core';
3
+ import { type WebMcpPolicySet } from '@tooluminati/policies';
4
+ export interface WebMcpProviderProps {
5
+ children: ReactNode;
6
+ tools?: WebMcpToolDescriptor[];
7
+ enabled?: boolean;
8
+ strict?: boolean;
9
+ namespace?: string;
10
+ policies?: WebMcpPolicySet;
11
+ policyContext?: PolicyContext;
12
+ modelContext?: WebMcpRegistryOptions['modelContext'];
13
+ getAppContext?: () => unknown;
14
+ onSecurityWarning?: WebMcpRegistryOptions['onSecurityWarning'];
15
+ onError?: WebMcpRegistryOptions['onError'];
16
+ }
17
+ export declare function WebMcpProvider({ children, tools, enabled, strict, namespace, policies, policyContext, modelContext, getAppContext, onSecurityWarning, onError, }: WebMcpProviderProps): import("react").JSX.Element;
18
+ //# sourceMappingURL=WebMcpProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebMcpProvider.d.ts","sourceRoot":"","sources":["../src/WebMcpProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAG/B,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,YAAY,CAAC,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAC;IACrD,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC;IAC9B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAC;IAC/D,OAAO,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;CAC5C;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,KAAU,EACV,OAAe,EACf,MAAc,EACd,SAAS,EACT,QAAyB,EACzB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,OAAO,GACR,EAAE,mBAAmB,+BA0ErB"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useContext, useEffect, useMemo, } from 'react';
3
+ import { WebMcpRegistry, } from '@tooluminati/core';
4
+ import { localDevPolicy, } from '@tooluminati/policies';
5
+ import { WebMcpContext } from './WebMcpContext';
6
+ export function WebMcpProvider({ children, tools = [], enabled = false, strict = false, namespace, policies = localDevPolicy, policyContext, modelContext, getAppContext, onSecurityWarning, onError, }) {
7
+ const parentContext = useContext(WebMcpContext);
8
+ useEffect(() => {
9
+ if (parentContext &&
10
+ namespace === undefined &&
11
+ typeof process !== 'undefined' &&
12
+ process.env.NODE_ENV !== 'production') {
13
+ console.warn('[tooluminati] Nested WebMcpProvider detected without a namespace. Provide namespace to avoid tool collisions.');
14
+ }
15
+ }, [namespace, parentContext]);
16
+ const registry = useMemo(() => new WebMcpRegistry({
17
+ enabled,
18
+ strict,
19
+ namespace,
20
+ policies,
21
+ policyContext,
22
+ modelContext,
23
+ getAppContext,
24
+ onSecurityWarning,
25
+ onError,
26
+ }), [
27
+ enabled,
28
+ getAppContext,
29
+ modelContext,
30
+ namespace,
31
+ onError,
32
+ onSecurityWarning,
33
+ policies,
34
+ policyContext,
35
+ strict,
36
+ ]);
37
+ useEffect(() => {
38
+ const registrations = tools.map((tool) => registry.registerTool(tool, { source: 'provider' }));
39
+ return () => {
40
+ for (const registration of registrations) {
41
+ registration.abort();
42
+ }
43
+ };
44
+ }, [registry, tools]);
45
+ useEffect(() => () => registry.unregisterAll(), [registry]);
46
+ const value = useMemo(() => {
47
+ const next = {
48
+ registry,
49
+ enabled,
50
+ policies,
51
+ };
52
+ if (policyContext !== undefined) {
53
+ next.policyContext = policyContext;
54
+ }
55
+ if (namespace !== undefined) {
56
+ next.namespace = namespace;
57
+ }
58
+ return next;
59
+ }, [enabled, namespace, policies, policyContext, registry]);
60
+ return (_jsx(WebMcpContext.Provider, { value: value, children: children }));
61
+ }
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { WebMcpToolDescriptor, WebMcpToolSource } from '@tooluminati/core';
3
+ export interface WebMcpScopeProps {
4
+ children: ReactNode;
5
+ tools: WebMcpToolDescriptor[];
6
+ source?: WebMcpToolSource;
7
+ namespaceSegment?: string | undefined;
8
+ }
9
+ export declare function WebMcpScope({ children, tools, source, namespaceSegment, }: WebMcpScopeProps): import("react").JSX.Element;
10
+ //# sourceMappingURL=WebMcpScope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebMcpScope.d.ts","sourceRoot":"","sources":["../src/WebMcpScope.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAI3B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,KAAK,EACL,MAAgB,EAChB,gBAAgB,GACjB,EAAE,gBAAgB,+BAkBlB"}
@@ -0,0 +1,16 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { useWebMcpContextValue } from './useWebMcpRegistry';
4
+ import { useWebMcpTools } from './useWebMcpTools';
5
+ export function WebMcpScope({ children, tools, source = 'scope', namespaceSegment, }) {
6
+ const { namespace } = useWebMcpContextValue();
7
+ const scopedTools = useMemo(() => namespaceSegment
8
+ ? tools.map((tool) => ({
9
+ ...tool,
10
+ name: `${namespaceSegment}.${tool.name}`,
11
+ }))
12
+ : tools, [namespaceSegment, tools]);
13
+ void namespace;
14
+ useWebMcpTools(scopedTools, [scopedTools], { source });
15
+ return _jsx(_Fragment, { children: children });
16
+ }
@@ -0,0 +1,2 @@
1
+ export declare function WebMcpSecurityBanner(): import("react").JSX.Element | null;
2
+ //# sourceMappingURL=WebMcpSecurityBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebMcpSecurityBanner.d.ts","sourceRoot":"","sources":["../src/WebMcpSecurityBanner.tsx"],"names":[],"mappings":"AAEA,wBAAgB,oBAAoB,uCAsBnC"}
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useWebMcpSecurityBanner } from './useWebMcpSecurityBanner';
3
+ export function WebMcpSecurityBanner() {
4
+ const banner = useWebMcpSecurityBanner();
5
+ if (!banner.enabled) {
6
+ return null;
7
+ }
8
+ return (_jsxs("aside", { "aria-live": "polite", style: {
9
+ background: '#fff3cd',
10
+ border: '1px solid #ffeeba',
11
+ color: '#856404',
12
+ padding: '0.75rem 1rem',
13
+ marginBottom: '1rem',
14
+ }, children: [_jsx("strong", { children: "Tooluminati diagnostics enabled." }), " ", banner.message, " Registered tools: ", banner.registeredTools.join(', ') || 'none', "."] }));
15
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,314 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ WebMcpContext: () => WebMcpContext,
24
+ WebMcpProvider: () => WebMcpProvider,
25
+ WebMcpScope: () => WebMcpScope,
26
+ WebMcpSecurityBanner: () => WebMcpSecurityBanner,
27
+ useLatestRef: () => useLatestRef,
28
+ useStableTool: () => useStableTool,
29
+ useWebMcpContextValue: () => useWebMcpContextValue,
30
+ useWebMcpRegistry: () => useWebMcpRegistry,
31
+ useWebMcpSecurityBanner: () => useWebMcpSecurityBanner,
32
+ useWebMcpTool: () => useWebMcpTool,
33
+ useWebMcpTools: () => useWebMcpTools
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/WebMcpContext.tsx
38
+ var import_react = require("react");
39
+ var WebMcpContext = (0, import_react.createContext)(
40
+ null
41
+ );
42
+
43
+ // src/WebMcpProvider.tsx
44
+ var import_react2 = require("react");
45
+ var import_core = require("@tooluminati/core");
46
+ var import_policies = require("@tooluminati/policies");
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ function WebMcpProvider({
49
+ children,
50
+ tools = [],
51
+ enabled = false,
52
+ strict = false,
53
+ namespace,
54
+ policies = import_policies.localDevPolicy,
55
+ policyContext,
56
+ modelContext,
57
+ getAppContext,
58
+ onSecurityWarning,
59
+ onError
60
+ }) {
61
+ const parentContext = (0, import_react2.useContext)(WebMcpContext);
62
+ (0, import_react2.useEffect)(() => {
63
+ if (parentContext && namespace === void 0 && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
64
+ console.warn(
65
+ "[tooluminati] Nested WebMcpProvider detected without a namespace. Provide namespace to avoid tool collisions."
66
+ );
67
+ }
68
+ }, [namespace, parentContext]);
69
+ const registry = (0, import_react2.useMemo)(
70
+ () => new import_core.WebMcpRegistry({
71
+ enabled,
72
+ strict,
73
+ namespace,
74
+ policies,
75
+ policyContext,
76
+ modelContext,
77
+ getAppContext,
78
+ onSecurityWarning,
79
+ onError
80
+ }),
81
+ [
82
+ enabled,
83
+ getAppContext,
84
+ modelContext,
85
+ namespace,
86
+ onError,
87
+ onSecurityWarning,
88
+ policies,
89
+ policyContext,
90
+ strict
91
+ ]
92
+ );
93
+ (0, import_react2.useEffect)(() => {
94
+ const registrations = tools.map(
95
+ (tool) => registry.registerTool(tool, { source: "provider" })
96
+ );
97
+ return () => {
98
+ for (const registration of registrations) {
99
+ registration.abort();
100
+ }
101
+ };
102
+ }, [registry, tools]);
103
+ (0, import_react2.useEffect)(() => () => registry.unregisterAll(), [registry]);
104
+ const value = (0, import_react2.useMemo)(() => {
105
+ const next = {
106
+ registry,
107
+ enabled,
108
+ policies
109
+ };
110
+ if (policyContext !== void 0) {
111
+ next.policyContext = policyContext;
112
+ }
113
+ if (namespace !== void 0) {
114
+ next.namespace = namespace;
115
+ }
116
+ return next;
117
+ }, [enabled, namespace, policies, policyContext, registry]);
118
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WebMcpContext.Provider, { value, children });
119
+ }
120
+
121
+ // src/WebMcpScope.tsx
122
+ var import_react5 = require("react");
123
+
124
+ // src/useWebMcpRegistry.ts
125
+ var import_react3 = require("react");
126
+ function useWebMcpRegistry() {
127
+ const context = (0, import_react3.useContext)(WebMcpContext);
128
+ if (!context) {
129
+ throw new Error("useWebMcpRegistry must be used within WebMcpProvider.");
130
+ }
131
+ return context.registry;
132
+ }
133
+ function useWebMcpContextValue(fallbackRegistry) {
134
+ const context = (0, import_react3.useContext)(WebMcpContext);
135
+ if (!context && !fallbackRegistry) {
136
+ throw new Error("WebMCP hooks must be used within WebMcpProvider.");
137
+ }
138
+ if (context) {
139
+ return context;
140
+ }
141
+ return {
142
+ registry: fallbackRegistry,
143
+ enabled: false
144
+ };
145
+ }
146
+
147
+ // src/useWebMcpTools.ts
148
+ var import_react4 = require("react");
149
+ function useWebMcpTools(tools, deps = [], options = {}) {
150
+ const { registry } = useWebMcpContextValue();
151
+ const exposedTo = options.exposedTo;
152
+ const source = options.source ?? "scope";
153
+ (0, import_react4.useEffect)(() => {
154
+ const registrations = tools.map(
155
+ (tool) => registry.registerTool(tool, {
156
+ ...options,
157
+ source
158
+ })
159
+ );
160
+ return () => {
161
+ for (const registration of registrations) {
162
+ registration.abort();
163
+ }
164
+ };
165
+ }, [registry, tools, exposedTo, source, ...deps]);
166
+ }
167
+
168
+ // src/WebMcpScope.tsx
169
+ var import_jsx_runtime2 = require("react/jsx-runtime");
170
+ function WebMcpScope({
171
+ children,
172
+ tools,
173
+ source = "scope",
174
+ namespaceSegment
175
+ }) {
176
+ const { namespace } = useWebMcpContextValue();
177
+ const scopedTools = (0, import_react5.useMemo)(
178
+ () => namespaceSegment ? tools.map((tool) => ({
179
+ ...tool,
180
+ name: `${namespaceSegment}.${tool.name}`
181
+ })) : tools,
182
+ [namespaceSegment, tools]
183
+ );
184
+ void namespace;
185
+ useWebMcpTools(scopedTools, [scopedTools], { source });
186
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
187
+ }
188
+
189
+ // src/useLatestRef.ts
190
+ var import_react6 = require("react");
191
+ function useLatestRef(value) {
192
+ const ref = (0, import_react6.useRef)(value);
193
+ (0, import_react6.useLayoutEffect)(() => {
194
+ ref.current = value;
195
+ });
196
+ return ref;
197
+ }
198
+
199
+ // src/useStableTool.ts
200
+ var import_react7 = require("react");
201
+ function useStableTool(factory, deps) {
202
+ return (0, import_react7.useMemo)(factory, deps);
203
+ }
204
+
205
+ // src/useWebMcpSecurityBanner.ts
206
+ var import_react8 = require("react");
207
+ var import_core2 = require("@tooluminati/core");
208
+ function useWebMcpSecurityBanner() {
209
+ const { enabled, registry } = useWebMcpContextValue();
210
+ return (0, import_react8.useMemo)(
211
+ () => ({
212
+ enabled,
213
+ supported: (0, import_core2.isWebMcpSupported)(),
214
+ registeredTools: registry.getRegisteredToolNames(),
215
+ message: enabled ? "Tooluminati diagnostics are enabled for this page." : "Tooluminati diagnostics are disabled."
216
+ }),
217
+ [enabled, registry]
218
+ );
219
+ }
220
+
221
+ // src/WebMcpSecurityBanner.tsx
222
+ var import_jsx_runtime3 = require("react/jsx-runtime");
223
+ function WebMcpSecurityBanner() {
224
+ const banner = useWebMcpSecurityBanner();
225
+ if (!banner.enabled) {
226
+ return null;
227
+ }
228
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
229
+ "aside",
230
+ {
231
+ "aria-live": "polite",
232
+ style: {
233
+ background: "#fff3cd",
234
+ border: "1px solid #ffeeba",
235
+ color: "#856404",
236
+ padding: "0.75rem 1rem",
237
+ marginBottom: "1rem"
238
+ },
239
+ children: [
240
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Tooluminati diagnostics enabled." }),
241
+ " ",
242
+ banner.message,
243
+ " Registered tools: ",
244
+ banner.registeredTools.join(", ") || "none",
245
+ "."
246
+ ]
247
+ }
248
+ );
249
+ }
250
+
251
+ // src/useWebMcpTool.ts
252
+ var import_react9 = require("react");
253
+ function stableSerialize(value) {
254
+ return JSON.stringify(
255
+ value,
256
+ (_key, current) => current && typeof current === "object" && !Array.isArray(current) ? Object.keys(current).sort().reduce((accumulator, key) => {
257
+ accumulator[key] = current[key];
258
+ return accumulator;
259
+ }, {}) : current
260
+ );
261
+ }
262
+ function useWebMcpTool(tool, deps = [], options = {}) {
263
+ const context = useWebMcpContextValue(options.fallbackRegistry);
264
+ const registry = options.fallbackRegistry ?? context.registry;
265
+ const toolRef = useLatestRef(tool);
266
+ const definitionKey = (0, import_react9.useMemo)(
267
+ () => stableSerialize({
268
+ name: tool.name,
269
+ title: tool.title,
270
+ description: tool.description,
271
+ inputSchema: tool.inputSchema,
272
+ annotations: tool.annotations,
273
+ exposedTo: options.exposedTo,
274
+ source: options.source
275
+ }),
276
+ [
277
+ tool.name,
278
+ tool.title,
279
+ tool.description,
280
+ tool.inputSchema,
281
+ tool.annotations,
282
+ options.exposedTo,
283
+ options.source
284
+ ]
285
+ );
286
+ (0, import_react9.useEffect)(() => {
287
+ const latest = toolRef.current;
288
+ const registration = registry.registerTool(
289
+ {
290
+ ...latest,
291
+ execute: (args, executionContext) => toolRef.current.execute(args, executionContext)
292
+ },
293
+ {
294
+ ...options,
295
+ source: options.source ?? "hook"
296
+ }
297
+ );
298
+ return () => registration.abort();
299
+ }, [definitionKey, registry, toolRef, options.exposedTo, options.source, ...deps]);
300
+ }
301
+ // Annotate the CommonJS export names for ESM import in node:
302
+ 0 && (module.exports = {
303
+ WebMcpContext,
304
+ WebMcpProvider,
305
+ WebMcpScope,
306
+ WebMcpSecurityBanner,
307
+ useLatestRef,
308
+ useStableTool,
309
+ useWebMcpContextValue,
310
+ useWebMcpRegistry,
311
+ useWebMcpSecurityBanner,
312
+ useWebMcpTool,
313
+ useWebMcpTools
314
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @experimental Tooluminati React bindings — public APIs may change.
3
+ */
4
+ export * from './WebMcpContext';
5
+ export * from './WebMcpProvider';
6
+ export * from './WebMcpScope';
7
+ export * from './useLatestRef';
8
+ export * from './useStableTool';
9
+ export * from './useWebMcpRegistry';
10
+ export * from './WebMcpSecurityBanner';
11
+ export * from './useWebMcpSecurityBanner';
12
+ export * from './useWebMcpTool';
13
+ export * from './useWebMcpTools';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,285 @@
1
+ // src/WebMcpContext.tsx
2
+ import { createContext } from "react";
3
+ var WebMcpContext = createContext(
4
+ null
5
+ );
6
+
7
+ // src/WebMcpProvider.tsx
8
+ import {
9
+ useContext,
10
+ useEffect,
11
+ useMemo
12
+ } from "react";
13
+ import {
14
+ WebMcpRegistry
15
+ } from "@tooluminati/core";
16
+ import {
17
+ localDevPolicy
18
+ } from "@tooluminati/policies";
19
+ import { jsx } from "react/jsx-runtime";
20
+ function WebMcpProvider({
21
+ children,
22
+ tools = [],
23
+ enabled = false,
24
+ strict = false,
25
+ namespace,
26
+ policies = localDevPolicy,
27
+ policyContext,
28
+ modelContext,
29
+ getAppContext,
30
+ onSecurityWarning,
31
+ onError
32
+ }) {
33
+ const parentContext = useContext(WebMcpContext);
34
+ useEffect(() => {
35
+ if (parentContext && namespace === void 0 && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
36
+ console.warn(
37
+ "[tooluminati] Nested WebMcpProvider detected without a namespace. Provide namespace to avoid tool collisions."
38
+ );
39
+ }
40
+ }, [namespace, parentContext]);
41
+ const registry = useMemo(
42
+ () => new WebMcpRegistry({
43
+ enabled,
44
+ strict,
45
+ namespace,
46
+ policies,
47
+ policyContext,
48
+ modelContext,
49
+ getAppContext,
50
+ onSecurityWarning,
51
+ onError
52
+ }),
53
+ [
54
+ enabled,
55
+ getAppContext,
56
+ modelContext,
57
+ namespace,
58
+ onError,
59
+ onSecurityWarning,
60
+ policies,
61
+ policyContext,
62
+ strict
63
+ ]
64
+ );
65
+ useEffect(() => {
66
+ const registrations = tools.map(
67
+ (tool) => registry.registerTool(tool, { source: "provider" })
68
+ );
69
+ return () => {
70
+ for (const registration of registrations) {
71
+ registration.abort();
72
+ }
73
+ };
74
+ }, [registry, tools]);
75
+ useEffect(() => () => registry.unregisterAll(), [registry]);
76
+ const value = useMemo(() => {
77
+ const next = {
78
+ registry,
79
+ enabled,
80
+ policies
81
+ };
82
+ if (policyContext !== void 0) {
83
+ next.policyContext = policyContext;
84
+ }
85
+ if (namespace !== void 0) {
86
+ next.namespace = namespace;
87
+ }
88
+ return next;
89
+ }, [enabled, namespace, policies, policyContext, registry]);
90
+ return /* @__PURE__ */ jsx(WebMcpContext.Provider, { value, children });
91
+ }
92
+
93
+ // src/WebMcpScope.tsx
94
+ import { useMemo as useMemo2 } from "react";
95
+
96
+ // src/useWebMcpRegistry.ts
97
+ import { useContext as useContext2 } from "react";
98
+ function useWebMcpRegistry() {
99
+ const context = useContext2(WebMcpContext);
100
+ if (!context) {
101
+ throw new Error("useWebMcpRegistry must be used within WebMcpProvider.");
102
+ }
103
+ return context.registry;
104
+ }
105
+ function useWebMcpContextValue(fallbackRegistry) {
106
+ const context = useContext2(WebMcpContext);
107
+ if (!context && !fallbackRegistry) {
108
+ throw new Error("WebMCP hooks must be used within WebMcpProvider.");
109
+ }
110
+ if (context) {
111
+ return context;
112
+ }
113
+ return {
114
+ registry: fallbackRegistry,
115
+ enabled: false
116
+ };
117
+ }
118
+
119
+ // src/useWebMcpTools.ts
120
+ import { useEffect as useEffect2 } from "react";
121
+ function useWebMcpTools(tools, deps = [], options = {}) {
122
+ const { registry } = useWebMcpContextValue();
123
+ const exposedTo = options.exposedTo;
124
+ const source = options.source ?? "scope";
125
+ useEffect2(() => {
126
+ const registrations = tools.map(
127
+ (tool) => registry.registerTool(tool, {
128
+ ...options,
129
+ source
130
+ })
131
+ );
132
+ return () => {
133
+ for (const registration of registrations) {
134
+ registration.abort();
135
+ }
136
+ };
137
+ }, [registry, tools, exposedTo, source, ...deps]);
138
+ }
139
+
140
+ // src/WebMcpScope.tsx
141
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
142
+ function WebMcpScope({
143
+ children,
144
+ tools,
145
+ source = "scope",
146
+ namespaceSegment
147
+ }) {
148
+ const { namespace } = useWebMcpContextValue();
149
+ const scopedTools = useMemo2(
150
+ () => namespaceSegment ? tools.map((tool) => ({
151
+ ...tool,
152
+ name: `${namespaceSegment}.${tool.name}`
153
+ })) : tools,
154
+ [namespaceSegment, tools]
155
+ );
156
+ void namespace;
157
+ useWebMcpTools(scopedTools, [scopedTools], { source });
158
+ return /* @__PURE__ */ jsx2(Fragment, { children });
159
+ }
160
+
161
+ // src/useLatestRef.ts
162
+ import { useLayoutEffect, useRef } from "react";
163
+ function useLatestRef(value) {
164
+ const ref = useRef(value);
165
+ useLayoutEffect(() => {
166
+ ref.current = value;
167
+ });
168
+ return ref;
169
+ }
170
+
171
+ // src/useStableTool.ts
172
+ import { useMemo as useMemo3 } from "react";
173
+ function useStableTool(factory, deps) {
174
+ return useMemo3(factory, deps);
175
+ }
176
+
177
+ // src/useWebMcpSecurityBanner.ts
178
+ import { useMemo as useMemo4 } from "react";
179
+ import { isWebMcpSupported } from "@tooluminati/core";
180
+ function useWebMcpSecurityBanner() {
181
+ const { enabled, registry } = useWebMcpContextValue();
182
+ return useMemo4(
183
+ () => ({
184
+ enabled,
185
+ supported: isWebMcpSupported(),
186
+ registeredTools: registry.getRegisteredToolNames(),
187
+ message: enabled ? "Tooluminati diagnostics are enabled for this page." : "Tooluminati diagnostics are disabled."
188
+ }),
189
+ [enabled, registry]
190
+ );
191
+ }
192
+
193
+ // src/WebMcpSecurityBanner.tsx
194
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
195
+ function WebMcpSecurityBanner() {
196
+ const banner = useWebMcpSecurityBanner();
197
+ if (!banner.enabled) {
198
+ return null;
199
+ }
200
+ return /* @__PURE__ */ jsxs(
201
+ "aside",
202
+ {
203
+ "aria-live": "polite",
204
+ style: {
205
+ background: "#fff3cd",
206
+ border: "1px solid #ffeeba",
207
+ color: "#856404",
208
+ padding: "0.75rem 1rem",
209
+ marginBottom: "1rem"
210
+ },
211
+ children: [
212
+ /* @__PURE__ */ jsx3("strong", { children: "Tooluminati diagnostics enabled." }),
213
+ " ",
214
+ banner.message,
215
+ " Registered tools: ",
216
+ banner.registeredTools.join(", ") || "none",
217
+ "."
218
+ ]
219
+ }
220
+ );
221
+ }
222
+
223
+ // src/useWebMcpTool.ts
224
+ import { useEffect as useEffect3, useMemo as useMemo5 } from "react";
225
+ function stableSerialize(value) {
226
+ return JSON.stringify(
227
+ value,
228
+ (_key, current) => current && typeof current === "object" && !Array.isArray(current) ? Object.keys(current).sort().reduce((accumulator, key) => {
229
+ accumulator[key] = current[key];
230
+ return accumulator;
231
+ }, {}) : current
232
+ );
233
+ }
234
+ function useWebMcpTool(tool, deps = [], options = {}) {
235
+ const context = useWebMcpContextValue(options.fallbackRegistry);
236
+ const registry = options.fallbackRegistry ?? context.registry;
237
+ const toolRef = useLatestRef(tool);
238
+ const definitionKey = useMemo5(
239
+ () => stableSerialize({
240
+ name: tool.name,
241
+ title: tool.title,
242
+ description: tool.description,
243
+ inputSchema: tool.inputSchema,
244
+ annotations: tool.annotations,
245
+ exposedTo: options.exposedTo,
246
+ source: options.source
247
+ }),
248
+ [
249
+ tool.name,
250
+ tool.title,
251
+ tool.description,
252
+ tool.inputSchema,
253
+ tool.annotations,
254
+ options.exposedTo,
255
+ options.source
256
+ ]
257
+ );
258
+ useEffect3(() => {
259
+ const latest = toolRef.current;
260
+ const registration = registry.registerTool(
261
+ {
262
+ ...latest,
263
+ execute: (args, executionContext) => toolRef.current.execute(args, executionContext)
264
+ },
265
+ {
266
+ ...options,
267
+ source: options.source ?? "hook"
268
+ }
269
+ );
270
+ return () => registration.abort();
271
+ }, [definitionKey, registry, toolRef, options.exposedTo, options.source, ...deps]);
272
+ }
273
+ export {
274
+ WebMcpContext,
275
+ WebMcpProvider,
276
+ WebMcpScope,
277
+ WebMcpSecurityBanner,
278
+ useLatestRef,
279
+ useStableTool,
280
+ useWebMcpContextValue,
281
+ useWebMcpRegistry,
282
+ useWebMcpSecurityBanner,
283
+ useWebMcpTool,
284
+ useWebMcpTools
285
+ };
@@ -0,0 +1,2 @@
1
+ export declare function useLatestRef<T>(value: T): import("react").RefObject<T>;
2
+ //# sourceMappingURL=useLatestRef.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLatestRef.d.ts","sourceRoot":"","sources":["../src/useLatestRef.ts"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,gCAQvC"}
@@ -0,0 +1,8 @@
1
+ import { useLayoutEffect, useRef } from 'react';
2
+ export function useLatestRef(value) {
3
+ const ref = useRef(value);
4
+ useLayoutEffect(() => {
5
+ ref.current = value;
6
+ });
7
+ return ref;
8
+ }
@@ -0,0 +1,4 @@
1
+ import { type DependencyList } from 'react';
2
+ import type { WebMcpToolDescriptor } from '@tooluminati/core';
3
+ export declare function useStableTool<TArgs, TResult>(factory: () => WebMcpToolDescriptor<TArgs, TResult>, deps: DependencyList): WebMcpToolDescriptor<TArgs, TResult>;
4
+ //# sourceMappingURL=useStableTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStableTool.d.ts","sourceRoot":"","sources":["../src/useStableTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAC1C,OAAO,EAAE,MAAM,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,EACnD,IAAI,EAAE,cAAc,GACnB,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,CAEtC"}
@@ -0,0 +1,4 @@
1
+ import { useMemo } from 'react';
2
+ export function useStableTool(factory, deps) {
3
+ return useMemo(factory, deps);
4
+ }
@@ -0,0 +1,4 @@
1
+ import type { WebMcpRegistry } from '@tooluminati/core';
2
+ export declare function useWebMcpRegistry(): WebMcpRegistry;
3
+ export declare function useWebMcpContextValue(fallbackRegistry?: WebMcpRegistry): import("./WebMcpContext").WebMcpReactContextValue;
4
+ //# sourceMappingURL=useWebMcpRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWebMcpRegistry.d.ts","sourceRoot":"","sources":["../src/useWebMcpRegistry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,wBAAgB,iBAAiB,mBAQhC;AAED,wBAAgB,qBAAqB,CAAC,gBAAgB,CAAC,EAAE,cAAc,qDAetE"}
@@ -0,0 +1,22 @@
1
+ import { useContext } from 'react';
2
+ import { WebMcpContext } from './WebMcpContext';
3
+ export function useWebMcpRegistry() {
4
+ const context = useContext(WebMcpContext);
5
+ if (!context) {
6
+ throw new Error('useWebMcpRegistry must be used within WebMcpProvider.');
7
+ }
8
+ return context.registry;
9
+ }
10
+ export function useWebMcpContextValue(fallbackRegistry) {
11
+ const context = useContext(WebMcpContext);
12
+ if (!context && !fallbackRegistry) {
13
+ throw new Error('WebMCP hooks must be used within WebMcpProvider.');
14
+ }
15
+ if (context) {
16
+ return context;
17
+ }
18
+ return {
19
+ registry: fallbackRegistry,
20
+ enabled: false,
21
+ };
22
+ }
@@ -0,0 +1,7 @@
1
+ export declare function useWebMcpSecurityBanner(): {
2
+ enabled: boolean;
3
+ supported: boolean;
4
+ registeredTools: string[];
5
+ message: string;
6
+ };
7
+ //# sourceMappingURL=useWebMcpSecurityBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWebMcpSecurityBanner.d.ts","sourceRoot":"","sources":["../src/useWebMcpSecurityBanner.ts"],"names":[],"mappings":"AAIA,wBAAgB,uBAAuB;;;;;EActC"}
@@ -0,0 +1,14 @@
1
+ import { useMemo } from 'react';
2
+ import { isWebMcpSupported } from '@tooluminati/core';
3
+ import { useWebMcpContextValue } from './useWebMcpRegistry';
4
+ export function useWebMcpSecurityBanner() {
5
+ const { enabled, registry } = useWebMcpContextValue();
6
+ return useMemo(() => ({
7
+ enabled,
8
+ supported: isWebMcpSupported(),
9
+ registeredTools: registry.getRegisteredToolNames(),
10
+ message: enabled
11
+ ? 'Tooluminati diagnostics are enabled for this page.'
12
+ : 'Tooluminati diagnostics are disabled.',
13
+ }), [enabled, registry]);
14
+ }
@@ -0,0 +1,7 @@
1
+ import { type DependencyList } from 'react';
2
+ import type { RegisterToolOptions, WebMcpRegistry, WebMcpToolDescriptor } from '@tooluminati/core';
3
+ export interface UseWebMcpToolOptions extends RegisterToolOptions {
4
+ fallbackRegistry?: WebMcpRegistry;
5
+ }
6
+ export declare function useWebMcpTool<TArgs, TResult>(tool: WebMcpToolDescriptor<TArgs, TResult>, deps?: DependencyList, options?: UseWebMcpToolOptions): void;
7
+ //# sourceMappingURL=useWebMcpTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWebMcpTool.d.ts","sourceRoot":"","sources":["../src/useWebMcpTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAChE,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAI3B,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,gBAAgB,CAAC,EAAE,cAAc,CAAC;CACnC;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAC1C,IAAI,EAAE,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC1C,IAAI,GAAE,cAAmB,EACzB,OAAO,GAAE,oBAAyB,GACjC,IAAI,CA2CN"}
@@ -0,0 +1,46 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import { useLatestRef } from './useLatestRef';
3
+ import { useWebMcpContextValue } from './useWebMcpRegistry';
4
+ function stableSerialize(value) {
5
+ return JSON.stringify(value, (_key, current) => current && typeof current === 'object' && !Array.isArray(current)
6
+ ? Object.keys(current)
7
+ .sort()
8
+ .reduce((accumulator, key) => {
9
+ accumulator[key] = current[key];
10
+ return accumulator;
11
+ }, {})
12
+ : current);
13
+ }
14
+ export function useWebMcpTool(tool, deps = [], options = {}) {
15
+ const context = useWebMcpContextValue(options.fallbackRegistry);
16
+ const registry = options.fallbackRegistry ?? context.registry;
17
+ const toolRef = useLatestRef(tool);
18
+ const definitionKey = useMemo(() => stableSerialize({
19
+ name: tool.name,
20
+ title: tool.title,
21
+ description: tool.description,
22
+ inputSchema: tool.inputSchema,
23
+ annotations: tool.annotations,
24
+ exposedTo: options.exposedTo,
25
+ source: options.source,
26
+ }), [
27
+ tool.name,
28
+ tool.title,
29
+ tool.description,
30
+ tool.inputSchema,
31
+ tool.annotations,
32
+ options.exposedTo,
33
+ options.source,
34
+ ]);
35
+ useEffect(() => {
36
+ const latest = toolRef.current;
37
+ const registration = registry.registerTool({
38
+ ...latest,
39
+ execute: (args, executionContext) => toolRef.current.execute(args, executionContext),
40
+ }, {
41
+ ...options,
42
+ source: options.source ?? 'hook',
43
+ });
44
+ return () => registration.abort();
45
+ }, [definitionKey, registry, toolRef, options.exposedTo, options.source, ...deps]);
46
+ }
@@ -0,0 +1,4 @@
1
+ import { type DependencyList } from 'react';
2
+ import type { RegisterToolOptions, WebMcpToolDescriptor } from '@tooluminati/core';
3
+ export declare function useWebMcpTools(tools: WebMcpToolDescriptor[], deps?: DependencyList, options?: RegisterToolOptions): void;
4
+ //# sourceMappingURL=useWebMcpTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWebMcpTools.d.ts","sourceRoot":"","sources":["../src/useWebMcpTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,cAAc,CAC5B,KAAK,EAAE,oBAAoB,EAAE,EAC7B,IAAI,GAAE,cAAmB,EACzB,OAAO,GAAE,mBAAwB,GAChC,IAAI,CAmBN"}
@@ -0,0 +1,18 @@
1
+ import { useEffect } from 'react';
2
+ import { useWebMcpContextValue } from './useWebMcpRegistry';
3
+ export function useWebMcpTools(tools, deps = [], options = {}) {
4
+ const { registry } = useWebMcpContextValue();
5
+ const exposedTo = options.exposedTo;
6
+ const source = options.source ?? 'scope';
7
+ useEffect(() => {
8
+ const registrations = tools.map((tool) => registry.registerTool(tool, {
9
+ ...options,
10
+ source,
11
+ }));
12
+ return () => {
13
+ for (const registration of registrations) {
14
+ registration.abort();
15
+ }
16
+ };
17
+ }, [registry, tools, exposedTo, source, ...deps]);
18
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@tooluminati/react",
3
+ "version": "0.1.0",
4
+ "description": "React provider, hooks, and scopes for Tooluminati.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "@tooluminati/core": "0.1.0",
19
+ "@tooluminati/policies": "0.1.0"
20
+ },
21
+ "peerDependencies": {
22
+ "react": ">=18.0.0"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "license": "MIT",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/jitterbox/Tooluminati.git",
34
+ "directory": "packages/react"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/jitterbox/Tooluminati/issues"
38
+ },
39
+ "homepage": "https://github.com/jitterbox/Tooluminati#readme",
40
+ "keywords": [
41
+ "tooluminati",
42
+ "webmcp",
43
+ "react",
44
+ "hooks",
45
+ "agents"
46
+ ],
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup src/index.ts --format esm,cjs && tsc -p tsconfig.json --emitDeclarationOnly",
52
+ "typecheck": "tsc --noEmit"
53
+ }
54
+ }