auto-webmcp 0.3.6 → 0.3.8
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 +95 -17
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +95 -17
- package/dist/auto-webmcp.esm.js.map +2 -2
- package/dist/auto-webmcp.iife.js +1 -1
- package/dist/auto-webmcp.iife.js.map +3 -3
- package/dist/discovery.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/auto-webmcp.esm.js
CHANGED
|
@@ -611,13 +611,14 @@ function buildSchema(form) {
|
|
|
611
611
|
required.push(key);
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
|
-
return { schema: { type: "object", properties, required }, fieldElements };
|
|
614
|
+
return { schema: { "$schema": "https://json-schema.org/draft/2020-12/schema", type: "object", properties, required }, fieldElements };
|
|
615
615
|
}
|
|
616
|
+
var AUTO_GENERATED_ID_RE = /^_r_[0-9a-z]+_$|^:[a-z0-9]+:$/i;
|
|
616
617
|
function resolveNativeControlFallbackKey(control) {
|
|
617
618
|
const el = control;
|
|
618
619
|
if (el.dataset["webmcpName"])
|
|
619
620
|
return sanitizeName(el.dataset["webmcpName"]);
|
|
620
|
-
if (control.id)
|
|
621
|
+
if (control.id && !AUTO_GENERATED_ID_RE.test(control.id))
|
|
621
622
|
return sanitizeName(control.id);
|
|
622
623
|
const label = control.getAttribute("aria-label");
|
|
623
624
|
if (label)
|
|
@@ -816,7 +817,24 @@ function analyzeOrphanInputGroup(container, inputs, submitBtn) {
|
|
|
816
817
|
const name = inferOrphanToolName(container, submitBtn);
|
|
817
818
|
const description = inferOrphanToolDescription(container);
|
|
818
819
|
const { schema: inputSchema, fieldElements } = buildSchemaFromInputs(inputs);
|
|
819
|
-
|
|
820
|
+
const annotations = inferOrphanAnnotations(submitBtn);
|
|
821
|
+
return { name, description, inputSchema, annotations, fieldElements };
|
|
822
|
+
}
|
|
823
|
+
function inferOrphanAnnotations(submitBtn) {
|
|
824
|
+
const annotations = {};
|
|
825
|
+
const submitText = submitBtn instanceof HTMLInputElement ? submitBtn.value.trim() : submitBtn?.textContent?.trim() ?? "";
|
|
826
|
+
if (READONLY_BUTTON_PATTERNS.test(submitText)) {
|
|
827
|
+
annotations.readOnlyHint = true;
|
|
828
|
+
annotations.idempotentHint = true;
|
|
829
|
+
}
|
|
830
|
+
if (DESTRUCTIVE_BUTTON_PATTERNS.test(submitText)) {
|
|
831
|
+
annotations.destructiveHint = true;
|
|
832
|
+
}
|
|
833
|
+
if (annotations.readOnlyHint !== true) {
|
|
834
|
+
annotations.openWorldHint = true;
|
|
835
|
+
}
|
|
836
|
+
const hasNonDefault = annotations.readOnlyHint === true || annotations.destructiveHint === true || annotations.idempotentHint === true || annotations.openWorldHint === false;
|
|
837
|
+
return hasNonDefault ? annotations : {};
|
|
820
838
|
}
|
|
821
839
|
function inferOrphanToolName(container, submitBtn) {
|
|
822
840
|
if (submitBtn) {
|
|
@@ -871,8 +889,8 @@ function buildSchemaFromInputs(inputs) {
|
|
|
871
889
|
const processedRadioGroups = /* @__PURE__ */ new Set();
|
|
872
890
|
const processedCheckboxGroups = /* @__PURE__ */ new Set();
|
|
873
891
|
for (const control of inputs) {
|
|
874
|
-
const
|
|
875
|
-
const fieldKey =
|
|
892
|
+
const rawName = control.name;
|
|
893
|
+
const fieldKey = (rawName ? sanitizeName(rawName) : null) || resolveNativeControlFallbackKey(control);
|
|
876
894
|
if (!fieldKey)
|
|
877
895
|
continue;
|
|
878
896
|
if (control instanceof HTMLInputElement && control.type === "radio") {
|
|
@@ -911,12 +929,12 @@ function buildSchemaFromInputs(inputs) {
|
|
|
911
929
|
}
|
|
912
930
|
}
|
|
913
931
|
properties[fieldKey] = schemaProp;
|
|
914
|
-
if (!
|
|
932
|
+
if (!rawName)
|
|
915
933
|
fieldElements.set(fieldKey, control);
|
|
916
934
|
if (control.required)
|
|
917
935
|
required.push(fieldKey);
|
|
918
936
|
}
|
|
919
|
-
return { schema: { type: "object", properties, required }, fieldElements };
|
|
937
|
+
return { schema: { "$schema": "https://json-schema.org/draft/2020-12/schema", type: "object", properties, required }, fieldElements };
|
|
920
938
|
}
|
|
921
939
|
|
|
922
940
|
// src/discovery.ts
|
|
@@ -1423,7 +1441,7 @@ async function registerForm(form, config) {
|
|
|
1423
1441
|
registeredForms.add(form);
|
|
1424
1442
|
registeredFormCount++;
|
|
1425
1443
|
if (config.debug) {
|
|
1426
|
-
console.
|
|
1444
|
+
console.log(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
|
|
1427
1445
|
}
|
|
1428
1446
|
emit("form:registered", form, metadata.name);
|
|
1429
1447
|
}
|
|
@@ -1435,7 +1453,7 @@ async function unregisterForm(form, config) {
|
|
|
1435
1453
|
await unregisterFormTool(form);
|
|
1436
1454
|
registeredForms.delete(form);
|
|
1437
1455
|
if (config.debug) {
|
|
1438
|
-
console.
|
|
1456
|
+
console.log(`[auto-webmcp] Unregistered: ${name}`);
|
|
1439
1457
|
}
|
|
1440
1458
|
emit("form:unregistered", form, name);
|
|
1441
1459
|
}
|
|
@@ -1536,7 +1554,7 @@ async function scanOrphanInputs(config) {
|
|
|
1536
1554
|
if (!isWebMCPSupported())
|
|
1537
1555
|
return;
|
|
1538
1556
|
const SUBMIT_BTN_SELECTOR = '[type="submit"]:not([disabled]), button:not([type]):not([disabled])';
|
|
1539
|
-
const SUBMIT_BTN_GROUPING_SELECTOR = '[type="submit"]
|
|
1557
|
+
const SUBMIT_BTN_GROUPING_SELECTOR = '[type="submit"]';
|
|
1540
1558
|
const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search/i;
|
|
1541
1559
|
const orphanInputs = Array.from(
|
|
1542
1560
|
document.querySelectorAll(
|
|
@@ -1544,11 +1562,17 @@ async function scanOrphanInputs(config) {
|
|
|
1544
1562
|
)
|
|
1545
1563
|
).filter((el) => {
|
|
1546
1564
|
if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase())) {
|
|
1565
|
+
console.log(`[auto-webmcp] orphan: skipping excluded type "${el.type}" (name="${el.name}" id="${el.id}")`);
|
|
1547
1566
|
return false;
|
|
1548
1567
|
}
|
|
1549
1568
|
const rect = el.getBoundingClientRect();
|
|
1550
|
-
|
|
1569
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
1570
|
+
console.log(`[auto-webmcp] orphan: skipping invisible input (name="${el.name}" id="${el.id}")`);
|
|
1571
|
+
return false;
|
|
1572
|
+
}
|
|
1573
|
+
return true;
|
|
1551
1574
|
});
|
|
1575
|
+
console.log(`[auto-webmcp] orphan: found ${orphanInputs.length} visible orphan input(s)`);
|
|
1552
1576
|
if (orphanInputs.length === 0)
|
|
1553
1577
|
return;
|
|
1554
1578
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -1565,10 +1589,12 @@ async function scanOrphanInputs(config) {
|
|
|
1565
1589
|
}
|
|
1566
1590
|
container = container.parentElement;
|
|
1567
1591
|
}
|
|
1592
|
+
console.log(`[auto-webmcp] orphan: input (name="${input.name}" id="${input.id}") grouped into container`, foundContainer);
|
|
1568
1593
|
if (!groups.has(foundContainer))
|
|
1569
1594
|
groups.set(foundContainer, []);
|
|
1570
1595
|
groups.get(foundContainer).push(input);
|
|
1571
1596
|
}
|
|
1597
|
+
console.log(`[auto-webmcp] orphan: ${groups.size} group(s) found`);
|
|
1572
1598
|
for (const [container, inputs] of groups) {
|
|
1573
1599
|
const allCandidates = Array.from(
|
|
1574
1600
|
container.querySelectorAll(SUBMIT_BTN_SELECTOR)
|
|
@@ -1577,6 +1603,12 @@ async function scanOrphanInputs(config) {
|
|
|
1577
1603
|
return r.width > 0 && r.height > 0;
|
|
1578
1604
|
});
|
|
1579
1605
|
let submitBtn = allCandidates[allCandidates.length - 1] ?? null;
|
|
1606
|
+
const disabledCandidates = Array.from(
|
|
1607
|
+
container.querySelectorAll(SUBMIT_BTN_GROUPING_SELECTOR)
|
|
1608
|
+
).filter((b) => b.disabled);
|
|
1609
|
+
if (!submitBtn && disabledCandidates.length > 0) {
|
|
1610
|
+
console.log(`[auto-webmcp] orphan: no enabled submit button found in container \u2014 ${disabledCandidates.length} disabled button(s) present:`, disabledCandidates.map((b) => b.textContent?.trim()));
|
|
1611
|
+
}
|
|
1580
1612
|
if (!submitBtn) {
|
|
1581
1613
|
const pageBtns = Array.from(document.querySelectorAll("button")).filter(
|
|
1582
1614
|
(b) => {
|
|
@@ -1585,36 +1617,82 @@ async function scanOrphanInputs(config) {
|
|
|
1585
1617
|
}
|
|
1586
1618
|
);
|
|
1587
1619
|
submitBtn = pageBtns[pageBtns.length - 1] ?? null;
|
|
1620
|
+
if (submitBtn)
|
|
1621
|
+
console.log(`[auto-webmcp] orphan: using page-wide fallback submit button: "${submitBtn.textContent?.trim()}"`);
|
|
1588
1622
|
}
|
|
1623
|
+
console.log(`[auto-webmcp] orphan: submit button for group:`, submitBtn ? `"${submitBtn.textContent?.trim()}" disabled=${submitBtn.disabled}` : "none");
|
|
1589
1624
|
const metadata = analyzeOrphanInputGroup(container, inputs, submitBtn);
|
|
1625
|
+
console.log(`[auto-webmcp] orphan: tool="${metadata.name}" schema keys:`, Object.keys(metadata.inputSchema.properties));
|
|
1590
1626
|
const inputPairs = [];
|
|
1591
1627
|
const schemaProps = metadata.inputSchema.properties;
|
|
1628
|
+
const AUTO_ID_RE = /^_r_[0-9a-z]+_$/i;
|
|
1592
1629
|
for (const el of inputs) {
|
|
1593
|
-
const
|
|
1630
|
+
const id = el.id && !AUTO_ID_RE.test(el.id) ? el.id : null;
|
|
1631
|
+
const key = el.name || el.dataset["webmcpName"] || id || el.getAttribute("aria-label") || (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el.placeholder || null : null) || null;
|
|
1594
1632
|
const safeKey = key ? key.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) : null;
|
|
1595
|
-
|
|
1633
|
+
const matched = !!(safeKey && schemaProps[safeKey]);
|
|
1634
|
+
console.log(`[auto-webmcp] orphan: field (name="${el.name}" id="${el.id}") rawKey="${key}" safeKey="${safeKey}" matched=${matched}`);
|
|
1635
|
+
if (matched) {
|
|
1596
1636
|
inputPairs.push({ key: safeKey, el });
|
|
1597
1637
|
}
|
|
1598
1638
|
}
|
|
1639
|
+
console.log(`[auto-webmcp] orphan: ${inputPairs.length}/${inputs.length} input(s) mapped to schema keys`);
|
|
1599
1640
|
const toolName = metadata.name;
|
|
1600
1641
|
const execute = async (params) => {
|
|
1642
|
+
console.log(`[auto-webmcp] orphan execute: tool="${toolName}" params=`, params);
|
|
1643
|
+
console.log(`[auto-webmcp] orphan execute: inputPairs=`, inputPairs.map((p) => p.key));
|
|
1601
1644
|
for (const { key, el } of inputPairs) {
|
|
1602
1645
|
if (params[key] !== void 0) {
|
|
1646
|
+
console.log(`[auto-webmcp] orphan execute: filling key="${key}" value=`, params[key], "element=", el);
|
|
1603
1647
|
fillElement(el, params[key]);
|
|
1648
|
+
console.log(`[auto-webmcp] orphan execute: after fill, element value=`, el.value);
|
|
1649
|
+
} else {
|
|
1650
|
+
console.log(`[auto-webmcp] orphan execute: key="${key}" not in params, skipping`);
|
|
1604
1651
|
}
|
|
1605
1652
|
}
|
|
1606
1653
|
window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
|
|
1607
|
-
|
|
1654
|
+
if (!config.autoSubmit) {
|
|
1655
|
+
console.log(`[auto-webmcp] orphan execute: autoSubmit=false, returning without clicking submit`);
|
|
1656
|
+
return { content: [{ type: "text", text: "Fields filled. Ready to submit." }] };
|
|
1657
|
+
}
|
|
1658
|
+
console.log(`[auto-webmcp] orphan execute: polling for enabled submit button (up to 2s)...`);
|
|
1659
|
+
let btn = null;
|
|
1660
|
+
const deadline = Date.now() + 2e3;
|
|
1661
|
+
while (Date.now() < deadline) {
|
|
1662
|
+
const candidates = Array.from(
|
|
1663
|
+
container.querySelectorAll(SUBMIT_BTN_SELECTOR)
|
|
1664
|
+
).filter((b) => {
|
|
1665
|
+
const r = b.getBoundingClientRect();
|
|
1666
|
+
return r.width > 0 && r.height > 0;
|
|
1667
|
+
});
|
|
1668
|
+
const last = candidates[candidates.length - 1] ?? null;
|
|
1669
|
+
if (last) {
|
|
1670
|
+
btn = last;
|
|
1671
|
+
break;
|
|
1672
|
+
}
|
|
1673
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1674
|
+
}
|
|
1675
|
+
if (!btn) {
|
|
1676
|
+
console.warn(`[auto-webmcp] orphan execute: submit button still disabled after 2s`);
|
|
1677
|
+
return { content: [{ type: "text", text: "Fields filled but the submit button is still disabled. The page may require additional input before submitting." }] };
|
|
1678
|
+
}
|
|
1679
|
+
console.log(`[auto-webmcp] orphan execute: clicking submit button "${btn.textContent?.trim()}"`);
|
|
1680
|
+
btn.click();
|
|
1681
|
+
return { content: [{ type: "text", text: "Fields filled and form submitted." }] };
|
|
1608
1682
|
};
|
|
1609
1683
|
try {
|
|
1610
|
-
|
|
1684
|
+
const toolDef = {
|
|
1611
1685
|
name: metadata.name,
|
|
1612
1686
|
description: metadata.description,
|
|
1613
1687
|
inputSchema: metadata.inputSchema,
|
|
1614
1688
|
execute
|
|
1615
|
-
}
|
|
1689
|
+
};
|
|
1690
|
+
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
1691
|
+
toolDef.annotations = metadata.annotations;
|
|
1692
|
+
}
|
|
1693
|
+
await navigator.modelContext.registerTool(toolDef);
|
|
1616
1694
|
if (config.debug) {
|
|
1617
|
-
console.
|
|
1695
|
+
console.log(`[auto-webmcp] Orphan tool registered: ${metadata.name}`, metadata);
|
|
1618
1696
|
}
|
|
1619
1697
|
} catch {
|
|
1620
1698
|
}
|