react-webmcp 0.1.1 → 0.2.1
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 +138 -19
- package/dist/index.d.mts +292 -12
- package/dist/index.d.ts +292 -12
- package/dist/index.js +391 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +371 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -2
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React6 = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
|
|
6
6
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var React6__default = /*#__PURE__*/_interopDefault(React6);
|
|
9
9
|
|
|
10
10
|
// src/hooks/useWebMCPTool.ts
|
|
11
11
|
|
|
@@ -23,10 +23,12 @@ function isWebMCPTestingAvailable() {
|
|
|
23
23
|
return typeof window !== "undefined" && typeof window.navigator !== "undefined" && !!window.navigator.modelContextTesting;
|
|
24
24
|
}
|
|
25
25
|
function warnIfUnavailable(hookName) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
if (process.env.NODE_ENV !== "production") {
|
|
27
|
+
if (!isWebMCPAvailable()) {
|
|
28
|
+
console.warn(
|
|
29
|
+
`[react-webmcp] ${hookName}: navigator.modelContext is not available. Ensure you are running Chrome 146+ with the "WebMCP for testing" flag enabled.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -35,11 +37,11 @@ function toolFingerprint(config) {
|
|
|
35
37
|
return `${config.name}::${config.description}::${JSON.stringify(config.inputSchema)}::${JSON.stringify(config.outputSchema ?? {})}::${JSON.stringify(config.annotations ?? {})}`;
|
|
36
38
|
}
|
|
37
39
|
function useWebMCPTool(config) {
|
|
38
|
-
const registeredNameRef =
|
|
39
|
-
const configRef =
|
|
40
|
+
const registeredNameRef = React6.useRef(null);
|
|
41
|
+
const configRef = React6.useRef(config);
|
|
40
42
|
configRef.current = config;
|
|
41
43
|
const fingerprint = toolFingerprint(config);
|
|
42
|
-
|
|
44
|
+
React6.useEffect(() => {
|
|
43
45
|
const mc = getModelContext();
|
|
44
46
|
if (!mc) {
|
|
45
47
|
warnIfUnavailable("useWebMCPTool");
|
|
@@ -55,17 +57,26 @@ function useWebMCPTool(config) {
|
|
|
55
57
|
name: config.name,
|
|
56
58
|
description: config.description,
|
|
57
59
|
inputSchema: config.inputSchema,
|
|
58
|
-
...config.outputSchema ? { outputSchema: config.outputSchema } : {},
|
|
59
|
-
...config.annotations ? { annotations: config.annotations } : {},
|
|
60
60
|
execute: (input) => {
|
|
61
61
|
return configRef.current.execute(input);
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
|
+
if (config.outputSchema) {
|
|
65
|
+
toolDef.outputSchema = config.outputSchema;
|
|
66
|
+
}
|
|
67
|
+
if (config.annotations) {
|
|
68
|
+
toolDef.annotations = config.annotations;
|
|
69
|
+
}
|
|
64
70
|
try {
|
|
65
71
|
mc.registerTool(toolDef);
|
|
66
72
|
registeredNameRef.current = config.name;
|
|
67
73
|
} catch (err) {
|
|
68
|
-
|
|
74
|
+
if (process.env.NODE_ENV !== "production") {
|
|
75
|
+
console.error(
|
|
76
|
+
`[react-webmcp] Failed to register tool "${config.name}":`,
|
|
77
|
+
err
|
|
78
|
+
);
|
|
79
|
+
}
|
|
69
80
|
}
|
|
70
81
|
return () => {
|
|
71
82
|
try {
|
|
@@ -82,25 +93,40 @@ function toolsFingerprint(tools) {
|
|
|
82
93
|
).join("|");
|
|
83
94
|
}
|
|
84
95
|
function useWebMCPContext(config) {
|
|
85
|
-
const toolsRef =
|
|
96
|
+
const toolsRef = React6.useRef(config.tools);
|
|
86
97
|
toolsRef.current = config.tools;
|
|
87
98
|
const fingerprint = toolsFingerprint(config.tools);
|
|
88
|
-
|
|
99
|
+
React6.useEffect(() => {
|
|
89
100
|
const mc = getModelContext();
|
|
90
101
|
if (!mc) {
|
|
91
102
|
warnIfUnavailable("useWebMCPContext");
|
|
92
103
|
return;
|
|
93
104
|
}
|
|
94
|
-
const stableTools = toolsRef.current.map((tool, idx) =>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
const stableTools = toolsRef.current.map((tool, idx) => {
|
|
106
|
+
const def = {
|
|
107
|
+
name: tool.name,
|
|
108
|
+
description: tool.description,
|
|
109
|
+
inputSchema: tool.inputSchema,
|
|
110
|
+
execute: (input) => {
|
|
111
|
+
return toolsRef.current[idx].execute(input);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
if (tool.annotations) {
|
|
115
|
+
def.annotations = tool.annotations;
|
|
116
|
+
}
|
|
117
|
+
if (tool.outputSchema) {
|
|
118
|
+
def.outputSchema = tool.outputSchema;
|
|
98
119
|
}
|
|
99
|
-
|
|
120
|
+
return def;
|
|
121
|
+
});
|
|
100
122
|
try {
|
|
101
|
-
mc.provideContext({
|
|
123
|
+
mc.provideContext({
|
|
124
|
+
tools: stableTools
|
|
125
|
+
});
|
|
102
126
|
} catch (err) {
|
|
103
|
-
|
|
127
|
+
if (process.env.NODE_ENV !== "production") {
|
|
128
|
+
console.error("[react-webmcp] Failed to provide context:", err);
|
|
129
|
+
}
|
|
104
130
|
}
|
|
105
131
|
return () => {
|
|
106
132
|
try {
|
|
@@ -111,7 +137,7 @@ function useWebMCPContext(config) {
|
|
|
111
137
|
}, [fingerprint]);
|
|
112
138
|
}
|
|
113
139
|
function useToolEvent(event, callback, toolNameFilter) {
|
|
114
|
-
|
|
140
|
+
React6.useEffect(() => {
|
|
115
141
|
const handler = (e) => {
|
|
116
142
|
const toolName = e.toolName ?? e.detail?.toolName;
|
|
117
143
|
if (!toolName) return;
|
|
@@ -134,8 +160,8 @@ function WebMCPForm({
|
|
|
134
160
|
children,
|
|
135
161
|
...rest
|
|
136
162
|
}) {
|
|
137
|
-
const formRef =
|
|
138
|
-
|
|
163
|
+
const formRef = React6.useRef(null);
|
|
164
|
+
React6.useEffect(() => {
|
|
139
165
|
const handleActivated = (e) => {
|
|
140
166
|
const name = e.toolName ?? e.detail?.toolName;
|
|
141
167
|
if (name === toolName && onToolActivated) {
|
|
@@ -155,7 +181,7 @@ function WebMCPForm({
|
|
|
155
181
|
window.removeEventListener("toolcancel", handleCancel);
|
|
156
182
|
};
|
|
157
183
|
}, [toolName, onToolActivated, onToolCancel]);
|
|
158
|
-
const handleSubmit =
|
|
184
|
+
const handleSubmit = React6.useCallback(
|
|
159
185
|
(e) => {
|
|
160
186
|
if (onSubmit) {
|
|
161
187
|
onSubmit(e.nativeEvent);
|
|
@@ -181,7 +207,7 @@ function WebMCPForm({
|
|
|
181
207
|
}
|
|
182
208
|
);
|
|
183
209
|
}
|
|
184
|
-
var WebMCPInput =
|
|
210
|
+
var WebMCPInput = React6__default.default.forwardRef(
|
|
185
211
|
({ toolParamTitle, toolParamDescription, ...rest }, ref) => {
|
|
186
212
|
const webmcpAttrs = {};
|
|
187
213
|
if (toolParamTitle) {
|
|
@@ -194,7 +220,7 @@ var WebMCPInput = React2__default.default.forwardRef(
|
|
|
194
220
|
}
|
|
195
221
|
);
|
|
196
222
|
WebMCPInput.displayName = "WebMCPInput";
|
|
197
|
-
var WebMCPSelect =
|
|
223
|
+
var WebMCPSelect = React6__default.default.forwardRef(({ toolParamTitle, toolParamDescription, children, ...rest }, ref) => {
|
|
198
224
|
const webmcpAttrs = {};
|
|
199
225
|
if (toolParamTitle) {
|
|
200
226
|
webmcpAttrs.toolparamtitle = toolParamTitle;
|
|
@@ -205,7 +231,7 @@ var WebMCPSelect = React2__default.default.forwardRef(({ toolParamTitle, toolPar
|
|
|
205
231
|
return /* @__PURE__ */ jsxRuntime.jsx("select", { ref, ...webmcpAttrs, ...rest, children });
|
|
206
232
|
});
|
|
207
233
|
WebMCPSelect.displayName = "WebMCPSelect";
|
|
208
|
-
var WebMCPTextarea =
|
|
234
|
+
var WebMCPTextarea = React6__default.default.forwardRef(({ toolParamTitle, toolParamDescription, ...rest }, ref) => {
|
|
209
235
|
const webmcpAttrs = {};
|
|
210
236
|
if (toolParamTitle) {
|
|
211
237
|
webmcpAttrs.toolparamtitle = toolParamTitle;
|
|
@@ -216,12 +242,12 @@ var WebMCPTextarea = React2__default.default.forwardRef(({ toolParamTitle, toolP
|
|
|
216
242
|
return /* @__PURE__ */ jsxRuntime.jsx("textarea", { ref, ...webmcpAttrs, ...rest });
|
|
217
243
|
});
|
|
218
244
|
WebMCPTextarea.displayName = "WebMCPTextarea";
|
|
219
|
-
var WebMCPReactContext =
|
|
245
|
+
var WebMCPReactContext = React6.createContext({
|
|
220
246
|
available: false,
|
|
221
247
|
testingAvailable: false
|
|
222
248
|
});
|
|
223
249
|
function WebMCPProvider({ children }) {
|
|
224
|
-
const value =
|
|
250
|
+
const value = React6.useMemo(
|
|
225
251
|
() => ({
|
|
226
252
|
available: isWebMCPAvailable(),
|
|
227
253
|
testingAvailable: isWebMCPTestingAvailable()
|
|
@@ -231,20 +257,354 @@ function WebMCPProvider({ children }) {
|
|
|
231
257
|
return /* @__PURE__ */ jsxRuntime.jsx(WebMCPReactContext.Provider, { value, children });
|
|
232
258
|
}
|
|
233
259
|
function useWebMCPStatus() {
|
|
234
|
-
return
|
|
260
|
+
return React6.useContext(WebMCPReactContext);
|
|
261
|
+
}
|
|
262
|
+
function extractOptions(children) {
|
|
263
|
+
const results = [];
|
|
264
|
+
React6__default.default.Children.toArray(children).forEach((child) => {
|
|
265
|
+
if (!React6__default.default.isValidElement(child)) return;
|
|
266
|
+
const props = child.props;
|
|
267
|
+
if (props.value !== void 0 && props.value !== null) {
|
|
268
|
+
const value = props.value;
|
|
269
|
+
let label;
|
|
270
|
+
if (typeof props.children === "string") {
|
|
271
|
+
label = props.children;
|
|
272
|
+
} else {
|
|
273
|
+
label = String(value);
|
|
274
|
+
}
|
|
275
|
+
results.push({ value, label });
|
|
276
|
+
}
|
|
277
|
+
if (props.children) {
|
|
278
|
+
results.push(...extractOptions(props.children));
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
return results;
|
|
282
|
+
}
|
|
283
|
+
function extractFields(children) {
|
|
284
|
+
const fields = [];
|
|
285
|
+
React6__default.default.Children.toArray(children).forEach((child) => {
|
|
286
|
+
if (!React6__default.default.isValidElement(child)) return;
|
|
287
|
+
const props = child.props;
|
|
288
|
+
const inputProps = props.inputProps;
|
|
289
|
+
const slotInput = props.slotProps?.input;
|
|
290
|
+
const name = props.name ?? inputProps?.name ?? slotInput?.name;
|
|
291
|
+
if (name) {
|
|
292
|
+
const field = { name };
|
|
293
|
+
if (props.type !== void 0) field.type = props.type;
|
|
294
|
+
if (props.required !== void 0) field.required = Boolean(props.required);
|
|
295
|
+
if (props.min !== void 0) field.min = Number(props.min);
|
|
296
|
+
if (props.max !== void 0) field.max = Number(props.max);
|
|
297
|
+
if (props.minLength !== void 0) field.minLength = Number(props.minLength);
|
|
298
|
+
if (props.maxLength !== void 0) field.maxLength = Number(props.maxLength);
|
|
299
|
+
if (props.pattern !== void 0) field.pattern = props.pattern;
|
|
300
|
+
if (props.children) {
|
|
301
|
+
const options = extractOptions(props.children);
|
|
302
|
+
if (options.length > 0) {
|
|
303
|
+
field.enumValues = options.map((o) => o.value);
|
|
304
|
+
field.oneOf = options;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
fields.push(field);
|
|
308
|
+
} else if (props.children) {
|
|
309
|
+
fields.push(...extractFields(props.children));
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
return fields;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/adapters/buildSchema.ts
|
|
316
|
+
function mapHtmlTypeToSchemaType(htmlType) {
|
|
317
|
+
switch (htmlType) {
|
|
318
|
+
case "number":
|
|
319
|
+
case "range":
|
|
320
|
+
return "number";
|
|
321
|
+
case "checkbox":
|
|
322
|
+
return "boolean";
|
|
323
|
+
default:
|
|
324
|
+
return "string";
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function buildInputSchema(fields) {
|
|
328
|
+
const properties = {};
|
|
329
|
+
const required = [];
|
|
330
|
+
const sortedFields = [...fields].sort((a, b) => a.name.localeCompare(b.name));
|
|
331
|
+
for (const field of sortedFields) {
|
|
332
|
+
const prop = {
|
|
333
|
+
type: mapHtmlTypeToSchemaType(field.type)
|
|
334
|
+
};
|
|
335
|
+
if (field.title) prop.title = field.title;
|
|
336
|
+
if (field.description) prop.description = field.description;
|
|
337
|
+
if (field.min !== void 0) prop.minimum = field.min;
|
|
338
|
+
if (field.max !== void 0) prop.maximum = field.max;
|
|
339
|
+
if (field.minLength !== void 0) prop.minLength = field.minLength;
|
|
340
|
+
if (field.maxLength !== void 0) prop.maxLength = field.maxLength;
|
|
341
|
+
if (field.pattern) prop.pattern = field.pattern;
|
|
342
|
+
if (field.enumValues && field.enumValues.length > 0) {
|
|
343
|
+
prop.enum = field.enumValues;
|
|
344
|
+
}
|
|
345
|
+
if (field.oneOf && field.oneOf.length > 0) {
|
|
346
|
+
prop.oneOf = field.oneOf.map((opt) => ({
|
|
347
|
+
const: opt.value,
|
|
348
|
+
title: opt.label
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
properties[field.name] = prop;
|
|
352
|
+
if (field.required) {
|
|
353
|
+
required.push(field.name);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const schema = {
|
|
357
|
+
type: "object",
|
|
358
|
+
properties
|
|
359
|
+
};
|
|
360
|
+
if (required.length > 0) {
|
|
361
|
+
schema.required = required.sort();
|
|
362
|
+
}
|
|
363
|
+
return schema;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/adapters/validateSchema.ts
|
|
367
|
+
function validateSchema(fields, options) {
|
|
368
|
+
if (process.env.NODE_ENV === "production") return;
|
|
369
|
+
const strict = options?.strict ?? false;
|
|
370
|
+
const issues = [];
|
|
371
|
+
const seen = /* @__PURE__ */ new Set();
|
|
372
|
+
for (const field of fields) {
|
|
373
|
+
if (seen.has(field.name)) {
|
|
374
|
+
issues.push(`Duplicate field name "${field.name}".`);
|
|
375
|
+
}
|
|
376
|
+
seen.add(field.name);
|
|
377
|
+
const schemaType = mapHtmlTypeToSchemaType(field.type);
|
|
378
|
+
if (field.pattern !== void 0 && schemaType !== "string") {
|
|
379
|
+
issues.push(
|
|
380
|
+
`Field "${field.name}": pattern is only valid for string types, but type is "${schemaType}".`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
if ((field.min !== void 0 || field.max !== void 0) && schemaType !== "number") {
|
|
384
|
+
issues.push(
|
|
385
|
+
`Field "${field.name}": min/max are only valid for number types, but type is "${schemaType}".`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if ((field.minLength !== void 0 || field.maxLength !== void 0) && schemaType !== "string") {
|
|
389
|
+
issues.push(
|
|
390
|
+
`Field "${field.name}": minLength/maxLength are only valid for string types, but type is "${schemaType}".`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
if (field.enumValues && field.enumValues.length > 0) {
|
|
394
|
+
for (const val of field.enumValues) {
|
|
395
|
+
const valType = typeof val;
|
|
396
|
+
if (schemaType === "string" && valType !== "string") {
|
|
397
|
+
issues.push(
|
|
398
|
+
`Field "${field.name}": enum value ${JSON.stringify(val)} is not a string.`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (schemaType === "number" && valType !== "number") {
|
|
402
|
+
issues.push(
|
|
403
|
+
`Field "${field.name}": enum value ${JSON.stringify(val)} is not a number.`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (schemaType === "boolean" && valType !== "boolean") {
|
|
407
|
+
issues.push(
|
|
408
|
+
`Field "${field.name}": enum value ${JSON.stringify(val)} is not a boolean.`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
for (const issue of issues) {
|
|
415
|
+
if (strict) {
|
|
416
|
+
throw new Error(`[react-webmcp] ${issue}`);
|
|
417
|
+
}
|
|
418
|
+
console.warn(`[react-webmcp] ${issue}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/adapters/useSchemaCollector.ts
|
|
423
|
+
var ToolContext = React6.createContext(null);
|
|
424
|
+
function fieldsFingerprint(fields) {
|
|
425
|
+
return fields.map(
|
|
426
|
+
(f) => `${f.name}::${f.type ?? ""}::${f.required ?? ""}::${f.title ?? ""}::${f.description ?? ""}::${JSON.stringify(f.enumValues ?? [])}::${JSON.stringify(f.oneOf ?? [])}::${f.min ?? ""}::${f.max ?? ""}::${f.minLength ?? ""}::${f.maxLength ?? ""}::${f.pattern ?? ""}`
|
|
427
|
+
).join("|");
|
|
235
428
|
}
|
|
429
|
+
function mergeField(base, override) {
|
|
430
|
+
const result = { ...base };
|
|
431
|
+
for (const key of Object.keys(override)) {
|
|
432
|
+
if (override[key] !== void 0) {
|
|
433
|
+
result[key] = override[key];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
function useSchemaCollector({
|
|
439
|
+
children,
|
|
440
|
+
fields: fieldsProp,
|
|
441
|
+
strict
|
|
442
|
+
}) {
|
|
443
|
+
const contextFieldsRef = React6.useRef(/* @__PURE__ */ new Map());
|
|
444
|
+
const [version, setVersion] = React6.useState(0);
|
|
445
|
+
const registerField = React6.useCallback((field) => {
|
|
446
|
+
contextFieldsRef.current.set(field.name, field);
|
|
447
|
+
setVersion((v) => v + 1);
|
|
448
|
+
}, []);
|
|
449
|
+
const unregisterField = React6.useCallback((name) => {
|
|
450
|
+
contextFieldsRef.current.delete(name);
|
|
451
|
+
setVersion((v) => v + 1);
|
|
452
|
+
}, []);
|
|
453
|
+
const childrenFields = extractFields(children);
|
|
454
|
+
const childrenFP = fieldsFingerprint(childrenFields);
|
|
455
|
+
const merged = React6.useMemo(() => {
|
|
456
|
+
const fieldMap = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const field of childrenFields) {
|
|
458
|
+
fieldMap.set(field.name, field);
|
|
459
|
+
}
|
|
460
|
+
if (fieldsProp) {
|
|
461
|
+
for (const [name, overrides] of Object.entries(fieldsProp)) {
|
|
462
|
+
const existing = fieldMap.get(name);
|
|
463
|
+
if (existing) {
|
|
464
|
+
fieldMap.set(name, mergeField(existing, overrides));
|
|
465
|
+
} else {
|
|
466
|
+
fieldMap.set(name, { name, ...overrides });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
for (const [name, field] of contextFieldsRef.current) {
|
|
471
|
+
const existing = fieldMap.get(name);
|
|
472
|
+
if (existing) {
|
|
473
|
+
fieldMap.set(name, mergeField(existing, field));
|
|
474
|
+
} else {
|
|
475
|
+
fieldMap.set(name, field);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const result = Array.from(fieldMap.values());
|
|
479
|
+
if (process.env.NODE_ENV !== "production") {
|
|
480
|
+
validateSchema(result, { strict });
|
|
481
|
+
}
|
|
482
|
+
return result;
|
|
483
|
+
}, [childrenFP, fieldsProp, version, strict]);
|
|
484
|
+
const mergedFP = fieldsFingerprint(merged);
|
|
485
|
+
const schema = React6.useMemo(
|
|
486
|
+
() => buildInputSchema(merged),
|
|
487
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
488
|
+
[mergedFP]
|
|
489
|
+
);
|
|
490
|
+
return { schema, registerField, unregisterField };
|
|
491
|
+
}
|
|
492
|
+
function WebMCPTool({
|
|
493
|
+
name,
|
|
494
|
+
description,
|
|
495
|
+
onExecute,
|
|
496
|
+
fields: fieldsProp,
|
|
497
|
+
strict,
|
|
498
|
+
autoSubmit,
|
|
499
|
+
annotations,
|
|
500
|
+
onToolActivated,
|
|
501
|
+
onToolCancel,
|
|
502
|
+
children
|
|
503
|
+
}) {
|
|
504
|
+
const { schema, registerField, unregisterField } = useSchemaCollector({
|
|
505
|
+
children,
|
|
506
|
+
fields: fieldsProp,
|
|
507
|
+
strict
|
|
508
|
+
});
|
|
509
|
+
const executeRef = React6.useRef(onExecute);
|
|
510
|
+
executeRef.current = onExecute;
|
|
511
|
+
useWebMCPTool({
|
|
512
|
+
name,
|
|
513
|
+
description,
|
|
514
|
+
inputSchema: schema,
|
|
515
|
+
annotations,
|
|
516
|
+
execute: (input) => executeRef.current(input)
|
|
517
|
+
});
|
|
518
|
+
React6.useEffect(() => {
|
|
519
|
+
if (typeof window === "undefined") return;
|
|
520
|
+
if (!onToolActivated && !onToolCancel) return;
|
|
521
|
+
const handleActivated = (e) => {
|
|
522
|
+
const toolName = e.toolName ?? e.detail?.toolName;
|
|
523
|
+
if (toolName === name && onToolActivated) {
|
|
524
|
+
onToolActivated(toolName);
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const handleCancel = (e) => {
|
|
528
|
+
const toolName = e.toolName ?? e.detail?.toolName;
|
|
529
|
+
if (toolName === name && onToolCancel) {
|
|
530
|
+
onToolCancel(toolName);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
window.addEventListener("toolactivated", handleActivated);
|
|
534
|
+
window.addEventListener("toolcancel", handleCancel);
|
|
535
|
+
return () => {
|
|
536
|
+
window.removeEventListener("toolactivated", handleActivated);
|
|
537
|
+
window.removeEventListener("toolcancel", handleCancel);
|
|
538
|
+
};
|
|
539
|
+
}, [name, onToolActivated, onToolCancel]);
|
|
540
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ToolContext.Provider, { value: { registerField, unregisterField }, children });
|
|
541
|
+
}
|
|
542
|
+
WebMCPTool.displayName = "WebMCP.Tool";
|
|
543
|
+
function fieldFingerprint(field) {
|
|
544
|
+
return `${field.name}::${field.type ?? ""}::${field.required ?? ""}::${field.title ?? ""}::${field.description ?? ""}::${JSON.stringify(field.enumValues ?? [])}::${JSON.stringify(field.oneOf ?? [])}::${field.min ?? ""}::${field.max ?? ""}::${field.minLength ?? ""}::${field.maxLength ?? ""}::${field.pattern ?? ""}`;
|
|
545
|
+
}
|
|
546
|
+
function useRegisterField(field) {
|
|
547
|
+
const ctx = React6.useContext(ToolContext);
|
|
548
|
+
const fp = fieldFingerprint(field);
|
|
549
|
+
React6.useEffect(() => {
|
|
550
|
+
if (!ctx) {
|
|
551
|
+
if (process.env.NODE_ENV !== "production") {
|
|
552
|
+
console.warn(
|
|
553
|
+
`[react-webmcp] useRegisterField: no WebMCP.Tool context found for field "${field.name}". Wrap this component in a <WebMCP.Tool> to register fields.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
ctx.registerField(field);
|
|
559
|
+
return () => {
|
|
560
|
+
try {
|
|
561
|
+
ctx.unregisterField(field.name);
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}, [fp]);
|
|
566
|
+
}
|
|
567
|
+
function WebMCPField({
|
|
568
|
+
children,
|
|
569
|
+
name,
|
|
570
|
+
...rest
|
|
571
|
+
}) {
|
|
572
|
+
const field = { name, ...rest };
|
|
573
|
+
if (!field.enumValues && !field.oneOf) {
|
|
574
|
+
const options = extractOptions(children);
|
|
575
|
+
if (options.length > 0) {
|
|
576
|
+
field.enumValues = options.map((o) => o.value);
|
|
577
|
+
field.oneOf = options;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
useRegisterField(field);
|
|
581
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
582
|
+
}
|
|
583
|
+
WebMCPField.displayName = "WebMCP.Field";
|
|
584
|
+
|
|
585
|
+
// src/adapters/index.ts
|
|
586
|
+
var WebMCP = { Tool: WebMCPTool, Field: WebMCPField };
|
|
236
587
|
|
|
588
|
+
exports.WebMCP = WebMCP;
|
|
589
|
+
exports.WebMCPField = WebMCPField;
|
|
237
590
|
exports.WebMCPForm = WebMCPForm;
|
|
238
591
|
exports.WebMCPInput = WebMCPInput;
|
|
239
592
|
exports.WebMCPProvider = WebMCPProvider;
|
|
240
593
|
exports.WebMCPSelect = WebMCPSelect;
|
|
241
594
|
exports.WebMCPTextarea = WebMCPTextarea;
|
|
595
|
+
exports.WebMCPTool = WebMCPTool;
|
|
596
|
+
exports.buildInputSchema = buildInputSchema;
|
|
597
|
+
exports.extractFields = extractFields;
|
|
598
|
+
exports.extractOptions = extractOptions;
|
|
242
599
|
exports.getModelContext = getModelContext;
|
|
243
600
|
exports.isWebMCPAvailable = isWebMCPAvailable;
|
|
244
601
|
exports.isWebMCPTestingAvailable = isWebMCPTestingAvailable;
|
|
602
|
+
exports.useRegisterField = useRegisterField;
|
|
603
|
+
exports.useSchemaCollector = useSchemaCollector;
|
|
245
604
|
exports.useToolEvent = useToolEvent;
|
|
246
605
|
exports.useWebMCPContext = useWebMCPContext;
|
|
247
606
|
exports.useWebMCPStatus = useWebMCPStatus;
|
|
248
607
|
exports.useWebMCPTool = useWebMCPTool;
|
|
608
|
+
exports.validateSchema = validateSchema;
|
|
249
609
|
//# sourceMappingURL=index.js.map
|
|
250
610
|
//# sourceMappingURL=index.js.map
|