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.
@@ -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,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"}
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;AAspBD;;;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,CAMd"}
@@ -630,13 +630,14 @@ function buildSchema(form) {
630
630
  required.push(key);
631
631
  }
632
632
  }
633
- return { schema: { type: "object", properties, required }, fieldElements };
633
+ return { schema: { "$schema": "https://json-schema.org/draft/2020-12/schema", type: "object", properties, required }, fieldElements };
634
634
  }
635
+ var AUTO_GENERATED_ID_RE = /^_r_[0-9a-z]+_$|^:[a-z0-9]+:$/i;
635
636
  function resolveNativeControlFallbackKey(control) {
636
637
  const el = control;
637
638
  if (el.dataset["webmcpName"])
638
639
  return sanitizeName(el.dataset["webmcpName"]);
639
- if (control.id)
640
+ if (control.id && !AUTO_GENERATED_ID_RE.test(control.id))
640
641
  return sanitizeName(control.id);
641
642
  const label = control.getAttribute("aria-label");
642
643
  if (label)
@@ -835,7 +836,24 @@ function analyzeOrphanInputGroup(container, inputs, submitBtn) {
835
836
  const name = inferOrphanToolName(container, submitBtn);
836
837
  const description = inferOrphanToolDescription(container);
837
838
  const { schema: inputSchema, fieldElements } = buildSchemaFromInputs(inputs);
838
- return { name, description, inputSchema, fieldElements };
839
+ const annotations = inferOrphanAnnotations(submitBtn);
840
+ return { name, description, inputSchema, annotations, fieldElements };
841
+ }
842
+ function inferOrphanAnnotations(submitBtn) {
843
+ const annotations = {};
844
+ const submitText = submitBtn instanceof HTMLInputElement ? submitBtn.value.trim() : submitBtn?.textContent?.trim() ?? "";
845
+ if (READONLY_BUTTON_PATTERNS.test(submitText)) {
846
+ annotations.readOnlyHint = true;
847
+ annotations.idempotentHint = true;
848
+ }
849
+ if (DESTRUCTIVE_BUTTON_PATTERNS.test(submitText)) {
850
+ annotations.destructiveHint = true;
851
+ }
852
+ if (annotations.readOnlyHint !== true) {
853
+ annotations.openWorldHint = true;
854
+ }
855
+ const hasNonDefault = annotations.readOnlyHint === true || annotations.destructiveHint === true || annotations.idempotentHint === true || annotations.openWorldHint === false;
856
+ return hasNonDefault ? annotations : {};
839
857
  }
840
858
  function inferOrphanToolName(container, submitBtn) {
841
859
  if (submitBtn) {
@@ -890,8 +908,8 @@ function buildSchemaFromInputs(inputs) {
890
908
  const processedRadioGroups = /* @__PURE__ */ new Set();
891
909
  const processedCheckboxGroups = /* @__PURE__ */ new Set();
892
910
  for (const control of inputs) {
893
- const name = control.name;
894
- const fieldKey = name || resolveNativeControlFallbackKey(control);
911
+ const rawName = control.name;
912
+ const fieldKey = (rawName ? sanitizeName(rawName) : null) || resolveNativeControlFallbackKey(control);
895
913
  if (!fieldKey)
896
914
  continue;
897
915
  if (control instanceof HTMLInputElement && control.type === "radio") {
@@ -930,12 +948,12 @@ function buildSchemaFromInputs(inputs) {
930
948
  }
931
949
  }
932
950
  properties[fieldKey] = schemaProp;
933
- if (!name)
951
+ if (!rawName)
934
952
  fieldElements.set(fieldKey, control);
935
953
  if (control.required)
936
954
  required.push(fieldKey);
937
955
  }
938
- return { schema: { type: "object", properties, required }, fieldElements };
956
+ return { schema: { "$schema": "https://json-schema.org/draft/2020-12/schema", type: "object", properties, required }, fieldElements };
939
957
  }
940
958
 
941
959
  // src/discovery.ts
@@ -1442,7 +1460,7 @@ async function registerForm(form, config) {
1442
1460
  registeredForms.add(form);
1443
1461
  registeredFormCount++;
1444
1462
  if (config.debug) {
1445
- console.debug(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
1463
+ console.log(`[auto-webmcp] Registered: ${metadata.name}`, metadata);
1446
1464
  }
1447
1465
  emit("form:registered", form, metadata.name);
1448
1466
  }
@@ -1454,7 +1472,7 @@ async function unregisterForm(form, config) {
1454
1472
  await unregisterFormTool(form);
1455
1473
  registeredForms.delete(form);
1456
1474
  if (config.debug) {
1457
- console.debug(`[auto-webmcp] Unregistered: ${name}`);
1475
+ console.log(`[auto-webmcp] Unregistered: ${name}`);
1458
1476
  }
1459
1477
  emit("form:unregistered", form, name);
1460
1478
  }
@@ -1555,7 +1573,7 @@ async function scanOrphanInputs(config) {
1555
1573
  if (!isWebMCPSupported())
1556
1574
  return;
1557
1575
  const SUBMIT_BTN_SELECTOR = '[type="submit"]:not([disabled]), button:not([type]):not([disabled])';
1558
- const SUBMIT_BTN_GROUPING_SELECTOR = '[type="submit"], button:not([type])';
1576
+ const SUBMIT_BTN_GROUPING_SELECTOR = '[type="submit"]';
1559
1577
  const SUBMIT_TEXT_RE = /subscribe|submit|sign[\s-]?up|send|join|go|search/i;
1560
1578
  const orphanInputs = Array.from(
1561
1579
  document.querySelectorAll(
@@ -1563,11 +1581,17 @@ async function scanOrphanInputs(config) {
1563
1581
  )
1564
1582
  ).filter((el) => {
1565
1583
  if (el instanceof HTMLInputElement && ORPHAN_EXCLUDED_TYPES.has(el.type.toLowerCase())) {
1584
+ console.log(`[auto-webmcp] orphan: skipping excluded type "${el.type}" (name="${el.name}" id="${el.id}")`);
1566
1585
  return false;
1567
1586
  }
1568
1587
  const rect = el.getBoundingClientRect();
1569
- return rect.width > 0 && rect.height > 0;
1588
+ if (rect.width === 0 || rect.height === 0) {
1589
+ console.log(`[auto-webmcp] orphan: skipping invisible input (name="${el.name}" id="${el.id}")`);
1590
+ return false;
1591
+ }
1592
+ return true;
1570
1593
  });
1594
+ console.log(`[auto-webmcp] orphan: found ${orphanInputs.length} visible orphan input(s)`);
1571
1595
  if (orphanInputs.length === 0)
1572
1596
  return;
1573
1597
  const groups = /* @__PURE__ */ new Map();
@@ -1584,10 +1608,12 @@ async function scanOrphanInputs(config) {
1584
1608
  }
1585
1609
  container = container.parentElement;
1586
1610
  }
1611
+ console.log(`[auto-webmcp] orphan: input (name="${input.name}" id="${input.id}") grouped into container`, foundContainer);
1587
1612
  if (!groups.has(foundContainer))
1588
1613
  groups.set(foundContainer, []);
1589
1614
  groups.get(foundContainer).push(input);
1590
1615
  }
1616
+ console.log(`[auto-webmcp] orphan: ${groups.size} group(s) found`);
1591
1617
  for (const [container, inputs] of groups) {
1592
1618
  const allCandidates = Array.from(
1593
1619
  container.querySelectorAll(SUBMIT_BTN_SELECTOR)
@@ -1596,6 +1622,12 @@ async function scanOrphanInputs(config) {
1596
1622
  return r.width > 0 && r.height > 0;
1597
1623
  });
1598
1624
  let submitBtn = allCandidates[allCandidates.length - 1] ?? null;
1625
+ const disabledCandidates = Array.from(
1626
+ container.querySelectorAll(SUBMIT_BTN_GROUPING_SELECTOR)
1627
+ ).filter((b) => b.disabled);
1628
+ if (!submitBtn && disabledCandidates.length > 0) {
1629
+ 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()));
1630
+ }
1599
1631
  if (!submitBtn) {
1600
1632
  const pageBtns = Array.from(document.querySelectorAll("button")).filter(
1601
1633
  (b) => {
@@ -1604,36 +1636,82 @@ async function scanOrphanInputs(config) {
1604
1636
  }
1605
1637
  );
1606
1638
  submitBtn = pageBtns[pageBtns.length - 1] ?? null;
1639
+ if (submitBtn)
1640
+ console.log(`[auto-webmcp] orphan: using page-wide fallback submit button: "${submitBtn.textContent?.trim()}"`);
1607
1641
  }
1642
+ console.log(`[auto-webmcp] orphan: submit button for group:`, submitBtn ? `"${submitBtn.textContent?.trim()}" disabled=${submitBtn.disabled}` : "none");
1608
1643
  const metadata = analyzeOrphanInputGroup(container, inputs, submitBtn);
1644
+ console.log(`[auto-webmcp] orphan: tool="${metadata.name}" schema keys:`, Object.keys(metadata.inputSchema.properties));
1609
1645
  const inputPairs = [];
1610
1646
  const schemaProps = metadata.inputSchema.properties;
1647
+ const AUTO_ID_RE = /^_r_[0-9a-z]+_$/i;
1611
1648
  for (const el of inputs) {
1612
- const key = el.name || el.dataset["webmcpName"] || el.id || el.getAttribute("aria-label") || null;
1649
+ const id = el.id && !AUTO_ID_RE.test(el.id) ? el.id : null;
1650
+ const key = el.name || el.dataset["webmcpName"] || id || el.getAttribute("aria-label") || (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el.placeholder || null : null) || null;
1613
1651
  const safeKey = key ? key.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) : null;
1614
- if (safeKey && schemaProps[safeKey]) {
1652
+ const matched = !!(safeKey && schemaProps[safeKey]);
1653
+ console.log(`[auto-webmcp] orphan: field (name="${el.name}" id="${el.id}") rawKey="${key}" safeKey="${safeKey}" matched=${matched}`);
1654
+ if (matched) {
1615
1655
  inputPairs.push({ key: safeKey, el });
1616
1656
  }
1617
1657
  }
1658
+ console.log(`[auto-webmcp] orphan: ${inputPairs.length}/${inputs.length} input(s) mapped to schema keys`);
1618
1659
  const toolName = metadata.name;
1619
1660
  const execute = async (params) => {
1661
+ console.log(`[auto-webmcp] orphan execute: tool="${toolName}" params=`, params);
1662
+ console.log(`[auto-webmcp] orphan execute: inputPairs=`, inputPairs.map((p) => p.key));
1620
1663
  for (const { key, el } of inputPairs) {
1621
1664
  if (params[key] !== void 0) {
1665
+ console.log(`[auto-webmcp] orphan execute: filling key="${key}" value=`, params[key], "element=", el);
1622
1666
  fillElement(el, params[key]);
1667
+ console.log(`[auto-webmcp] orphan execute: after fill, element value=`, el.value);
1668
+ } else {
1669
+ console.log(`[auto-webmcp] orphan execute: key="${key}" not in params, skipping`);
1623
1670
  }
1624
1671
  }
1625
1672
  window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
1626
- return { content: [{ type: "text", text: "Fields filled. Ready to submit." }] };
1673
+ if (!config.autoSubmit) {
1674
+ console.log(`[auto-webmcp] orphan execute: autoSubmit=false, returning without clicking submit`);
1675
+ return { content: [{ type: "text", text: "Fields filled. Ready to submit." }] };
1676
+ }
1677
+ console.log(`[auto-webmcp] orphan execute: polling for enabled submit button (up to 2s)...`);
1678
+ let btn = null;
1679
+ const deadline = Date.now() + 2e3;
1680
+ while (Date.now() < deadline) {
1681
+ const candidates = Array.from(
1682
+ container.querySelectorAll(SUBMIT_BTN_SELECTOR)
1683
+ ).filter((b) => {
1684
+ const r = b.getBoundingClientRect();
1685
+ return r.width > 0 && r.height > 0;
1686
+ });
1687
+ const last = candidates[candidates.length - 1] ?? null;
1688
+ if (last) {
1689
+ btn = last;
1690
+ break;
1691
+ }
1692
+ await new Promise((r) => setTimeout(r, 100));
1693
+ }
1694
+ if (!btn) {
1695
+ console.warn(`[auto-webmcp] orphan execute: submit button still disabled after 2s`);
1696
+ return { content: [{ type: "text", text: "Fields filled but the submit button is still disabled. The page may require additional input before submitting." }] };
1697
+ }
1698
+ console.log(`[auto-webmcp] orphan execute: clicking submit button "${btn.textContent?.trim()}"`);
1699
+ btn.click();
1700
+ return { content: [{ type: "text", text: "Fields filled and form submitted." }] };
1627
1701
  };
1628
1702
  try {
1629
- await navigator.modelContext.registerTool({
1703
+ const toolDef = {
1630
1704
  name: metadata.name,
1631
1705
  description: metadata.description,
1632
1706
  inputSchema: metadata.inputSchema,
1633
1707
  execute
1634
- });
1708
+ };
1709
+ if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
1710
+ toolDef.annotations = metadata.annotations;
1711
+ }
1712
+ await navigator.modelContext.registerTool(toolDef);
1635
1713
  if (config.debug) {
1636
- console.debug(`[auto-webmcp] Orphan tool registered: ${metadata.name}`, metadata);
1714
+ console.log(`[auto-webmcp] Orphan tool registered: ${metadata.name}`, metadata);
1637
1715
  }
1638
1716
  } catch {
1639
1717
  }