auto-webmcp 0.3.15 → 0.3.16
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.map +1 -1
- package/dist/auto-webmcp.cjs.js +209 -115
- package/dist/auto-webmcp.cjs.js.map +4 -4
- package/dist/auto-webmcp.esm.js +209 -122
- package/dist/auto-webmcp.esm.js.map +4 -4
- package/dist/auto-webmcp.iife.js +1 -1
- package/dist/auto-webmcp.iife.js.map +4 -4
- package/dist/discovery.d.ts.map +1 -1
- package/dist/interceptor.d.ts +1 -1
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/registry.d.ts +7 -3
- package/dist/registry.d.ts.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/auto-webmcp.esm.js
CHANGED
|
@@ -1,83 +1,3 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __esm = (fn, res) => function __init() {
|
|
4
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
-
};
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// src/registry.ts
|
|
12
|
-
var registry_exports = {};
|
|
13
|
-
__export(registry_exports, {
|
|
14
|
-
getAllRegisteredTools: () => getAllRegisteredTools,
|
|
15
|
-
getRegisteredToolName: () => getRegisteredToolName,
|
|
16
|
-
isWebMCPSupported: () => isWebMCPSupported,
|
|
17
|
-
registerFormTool: () => registerFormTool,
|
|
18
|
-
unregisterAll: () => unregisterAll,
|
|
19
|
-
unregisterFormTool: () => unregisterFormTool
|
|
20
|
-
});
|
|
21
|
-
function isWebMCPSupported() {
|
|
22
|
-
return typeof navigator !== "undefined" && typeof navigator.modelContext !== "undefined";
|
|
23
|
-
}
|
|
24
|
-
async function registerFormTool(form, metadata, execute) {
|
|
25
|
-
if (!isWebMCPSupported())
|
|
26
|
-
return;
|
|
27
|
-
const existing = registeredTools.get(form);
|
|
28
|
-
if (existing) {
|
|
29
|
-
await unregisterFormTool(form);
|
|
30
|
-
}
|
|
31
|
-
const toolDef = {
|
|
32
|
-
name: metadata.name,
|
|
33
|
-
description: metadata.description,
|
|
34
|
-
inputSchema: metadata.inputSchema,
|
|
35
|
-
execute
|
|
36
|
-
};
|
|
37
|
-
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
38
|
-
toolDef.annotations = metadata.annotations;
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
await navigator.modelContext.registerTool(toolDef);
|
|
42
|
-
} catch {
|
|
43
|
-
try {
|
|
44
|
-
await navigator.modelContext.unregisterTool(metadata.name);
|
|
45
|
-
await navigator.modelContext.registerTool(toolDef);
|
|
46
|
-
} catch {
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
registeredTools.set(form, metadata.name);
|
|
50
|
-
}
|
|
51
|
-
async function unregisterFormTool(form) {
|
|
52
|
-
if (!isWebMCPSupported())
|
|
53
|
-
return;
|
|
54
|
-
const name = registeredTools.get(form);
|
|
55
|
-
if (!name)
|
|
56
|
-
return;
|
|
57
|
-
try {
|
|
58
|
-
await navigator.modelContext.unregisterTool(name);
|
|
59
|
-
} catch {
|
|
60
|
-
}
|
|
61
|
-
registeredTools.delete(form);
|
|
62
|
-
}
|
|
63
|
-
function getRegisteredToolName(form) {
|
|
64
|
-
return registeredTools.get(form);
|
|
65
|
-
}
|
|
66
|
-
function getAllRegisteredTools() {
|
|
67
|
-
return Array.from(registeredTools.entries()).map(([form, name]) => ({ form, name }));
|
|
68
|
-
}
|
|
69
|
-
async function unregisterAll() {
|
|
70
|
-
const entries = Array.from(registeredTools.entries());
|
|
71
|
-
await Promise.all(entries.map(([form]) => unregisterFormTool(form)));
|
|
72
|
-
}
|
|
73
|
-
var registeredTools;
|
|
74
|
-
var init_registry = __esm({
|
|
75
|
-
"src/registry.ts"() {
|
|
76
|
-
"use strict";
|
|
77
|
-
registeredTools = /* @__PURE__ */ new Map();
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
1
|
// src/config.ts
|
|
82
2
|
function resolveConfig(userConfig) {
|
|
83
3
|
return {
|
|
@@ -231,19 +151,19 @@ function mapSelectElement(select) {
|
|
|
231
151
|
return { type: "string", enum: enumValues, oneOf };
|
|
232
152
|
}
|
|
233
153
|
function collectCheckboxEnum(form, name) {
|
|
234
|
-
return Array.from(
|
|
235
|
-
|
|
154
|
+
return Array.from(form.elements).filter(
|
|
155
|
+
(el) => el instanceof HTMLInputElement && el.type === "checkbox" && el.name === name
|
|
236
156
|
).map((cb) => cb.value).filter((v) => v !== "" && v !== "on");
|
|
237
157
|
}
|
|
238
158
|
function collectRadioEnum(form, name) {
|
|
239
|
-
const radios = Array.from(
|
|
240
|
-
|
|
159
|
+
const radios = Array.from(form.elements).filter(
|
|
160
|
+
(el) => el instanceof HTMLInputElement && el.type === "radio" && el.name === name
|
|
241
161
|
);
|
|
242
162
|
return radios.map((r) => r.value).filter((v) => v !== "");
|
|
243
163
|
}
|
|
244
164
|
function collectRadioOneOf(form, name) {
|
|
245
|
-
const radios = Array.from(
|
|
246
|
-
|
|
165
|
+
const radios = Array.from(form.elements).filter(
|
|
166
|
+
(el) => el instanceof HTMLInputElement && el.type === "radio" && el.name === name
|
|
247
167
|
).filter((r) => r.value !== "");
|
|
248
168
|
return radios.map((r) => {
|
|
249
169
|
const title = getRadioLabelText(r);
|
|
@@ -503,20 +423,26 @@ function collectShadowControls(root, visited = /* @__PURE__ */ new Set()) {
|
|
|
503
423
|
}
|
|
504
424
|
return results;
|
|
505
425
|
}
|
|
426
|
+
function collectFormAssociatedControls(form) {
|
|
427
|
+
const controls = Array.from(form.elements).filter(
|
|
428
|
+
(el) => el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement
|
|
429
|
+
);
|
|
430
|
+
const seen = new Set(controls);
|
|
431
|
+
for (const shadowControl of collectShadowControls(form)) {
|
|
432
|
+
if (!seen.has(shadowControl)) {
|
|
433
|
+
controls.push(shadowControl);
|
|
434
|
+
seen.add(shadowControl);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return controls;
|
|
438
|
+
}
|
|
506
439
|
function buildSchema(form) {
|
|
507
440
|
const properties = {};
|
|
508
441
|
const required = [];
|
|
509
442
|
const fieldElements = /* @__PURE__ */ new Map();
|
|
510
443
|
const processedRadioGroups = /* @__PURE__ */ new Set();
|
|
511
444
|
const processedCheckboxGroups = /* @__PURE__ */ new Set();
|
|
512
|
-
const controls =
|
|
513
|
-
...Array.from(
|
|
514
|
-
form.querySelectorAll(
|
|
515
|
-
"input, textarea, select"
|
|
516
|
-
)
|
|
517
|
-
),
|
|
518
|
-
...collectShadowControls(form)
|
|
519
|
-
];
|
|
445
|
+
const controls = collectFormAssociatedControls(form);
|
|
520
446
|
for (const control of controls) {
|
|
521
447
|
const name = control.name;
|
|
522
448
|
const fieldKey = name || resolveNativeControlFallbackKey(control);
|
|
@@ -549,8 +475,8 @@ function buildSchema(form) {
|
|
|
549
475
|
const radioOneOf = collectRadioOneOf(form, fieldKey);
|
|
550
476
|
if (radioOneOf.length > 0)
|
|
551
477
|
schemaProp.oneOf = radioOneOf;
|
|
552
|
-
const checkedRadio = form.
|
|
553
|
-
|
|
478
|
+
const checkedRadio = Array.from(form.elements).find(
|
|
479
|
+
(el) => el instanceof HTMLInputElement && el.type === "radio" && el.name === fieldKey && el.checked
|
|
554
480
|
);
|
|
555
481
|
if (checkedRadio?.value)
|
|
556
482
|
schemaProp.default = checkedRadio.value;
|
|
@@ -565,10 +491,8 @@ function buildSchema(form) {
|
|
|
565
491
|
};
|
|
566
492
|
if (schemaProp.description)
|
|
567
493
|
arrayProp.description = schemaProp.description;
|
|
568
|
-
const checkedBoxes = Array.from(
|
|
569
|
-
|
|
570
|
-
`input[type="checkbox"][name="${CSS.escape(fieldKey)}"]:checked`
|
|
571
|
-
)
|
|
494
|
+
const checkedBoxes = Array.from(form.elements).filter(
|
|
495
|
+
(el) => el instanceof HTMLInputElement && el.type === "checkbox" && el.name === fieldKey && el.checked
|
|
572
496
|
).map((b) => b.value);
|
|
573
497
|
if (checkedBoxes.length > 0)
|
|
574
498
|
arrayProp.default = checkedBoxes;
|
|
@@ -1038,8 +962,68 @@ function buildSchemaFromInputs(inputs) {
|
|
|
1038
962
|
return { schema: { "$schema": "https://json-schema.org/draft/2020-12/schema", type: "object", properties, required }, fieldElements };
|
|
1039
963
|
}
|
|
1040
964
|
|
|
1041
|
-
// src/
|
|
1042
|
-
|
|
965
|
+
// src/registry.ts
|
|
966
|
+
var registeredTools = /* @__PURE__ */ new Map();
|
|
967
|
+
var registrationControllers = /* @__PURE__ */ new Map();
|
|
968
|
+
function isWebMCPSupported() {
|
|
969
|
+
return typeof navigator !== "undefined" && typeof navigator.modelContext !== "undefined";
|
|
970
|
+
}
|
|
971
|
+
async function registerFormTool(form, metadata, execute) {
|
|
972
|
+
if (!isWebMCPSupported())
|
|
973
|
+
return;
|
|
974
|
+
const existing = registeredTools.get(form);
|
|
975
|
+
if (existing) {
|
|
976
|
+
await unregisterFormTool(form);
|
|
977
|
+
}
|
|
978
|
+
const toolDef = {
|
|
979
|
+
name: metadata.name,
|
|
980
|
+
description: metadata.description,
|
|
981
|
+
inputSchema: metadata.inputSchema,
|
|
982
|
+
execute
|
|
983
|
+
};
|
|
984
|
+
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
985
|
+
toolDef.annotations = metadata.annotations;
|
|
986
|
+
}
|
|
987
|
+
const controller = new AbortController();
|
|
988
|
+
registrationControllers.set(form, controller);
|
|
989
|
+
try {
|
|
990
|
+
await navigator.modelContext.registerTool(toolDef, { signal: controller.signal });
|
|
991
|
+
} catch {
|
|
992
|
+
try {
|
|
993
|
+
await navigator.modelContext.unregisterTool?.(metadata.name);
|
|
994
|
+
await navigator.modelContext.registerTool(toolDef, { signal: controller.signal });
|
|
995
|
+
} catch {
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
registeredTools.set(form, metadata.name);
|
|
999
|
+
}
|
|
1000
|
+
async function unregisterFormTool(form) {
|
|
1001
|
+
if (!isWebMCPSupported())
|
|
1002
|
+
return;
|
|
1003
|
+
const name = registeredTools.get(form);
|
|
1004
|
+
if (!name)
|
|
1005
|
+
return;
|
|
1006
|
+
const controller = registrationControllers.get(form);
|
|
1007
|
+
if (controller) {
|
|
1008
|
+
controller.abort();
|
|
1009
|
+
registrationControllers.delete(form);
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
await navigator.modelContext.unregisterTool?.(name);
|
|
1013
|
+
} catch {
|
|
1014
|
+
}
|
|
1015
|
+
registeredTools.delete(form);
|
|
1016
|
+
}
|
|
1017
|
+
function getRegisteredToolName(form) {
|
|
1018
|
+
return registeredTools.get(form);
|
|
1019
|
+
}
|
|
1020
|
+
function getAllRegisteredTools() {
|
|
1021
|
+
return Array.from(registeredTools.entries()).map(([form, name]) => ({ form, name }));
|
|
1022
|
+
}
|
|
1023
|
+
async function unregisterAll() {
|
|
1024
|
+
const entries = Array.from(registeredTools.entries());
|
|
1025
|
+
await Promise.all(entries.map(([form]) => unregisterFormTool(form)));
|
|
1026
|
+
}
|
|
1043
1027
|
|
|
1044
1028
|
// src/interceptor.ts
|
|
1045
1029
|
var pendingExecutions = /* @__PURE__ */ new WeakMap();
|
|
@@ -1056,7 +1040,20 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1056
1040
|
formFieldElements.set(form, metadata.fieldElements);
|
|
1057
1041
|
}
|
|
1058
1042
|
attachSubmitInterceptor(form, toolName);
|
|
1059
|
-
return async (params) => {
|
|
1043
|
+
return async (params, client) => {
|
|
1044
|
+
const modelContextClient = client;
|
|
1045
|
+
if (config.autoSubmit && metadata?.annotations?.destructiveHint === true && typeof modelContextClient?.requestUserInteraction === "function") {
|
|
1046
|
+
const approved = await modelContextClient.requestUserInteraction(async () => {
|
|
1047
|
+
return new Promise((resolve) => {
|
|
1048
|
+
const ok = window.confirm(`Agent requested a destructive action via "${toolName}". Continue?`);
|
|
1049
|
+
resolve(ok);
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
if (!approved) {
|
|
1053
|
+
window.dispatchEvent(new CustomEvent("toolcancel", { detail: { toolName } }));
|
|
1054
|
+
return { content: [{ type: "text", text: `Cancelled "${toolName}" by user.` }] };
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1060
1057
|
pendingFillWarnings.set(form, []);
|
|
1061
1058
|
pendingWarnings.delete(form);
|
|
1062
1059
|
fillFormFields(form, params);
|
|
@@ -1193,7 +1190,23 @@ function findInShadowRoots(root, selector) {
|
|
|
1193
1190
|
}
|
|
1194
1191
|
return null;
|
|
1195
1192
|
}
|
|
1193
|
+
function getAssociatedInputsByName(form, type, name) {
|
|
1194
|
+
return Array.from(form.elements).filter(
|
|
1195
|
+
(el) => el instanceof HTMLInputElement && el.type === type && el.name === name
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1196
1198
|
function findNativeField(form, key) {
|
|
1199
|
+
const named = form.elements.namedItem(key);
|
|
1200
|
+
if (typeof named === "object" && named !== null && (named instanceof HTMLInputElement || named instanceof HTMLTextAreaElement || named instanceof HTMLSelectElement)) {
|
|
1201
|
+
return named;
|
|
1202
|
+
}
|
|
1203
|
+
if (named instanceof RadioNodeList) {
|
|
1204
|
+
const first = named[0];
|
|
1205
|
+
const firstObj = first;
|
|
1206
|
+
if (firstObj instanceof HTMLInputElement || firstObj instanceof HTMLTextAreaElement || firstObj instanceof HTMLSelectElement) {
|
|
1207
|
+
return firstObj;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1197
1210
|
const esc = CSS.escape(key);
|
|
1198
1211
|
const light = form.querySelector(`[name="${esc}"]`) ?? form.querySelector(
|
|
1199
1212
|
`input#${esc}, textarea#${esc}, select#${esc}`
|
|
@@ -1213,10 +1226,7 @@ function fillFormFields(form, params) {
|
|
|
1213
1226
|
fillInput(input, form, key, value);
|
|
1214
1227
|
if (input.type === "checkbox") {
|
|
1215
1228
|
if (Array.isArray(value)) {
|
|
1216
|
-
|
|
1217
|
-
snapshot[key] = Array.from(
|
|
1218
|
-
form.querySelectorAll(`input[type="checkbox"][name="${esc}"]`)
|
|
1219
|
-
).filter((b) => b.checked).map((b) => b.value);
|
|
1229
|
+
snapshot[key] = getAssociatedInputsByName(form, "checkbox", key).filter((b) => b.checked).map((b) => b.value);
|
|
1220
1230
|
} else {
|
|
1221
1231
|
snapshot[key] = input.checked;
|
|
1222
1232
|
}
|
|
@@ -1265,8 +1275,7 @@ function fillInput(input, form, key, value) {
|
|
|
1265
1275
|
const type = input.type.toLowerCase();
|
|
1266
1276
|
if (type === "checkbox") {
|
|
1267
1277
|
if (Array.isArray(value)) {
|
|
1268
|
-
const
|
|
1269
|
-
const allBoxes = form.querySelectorAll(`input[type="checkbox"][name="${esc}"]`);
|
|
1278
|
+
const allBoxes = getAssociatedInputsByName(form, "checkbox", key);
|
|
1270
1279
|
for (const box of allBoxes) {
|
|
1271
1280
|
setReactChecked(box, value.map(String).includes(box.value));
|
|
1272
1281
|
}
|
|
@@ -1307,10 +1316,7 @@ function fillInput(input, form, key, value) {
|
|
|
1307
1316
|
return;
|
|
1308
1317
|
}
|
|
1309
1318
|
if (type === "radio") {
|
|
1310
|
-
const
|
|
1311
|
-
const radios = form.querySelectorAll(
|
|
1312
|
-
`input[type="radio"][name="${esc}"]`
|
|
1313
|
-
);
|
|
1319
|
+
const radios = getAssociatedInputsByName(form, "radio", key);
|
|
1314
1320
|
for (const radio of radios) {
|
|
1315
1321
|
if (radio.value === String(value)) {
|
|
1316
1322
|
if (_checkedSetter) {
|
|
@@ -1633,9 +1639,35 @@ function isExcluded(form, config) {
|
|
|
1633
1639
|
}
|
|
1634
1640
|
return false;
|
|
1635
1641
|
}
|
|
1642
|
+
function withNumericSuffix(baseName, n) {
|
|
1643
|
+
const suffix = `_${n}`;
|
|
1644
|
+
return `${baseName.slice(0, Math.max(1, 64 - suffix.length))}${suffix}`;
|
|
1645
|
+
}
|
|
1646
|
+
function getUsedToolNames(excludeForm) {
|
|
1647
|
+
const names = new Set(registeredOrphanToolNames);
|
|
1648
|
+
for (const { form, name } of getAllRegisteredTools()) {
|
|
1649
|
+
if (excludeForm && form === excludeForm)
|
|
1650
|
+
continue;
|
|
1651
|
+
names.add(name);
|
|
1652
|
+
}
|
|
1653
|
+
return names;
|
|
1654
|
+
}
|
|
1655
|
+
function ensureUniqueToolName(baseName, excludeForm) {
|
|
1656
|
+
const used = getUsedToolNames(excludeForm);
|
|
1657
|
+
if (!used.has(baseName))
|
|
1658
|
+
return baseName;
|
|
1659
|
+
let i = 2;
|
|
1660
|
+
let candidate = withNumericSuffix(baseName, i);
|
|
1661
|
+
while (used.has(candidate)) {
|
|
1662
|
+
i++;
|
|
1663
|
+
candidate = withNumericSuffix(baseName, i);
|
|
1664
|
+
}
|
|
1665
|
+
return candidate;
|
|
1666
|
+
}
|
|
1636
1667
|
async function registerForm(form, config) {
|
|
1637
1668
|
if (isExcluded(form, config))
|
|
1638
1669
|
return;
|
|
1670
|
+
const previousName = getRegisteredToolName(form);
|
|
1639
1671
|
let override;
|
|
1640
1672
|
for (const [selector, ovr] of Object.entries(config.overrides)) {
|
|
1641
1673
|
try {
|
|
@@ -1647,6 +1679,11 @@ async function registerForm(form, config) {
|
|
|
1647
1679
|
}
|
|
1648
1680
|
}
|
|
1649
1681
|
const metadata = analyzeForm(form, override);
|
|
1682
|
+
const resolvedName = ensureUniqueToolName(metadata.name, form);
|
|
1683
|
+
if (resolvedName !== metadata.name && config.debug) {
|
|
1684
|
+
console.warn(`[auto-webmcp] tool name collision: "${metadata.name}" renamed to "${resolvedName}"`);
|
|
1685
|
+
}
|
|
1686
|
+
metadata.name = resolvedName;
|
|
1650
1687
|
if (config.debug) {
|
|
1651
1688
|
warnToolQuality(metadata.name, metadata.description);
|
|
1652
1689
|
}
|
|
@@ -1658,6 +1695,9 @@ async function registerForm(form, config) {
|
|
|
1658
1695
|
'[type="submit"], button[data-variant="primary"], button:not([type])'
|
|
1659
1696
|
) ?? null;
|
|
1660
1697
|
const pendingBtns = window["__pendingSubmitBtns"] ??= {};
|
|
1698
|
+
if (previousName && previousName !== metadata.name) {
|
|
1699
|
+
delete pendingBtns[previousName];
|
|
1700
|
+
}
|
|
1661
1701
|
pendingBtns[metadata.name] = formSubmitBtn;
|
|
1662
1702
|
if (config.debug) {
|
|
1663
1703
|
console.log(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
|
|
@@ -1665,12 +1705,14 @@ async function registerForm(form, config) {
|
|
|
1665
1705
|
emit("form:registered", form, metadata.name);
|
|
1666
1706
|
}
|
|
1667
1707
|
async function unregisterForm(form, config) {
|
|
1668
|
-
const
|
|
1669
|
-
const name = getRegisteredToolName2(form);
|
|
1708
|
+
const name = getRegisteredToolName(form);
|
|
1670
1709
|
if (!name)
|
|
1671
1710
|
return;
|
|
1672
1711
|
await unregisterFormTool(form);
|
|
1673
1712
|
registeredForms.delete(form);
|
|
1713
|
+
const pendingBtns = window["__pendingSubmitBtns"];
|
|
1714
|
+
if (pendingBtns)
|
|
1715
|
+
delete pendingBtns[name];
|
|
1674
1716
|
if (config.debug) {
|
|
1675
1717
|
console.log(`[auto-webmcp] Unregistered: ${name}`);
|
|
1676
1718
|
}
|
|
@@ -1719,11 +1761,47 @@ function scheduleReAnalysis(form, config) {
|
|
|
1719
1761
|
}, RE_ANALYSIS_DEBOUNCE_MS)
|
|
1720
1762
|
);
|
|
1721
1763
|
}
|
|
1764
|
+
function scheduleFormReAnalysisById(formId, config) {
|
|
1765
|
+
const owner = document.getElementById(formId);
|
|
1766
|
+
if (owner instanceof HTMLFormElement && registeredForms.has(owner)) {
|
|
1767
|
+
scheduleReAnalysis(owner, config);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function resolveOwnerForm(el) {
|
|
1771
|
+
const closest = el.closest("form");
|
|
1772
|
+
if (closest instanceof HTMLFormElement)
|
|
1773
|
+
return closest;
|
|
1774
|
+
const explicitOwner = el.form;
|
|
1775
|
+
if (explicitOwner instanceof HTMLFormElement)
|
|
1776
|
+
return explicitOwner;
|
|
1777
|
+
const formId = el.getAttribute("form");
|
|
1778
|
+
if (formId) {
|
|
1779
|
+
const byId = document.getElementById(formId);
|
|
1780
|
+
if (byId instanceof HTMLFormElement)
|
|
1781
|
+
return byId;
|
|
1782
|
+
}
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1722
1785
|
function startObserver(config) {
|
|
1723
1786
|
if (observer)
|
|
1724
1787
|
return;
|
|
1725
1788
|
observer = new MutationObserver((mutations) => {
|
|
1726
1789
|
for (const mutation of mutations) {
|
|
1790
|
+
if (mutation.type === "attributes" && mutation.target instanceof Element) {
|
|
1791
|
+
const target = mutation.target;
|
|
1792
|
+
const ownerForm = resolveOwnerForm(target);
|
|
1793
|
+
if (ownerForm && registeredForms.has(ownerForm)) {
|
|
1794
|
+
scheduleReAnalysis(ownerForm, config);
|
|
1795
|
+
} else if (target instanceof HTMLFormElement) {
|
|
1796
|
+
void registerForm(target, config);
|
|
1797
|
+
} else if (isInterestingNode(target) && !target.closest("form")) {
|
|
1798
|
+
scheduleOrphanRescan(config);
|
|
1799
|
+
}
|
|
1800
|
+
if (mutation.attributeName === "form" && mutation.oldValue) {
|
|
1801
|
+
scheduleFormReAnalysisById(mutation.oldValue, config);
|
|
1802
|
+
}
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1727
1805
|
for (const node of mutation.addedNodes) {
|
|
1728
1806
|
if (!(node instanceof Element))
|
|
1729
1807
|
continue;
|
|
@@ -1752,7 +1830,12 @@ function startObserver(config) {
|
|
|
1752
1830
|
}
|
|
1753
1831
|
}
|
|
1754
1832
|
});
|
|
1755
|
-
observer.observe(document.body, {
|
|
1833
|
+
observer.observe(document.body, {
|
|
1834
|
+
childList: true,
|
|
1835
|
+
subtree: true,
|
|
1836
|
+
attributes: true,
|
|
1837
|
+
attributeOldValue: true
|
|
1838
|
+
});
|
|
1756
1839
|
}
|
|
1757
1840
|
function listenForRouteChanges(config) {
|
|
1758
1841
|
window.addEventListener("hashchange", () => scanForms(config));
|
|
@@ -1896,6 +1979,15 @@ async function scanOrphanInputs(config) {
|
|
|
1896
1979
|
}
|
|
1897
1980
|
console.log(`[auto-webmcp] orphan: submit button for group:`, submitBtn ? `"${submitBtn.textContent?.trim()}" disabled=${submitBtn.disabled}` : "none");
|
|
1898
1981
|
const metadata = analyzeOrphanInputGroup(container, inputs, submitBtn);
|
|
1982
|
+
if (registeredOrphanToolNames.has(metadata.name)) {
|
|
1983
|
+
console.log(`[auto-webmcp] orphan: "${metadata.name}" already registered, skipping`);
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
1986
|
+
const orphanName = ensureUniqueToolName(metadata.name);
|
|
1987
|
+
if (orphanName !== metadata.name && config.debug) {
|
|
1988
|
+
console.warn(`[auto-webmcp] orphan tool name collision: "${metadata.name}" renamed to "${orphanName}"`);
|
|
1989
|
+
}
|
|
1990
|
+
metadata.name = orphanName;
|
|
1899
1991
|
console.log(`[auto-webmcp] orphan: tool="${metadata.name}" schema keys:`, Object.keys(metadata.inputSchema.properties));
|
|
1900
1992
|
const inputPairs = [];
|
|
1901
1993
|
const schemaProps = metadata.inputSchema.properties;
|
|
@@ -1916,7 +2008,7 @@ async function scanOrphanInputs(config) {
|
|
|
1916
2008
|
continue;
|
|
1917
2009
|
}
|
|
1918
2010
|
const toolName = metadata.name;
|
|
1919
|
-
const execute = async (params) => {
|
|
2011
|
+
const execute = async (params, _client) => {
|
|
1920
2012
|
console.log(`[auto-webmcp] orphan execute: tool="${toolName}" params=`, params);
|
|
1921
2013
|
console.log(`[auto-webmcp] orphan execute: inputPairs=`, inputPairs.map((p) => p.key));
|
|
1922
2014
|
for (const { key, el } of inputPairs) {
|
|
@@ -1985,10 +2077,6 @@ async function scanOrphanInputs(config) {
|
|
|
1985
2077
|
return { content: [{ type: "text", text: "Fields filled and form submitted." }] };
|
|
1986
2078
|
};
|
|
1987
2079
|
try {
|
|
1988
|
-
if (registeredOrphanToolNames.has(metadata.name)) {
|
|
1989
|
-
console.log(`[auto-webmcp] orphan: "${metadata.name}" already registered, skipping`);
|
|
1990
|
-
continue;
|
|
1991
|
-
}
|
|
1992
2080
|
const toolDef = {
|
|
1993
2081
|
name: metadata.name,
|
|
1994
2082
|
description: metadata.description,
|
|
@@ -2039,7 +2127,6 @@ function stopDiscovery() {
|
|
|
2039
2127
|
}
|
|
2040
2128
|
|
|
2041
2129
|
// src/index.ts
|
|
2042
|
-
init_registry();
|
|
2043
2130
|
async function autoWebMCP(config) {
|
|
2044
2131
|
const resolved = resolveConfig(config);
|
|
2045
2132
|
if (resolved.debug) {
|