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