auto-webmcp 0.2.10 → 0.3.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/dist/analyzer.d.ts +7 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/auto-webmcp.cjs.js +169 -32
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +169 -32
- package/dist/auto-webmcp.esm.js.map +2 -2
- package/dist/auto-webmcp.iife.js +2 -2
- package/dist/auto-webmcp.iife.js.map +3 -3
- package/dist/interceptor.d.ts +14 -0
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/registry.d.ts +7 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/analyzer.d.ts
CHANGED
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { JsonSchema } from './schema.js';
|
|
5
5
|
import { FormOverride } from './config.js';
|
|
6
|
+
export interface ToolAnnotations {
|
|
7
|
+
readOnlyHint?: boolean;
|
|
8
|
+
destructiveHint?: boolean;
|
|
9
|
+
idempotentHint?: boolean;
|
|
10
|
+
openWorldHint?: boolean;
|
|
11
|
+
}
|
|
6
12
|
export interface ToolMetadata {
|
|
7
13
|
name: string;
|
|
8
14
|
description: string;
|
|
9
15
|
inputSchema: JsonSchema;
|
|
16
|
+
annotations?: ToolAnnotations;
|
|
10
17
|
/** Key → DOM element for fields not addressable by name (id-keyed or ARIA-role controls). */
|
|
11
18
|
fieldElements?: Map<string, Element>;
|
|
12
19
|
}
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAmJ,MAAM,aAAa,CAAC;AAC1L,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAmJ,MAAM,aAAa,CAAC;AAC1L,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAOxF;AAipBD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,KAAK,CAAC,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC,EACzE,SAAS,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,GACrD,YAAY,CAKd"}
|
package/dist/auto-webmcp.cjs.js
CHANGED
|
@@ -40,22 +40,21 @@ async function registerFormTool(form, metadata, execute) {
|
|
|
40
40
|
if (existing) {
|
|
41
41
|
await unregisterFormTool(form);
|
|
42
42
|
}
|
|
43
|
+
const toolDef = {
|
|
44
|
+
name: metadata.name,
|
|
45
|
+
description: metadata.description,
|
|
46
|
+
inputSchema: metadata.inputSchema,
|
|
47
|
+
execute
|
|
48
|
+
};
|
|
49
|
+
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
50
|
+
toolDef.annotations = metadata.annotations;
|
|
51
|
+
}
|
|
43
52
|
try {
|
|
44
|
-
await navigator.modelContext.registerTool(
|
|
45
|
-
name: metadata.name,
|
|
46
|
-
description: metadata.description,
|
|
47
|
-
inputSchema: metadata.inputSchema,
|
|
48
|
-
execute
|
|
49
|
-
});
|
|
53
|
+
await navigator.modelContext.registerTool(toolDef);
|
|
50
54
|
} catch {
|
|
51
55
|
try {
|
|
52
56
|
await navigator.modelContext.unregisterTool(metadata.name);
|
|
53
|
-
await navigator.modelContext.registerTool(
|
|
54
|
-
name: metadata.name,
|
|
55
|
-
description: metadata.description,
|
|
56
|
-
inputSchema: metadata.inputSchema,
|
|
57
|
-
execute
|
|
58
|
-
});
|
|
57
|
+
await navigator.modelContext.registerTool(toolDef);
|
|
59
58
|
} catch {
|
|
60
59
|
}
|
|
61
60
|
}
|
|
@@ -340,7 +339,8 @@ function analyzeForm(form, override) {
|
|
|
340
339
|
const name = override?.name ?? inferToolName(form);
|
|
341
340
|
const description = override?.description ?? inferToolDescription(form);
|
|
342
341
|
const { schema: inputSchema, fieldElements } = buildSchema(form);
|
|
343
|
-
|
|
342
|
+
const annotations = inferAnnotations(form);
|
|
343
|
+
return { name, description, inputSchema, annotations, fieldElements };
|
|
344
344
|
}
|
|
345
345
|
function inferToolName(form) {
|
|
346
346
|
const nativeName = form.getAttribute("toolname");
|
|
@@ -437,17 +437,105 @@ function inferToolDescription(form) {
|
|
|
437
437
|
return pageTitle;
|
|
438
438
|
return "Submit form";
|
|
439
439
|
}
|
|
440
|
+
var READONLY_BUTTON_PATTERNS = /^(search|find|look|filter|browse|view|show|check|preview|get|fetch|retrieve|load)\b/i;
|
|
441
|
+
var DESTRUCTIVE_BUTTON_PATTERNS = /^(delete|remove|cancel|terminate|destroy|purge|revoke|unsubscribe|deactivate)\b/i;
|
|
442
|
+
var DESTRUCTIVE_URL_PATTERNS = /\/(delete|remove|cancel|destroy)\b/i;
|
|
443
|
+
function inferAnnotations(form) {
|
|
444
|
+
const annotations = {};
|
|
445
|
+
if (form.dataset["webmcpReadonly"] !== void 0) {
|
|
446
|
+
annotations.readOnlyHint = form.dataset["webmcpReadonly"] !== "false";
|
|
447
|
+
}
|
|
448
|
+
if (form.dataset["webmcpDestructive"] !== void 0) {
|
|
449
|
+
annotations.destructiveHint = form.dataset["webmcpDestructive"] !== "false";
|
|
450
|
+
}
|
|
451
|
+
if (form.dataset["webmcpIdempotent"] !== void 0) {
|
|
452
|
+
annotations.idempotentHint = form.dataset["webmcpIdempotent"] !== "false";
|
|
453
|
+
}
|
|
454
|
+
if (form.dataset["webmcpOpenworld"] !== void 0) {
|
|
455
|
+
annotations.openWorldHint = form.dataset["webmcpOpenworld"] !== "false";
|
|
456
|
+
}
|
|
457
|
+
if (annotations.readOnlyHint === void 0) {
|
|
458
|
+
const isGet = form.method.toLowerCase() === "get";
|
|
459
|
+
const submitText = getSubmitButtonText(form);
|
|
460
|
+
const isReadLabel = submitText ? READONLY_BUTTON_PATTERNS.test(submitText.trim()) : false;
|
|
461
|
+
if (isGet || isReadLabel)
|
|
462
|
+
annotations.readOnlyHint = true;
|
|
463
|
+
}
|
|
464
|
+
if (annotations.destructiveHint === void 0) {
|
|
465
|
+
const submitText = getSubmitButtonText(form);
|
|
466
|
+
const isDestructiveLabel = submitText ? DESTRUCTIVE_BUTTON_PATTERNS.test(submitText.trim()) : false;
|
|
467
|
+
const isDestructiveUrl = form.action ? DESTRUCTIVE_URL_PATTERNS.test(form.action) : false;
|
|
468
|
+
if (isDestructiveLabel || isDestructiveUrl)
|
|
469
|
+
annotations.destructiveHint = true;
|
|
470
|
+
}
|
|
471
|
+
if (annotations.idempotentHint === void 0) {
|
|
472
|
+
if (annotations.readOnlyHint === true || form.method.toLowerCase() === "get") {
|
|
473
|
+
annotations.idempotentHint = true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (annotations.openWorldHint === void 0) {
|
|
477
|
+
annotations.openWorldHint = annotations.readOnlyHint !== true;
|
|
478
|
+
}
|
|
479
|
+
const hasNonDefault = annotations.readOnlyHint === true || annotations.destructiveHint === true || annotations.idempotentHint === true || annotations.openWorldHint === false;
|
|
480
|
+
return hasNonDefault ? annotations : {};
|
|
481
|
+
}
|
|
482
|
+
function extractDefaultValue(control) {
|
|
483
|
+
if (control instanceof HTMLInputElement) {
|
|
484
|
+
const type = control.type.toLowerCase();
|
|
485
|
+
if (type === "checkbox")
|
|
486
|
+
return control.checked ? true : void 0;
|
|
487
|
+
if (type === "radio")
|
|
488
|
+
return void 0;
|
|
489
|
+
if (type === "number" || type === "range") {
|
|
490
|
+
return control.value !== "" ? parseFloat(control.value) : void 0;
|
|
491
|
+
}
|
|
492
|
+
return control.value !== "" ? control.value : void 0;
|
|
493
|
+
}
|
|
494
|
+
if (control instanceof HTMLTextAreaElement) {
|
|
495
|
+
return control.value !== "" ? control.value : void 0;
|
|
496
|
+
}
|
|
497
|
+
if (control instanceof HTMLSelectElement) {
|
|
498
|
+
if (control.multiple) {
|
|
499
|
+
const selected = Array.from(control.options).filter((o) => o.selected).map((o) => o.value);
|
|
500
|
+
return selected.length > 0 ? selected : void 0;
|
|
501
|
+
}
|
|
502
|
+
return control.value !== "" ? control.value : void 0;
|
|
503
|
+
}
|
|
504
|
+
return void 0;
|
|
505
|
+
}
|
|
506
|
+
function collectShadowControls(root, visited = /* @__PURE__ */ new Set()) {
|
|
507
|
+
if (visited.has(root))
|
|
508
|
+
return [];
|
|
509
|
+
visited.add(root);
|
|
510
|
+
const results = [];
|
|
511
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
512
|
+
if (el.shadowRoot) {
|
|
513
|
+
results.push(
|
|
514
|
+
...Array.from(
|
|
515
|
+
el.shadowRoot.querySelectorAll(
|
|
516
|
+
"input, textarea, select"
|
|
517
|
+
)
|
|
518
|
+
),
|
|
519
|
+
...collectShadowControls(el.shadowRoot, visited)
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return results;
|
|
524
|
+
}
|
|
440
525
|
function buildSchema(form) {
|
|
441
526
|
const properties = {};
|
|
442
527
|
const required = [];
|
|
443
528
|
const fieldElements = /* @__PURE__ */ new Map();
|
|
444
529
|
const processedRadioGroups = /* @__PURE__ */ new Set();
|
|
445
530
|
const processedCheckboxGroups = /* @__PURE__ */ new Set();
|
|
446
|
-
const controls =
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
531
|
+
const controls = [
|
|
532
|
+
...Array.from(
|
|
533
|
+
form.querySelectorAll(
|
|
534
|
+
"input, textarea, select"
|
|
535
|
+
)
|
|
536
|
+
),
|
|
537
|
+
...collectShadowControls(form)
|
|
538
|
+
];
|
|
451
539
|
for (const control of controls) {
|
|
452
540
|
const name = control.name;
|
|
453
541
|
const fieldKey = name || resolveNativeControlFallbackKey(control);
|
|
@@ -472,11 +560,19 @@ function buildSchema(form) {
|
|
|
472
560
|
const desc = inferFieldDescription(control);
|
|
473
561
|
if (desc)
|
|
474
562
|
schemaProp.description = desc;
|
|
563
|
+
const defaultVal = extractDefaultValue(control);
|
|
564
|
+
if (defaultVal !== void 0)
|
|
565
|
+
schemaProp.default = defaultVal;
|
|
475
566
|
if (control instanceof HTMLInputElement && control.type === "radio") {
|
|
476
567
|
schemaProp.enum = collectRadioEnum(form, fieldKey);
|
|
477
568
|
const radioOneOf = collectRadioOneOf(form, fieldKey);
|
|
478
569
|
if (radioOneOf.length > 0)
|
|
479
570
|
schemaProp.oneOf = radioOneOf;
|
|
571
|
+
const checkedRadio = form.querySelector(
|
|
572
|
+
`input[type="radio"][name="${CSS.escape(fieldKey)}"]:checked`
|
|
573
|
+
);
|
|
574
|
+
if (checkedRadio?.value)
|
|
575
|
+
schemaProp.default = checkedRadio.value;
|
|
480
576
|
}
|
|
481
577
|
if (control instanceof HTMLInputElement && control.type === "checkbox") {
|
|
482
578
|
const checkboxValues = collectCheckboxEnum(form, fieldKey);
|
|
@@ -488,6 +584,13 @@ function buildSchema(form) {
|
|
|
488
584
|
};
|
|
489
585
|
if (schemaProp.description)
|
|
490
586
|
arrayProp.description = schemaProp.description;
|
|
587
|
+
const checkedBoxes = Array.from(
|
|
588
|
+
form.querySelectorAll(
|
|
589
|
+
`input[type="checkbox"][name="${CSS.escape(fieldKey)}"]:checked`
|
|
590
|
+
)
|
|
591
|
+
).map((b) => b.value);
|
|
592
|
+
if (checkedBoxes.length > 0)
|
|
593
|
+
arrayProp.default = checkedBoxes;
|
|
491
594
|
properties[fieldKey] = arrayProp;
|
|
492
595
|
if (control.required)
|
|
493
596
|
required.push(fieldKey);
|
|
@@ -857,6 +960,9 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
857
960
|
return async (params) => {
|
|
858
961
|
pendingFillWarnings.set(form, []);
|
|
859
962
|
fillFormFields(form, params);
|
|
963
|
+
const missingNow = getMissingRequired(metadata, params);
|
|
964
|
+
if (missingNow.length > 0)
|
|
965
|
+
pendingWarnings.set(form, missingNow);
|
|
860
966
|
window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
|
|
861
967
|
return new Promise((resolve, reject) => {
|
|
862
968
|
pendingExecutions.set(form, { resolve, reject });
|
|
@@ -883,9 +989,10 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
883
989
|
attachSubmitInterceptor(submitForm, toolName);
|
|
884
990
|
}
|
|
885
991
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
pendingWarnings.
|
|
992
|
+
if (submitForm !== form && pendingWarnings.has(form)) {
|
|
993
|
+
pendingWarnings.set(submitForm, pendingWarnings.get(form));
|
|
994
|
+
pendingWarnings.delete(form);
|
|
995
|
+
}
|
|
889
996
|
submitForm.requestSubmit();
|
|
890
997
|
} catch (err) {
|
|
891
998
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -907,17 +1014,37 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
907
1014
|
pendingExecutions.delete(form);
|
|
908
1015
|
const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
|
|
909
1016
|
lastFilledSnapshot.delete(form);
|
|
910
|
-
const
|
|
1017
|
+
const missingRequired = pendingWarnings.get(form) ?? [];
|
|
911
1018
|
pendingWarnings.delete(form);
|
|
912
1019
|
const fillWarnings = pendingFillWarnings.get(form) ?? [];
|
|
913
1020
|
pendingFillWarnings.delete(form);
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
1021
|
+
const skippedFields = fillWarnings.filter((w) => w.type === "not_filled").map((w) => w.field);
|
|
1022
|
+
const structured = {
|
|
1023
|
+
status: missingRequired.length > 0 || skippedFields.length > 0 ? "partial" : "success",
|
|
1024
|
+
filled_fields: formData,
|
|
1025
|
+
skipped_fields: skippedFields,
|
|
1026
|
+
missing_required: missingRequired,
|
|
1027
|
+
warnings: [
|
|
1028
|
+
...missingRequired.map((f) => ({
|
|
1029
|
+
field: f,
|
|
1030
|
+
type: "missing_required",
|
|
1031
|
+
message: `required field "${f}" was not provided`
|
|
1032
|
+
})),
|
|
1033
|
+
...fillWarnings
|
|
1034
|
+
]
|
|
1035
|
+
};
|
|
1036
|
+
const allWarnMessages = [
|
|
1037
|
+
...missingRequired.length ? [`required fields were not filled: ${missingRequired.join(", ")}`] : [],
|
|
1038
|
+
...fillWarnings.map((w) => w.message)
|
|
917
1039
|
];
|
|
918
|
-
const warningText =
|
|
1040
|
+
const warningText = allWarnMessages.length ? ` Note: ${allWarnMessages.join("; ")}.` : "";
|
|
919
1041
|
const text = `Form submitted. Fields: ${JSON.stringify(formData)}${warningText}`;
|
|
920
|
-
const result = {
|
|
1042
|
+
const result = {
|
|
1043
|
+
content: [
|
|
1044
|
+
{ type: "text", text },
|
|
1045
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
1046
|
+
]
|
|
1047
|
+
};
|
|
921
1048
|
if (e.agentInvoked && typeof e.respondWith === "function") {
|
|
922
1049
|
e.preventDefault();
|
|
923
1050
|
e.respondWith(Promise.resolve(result));
|
|
@@ -1051,16 +1178,26 @@ function fillInput(input, form, key, value) {
|
|
|
1051
1178
|
const raw = String(value ?? "");
|
|
1052
1179
|
const num = Number(raw);
|
|
1053
1180
|
if (raw === "" || isNaN(num)) {
|
|
1054
|
-
pendingFillWarnings.get(form)?.push(
|
|
1181
|
+
pendingFillWarnings.get(form)?.push({
|
|
1182
|
+
field: key,
|
|
1183
|
+
type: "type_mismatch",
|
|
1184
|
+
message: `"${key}" expects a number, got: ${JSON.stringify(value)}`,
|
|
1185
|
+
original: value
|
|
1186
|
+
});
|
|
1055
1187
|
return;
|
|
1056
1188
|
}
|
|
1057
1189
|
const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
|
|
1058
1190
|
const max = input.max !== "" ? parseFloat(input.max) : Infinity;
|
|
1059
1191
|
if (num < min || num > max) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1192
|
+
const clamped = Math.min(Math.max(num, min), max);
|
|
1193
|
+
pendingFillWarnings.get(form)?.push({
|
|
1194
|
+
field: key,
|
|
1195
|
+
type: "clamped",
|
|
1196
|
+
message: `"${key}" value ${num} is outside allowed range [${input.min || "?"}, ${input.max || "?"}], clamped to ${clamped}`,
|
|
1197
|
+
original: num,
|
|
1198
|
+
actual: clamped
|
|
1199
|
+
});
|
|
1200
|
+
input.value = String(clamped);
|
|
1064
1201
|
} else {
|
|
1065
1202
|
input.value = String(num);
|
|
1066
1203
|
}
|