@geometra/mcp 1.43.0 → 1.44.0

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/server.js CHANGED
@@ -3546,6 +3546,62 @@ function suggestRecovery(field, error) {
3546
3546
  }
3547
3547
  return undefined;
3548
3548
  }
3549
+ // Normalize a value for verifyFills comparison so caller-friendly inputs
3550
+ // match site-formatted readbacks. The legacy comparison was strict lowercase
3551
+ // only — which broke every form that auto-formats phone numbers, dates, or
3552
+ // currency fields. Greenhouse turns "+1-929-608-1737" into "(929) 608-1737";
3553
+ // Workday turns "$160000" into "$160,000.00"; Lever turns "2026-01-01" into
3554
+ // "01/01/2026". The lowercase comparator flagged all of these as mismatches
3555
+ // even though the field state was correct, forcing every caller to either
3556
+ // disable verifyFills or wrap it in defensive try/catch.
3557
+ //
3558
+ // The fix: detect phone-like and number-like values and compare on the
3559
+ // canonical digit sequence. Plain text and short strings still go through
3560
+ // the strict lowercase comparator so unrelated content can't accidentally
3561
+ // match (e.g. "1234 Main St" vs "12-34 Main").
3562
+ function looksLikePhoneNumber(value) {
3563
+ // Heuristic: at least 7 digits, and after stripping whitespace + the
3564
+ // common phone separator characters (+ - . ( ) space ext) only digits
3565
+ // and a single optional leading + remain. Catches international and
3566
+ // domestic formats without false-matching addresses or IDs.
3567
+ const stripped = value.replace(/[\s().\-+x]|ext\.?/gi, '');
3568
+ if (stripped.length < 7)
3569
+ return false;
3570
+ if (!/^\d+$/.test(stripped))
3571
+ return false;
3572
+ return /\d.*[\s().\-+]|^\+\d/.test(value) || /^\d{7,}$/.test(value);
3573
+ }
3574
+ function looksLikeFormattedNumber(value) {
3575
+ // Catches "$160,000.00" / "1,000,000" / "1.5e6" style readbacks where the
3576
+ // site adds thousands separators or currency prefixes. Requires at least
3577
+ // one comma OR currency prefix to avoid matching plain text.
3578
+ if (!/[$€£¥,]/.test(value))
3579
+ return false;
3580
+ return /\d/.test(value);
3581
+ }
3582
+ function digitSignature(value) {
3583
+ return value.replace(/\D+/g, '');
3584
+ }
3585
+ function valuesEquivalent(expected, actual) {
3586
+ if (expected.toLowerCase() === actual.toLowerCase())
3587
+ return true;
3588
+ // Both look like phones → compare digit signatures only
3589
+ if (looksLikePhoneNumber(expected) && looksLikePhoneNumber(actual)) {
3590
+ return digitSignature(expected) === digitSignature(actual);
3591
+ }
3592
+ // Both look like formatted numbers → compare digit signatures only
3593
+ if (looksLikeFormattedNumber(expected) || looksLikeFormattedNumber(actual)) {
3594
+ const eDigits = digitSignature(expected);
3595
+ const aDigits = digitSignature(actual);
3596
+ if (eDigits && aDigits && eDigits === aDigits)
3597
+ return true;
3598
+ }
3599
+ // Whitespace normalization for everything else (handles "Charlie Greenman"
3600
+ // vs "Charlie Greenman" auto-collapsed by ATS forms)
3601
+ const eNorm = expected.replace(/\s+/g, ' ').trim().toLowerCase();
3602
+ const aNorm = actual.replace(/\s+/g, ' ').trim().toLowerCase();
3603
+ return eNorm === aNorm;
3604
+ }
3549
3605
  function verifyFormFills(session, planned) {
3550
3606
  const a11y = sessionA11y(session);
3551
3607
  if (!a11y)
@@ -3566,7 +3622,7 @@ function verifyFormFills(session, planned) {
3566
3622
  if (!actual || !expected) {
3567
3623
  mismatches.push({ fieldLabel: label, expected, actual, ...(p.field.fieldId ? { fieldId: p.field.fieldId } : {}) });
3568
3624
  }
3569
- else if (actual.toLowerCase() !== expected.toLowerCase()) {
3625
+ else if (!valuesEquivalent(expected, actual)) {
3570
3626
  mismatches.push({ fieldLabel: label, expected, actual, ...(p.field.fieldId ? { fieldId: p.field.fieldId } : {}) });
3571
3627
  }
3572
3628
  else {
package/dist/session.js CHANGED
@@ -1782,12 +1782,26 @@ function compactSchemaContext(context, label) {
1782
1782
  return Object.keys(out).length > 0 ? out : undefined;
1783
1783
  }
1784
1784
  function compactSchemaValue(value, inlineLimit = 80) {
1785
- const normalized = sanitizeInlineName(value, Math.max(120, inlineLimit + 32));
1786
- if (!normalized)
1785
+ // Measure the length of the FULL whitespace-normalized value first, before
1786
+ // any inline truncation. The previous implementation called sanitizeInlineName
1787
+ // with max=120 and then read normalized.length, which capped reported length
1788
+ // at 120 even when the actual filled content was 1000+ characters. That made
1789
+ // form-required snapshots look like long-textarea fills had only landed
1790
+ // ~120 chars, when in reality the field was correctly filled — agents then
1791
+ // re-typed the same content thinking they had a partial fill, doubling the
1792
+ // value or hitting the textarea length cap.
1793
+ if (!value)
1794
+ return {};
1795
+ const fullNormalized = normalizeUiText(value);
1796
+ if (!fullNormalized)
1787
1797
  return {};
1788
- return normalized.length <= inlineLimit
1789
- ? { value: normalized }
1790
- : { valueLength: normalized.length };
1798
+ const fullLength = fullNormalized.length;
1799
+ const inlineNormalized = sanitizeInlineName(value, Math.max(120, inlineLimit + 32));
1800
+ if (!inlineNormalized)
1801
+ return { valueLength: fullLength };
1802
+ return fullLength <= inlineLimit
1803
+ ? { value: inlineNormalized }
1804
+ : { valueLength: fullLength };
1791
1805
  }
1792
1806
  function schemaOptionLabel(node) {
1793
1807
  return sanitizeFieldName(node.name, 80) ?? sanitizeInlineName(node.name, 80);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geometra/mcp",
3
- "version": "1.43.0",
3
+ "version": "1.44.0",
4
4
  "description": "MCP server for Geometra — interact with running Geometra apps via the geometry protocol, no browser needed",
5
5
  "license": "MIT",
6
6
  "type": "module",