auto-webmcp 0.3.14 → 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 +391 -135
- package/dist/auto-webmcp.cjs.js.map +4 -4
- package/dist/auto-webmcp.esm.js +391 -142
- 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 +11 -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.cjs.js
CHANGED
|
@@ -3,9 +3,6 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __esm = (fn, res) => function __init() {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
};
|
|
9
6
|
var __export = (target, all) => {
|
|
10
7
|
for (var name in all)
|
|
11
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -20,76 +17,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
17
|
};
|
|
21
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
19
|
|
|
23
|
-
// src/registry.ts
|
|
24
|
-
var registry_exports = {};
|
|
25
|
-
__export(registry_exports, {
|
|
26
|
-
getAllRegisteredTools: () => getAllRegisteredTools,
|
|
27
|
-
getRegisteredToolName: () => getRegisteredToolName,
|
|
28
|
-
isWebMCPSupported: () => isWebMCPSupported,
|
|
29
|
-
registerFormTool: () => registerFormTool,
|
|
30
|
-
unregisterAll: () => unregisterAll,
|
|
31
|
-
unregisterFormTool: () => unregisterFormTool
|
|
32
|
-
});
|
|
33
|
-
function isWebMCPSupported() {
|
|
34
|
-
return typeof navigator !== "undefined" && typeof navigator.modelContext !== "undefined";
|
|
35
|
-
}
|
|
36
|
-
async function registerFormTool(form, metadata, execute) {
|
|
37
|
-
if (!isWebMCPSupported())
|
|
38
|
-
return;
|
|
39
|
-
const existing = registeredTools.get(form);
|
|
40
|
-
if (existing) {
|
|
41
|
-
await unregisterFormTool(form);
|
|
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
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
await navigator.modelContext.registerTool(toolDef);
|
|
54
|
-
} catch {
|
|
55
|
-
try {
|
|
56
|
-
await navigator.modelContext.unregisterTool(metadata.name);
|
|
57
|
-
await navigator.modelContext.registerTool(toolDef);
|
|
58
|
-
} catch {
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
registeredTools.set(form, metadata.name);
|
|
62
|
-
}
|
|
63
|
-
async function unregisterFormTool(form) {
|
|
64
|
-
if (!isWebMCPSupported())
|
|
65
|
-
return;
|
|
66
|
-
const name = registeredTools.get(form);
|
|
67
|
-
if (!name)
|
|
68
|
-
return;
|
|
69
|
-
try {
|
|
70
|
-
await navigator.modelContext.unregisterTool(name);
|
|
71
|
-
} catch {
|
|
72
|
-
}
|
|
73
|
-
registeredTools.delete(form);
|
|
74
|
-
}
|
|
75
|
-
function getRegisteredToolName(form) {
|
|
76
|
-
return registeredTools.get(form);
|
|
77
|
-
}
|
|
78
|
-
function getAllRegisteredTools() {
|
|
79
|
-
return Array.from(registeredTools.entries()).map(([form, name]) => ({ form, name }));
|
|
80
|
-
}
|
|
81
|
-
async function unregisterAll() {
|
|
82
|
-
const entries = Array.from(registeredTools.entries());
|
|
83
|
-
await Promise.all(entries.map(([form]) => unregisterFormTool(form)));
|
|
84
|
-
}
|
|
85
|
-
var registeredTools;
|
|
86
|
-
var init_registry = __esm({
|
|
87
|
-
"src/registry.ts"() {
|
|
88
|
-
"use strict";
|
|
89
|
-
registeredTools = /* @__PURE__ */ new Map();
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
20
|
// src/index.ts
|
|
94
21
|
var src_exports = {};
|
|
95
22
|
__export(src_exports, {
|
|
@@ -250,19 +177,19 @@ function mapSelectElement(select) {
|
|
|
250
177
|
return { type: "string", enum: enumValues, oneOf };
|
|
251
178
|
}
|
|
252
179
|
function collectCheckboxEnum(form, name) {
|
|
253
|
-
return Array.from(
|
|
254
|
-
|
|
180
|
+
return Array.from(form.elements).filter(
|
|
181
|
+
(el) => el instanceof HTMLInputElement && el.type === "checkbox" && el.name === name
|
|
255
182
|
).map((cb) => cb.value).filter((v) => v !== "" && v !== "on");
|
|
256
183
|
}
|
|
257
184
|
function collectRadioEnum(form, name) {
|
|
258
|
-
const radios = Array.from(
|
|
259
|
-
|
|
185
|
+
const radios = Array.from(form.elements).filter(
|
|
186
|
+
(el) => el instanceof HTMLInputElement && el.type === "radio" && el.name === name
|
|
260
187
|
);
|
|
261
188
|
return radios.map((r) => r.value).filter((v) => v !== "");
|
|
262
189
|
}
|
|
263
190
|
function collectRadioOneOf(form, name) {
|
|
264
|
-
const radios = Array.from(
|
|
265
|
-
|
|
191
|
+
const radios = Array.from(form.elements).filter(
|
|
192
|
+
(el) => el instanceof HTMLInputElement && el.type === "radio" && el.name === name
|
|
266
193
|
).filter((r) => r.value !== "");
|
|
267
194
|
return radios.map((r) => {
|
|
268
195
|
const title = getRadioLabelText(r);
|
|
@@ -509,32 +436,39 @@ function collectShadowControls(root, visited = /* @__PURE__ */ new Set()) {
|
|
|
509
436
|
const results = [];
|
|
510
437
|
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
511
438
|
if (el.shadowRoot) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
)
|
|
517
|
-
),
|
|
518
|
-
...collectShadowControls(el.shadowRoot, visited)
|
|
439
|
+
const found = Array.from(
|
|
440
|
+
el.shadowRoot.querySelectorAll(
|
|
441
|
+
"input, textarea, select"
|
|
442
|
+
)
|
|
519
443
|
);
|
|
444
|
+
if (found.length > 0) {
|
|
445
|
+
console.log(`[auto-webmcp] shadow: found ${found.length} control(s) in ${el.tagName.toLowerCase()} shadow root:`, found.map((f) => `${f.tagName.toLowerCase()}[type=${f.type ?? "?"}][name="${f.name}"][id="${f.id}"]`));
|
|
446
|
+
}
|
|
447
|
+
results.push(...found, ...collectShadowControls(el.shadowRoot, visited));
|
|
520
448
|
}
|
|
521
449
|
}
|
|
522
450
|
return results;
|
|
523
451
|
}
|
|
452
|
+
function collectFormAssociatedControls(form) {
|
|
453
|
+
const controls = Array.from(form.elements).filter(
|
|
454
|
+
(el) => el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement
|
|
455
|
+
);
|
|
456
|
+
const seen = new Set(controls);
|
|
457
|
+
for (const shadowControl of collectShadowControls(form)) {
|
|
458
|
+
if (!seen.has(shadowControl)) {
|
|
459
|
+
controls.push(shadowControl);
|
|
460
|
+
seen.add(shadowControl);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return controls;
|
|
464
|
+
}
|
|
524
465
|
function buildSchema(form) {
|
|
525
466
|
const properties = {};
|
|
526
467
|
const required = [];
|
|
527
468
|
const fieldElements = /* @__PURE__ */ new Map();
|
|
528
469
|
const processedRadioGroups = /* @__PURE__ */ new Set();
|
|
529
470
|
const processedCheckboxGroups = /* @__PURE__ */ new Set();
|
|
530
|
-
const controls =
|
|
531
|
-
...Array.from(
|
|
532
|
-
form.querySelectorAll(
|
|
533
|
-
"input, textarea, select"
|
|
534
|
-
)
|
|
535
|
-
),
|
|
536
|
-
...collectShadowControls(form)
|
|
537
|
-
];
|
|
471
|
+
const controls = collectFormAssociatedControls(form);
|
|
538
472
|
for (const control of controls) {
|
|
539
473
|
const name = control.name;
|
|
540
474
|
const fieldKey = name || resolveNativeControlFallbackKey(control);
|
|
@@ -567,8 +501,8 @@ function buildSchema(form) {
|
|
|
567
501
|
const radioOneOf = collectRadioOneOf(form, fieldKey);
|
|
568
502
|
if (radioOneOf.length > 0)
|
|
569
503
|
schemaProp.oneOf = radioOneOf;
|
|
570
|
-
const checkedRadio = form.
|
|
571
|
-
|
|
504
|
+
const checkedRadio = Array.from(form.elements).find(
|
|
505
|
+
(el) => el instanceof HTMLInputElement && el.type === "radio" && el.name === fieldKey && el.checked
|
|
572
506
|
);
|
|
573
507
|
if (checkedRadio?.value)
|
|
574
508
|
schemaProp.default = checkedRadio.value;
|
|
@@ -583,10 +517,8 @@ function buildSchema(form) {
|
|
|
583
517
|
};
|
|
584
518
|
if (schemaProp.description)
|
|
585
519
|
arrayProp.description = schemaProp.description;
|
|
586
|
-
const checkedBoxes = Array.from(
|
|
587
|
-
|
|
588
|
-
`input[type="checkbox"][name="${CSS.escape(fieldKey)}"]:checked`
|
|
589
|
-
)
|
|
520
|
+
const checkedBoxes = Array.from(form.elements).filter(
|
|
521
|
+
(el) => el instanceof HTMLInputElement && el.type === "checkbox" && el.name === fieldKey && el.checked
|
|
590
522
|
).map((b) => b.value);
|
|
591
523
|
if (checkedBoxes.length > 0)
|
|
592
524
|
arrayProp.default = checkedBoxes;
|
|
@@ -600,9 +532,23 @@ function buildSchema(form) {
|
|
|
600
532
|
if (!name) {
|
|
601
533
|
fieldElements.set(fieldKey, control);
|
|
602
534
|
}
|
|
603
|
-
|
|
604
|
-
|
|
535
|
+
let isRequired = control.required;
|
|
536
|
+
if (!isRequired) {
|
|
537
|
+
let hostNode = control;
|
|
538
|
+
while (true) {
|
|
539
|
+
const root = hostNode.getRootNode();
|
|
540
|
+
if (!(root instanceof ShadowRoot))
|
|
541
|
+
break;
|
|
542
|
+
const host = root.host;
|
|
543
|
+
if (host.hasAttribute("required") || host.getAttribute("aria-required") === "true") {
|
|
544
|
+
isRequired = true;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
hostNode = host;
|
|
548
|
+
}
|
|
605
549
|
}
|
|
550
|
+
if (isRequired)
|
|
551
|
+
required.push(fieldKey);
|
|
606
552
|
}
|
|
607
553
|
const ariaControls = collectAriaControls(form);
|
|
608
554
|
const processedAriaRadioGroups = /* @__PURE__ */ new Set();
|
|
@@ -645,11 +591,40 @@ function resolveNativeControlFallbackKey(control) {
|
|
|
645
591
|
if ((control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) && control.placeholder?.trim()) {
|
|
646
592
|
return sanitizeName(control.placeholder.trim());
|
|
647
593
|
}
|
|
594
|
+
const hostKey = resolveShadowHostKey(control);
|
|
595
|
+
if (hostKey)
|
|
596
|
+
return hostKey;
|
|
648
597
|
if (control instanceof HTMLInputElement && control.type !== "text") {
|
|
649
598
|
return control.type;
|
|
650
599
|
}
|
|
651
600
|
return null;
|
|
652
601
|
}
|
|
602
|
+
function resolveShadowHostKey(el) {
|
|
603
|
+
let node = el;
|
|
604
|
+
while (true) {
|
|
605
|
+
const root = node.getRootNode();
|
|
606
|
+
if (!(root instanceof ShadowRoot))
|
|
607
|
+
break;
|
|
608
|
+
const host = root.host;
|
|
609
|
+
const fieldName = host.getAttribute("field-name");
|
|
610
|
+
if (fieldName) {
|
|
611
|
+
console.log("[auto-webmcp] shadow host key: field-name=", fieldName);
|
|
612
|
+
return sanitizeName(fieldName);
|
|
613
|
+
}
|
|
614
|
+
const hostLabel = host.getAttribute("label") || host.getAttribute("aria-label");
|
|
615
|
+
if (hostLabel) {
|
|
616
|
+
console.log("[auto-webmcp] shadow host key: label=", hostLabel);
|
|
617
|
+
return sanitizeName(hostLabel);
|
|
618
|
+
}
|
|
619
|
+
const hostName = host.getAttribute("name");
|
|
620
|
+
if (hostName) {
|
|
621
|
+
console.log("[auto-webmcp] shadow host key: name=", hostName);
|
|
622
|
+
return sanitizeName(hostName);
|
|
623
|
+
}
|
|
624
|
+
node = host;
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
653
628
|
function resolveAriaElementKey(el) {
|
|
654
629
|
if (el.dataset["webmcpName"])
|
|
655
630
|
return sanitizeName(el.dataset["webmcpName"]);
|
|
@@ -811,12 +786,40 @@ function getAssociatedLabelText(control) {
|
|
|
811
786
|
return text;
|
|
812
787
|
}
|
|
813
788
|
}
|
|
789
|
+
const ownRoot = control.getRootNode();
|
|
790
|
+
if (ownRoot instanceof ShadowRoot) {
|
|
791
|
+
if (control.id) {
|
|
792
|
+
const shadowLabel = ownRoot.querySelector(`label[for="${CSS.escape(control.id)}"]`);
|
|
793
|
+
if (shadowLabel) {
|
|
794
|
+
const text = labelTextWithoutNested(shadowLabel);
|
|
795
|
+
if (text)
|
|
796
|
+
return text;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
const anyLabel = ownRoot.querySelector("label");
|
|
800
|
+
if (anyLabel) {
|
|
801
|
+
const text = labelTextWithoutNested(anyLabel);
|
|
802
|
+
if (text)
|
|
803
|
+
return text;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
814
806
|
const parent = control.closest("label");
|
|
815
807
|
if (parent) {
|
|
816
808
|
const text = labelTextWithoutNested(parent);
|
|
817
809
|
if (text)
|
|
818
810
|
return text;
|
|
819
811
|
}
|
|
812
|
+
let node = control;
|
|
813
|
+
while (true) {
|
|
814
|
+
const root = node.getRootNode();
|
|
815
|
+
if (!(root instanceof ShadowRoot))
|
|
816
|
+
break;
|
|
817
|
+
const host = root.host;
|
|
818
|
+
const hostLabel = host.getAttribute("label") || host.getAttribute("aria-label");
|
|
819
|
+
if (hostLabel)
|
|
820
|
+
return hostLabel;
|
|
821
|
+
node = host;
|
|
822
|
+
}
|
|
820
823
|
return "";
|
|
821
824
|
}
|
|
822
825
|
function labelTextWithoutNested(label) {
|
|
@@ -985,8 +988,68 @@ function buildSchemaFromInputs(inputs) {
|
|
|
985
988
|
return { schema: { "$schema": "https://json-schema.org/draft/2020-12/schema", type: "object", properties, required }, fieldElements };
|
|
986
989
|
}
|
|
987
990
|
|
|
988
|
-
// src/
|
|
989
|
-
|
|
991
|
+
// src/registry.ts
|
|
992
|
+
var registeredTools = /* @__PURE__ */ new Map();
|
|
993
|
+
var registrationControllers = /* @__PURE__ */ new Map();
|
|
994
|
+
function isWebMCPSupported() {
|
|
995
|
+
return typeof navigator !== "undefined" && typeof navigator.modelContext !== "undefined";
|
|
996
|
+
}
|
|
997
|
+
async function registerFormTool(form, metadata, execute) {
|
|
998
|
+
if (!isWebMCPSupported())
|
|
999
|
+
return;
|
|
1000
|
+
const existing = registeredTools.get(form);
|
|
1001
|
+
if (existing) {
|
|
1002
|
+
await unregisterFormTool(form);
|
|
1003
|
+
}
|
|
1004
|
+
const toolDef = {
|
|
1005
|
+
name: metadata.name,
|
|
1006
|
+
description: metadata.description,
|
|
1007
|
+
inputSchema: metadata.inputSchema,
|
|
1008
|
+
execute
|
|
1009
|
+
};
|
|
1010
|
+
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
1011
|
+
toolDef.annotations = metadata.annotations;
|
|
1012
|
+
}
|
|
1013
|
+
const controller = new AbortController();
|
|
1014
|
+
registrationControllers.set(form, controller);
|
|
1015
|
+
try {
|
|
1016
|
+
await navigator.modelContext.registerTool(toolDef, { signal: controller.signal });
|
|
1017
|
+
} catch {
|
|
1018
|
+
try {
|
|
1019
|
+
await navigator.modelContext.unregisterTool?.(metadata.name);
|
|
1020
|
+
await navigator.modelContext.registerTool(toolDef, { signal: controller.signal });
|
|
1021
|
+
} catch {
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
registeredTools.set(form, metadata.name);
|
|
1025
|
+
}
|
|
1026
|
+
async function unregisterFormTool(form) {
|
|
1027
|
+
if (!isWebMCPSupported())
|
|
1028
|
+
return;
|
|
1029
|
+
const name = registeredTools.get(form);
|
|
1030
|
+
if (!name)
|
|
1031
|
+
return;
|
|
1032
|
+
const controller = registrationControllers.get(form);
|
|
1033
|
+
if (controller) {
|
|
1034
|
+
controller.abort();
|
|
1035
|
+
registrationControllers.delete(form);
|
|
1036
|
+
}
|
|
1037
|
+
try {
|
|
1038
|
+
await navigator.modelContext.unregisterTool?.(name);
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
1041
|
+
registeredTools.delete(form);
|
|
1042
|
+
}
|
|
1043
|
+
function getRegisteredToolName(form) {
|
|
1044
|
+
return registeredTools.get(form);
|
|
1045
|
+
}
|
|
1046
|
+
function getAllRegisteredTools() {
|
|
1047
|
+
return Array.from(registeredTools.entries()).map(([form, name]) => ({ form, name }));
|
|
1048
|
+
}
|
|
1049
|
+
async function unregisterAll() {
|
|
1050
|
+
const entries = Array.from(registeredTools.entries());
|
|
1051
|
+
await Promise.all(entries.map(([form]) => unregisterFormTool(form)));
|
|
1052
|
+
}
|
|
990
1053
|
|
|
991
1054
|
// src/interceptor.ts
|
|
992
1055
|
var pendingExecutions = /* @__PURE__ */ new WeakMap();
|
|
@@ -1003,7 +1066,20 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1003
1066
|
formFieldElements.set(form, metadata.fieldElements);
|
|
1004
1067
|
}
|
|
1005
1068
|
attachSubmitInterceptor(form, toolName);
|
|
1006
|
-
return async (params) => {
|
|
1069
|
+
return async (params, client) => {
|
|
1070
|
+
const modelContextClient = client;
|
|
1071
|
+
if (config.autoSubmit && metadata?.annotations?.destructiveHint === true && typeof modelContextClient?.requestUserInteraction === "function") {
|
|
1072
|
+
const approved = await modelContextClient.requestUserInteraction(async () => {
|
|
1073
|
+
return new Promise((resolve) => {
|
|
1074
|
+
const ok = window.confirm(`Agent requested a destructive action via "${toolName}". Continue?`);
|
|
1075
|
+
resolve(ok);
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
if (!approved) {
|
|
1079
|
+
window.dispatchEvent(new CustomEvent("toolcancel", { detail: { toolName } }));
|
|
1080
|
+
return { content: [{ type: "text", text: `Cancelled "${toolName}" by user.` }] };
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1007
1083
|
pendingFillWarnings.set(form, []);
|
|
1008
1084
|
pendingWarnings.delete(form);
|
|
1009
1085
|
fillFormFields(form, params);
|
|
@@ -1140,7 +1216,23 @@ function findInShadowRoots(root, selector) {
|
|
|
1140
1216
|
}
|
|
1141
1217
|
return null;
|
|
1142
1218
|
}
|
|
1219
|
+
function getAssociatedInputsByName(form, type, name) {
|
|
1220
|
+
return Array.from(form.elements).filter(
|
|
1221
|
+
(el) => el instanceof HTMLInputElement && el.type === type && el.name === name
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1143
1224
|
function findNativeField(form, key) {
|
|
1225
|
+
const named = form.elements.namedItem(key);
|
|
1226
|
+
if (typeof named === "object" && named !== null && (named instanceof HTMLInputElement || named instanceof HTMLTextAreaElement || named instanceof HTMLSelectElement)) {
|
|
1227
|
+
return named;
|
|
1228
|
+
}
|
|
1229
|
+
if (named instanceof RadioNodeList) {
|
|
1230
|
+
const first = named[0];
|
|
1231
|
+
const firstObj = first;
|
|
1232
|
+
if (firstObj instanceof HTMLInputElement || firstObj instanceof HTMLTextAreaElement || firstObj instanceof HTMLSelectElement) {
|
|
1233
|
+
return firstObj;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1144
1236
|
const esc = CSS.escape(key);
|
|
1145
1237
|
const light = form.querySelector(`[name="${esc}"]`) ?? form.querySelector(
|
|
1146
1238
|
`input#${esc}, textarea#${esc}, select#${esc}`
|
|
@@ -1160,10 +1252,7 @@ function fillFormFields(form, params) {
|
|
|
1160
1252
|
fillInput(input, form, key, value);
|
|
1161
1253
|
if (input.type === "checkbox") {
|
|
1162
1254
|
if (Array.isArray(value)) {
|
|
1163
|
-
|
|
1164
|
-
snapshot[key] = Array.from(
|
|
1165
|
-
form.querySelectorAll(`input[type="checkbox"][name="${esc}"]`)
|
|
1166
|
-
).filter((b) => b.checked).map((b) => b.value);
|
|
1255
|
+
snapshot[key] = getAssociatedInputsByName(form, "checkbox", key).filter((b) => b.checked).map((b) => b.value);
|
|
1167
1256
|
} else {
|
|
1168
1257
|
snapshot[key] = input.checked;
|
|
1169
1258
|
}
|
|
@@ -1212,8 +1301,7 @@ function fillInput(input, form, key, value) {
|
|
|
1212
1301
|
const type = input.type.toLowerCase();
|
|
1213
1302
|
if (type === "checkbox") {
|
|
1214
1303
|
if (Array.isArray(value)) {
|
|
1215
|
-
const
|
|
1216
|
-
const allBoxes = form.querySelectorAll(`input[type="checkbox"][name="${esc}"]`);
|
|
1304
|
+
const allBoxes = getAssociatedInputsByName(form, "checkbox", key);
|
|
1217
1305
|
for (const box of allBoxes) {
|
|
1218
1306
|
setReactChecked(box, value.map(String).includes(box.value));
|
|
1219
1307
|
}
|
|
@@ -1254,10 +1342,7 @@ function fillInput(input, form, key, value) {
|
|
|
1254
1342
|
return;
|
|
1255
1343
|
}
|
|
1256
1344
|
if (type === "radio") {
|
|
1257
|
-
const
|
|
1258
|
-
const radios = form.querySelectorAll(
|
|
1259
|
-
`input[type="radio"][name="${esc}"]`
|
|
1260
|
-
);
|
|
1345
|
+
const radios = getAssociatedInputsByName(form, "radio", key);
|
|
1261
1346
|
for (const radio of radios) {
|
|
1262
1347
|
if (radio.value === String(value)) {
|
|
1263
1348
|
if (_checkedSetter) {
|
|
@@ -1516,6 +1601,51 @@ function getMissingRequired(metadata, params) {
|
|
|
1516
1601
|
return [];
|
|
1517
1602
|
return metadata.inputSchema.required.filter((fieldKey) => !(fieldKey in params));
|
|
1518
1603
|
}
|
|
1604
|
+
async function fillComboboxButton(el, value) {
|
|
1605
|
+
const text = String(value ?? "").trim();
|
|
1606
|
+
console.log("[auto-webmcp] fillComboboxButton: clicking button, value=", JSON.stringify(text));
|
|
1607
|
+
el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
1608
|
+
const listbox = await new Promise((resolve) => {
|
|
1609
|
+
const deadline = Date.now() + 1e3;
|
|
1610
|
+
const poll = () => {
|
|
1611
|
+
const candidate = document.querySelector('[role="listbox"]') ?? document.querySelector('[role="option"]')?.closest('[role="listbox"]') ?? null;
|
|
1612
|
+
if (candidate) {
|
|
1613
|
+
resolve(candidate);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
if (Date.now() >= deadline) {
|
|
1617
|
+
resolve(null);
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
setTimeout(poll, 50);
|
|
1621
|
+
};
|
|
1622
|
+
poll();
|
|
1623
|
+
});
|
|
1624
|
+
if (!listbox) {
|
|
1625
|
+
console.warn("[auto-webmcp] fillComboboxButton: listbox did not appear after 1s");
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
const options = Array.from(listbox.querySelectorAll('[role="option"]'));
|
|
1629
|
+
console.log("[auto-webmcp] fillComboboxButton: listbox has", options.length, "options");
|
|
1630
|
+
const lowerValue = text.toLowerCase();
|
|
1631
|
+
const match = options.find((opt) => {
|
|
1632
|
+
const dataValue = (opt.getAttribute("data-value") ?? "").toLowerCase();
|
|
1633
|
+
const ariaLabel = (opt.getAttribute("aria-label") ?? "").toLowerCase();
|
|
1634
|
+
const optText = (opt.textContent ?? "").trim().toLowerCase();
|
|
1635
|
+
return dataValue === lowerValue || ariaLabel === lowerValue || optText === lowerValue;
|
|
1636
|
+
});
|
|
1637
|
+
if (match) {
|
|
1638
|
+
console.log("[auto-webmcp] fillComboboxButton: clicking option", match.textContent?.trim());
|
|
1639
|
+
match.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
1640
|
+
} else {
|
|
1641
|
+
console.warn(
|
|
1642
|
+
"[auto-webmcp] fillComboboxButton: no option matched",
|
|
1643
|
+
JSON.stringify(text),
|
|
1644
|
+
"available:",
|
|
1645
|
+
options.map((o) => o.textContent?.trim())
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1519
1649
|
|
|
1520
1650
|
// src/discovery.ts
|
|
1521
1651
|
function emit(type, form, toolName) {
|
|
@@ -1535,9 +1665,35 @@ function isExcluded(form, config) {
|
|
|
1535
1665
|
}
|
|
1536
1666
|
return false;
|
|
1537
1667
|
}
|
|
1668
|
+
function withNumericSuffix(baseName, n) {
|
|
1669
|
+
const suffix = `_${n}`;
|
|
1670
|
+
return `${baseName.slice(0, Math.max(1, 64 - suffix.length))}${suffix}`;
|
|
1671
|
+
}
|
|
1672
|
+
function getUsedToolNames(excludeForm) {
|
|
1673
|
+
const names = new Set(registeredOrphanToolNames);
|
|
1674
|
+
for (const { form, name } of getAllRegisteredTools()) {
|
|
1675
|
+
if (excludeForm && form === excludeForm)
|
|
1676
|
+
continue;
|
|
1677
|
+
names.add(name);
|
|
1678
|
+
}
|
|
1679
|
+
return names;
|
|
1680
|
+
}
|
|
1681
|
+
function ensureUniqueToolName(baseName, excludeForm) {
|
|
1682
|
+
const used = getUsedToolNames(excludeForm);
|
|
1683
|
+
if (!used.has(baseName))
|
|
1684
|
+
return baseName;
|
|
1685
|
+
let i = 2;
|
|
1686
|
+
let candidate = withNumericSuffix(baseName, i);
|
|
1687
|
+
while (used.has(candidate)) {
|
|
1688
|
+
i++;
|
|
1689
|
+
candidate = withNumericSuffix(baseName, i);
|
|
1690
|
+
}
|
|
1691
|
+
return candidate;
|
|
1692
|
+
}
|
|
1538
1693
|
async function registerForm(form, config) {
|
|
1539
1694
|
if (isExcluded(form, config))
|
|
1540
1695
|
return;
|
|
1696
|
+
const previousName = getRegisteredToolName(form);
|
|
1541
1697
|
let override;
|
|
1542
1698
|
for (const [selector, ovr] of Object.entries(config.overrides)) {
|
|
1543
1699
|
try {
|
|
@@ -1549,6 +1705,11 @@ async function registerForm(form, config) {
|
|
|
1549
1705
|
}
|
|
1550
1706
|
}
|
|
1551
1707
|
const metadata = analyzeForm(form, override);
|
|
1708
|
+
const resolvedName = ensureUniqueToolName(metadata.name, form);
|
|
1709
|
+
if (resolvedName !== metadata.name && config.debug) {
|
|
1710
|
+
console.warn(`[auto-webmcp] tool name collision: "${metadata.name}" renamed to "${resolvedName}"`);
|
|
1711
|
+
}
|
|
1712
|
+
metadata.name = resolvedName;
|
|
1552
1713
|
if (config.debug) {
|
|
1553
1714
|
warnToolQuality(metadata.name, metadata.description);
|
|
1554
1715
|
}
|
|
@@ -1560,6 +1721,9 @@ async function registerForm(form, config) {
|
|
|
1560
1721
|
'[type="submit"], button[data-variant="primary"], button:not([type])'
|
|
1561
1722
|
) ?? null;
|
|
1562
1723
|
const pendingBtns = window["__pendingSubmitBtns"] ??= {};
|
|
1724
|
+
if (previousName && previousName !== metadata.name) {
|
|
1725
|
+
delete pendingBtns[previousName];
|
|
1726
|
+
}
|
|
1563
1727
|
pendingBtns[metadata.name] = formSubmitBtn;
|
|
1564
1728
|
if (config.debug) {
|
|
1565
1729
|
console.log(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
|
|
@@ -1567,12 +1731,14 @@ async function registerForm(form, config) {
|
|
|
1567
1731
|
emit("form:registered", form, metadata.name);
|
|
1568
1732
|
}
|
|
1569
1733
|
async function unregisterForm(form, config) {
|
|
1570
|
-
const
|
|
1571
|
-
const name = getRegisteredToolName2(form);
|
|
1734
|
+
const name = getRegisteredToolName(form);
|
|
1572
1735
|
if (!name)
|
|
1573
1736
|
return;
|
|
1574
1737
|
await unregisterFormTool(form);
|
|
1575
1738
|
registeredForms.delete(form);
|
|
1739
|
+
const pendingBtns = window["__pendingSubmitBtns"];
|
|
1740
|
+
if (pendingBtns)
|
|
1741
|
+
delete pendingBtns[name];
|
|
1576
1742
|
if (config.debug) {
|
|
1577
1743
|
console.log(`[auto-webmcp] Unregistered: ${name}`);
|
|
1578
1744
|
}
|
|
@@ -1583,6 +1749,17 @@ var registeredForms = /* @__PURE__ */ new WeakSet();
|
|
|
1583
1749
|
var registeredFormCount = 0;
|
|
1584
1750
|
var reAnalysisTimers = /* @__PURE__ */ new Map();
|
|
1585
1751
|
var RE_ANALYSIS_DEBOUNCE_MS = 300;
|
|
1752
|
+
var orphanRescanTimer = null;
|
|
1753
|
+
var ORPHAN_RESCAN_DEBOUNCE_MS = 500;
|
|
1754
|
+
var registeredOrphanToolNames = /* @__PURE__ */ new Set();
|
|
1755
|
+
function scheduleOrphanRescan(config) {
|
|
1756
|
+
if (orphanRescanTimer)
|
|
1757
|
+
clearTimeout(orphanRescanTimer);
|
|
1758
|
+
orphanRescanTimer = setTimeout(() => {
|
|
1759
|
+
orphanRescanTimer = null;
|
|
1760
|
+
void scanOrphanInputs(config);
|
|
1761
|
+
}, ORPHAN_RESCAN_DEBOUNCE_MS);
|
|
1762
|
+
}
|
|
1586
1763
|
function isInterestingNode(node) {
|
|
1587
1764
|
const tag = node.tagName.toLowerCase();
|
|
1588
1765
|
if (tag === "input" || tag === "textarea" || tag === "select")
|
|
@@ -1610,11 +1787,47 @@ function scheduleReAnalysis(form, config) {
|
|
|
1610
1787
|
}, RE_ANALYSIS_DEBOUNCE_MS)
|
|
1611
1788
|
);
|
|
1612
1789
|
}
|
|
1790
|
+
function scheduleFormReAnalysisById(formId, config) {
|
|
1791
|
+
const owner = document.getElementById(formId);
|
|
1792
|
+
if (owner instanceof HTMLFormElement && registeredForms.has(owner)) {
|
|
1793
|
+
scheduleReAnalysis(owner, config);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
function resolveOwnerForm(el) {
|
|
1797
|
+
const closest = el.closest("form");
|
|
1798
|
+
if (closest instanceof HTMLFormElement)
|
|
1799
|
+
return closest;
|
|
1800
|
+
const explicitOwner = el.form;
|
|
1801
|
+
if (explicitOwner instanceof HTMLFormElement)
|
|
1802
|
+
return explicitOwner;
|
|
1803
|
+
const formId = el.getAttribute("form");
|
|
1804
|
+
if (formId) {
|
|
1805
|
+
const byId = document.getElementById(formId);
|
|
1806
|
+
if (byId instanceof HTMLFormElement)
|
|
1807
|
+
return byId;
|
|
1808
|
+
}
|
|
1809
|
+
return null;
|
|
1810
|
+
}
|
|
1613
1811
|
function startObserver(config) {
|
|
1614
1812
|
if (observer)
|
|
1615
1813
|
return;
|
|
1616
1814
|
observer = new MutationObserver((mutations) => {
|
|
1617
1815
|
for (const mutation of mutations) {
|
|
1816
|
+
if (mutation.type === "attributes" && mutation.target instanceof Element) {
|
|
1817
|
+
const target = mutation.target;
|
|
1818
|
+
const ownerForm = resolveOwnerForm(target);
|
|
1819
|
+
if (ownerForm && registeredForms.has(ownerForm)) {
|
|
1820
|
+
scheduleReAnalysis(ownerForm, config);
|
|
1821
|
+
} else if (target instanceof HTMLFormElement) {
|
|
1822
|
+
void registerForm(target, config);
|
|
1823
|
+
} else if (isInterestingNode(target) && !target.closest("form")) {
|
|
1824
|
+
scheduleOrphanRescan(config);
|
|
1825
|
+
}
|
|
1826
|
+
if (mutation.attributeName === "form" && mutation.oldValue) {
|
|
1827
|
+
scheduleFormReAnalysisById(mutation.oldValue, config);
|
|
1828
|
+
}
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1618
1831
|
for (const node of mutation.addedNodes) {
|
|
1619
1832
|
if (!(node instanceof Element))
|
|
1620
1833
|
continue;
|
|
@@ -1629,6 +1842,9 @@ function startObserver(config) {
|
|
|
1629
1842
|
for (const form of Array.from(node.querySelectorAll("form"))) {
|
|
1630
1843
|
void registerForm(form, config);
|
|
1631
1844
|
}
|
|
1845
|
+
if (isInterestingNode(node) && !node.closest("form")) {
|
|
1846
|
+
scheduleOrphanRescan(config);
|
|
1847
|
+
}
|
|
1632
1848
|
}
|
|
1633
1849
|
for (const node of mutation.removedNodes) {
|
|
1634
1850
|
if (!(node instanceof Element))
|
|
@@ -1640,7 +1856,12 @@ function startObserver(config) {
|
|
|
1640
1856
|
}
|
|
1641
1857
|
}
|
|
1642
1858
|
});
|
|
1643
|
-
observer.observe(document.body, {
|
|
1859
|
+
observer.observe(document.body, {
|
|
1860
|
+
childList: true,
|
|
1861
|
+
subtree: true,
|
|
1862
|
+
attributes: true,
|
|
1863
|
+
attributeOldValue: true
|
|
1864
|
+
});
|
|
1644
1865
|
}
|
|
1645
1866
|
function listenForRouteChanges(config) {
|
|
1646
1867
|
window.addEventListener("hashchange", () => scanForms(config));
|
|
@@ -1676,10 +1897,10 @@ async function scanOrphanInputs(config) {
|
|
|
1676
1897
|
return;
|
|
1677
1898
|
const SUBMIT_BTN_SELECTOR = '[type="submit"]:not([disabled]), button[data-variant="primary"]:not([disabled])';
|
|
1678
1899
|
const SUBMIT_BTN_GROUPING_SELECTOR = '[type="submit"], button[data-variant="primary"]';
|
|
1679
|
-
const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search|post|tweet|publish/i;
|
|
1900
|
+
const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search|post|tweet|publish|save/i;
|
|
1680
1901
|
const orphanInputs = Array.from(
|
|
1681
1902
|
document.querySelectorAll(
|
|
1682
|
-
'input:not(form input), textarea:not(form textarea), select:not(form select), [role="textbox"]:not(form [role="textbox"]):not(input):not(textarea), [role="searchbox"]:not(form [role="searchbox"]):not(input):not(textarea), [contenteditable="true"]:not(form [contenteditable="true"]):not(input):not(textarea)'
|
|
1903
|
+
'input:not(form input), textarea:not(form textarea), select:not(form select), [role="textbox"]:not(form [role="textbox"]):not(input):not(textarea), [role="searchbox"]:not(form [role="searchbox"]):not(input):not(textarea), [contenteditable="true"]:not(form [contenteditable="true"]):not(input):not(textarea), button[role="combobox"]:not(form button[role="combobox"])'
|
|
1683
1904
|
)
|
|
1684
1905
|
).filter((el) => {
|
|
1685
1906
|
if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase())) {
|
|
@@ -1784,6 +2005,15 @@ async function scanOrphanInputs(config) {
|
|
|
1784
2005
|
}
|
|
1785
2006
|
console.log(`[auto-webmcp] orphan: submit button for group:`, submitBtn ? `"${submitBtn.textContent?.trim()}" disabled=${submitBtn.disabled}` : "none");
|
|
1786
2007
|
const metadata = analyzeOrphanInputGroup(container, inputs, submitBtn);
|
|
2008
|
+
if (registeredOrphanToolNames.has(metadata.name)) {
|
|
2009
|
+
console.log(`[auto-webmcp] orphan: "${metadata.name}" already registered, skipping`);
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
const orphanName = ensureUniqueToolName(metadata.name);
|
|
2013
|
+
if (orphanName !== metadata.name && config.debug) {
|
|
2014
|
+
console.warn(`[auto-webmcp] orphan tool name collision: "${metadata.name}" renamed to "${orphanName}"`);
|
|
2015
|
+
}
|
|
2016
|
+
metadata.name = orphanName;
|
|
1787
2017
|
console.log(`[auto-webmcp] orphan: tool="${metadata.name}" schema keys:`, Object.keys(metadata.inputSchema.properties));
|
|
1788
2018
|
const inputPairs = [];
|
|
1789
2019
|
const schemaProps = metadata.inputSchema.properties;
|
|
@@ -1804,13 +2034,17 @@ async function scanOrphanInputs(config) {
|
|
|
1804
2034
|
continue;
|
|
1805
2035
|
}
|
|
1806
2036
|
const toolName = metadata.name;
|
|
1807
|
-
const execute = async (params) => {
|
|
2037
|
+
const execute = async (params, _client) => {
|
|
1808
2038
|
console.log(`[auto-webmcp] orphan execute: tool="${toolName}" params=`, params);
|
|
1809
2039
|
console.log(`[auto-webmcp] orphan execute: inputPairs=`, inputPairs.map((p) => p.key));
|
|
1810
2040
|
for (const { key, el } of inputPairs) {
|
|
1811
2041
|
if (params[key] !== void 0) {
|
|
1812
2042
|
console.log(`[auto-webmcp] orphan execute: filling key="${key}" value=`, params[key], "element=", el);
|
|
1813
|
-
|
|
2043
|
+
if (el.getAttribute("role") === "combobox" && el.tagName.toLowerCase() === "button") {
|
|
2044
|
+
await fillComboboxButton(el, params[key]);
|
|
2045
|
+
} else {
|
|
2046
|
+
fillElement(el, params[key]);
|
|
2047
|
+
}
|
|
1814
2048
|
console.log(`[auto-webmcp] orphan execute: after fill, element value=`, el.value);
|
|
1815
2049
|
} else {
|
|
1816
2050
|
console.log(`[auto-webmcp] orphan execute: key="${key}" not in params, skipping`);
|
|
@@ -1822,22 +2056,43 @@ async function scanOrphanInputs(config) {
|
|
|
1822
2056
|
console.log(`[auto-webmcp] orphan execute: autoSubmit=false, returning without clicking submit`);
|
|
1823
2057
|
return { content: [{ type: "text", text: "Fields filled. Ready to submit." }] };
|
|
1824
2058
|
}
|
|
1825
|
-
console.log(`[auto-webmcp] orphan execute:
|
|
2059
|
+
console.log(`[auto-webmcp] orphan execute: resolving submit button (up to 2s)...`);
|
|
1826
2060
|
let btn = null;
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
const
|
|
1830
|
-
|
|
2061
|
+
if (submitBtn && document.contains(submitBtn)) {
|
|
2062
|
+
const isEnabled = !submitBtn.disabled && submitBtn.getAttribute("aria-disabled") !== "true";
|
|
2063
|
+
const r = submitBtn.getBoundingClientRect();
|
|
2064
|
+
if (isEnabled && r.width > 0 && r.height > 0) {
|
|
2065
|
+
btn = submitBtn;
|
|
2066
|
+
console.log(`[auto-webmcp] orphan execute: using captured submit button "${btn.textContent?.trim()}"`);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (!btn) {
|
|
2070
|
+
const deadline = Date.now() + 2e3;
|
|
2071
|
+
while (Date.now() < deadline) {
|
|
2072
|
+
const candidates = Array.from(
|
|
2073
|
+
container.querySelectorAll(SUBMIT_BTN_SELECTOR)
|
|
2074
|
+
).filter((b) => {
|
|
2075
|
+
const r = b.getBoundingClientRect();
|
|
2076
|
+
return r.width > 0 && r.height > 0;
|
|
2077
|
+
});
|
|
2078
|
+
const last = candidates[candidates.length - 1] ?? null;
|
|
2079
|
+
if (last) {
|
|
2080
|
+
btn = last;
|
|
2081
|
+
break;
|
|
2082
|
+
}
|
|
2083
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
if (!btn) {
|
|
2087
|
+
const textBtns = Array.from(
|
|
2088
|
+
(container !== document.body ? container : document).querySelectorAll('button, [role="button"]')
|
|
1831
2089
|
).filter((b) => {
|
|
1832
2090
|
const r = b.getBoundingClientRect();
|
|
1833
|
-
return r.width > 0 && r.height > 0;
|
|
2091
|
+
return r.width > 0 && r.height > 0 && !b.disabled && b.getAttribute("aria-disabled") !== "true" && SUBMIT_TEXT_RE.test(b.textContent ?? "");
|
|
1834
2092
|
});
|
|
1835
|
-
|
|
1836
|
-
if (
|
|
1837
|
-
|
|
1838
|
-
break;
|
|
1839
|
-
}
|
|
1840
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
2093
|
+
btn = textBtns[textBtns.length - 1] ?? null;
|
|
2094
|
+
if (btn)
|
|
2095
|
+
console.log(`[auto-webmcp] orphan execute: using text-matched fallback button "${btn.textContent?.trim()}"`);
|
|
1841
2096
|
}
|
|
1842
2097
|
if (!btn) {
|
|
1843
2098
|
console.warn(`[auto-webmcp] orphan execute: submit button still disabled after 2s`);
|
|
@@ -1858,6 +2113,7 @@ async function scanOrphanInputs(config) {
|
|
|
1858
2113
|
toolDef.annotations = metadata.annotations;
|
|
1859
2114
|
}
|
|
1860
2115
|
await navigator.modelContext.registerTool(toolDef);
|
|
2116
|
+
registeredOrphanToolNames.add(metadata.name);
|
|
1861
2117
|
const pendingBtns = window["__pendingSubmitBtns"] ??= {};
|
|
1862
2118
|
pendingBtns[metadata.name] = submitBtn;
|
|
1863
2119
|
if (config.debug) {
|
|
@@ -1885,6 +2141,7 @@ async function startDiscovery(config) {
|
|
|
1885
2141
|
);
|
|
1886
2142
|
}
|
|
1887
2143
|
registeredFormCount = 0;
|
|
2144
|
+
registeredOrphanToolNames.clear();
|
|
1888
2145
|
startObserver(config);
|
|
1889
2146
|
listenForRouteChanges(config);
|
|
1890
2147
|
await scanForms(config);
|
|
@@ -1896,7 +2153,6 @@ function stopDiscovery() {
|
|
|
1896
2153
|
}
|
|
1897
2154
|
|
|
1898
2155
|
// src/index.ts
|
|
1899
|
-
init_registry();
|
|
1900
2156
|
async function autoWebMCP(config) {
|
|
1901
2157
|
const resolved = resolveConfig(config);
|
|
1902
2158
|
if (resolved.debug) {
|