@geometra/mcp 1.44.0 → 1.46.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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { valuesEquivalent } from '../server.js';
|
|
3
|
+
describe('valuesEquivalent', () => {
|
|
4
|
+
describe('plain strings', () => {
|
|
5
|
+
it('matches identical strings case-insensitively', () => {
|
|
6
|
+
expect(valuesEquivalent('Charlie', 'charlie')).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it('matches with whitespace collapse', () => {
|
|
9
|
+
expect(valuesEquivalent('Charlie Greenman', 'Charlie Greenman')).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
it('rejects unrelated strings', () => {
|
|
12
|
+
expect(valuesEquivalent('Charlie', 'Alice')).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('phone numbers', () => {
|
|
16
|
+
it('matches identical phone signatures', () => {
|
|
17
|
+
expect(valuesEquivalent('9296081737', '9296081737')).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
// Bug surfaced by JobForge round-2 marathon — Cloudflare FDE NYC #312.
|
|
20
|
+
// Greenhouse formats `+1-929-608-1737` → `(929) 608-1737`. The expected
|
|
21
|
+
// side carries the country code; the actual readback drops it. The
|
|
22
|
+
// strict-equality digit comparison previously rejected this as a
|
|
23
|
+
// mismatch even though the underlying value is correct.
|
|
24
|
+
it('matches phone numbers when expected has country code and actual does not', () => {
|
|
25
|
+
expect(valuesEquivalent('+1-929-608-1737', '(929) 608-1737')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it('matches phone numbers when actual has country code and expected does not', () => {
|
|
28
|
+
expect(valuesEquivalent('(929) 608-1737', '+1-929-608-1737')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('matches across many ATS auto-format variants', () => {
|
|
31
|
+
const expected = '+19296081737';
|
|
32
|
+
expect(valuesEquivalent(expected, '(929) 608-1737')).toBe(true);
|
|
33
|
+
expect(valuesEquivalent(expected, '929-608-1737')).toBe(true);
|
|
34
|
+
expect(valuesEquivalent(expected, '929.608.1737')).toBe(true);
|
|
35
|
+
expect(valuesEquivalent(expected, '929 608 1737')).toBe(true);
|
|
36
|
+
expect(valuesEquivalent(expected, '+1 (929) 608-1737')).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it('rejects two phone numbers that differ in the local 10 digits', () => {
|
|
39
|
+
expect(valuesEquivalent('+1-929-608-1737', '(415) 555-0123')).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
it('rejects suffix-match below the 7-digit local minimum', () => {
|
|
42
|
+
// Pure 6-digit IDs must not match against a longer phone signature
|
|
43
|
+
// even if the IDs happen to be a digit-suffix.
|
|
44
|
+
expect(valuesEquivalent('19296081737', '081737')).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('formatted numbers', () => {
|
|
48
|
+
it('matches a salary across comma formatting', () => {
|
|
49
|
+
expect(valuesEquivalent('$160000', '$160,000')).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
package/dist/server.d.ts
CHANGED
|
@@ -21,5 +21,6 @@ interface NodeFilter {
|
|
|
21
21
|
busy?: boolean;
|
|
22
22
|
}
|
|
23
23
|
export declare function createServer(): McpServer;
|
|
24
|
+
export declare function valuesEquivalent(expected: string, actual: string): boolean;
|
|
24
25
|
export declare function findNodes(node: A11yNode, filter: NodeFilter): A11yNode[];
|
|
25
26
|
export {};
|
package/dist/server.js
CHANGED
|
@@ -2600,7 +2600,26 @@ function fieldStatePayload(session, fieldLabel) {
|
|
|
2600
2600
|
};
|
|
2601
2601
|
}
|
|
2602
2602
|
function waitStatusPayload(wait) {
|
|
2603
|
-
|
|
2603
|
+
if (!wait)
|
|
2604
|
+
return {};
|
|
2605
|
+
const payload = { wait: wait.status };
|
|
2606
|
+
// Surface navigation info from proxy click handlers so callers can tell
|
|
2607
|
+
// when a click triggered a full-page nav (form submit → thank-you page).
|
|
2608
|
+
// Without this, the proxy session may die on the next request and the
|
|
2609
|
+
// caller would see session_not_found with no clue WHY. Bug surfaced by
|
|
2610
|
+
// JobForge round-2 marathon — Cloudflare FDE NYC #312 and Airtable PM
|
|
2611
|
+
// AI #94 both had Submit-clicks that navigated and tore down the proxy.
|
|
2612
|
+
if (wait.result && typeof wait.result === 'object') {
|
|
2613
|
+
const result = wait.result;
|
|
2614
|
+
if (result.navigated === true) {
|
|
2615
|
+
payload.navigated = true;
|
|
2616
|
+
if (typeof result.pageUrl === 'string')
|
|
2617
|
+
payload.pageUrl = result.pageUrl;
|
|
2618
|
+
if (typeof result.urlBefore === 'string')
|
|
2619
|
+
payload.urlBefore = result.urlBefore;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
return payload;
|
|
2604
2623
|
}
|
|
2605
2624
|
function compactFilterPayload(filter) {
|
|
2606
2625
|
return Object.fromEntries(Object.entries(filter).filter(([, value]) => value !== undefined));
|
|
@@ -3582,12 +3601,32 @@ function looksLikeFormattedNumber(value) {
|
|
|
3582
3601
|
function digitSignature(value) {
|
|
3583
3602
|
return value.replace(/\D+/g, '');
|
|
3584
3603
|
}
|
|
3585
|
-
function valuesEquivalent(expected, actual) {
|
|
3604
|
+
export function valuesEquivalent(expected, actual) {
|
|
3586
3605
|
if (expected.toLowerCase() === actual.toLowerCase())
|
|
3587
3606
|
return true;
|
|
3588
|
-
// Both look like phones → compare digit signatures
|
|
3607
|
+
// Both look like phones → compare digit signatures, but allow one to be
|
|
3608
|
+
// a suffix of the other so that an explicit country code on the expected
|
|
3609
|
+
// side ("+1-929-608-1737" → digits "19296081737") still matches an ATS
|
|
3610
|
+
// readback that omits it ("(929) 608-1737" → digits "9296081737"). The
|
|
3611
|
+
// suffix check is direction-agnostic — either side may carry the country
|
|
3612
|
+
// code. Without this, Greenhouse/Workday/Lever auto-formatted phone
|
|
3613
|
+
// fields false-flag as mismatched even though the value is correct.
|
|
3614
|
+
// Bug surfaced by JobForge round-2 marathon — Cloudflare FDE NYC #312.
|
|
3589
3615
|
if (looksLikePhoneNumber(expected) && looksLikePhoneNumber(actual)) {
|
|
3590
|
-
|
|
3616
|
+
const eDigits = digitSignature(expected);
|
|
3617
|
+
const aDigits = digitSignature(actual);
|
|
3618
|
+
if (!eDigits || !aDigits)
|
|
3619
|
+
return false;
|
|
3620
|
+
if (eDigits === aDigits)
|
|
3621
|
+
return true;
|
|
3622
|
+
// Suffix tolerance for country-code drift — only accept if the longer
|
|
3623
|
+
// signature ends with the shorter, AND the shorter is at least 7 digits
|
|
3624
|
+
// (NANP local minimum) so we don't false-match on tiny extension ids.
|
|
3625
|
+
const longer = eDigits.length >= aDigits.length ? eDigits : aDigits;
|
|
3626
|
+
const shorter = eDigits.length >= aDigits.length ? aDigits : eDigits;
|
|
3627
|
+
if (shorter.length >= 7 && longer.endsWith(shorter))
|
|
3628
|
+
return true;
|
|
3629
|
+
return false;
|
|
3591
3630
|
}
|
|
3592
3631
|
// Both look like formatted numbers → compare digit signatures only
|
|
3593
3632
|
if (looksLikeFormattedNumber(expected) || looksLikeFormattedNumber(actual)) {
|
package/dist/session.js
CHANGED
|
@@ -645,19 +645,46 @@ export function connect(url, opts) {
|
|
|
645
645
|
let resolved = false;
|
|
646
646
|
let lastMessageAt = Date.now();
|
|
647
647
|
let heartbeatInterval = null;
|
|
648
|
+
let pendingPongBy = null;
|
|
649
|
+
// Heartbeat: send a real WS-level ping every 15s and only tear the socket
|
|
650
|
+
// down if the peer fails to respond to two consecutive pings (i.e. ~45s of
|
|
651
|
+
// true unresponsiveness). Previous versions used a dumb idle timer that
|
|
652
|
+
// closed the socket after 30s of no inbound frames — which killed sessions
|
|
653
|
+
// during normal form-submission flows where the DOM is legitimately idle
|
|
654
|
+
// for 20-30+ seconds while the backend processes (Greenhouse submit →
|
|
655
|
+
// security-code dialog is the canonical repro). A real ping/pong cycle
|
|
656
|
+
// distinguishes a silent-but-healthy session from a dead one.
|
|
648
657
|
function startHeartbeat() {
|
|
649
658
|
if (heartbeatInterval)
|
|
650
659
|
return;
|
|
651
660
|
heartbeatInterval = setInterval(() => {
|
|
652
|
-
|
|
661
|
+
// If we're waiting on a pong and it's overdue, the peer is dead.
|
|
662
|
+
if (pendingPongBy !== null && Date.now() > pendingPongBy) {
|
|
653
663
|
try {
|
|
654
664
|
ws.close();
|
|
655
665
|
}
|
|
656
666
|
catch { /* ignore */ }
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
// Only send a new ping if we haven't heard anything for a while,
|
|
670
|
+
// to avoid spamming a chatty session.
|
|
671
|
+
if (Date.now() - lastMessageAt > 10_000) {
|
|
672
|
+
try {
|
|
673
|
+
ws.ping();
|
|
674
|
+
// Allow 30s for the pong before declaring the peer dead.
|
|
675
|
+
pendingPongBy = Date.now() + 30_000;
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
/* if ping throws, the socket is already gone — let 'close' handle */
|
|
679
|
+
}
|
|
657
680
|
}
|
|
658
|
-
},
|
|
681
|
+
}, 15_000);
|
|
659
682
|
heartbeatInterval.unref();
|
|
660
683
|
}
|
|
684
|
+
ws.on('pong', () => {
|
|
685
|
+
lastMessageAt = Date.now();
|
|
686
|
+
pendingPongBy = null;
|
|
687
|
+
});
|
|
661
688
|
const timeout = setTimeout(() => {
|
|
662
689
|
if (!resolved) {
|
|
663
690
|
resolved = true;
|
package/package.json
CHANGED