@geometra/mcp 1.61.2 → 1.61.3
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/README.md +6 -5
- package/dist/proxy-spawn.d.ts +2 -0
- package/dist/proxy-spawn.js +20 -0
- package/dist/server.js +34 -9
- package/dist/session.d.ts +3 -0
- package/dist/session.js +19 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,14 +18,14 @@ Geometra proxy: Chromium → DOM geometry → same WebSocket as native →
|
|
|
18
18
|
|
|
19
19
|
Use Geometra MCP when an LLM needs to explore, interpret, and operate a real UI with compact semantic state instead of repeatedly consuming large browser snapshots. Keep Playwright-style tooling for deterministic scripts, DOM-oriented test automation, and compatibility fallback paths while Geometra closes remaining live-site gaps.
|
|
20
20
|
|
|
21
|
-
Proxy-backed sessions stay warm by default on disconnect, and MCP now keeps a small warm pool so compatible headed and
|
|
21
|
+
Proxy-backed sessions stay warm by default on disconnect, and MCP now keeps a small warm pool so compatible headed, headless, and stealth workflows do not immediately evict each other.
|
|
22
22
|
|
|
23
23
|
## Tools
|
|
24
24
|
|
|
25
25
|
| Tool | Description |
|
|
26
26
|
|---|---|
|
|
27
|
-
| `geometra_connect` | Connect with `url` (ws://…) **or** `pageUrl` (https://…) to auto-start geometra-proxy; can inline `formSchema` and/or `pageModel`, or defer the page model for a faster first connect response |
|
|
28
|
-
| `geometra_prepare_browser` | Pre-launch and pre-navigate a reusable proxy/browser for `pageUrl` without creating an active session;
|
|
27
|
+
| `geometra_connect` | Connect with `url` (ws://…) **or** `pageUrl` (https://…) to auto-start geometra-proxy; pass `stealth: true` for CloakBrowser's patched Chromium; can inline `formSchema` and/or `pageModel`, or defer the page model for a faster first connect response |
|
|
28
|
+
| `geometra_prepare_browser` | Pre-launch and pre-navigate a reusable proxy/browser for `pageUrl` without creating an active session; supports the same headed/headless/stealth browser settings as `geometra_connect` |
|
|
29
29
|
| `geometra_query` | Find elements by stable id, role, name, text content, prompt/section/item context, current value, or semantic state such as `invalid`, `required`, or `busy` |
|
|
30
30
|
| `geometra_wait_for` | Wait for a semantic condition instead of guessing sleeps (`busy`, `disabled`, alerts, values, etc.). **Strict parameters** — use `text` plus `present: false` to wait until a substring disappears (e.g. “Parsing your resume”); there is no `textGone` field |
|
|
31
31
|
| `geometra_wait_for_resume_parse` | Convenience wait until a parsing banner is gone (defaults: `text`: `"Parsing"`, `present` implied false). Same engine as `geometra_wait_for` |
|
|
@@ -252,9 +252,10 @@ In another terminal (from repo root after `npm install` / `bun install` and `bun
|
|
|
252
252
|
```bash
|
|
253
253
|
npx geometra-proxy http://localhost:8080 --port 3200
|
|
254
254
|
# Requires Chromium: npx playwright install chromium
|
|
255
|
+
# Optional stealth prefetch: npx cloakbrowser install
|
|
255
256
|
```
|
|
256
257
|
|
|
257
|
-
`geometra-proxy` opens a **visible Chromium window by default**. For servers or CI, pass **`--headless`** or set **`GEOMETRA_HEADLESS=1`**. Optional **`--slow-mo <ms>`** slows Playwright actions so they are easier to watch. Headed vs headless usually does **not** materially change token usage, since token usage is driven by MCP tool output rather than whether Chromium is visible.
|
|
258
|
+
`geometra-proxy` opens a **visible Chromium window by default**. For servers or CI, pass **`--headless`** or set **`GEOMETRA_HEADLESS=1`**. Pass **`--stealth`**, **`stealth: true`** on MCP tools, or **`GEOMETRA_STEALTH=1`** to use CloakBrowser's patched Chromium through the same proxy protocol. Optional **`--slow-mo <ms>`** slows Playwright actions so they are easier to watch. Headed vs headless usually does **not** materially change token usage, since token usage is driven by MCP tool output rather than whether Chromium is visible.
|
|
258
259
|
|
|
259
260
|
Point MCP at `ws://127.0.0.1:3200` instead of a native Geometra server. The proxy translates clicks and keyboard messages into Playwright actions and streams updated geometry.
|
|
260
261
|
|
|
@@ -341,7 +342,7 @@ For long application flows, prefer one of these patterns:
|
|
|
341
342
|
4. `geometra_snapshot({ view: "form-required" })` when you need the remaining required fields including offscreen ones
|
|
342
343
|
5. `geometra_reveal` for far-below-fold targets such as submit buttons (auto-scales reveal steps when you omit `maxSteps`)
|
|
343
344
|
6. `geometra_click({ ..., waitFor: ... })` when one action should also wait for the next semantic state
|
|
344
|
-
7. `geometra_prepare_browser({ pageUrl, headless, width, height })` when you can hide browser startup before the real task and want the next proxy-backed flow to attach warm
|
|
345
|
+
7. `geometra_prepare_browser({ pageUrl, headless, stealth, width, height })` when you can hide browser startup before the real task and want the next proxy-backed flow to attach warm
|
|
345
346
|
8. `geometra_run_actions` when you need mixed navigation + waits + field entry, especially with `pageUrl` / `url` for a one-call flow
|
|
346
347
|
9. `geometra_connect({ pageUrl, returnPageModel: true })` when you want connect + summary-first exploration in one turn
|
|
347
348
|
10. `geometra_connect({ pageUrl, returnPageModel: true, pageModelMode: "deferred" })` when first-response latency matters more than inlining the page model; follow with `geometra_page_model`
|
package/dist/proxy-spawn.d.ts
CHANGED
|
@@ -31,9 +31,11 @@ export interface SpawnProxyParams {
|
|
|
31
31
|
width?: number;
|
|
32
32
|
height?: number;
|
|
33
33
|
slowMo?: number;
|
|
34
|
+
stealth?: boolean;
|
|
34
35
|
eagerInitialExtract?: boolean;
|
|
35
36
|
proxy?: SpawnProxyConfig;
|
|
36
37
|
}
|
|
38
|
+
export declare function resolveStealthMode(stealth?: boolean): boolean;
|
|
37
39
|
export declare function startEmbeddedGeometraProxy(opts: SpawnProxyParams): Promise<{
|
|
38
40
|
runtime: EmbeddedProxyRuntime;
|
|
39
41
|
wsUrl: string;
|
package/dist/proxy-spawn.js
CHANGED
|
@@ -125,6 +125,18 @@ function buildLocalProxyDistIfPossible(packageDir, entryFile, errors) {
|
|
|
125
125
|
}
|
|
126
126
|
return undefined;
|
|
127
127
|
}
|
|
128
|
+
function envRequestsStealth() {
|
|
129
|
+
const explicit = process.env.GEOMETRA_STEALTH;
|
|
130
|
+
if (explicit !== undefined) {
|
|
131
|
+
const v = explicit.toLowerCase();
|
|
132
|
+
return v === '1' || v === 'true' || v === 'yes' || v === 'stealth' || v === 'cloak';
|
|
133
|
+
}
|
|
134
|
+
const browser = (process.env.GEOMETRA_BROWSER ?? '').toLowerCase();
|
|
135
|
+
return browser === 'stealth' || browser === 'cloak' || browser === 'cloakbrowser';
|
|
136
|
+
}
|
|
137
|
+
export function resolveStealthMode(stealth) {
|
|
138
|
+
return stealth ?? envRequestsStealth();
|
|
139
|
+
}
|
|
128
140
|
export async function startEmbeddedGeometraProxy(opts) {
|
|
129
141
|
const runtimePath = resolveProxyRuntimePath();
|
|
130
142
|
const runtimeModule = await import(pathToFileURL(runtimePath).href);
|
|
@@ -138,6 +150,7 @@ export async function startEmbeddedGeometraProxy(opts) {
|
|
|
138
150
|
height: opts.height,
|
|
139
151
|
headed: opts.headless !== true,
|
|
140
152
|
slowMo: opts.slowMo,
|
|
153
|
+
...(opts.stealth !== undefined && { stealth: opts.stealth }),
|
|
141
154
|
eagerInitialExtract: opts.eagerInitialExtract,
|
|
142
155
|
...(opts.proxy && { proxy: opts.proxy }),
|
|
143
156
|
});
|
|
@@ -168,6 +181,9 @@ export function formatProxyStartupFailure(message, opts) {
|
|
|
168
181
|
if (/Executable doesn't exist|playwright install chromium|browserType\.launch/i.test(message)) {
|
|
169
182
|
hints.push('Install Chromium with: npx playwright install chromium');
|
|
170
183
|
}
|
|
184
|
+
if (/cloakbrowser|CLOAKBROWSER|ERR_MODULE_NOT_FOUND|Cannot find package/i.test(message)) {
|
|
185
|
+
hints.push('Stealth mode uses CloakBrowser. Install dependencies with npm install, or disable stealth with stealth=false / GEOMETRA_STEALTH=0. To prefetch the patched Chromium binary, run: npx cloakbrowser install');
|
|
186
|
+
}
|
|
171
187
|
if (opts.port > 0 && /EADDRINUSE|address already in use/i.test(message)) {
|
|
172
188
|
hints.push(`Requested port ${opts.port} is unavailable. Omit the port to use an ephemeral OS-assigned port, or choose another local port.`);
|
|
173
189
|
}
|
|
@@ -191,6 +207,10 @@ export function spawnGeometraProxy(opts) {
|
|
|
191
207
|
args.push('--headless');
|
|
192
208
|
else if (opts.headless === false)
|
|
193
209
|
args.push('--headed');
|
|
210
|
+
if (opts.stealth === true)
|
|
211
|
+
args.push('--stealth');
|
|
212
|
+
else if (opts.stealth === false)
|
|
213
|
+
args.push('--no-stealth');
|
|
194
214
|
if (opts.eagerInitialExtract === false)
|
|
195
215
|
args.push('--lazy-initial-extract');
|
|
196
216
|
if (opts.proxy?.server) {
|
package/dist/server.js
CHANGED
|
@@ -17,6 +17,12 @@ function detailInput() {
|
|
|
17
17
|
.default('minimal')
|
|
18
18
|
.describe('`terse` returns compact machine-friendly JSON. `minimal` (default) returns short human-readable summaries. `verbose` adds fuller fallback context.');
|
|
19
19
|
}
|
|
20
|
+
function stealthInput() {
|
|
21
|
+
return z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Launch CloakBrowser stealth Chromium for proxy-backed pageUrl sessions. Default false unless GEOMETRA_STEALTH=1 or GEOMETRA_BROWSER=stealth is set.');
|
|
25
|
+
}
|
|
20
26
|
function formSchemaFormatInput() {
|
|
21
27
|
return z
|
|
22
28
|
.enum(['compact', 'packed'])
|
|
@@ -401,7 +407,7 @@ export function createServer() {
|
|
|
401
407
|
|
|
402
408
|
Use \`url\` (ws://…) only when a Geometra/native server or an already-running proxy is listening. If you accidentally pass \`https://…\` in \`url\`, MCP treats it like \`pageUrl\` and starts the proxy for you.
|
|
403
409
|
|
|
404
|
-
Chromium opens **visible** by default unless \`headless: true\`. File upload / wheel / native \`<select>\` need the proxy path (\`pageUrl\` or ws to proxy). Set \`returnForms: true\` and/or \`returnPageModel: true\` when you want a lower-turn startup response. When connect first-response latency matters more than inlining the page model, pair \`returnPageModel: true\` with \`pageModelMode: "deferred"\` and call \`geometra_page_model\` next.
|
|
410
|
+
Chromium opens **visible** by default unless \`headless: true\`. Pass \`stealth: true\` (or set \`GEOMETRA_STEALTH=1\`) to launch CloakBrowser's patched Chromium instead of stock Playwright Chromium. File upload / wheel / native \`<select>\` need the proxy path (\`pageUrl\` or ws to proxy). Set \`returnForms: true\` and/or \`returnPageModel: true\` when you want a lower-turn startup response. When connect first-response latency matters more than inlining the page model, pair \`returnPageModel: true\` with \`pageModelMode: "deferred"\` and call \`geometra_page_model\` next.
|
|
405
411
|
|
|
406
412
|
**Parallelism:** by default, geometra MCP pools and reuses Chromium instances across sessions for speed. That pooling is safe for read-only exploration, but it shares localStorage / cookies / page state across whichever sessions land on the same proxy — which means **two parallel form-submission flows can contaminate each other** (one job's email/autocomplete state leaks into another, or worse, two agents end up driving the same browser tab). For parallel apply / form submission, pass \`isolated: true\`. Each isolated session gets its own brand-new Chromium that is destroyed on disconnect, never enters the pool, and is guaranteed independent of every other session. The cost is ~1–2s of extra startup vs the ~50ms reusable-proxy attach.`, {
|
|
407
413
|
url: z
|
|
@@ -433,6 +439,7 @@ Chromium opens **visible** by default unless \`headless: true\`. File upload / w
|
|
|
433
439
|
.nonnegative()
|
|
434
440
|
.optional()
|
|
435
441
|
.describe('Playwright slowMo (ms) on spawned proxy for easier visual following.'),
|
|
442
|
+
stealth: stealthInput(),
|
|
436
443
|
isolated: z
|
|
437
444
|
.boolean()
|
|
438
445
|
.optional()
|
|
@@ -506,6 +513,7 @@ Chromium opens **visible** by default unless \`headless: true\`. File upload / w
|
|
|
506
513
|
width: input.width,
|
|
507
514
|
height: input.height,
|
|
508
515
|
slowMo: input.slowMo,
|
|
516
|
+
...(input.stealth !== undefined && { stealth: input.stealth }),
|
|
509
517
|
isolated: input.isolated,
|
|
510
518
|
proxy: input.proxy,
|
|
511
519
|
awaitInitialFrame: deferInlinePageModel ? false : undefined,
|
|
@@ -578,6 +586,7 @@ Use this when you can prepare ahead of the user-facing task so the next \`geomet
|
|
|
578
586
|
.nonnegative()
|
|
579
587
|
.optional()
|
|
580
588
|
.describe('Playwright slowMo (ms) for the warmed browser.'),
|
|
589
|
+
stealth: stealthInput(),
|
|
581
590
|
proxy: z
|
|
582
591
|
.object({
|
|
583
592
|
server: z
|
|
@@ -592,9 +601,18 @@ Use this when you can prepare ahead of the user-facing task so the next \`geomet
|
|
|
592
601
|
})
|
|
593
602
|
.optional()
|
|
594
603
|
.describe('BYO outbound proxy for the warmed Chromium. The pool entry is partitioned by proxy identity, so a later geometra_connect with the same proxy config will reuse this warmed browser; a different proxy config (or no proxy) will not.'),
|
|
595
|
-
}, async ({ pageUrl, port, headless, width, height, slowMo, proxy }) => {
|
|
604
|
+
}, async ({ pageUrl, port, headless, width, height, slowMo, stealth, proxy }) => {
|
|
596
605
|
try {
|
|
597
|
-
const prepared = await prewarmProxy({
|
|
606
|
+
const prepared = await prewarmProxy({
|
|
607
|
+
pageUrl,
|
|
608
|
+
port,
|
|
609
|
+
headless,
|
|
610
|
+
width,
|
|
611
|
+
height,
|
|
612
|
+
slowMo,
|
|
613
|
+
...(stealth !== undefined && { stealth }),
|
|
614
|
+
proxy,
|
|
615
|
+
});
|
|
598
616
|
return ok(JSON.stringify(prepared));
|
|
599
617
|
}
|
|
600
618
|
catch (e) {
|
|
@@ -961,6 +979,7 @@ Pass \`valuesById\` with field ids from \`geometra_form_schema\` for the most st
|
|
|
961
979
|
width: z.number().int().positive().optional().describe('Viewport width for auto-connected sessions.'),
|
|
962
980
|
height: z.number().int().positive().optional().describe('Viewport height for auto-connected sessions.'),
|
|
963
981
|
slowMo: z.number().int().nonnegative().optional().describe('Playwright slowMo (ms) when auto-spawning a proxy.'),
|
|
982
|
+
stealth: stealthInput(),
|
|
964
983
|
formId: z.string().optional().describe('Optional form id from geometra_form_schema or geometra_page_model'),
|
|
965
984
|
valuesById: formValuesRecordSchema.optional().describe('Form values keyed by stable field id from geometra_form_schema'),
|
|
966
985
|
valuesByLabel: formValuesRecordSchema.optional().describe('Form values keyed by schema field label'),
|
|
@@ -998,7 +1017,7 @@ Pass \`valuesById\` with field ids from \`geometra_form_schema\` for the most st
|
|
|
998
1017
|
.describe('When auto-connecting via pageUrl/url, request an isolated proxy (own brand-new Chromium, destroyed on disconnect). Required for safe parallel form submission. See geometra_connect for details. Ignored when reusing an existing sessionId — set isolated on the original geometra_connect for that case.'),
|
|
999
1018
|
detail: detailInput(),
|
|
1000
1019
|
sessionId: sessionIdInput,
|
|
1001
|
-
}, async ({ url, pageUrl, port, headless, width, height, slowMo, formId, valuesById, valuesByLabel, stopOnError, failOnInvalid, includeSteps, resumeFromIndex, verifyFills, skipPreFilled, isolated, detail, sessionId }) => {
|
|
1020
|
+
}, async ({ url, pageUrl, port, headless, width, height, slowMo, stealth, formId, valuesById, valuesByLabel, stopOnError, failOnInvalid, includeSteps, resumeFromIndex, verifyFills, skipPreFilled, isolated, detail, sessionId }) => {
|
|
1002
1021
|
const directFields = !includeSteps && !formId && Object.keys(valuesById ?? {}).length === 0
|
|
1003
1022
|
? directLabelBatchFields(valuesByLabel)
|
|
1004
1023
|
: null;
|
|
@@ -1011,6 +1030,7 @@ Pass \`valuesById\` with field ids from \`geometra_form_schema\` for the most st
|
|
|
1011
1030
|
width,
|
|
1012
1031
|
height,
|
|
1013
1032
|
slowMo,
|
|
1033
|
+
stealth,
|
|
1014
1034
|
isolated,
|
|
1015
1035
|
awaitInitialFrame: directFields ? false : undefined,
|
|
1016
1036
|
}, 'Not connected. Call geometra_connect first, or pass pageUrl/url to geometra_fill_form.');
|
|
@@ -1270,6 +1290,7 @@ Pass \`pageUrl\`/\`url\` to auto-connect in the same call — use \`isolated: tr
|
|
|
1270
1290
|
width: z.number().int().positive().optional().describe('Viewport width for auto-connected sessions.'),
|
|
1271
1291
|
height: z.number().int().positive().optional().describe('Viewport height for auto-connected sessions.'),
|
|
1272
1292
|
slowMo: z.number().int().nonnegative().optional().describe('Playwright slowMo (ms) when auto-spawning a proxy.'),
|
|
1293
|
+
stealth: stealthInput(),
|
|
1273
1294
|
isolated: z.boolean().optional().default(false).describe('When auto-connecting via pageUrl/url, request an isolated proxy. Required for safe parallel form submission.'),
|
|
1274
1295
|
formId: z.string().optional().describe('Optional form id from geometra_form_schema or geometra_page_model'),
|
|
1275
1296
|
valuesById: formValuesRecordSchema.optional().describe('Form values keyed by stable field id from geometra_form_schema'),
|
|
@@ -1283,11 +1304,11 @@ Pass \`pageUrl\`/\`url\` to auto-connect in the same call — use \`isolated: tr
|
|
|
1283
1304
|
failOnInvalid: z.boolean().optional().default(false).describe('Return an error if invalid fields remain after the submit wait resolves.'),
|
|
1284
1305
|
detail: detailInput(),
|
|
1285
1306
|
sessionId: sessionIdInput,
|
|
1286
|
-
}, async ({ url, pageUrl, port, headless, width, height, slowMo, isolated, formId, valuesById, valuesByLabel, submit, submitIndex, submitTimeoutMs, waitFor, skipFill, softTimeoutMs, failOnInvalid, detail, sessionId }) => {
|
|
1307
|
+
}, async ({ url, pageUrl, port, headless, width, height, slowMo, stealth, isolated, formId, valuesById, valuesByLabel, submit, submitIndex, submitTimeoutMs, waitFor, skipFill, softTimeoutMs, failOnInvalid, detail, sessionId }) => {
|
|
1287
1308
|
const toolStartedAt = performance.now();
|
|
1288
1309
|
const effectiveSoftTimeoutMs = softTimeoutMs ?? HOST_SAFE_TOOL_TIMEOUT_MS;
|
|
1289
1310
|
const deadlineAt = toolStartedAt + effectiveSoftTimeoutMs;
|
|
1290
|
-
const resolved = await ensureToolSession({ sessionId, url, pageUrl, port, headless, width, height, slowMo, isolated }, 'Not connected. Call geometra_connect first, or pass pageUrl/url to geometra_submit_form.');
|
|
1311
|
+
const resolved = await ensureToolSession({ sessionId, url, pageUrl, port, headless, width, height, slowMo, stealth, isolated }, 'Not connected. Call geometra_connect first, or pass pageUrl/url to geometra_submit_form.');
|
|
1291
1312
|
if (!resolved.ok)
|
|
1292
1313
|
return err(resolved.error);
|
|
1293
1314
|
const session = resolved.session;
|
|
@@ -1572,6 +1593,7 @@ Supported step types: \`click\`, \`type\`, \`key\`, \`upload_files\`, \`pick_lis
|
|
|
1572
1593
|
width: z.number().int().positive().optional().describe('Viewport width for auto-connected sessions.'),
|
|
1573
1594
|
height: z.number().int().positive().optional().describe('Viewport height for auto-connected sessions.'),
|
|
1574
1595
|
slowMo: z.number().int().nonnegative().optional().describe('Playwright slowMo (ms) when auto-spawning a proxy.'),
|
|
1596
|
+
stealth: stealthInput(),
|
|
1575
1597
|
isolated: z
|
|
1576
1598
|
.boolean()
|
|
1577
1599
|
.optional()
|
|
@@ -1595,7 +1617,7 @@ Supported step types: \`click\`, \`type\`, \`key\`, \`upload_files\`, \`pick_lis
|
|
|
1595
1617
|
output: z.enum(['full', 'final']).optional().default('full').describe('`full` (default) returns counts and optional step listings. `final` keeps only completion state plus final semantic signals.'),
|
|
1596
1618
|
detail: detailInput(),
|
|
1597
1619
|
sessionId: sessionIdInput,
|
|
1598
|
-
}, async ({ url, pageUrl, port, headless, width, height, slowMo, isolated, actions, resumeFromIndex, softTimeoutMs, stopOnError, includeSteps, output, detail, sessionId }) => {
|
|
1620
|
+
}, async ({ url, pageUrl, port, headless, width, height, slowMo, stealth, isolated, actions, resumeFromIndex, softTimeoutMs, stopOnError, includeSteps, output, detail, sessionId }) => {
|
|
1599
1621
|
const toolStartedAt = performance.now();
|
|
1600
1622
|
const effectiveSoftTimeoutMs = softTimeoutMs ?? HOST_SAFE_TOOL_TIMEOUT_MS;
|
|
1601
1623
|
const deadlineAt = toolStartedAt + effectiveSoftTimeoutMs;
|
|
@@ -1608,6 +1630,7 @@ Supported step types: \`click\`, \`type\`, \`key\`, \`upload_files\`, \`pick_lis
|
|
|
1608
1630
|
width,
|
|
1609
1631
|
height,
|
|
1610
1632
|
slowMo,
|
|
1633
|
+
stealth,
|
|
1611
1634
|
isolated,
|
|
1612
1635
|
awaitInitialFrame: canDeferInitialFrameForRunActions(actions) ? false : undefined,
|
|
1613
1636
|
}, 'Not connected. Call geometra_connect first, or pass pageUrl/url to geometra_run_actions.');
|
|
@@ -1800,6 +1823,7 @@ Unlike geometra_expand_section, this collapses repeated radio/button groups into
|
|
|
1800
1823
|
width: z.number().int().positive().optional().describe('Viewport width for auto-connected sessions.'),
|
|
1801
1824
|
height: z.number().int().positive().optional().describe('Viewport height for auto-connected sessions.'),
|
|
1802
1825
|
slowMo: z.number().int().nonnegative().optional().describe('Playwright slowMo (ms) when auto-spawning a proxy.'),
|
|
1826
|
+
stealth: stealthInput(),
|
|
1803
1827
|
isolated: z
|
|
1804
1828
|
.boolean()
|
|
1805
1829
|
.optional()
|
|
@@ -1814,8 +1838,8 @@ Unlike geometra_expand_section, this collapses repeated radio/button groups into
|
|
|
1814
1838
|
sinceSchemaId: z.string().optional().describe('If the current schema matches this id, return changed=false without resending forms'),
|
|
1815
1839
|
format: formSchemaFormatInput(),
|
|
1816
1840
|
sessionId: sessionIdInput,
|
|
1817
|
-
}, async ({ url, pageUrl, port, headless, width, height, slowMo, isolated, formId, maxFields, onlyRequiredFields, onlyInvalidFields, includeOptions, includeContext, sinceSchemaId, format, sessionId }) => {
|
|
1818
|
-
const resolved = await ensureToolSession({ sessionId, url, pageUrl, port, headless, width, height, slowMo, isolated }, 'Not connected. Call geometra_connect first, or pass pageUrl/url to geometra_form_schema.');
|
|
1841
|
+
}, async ({ url, pageUrl, port, headless, width, height, slowMo, stealth, isolated, formId, maxFields, onlyRequiredFields, onlyInvalidFields, includeOptions, includeContext, sinceSchemaId, format, sessionId }) => {
|
|
1842
|
+
const resolved = await ensureToolSession({ sessionId, url, pageUrl, port, headless, width, height, slowMo, stealth, isolated }, 'Not connected. Call geometra_connect first, or pass pageUrl/url to geometra_form_schema.');
|
|
1819
1843
|
if (!resolved.ok)
|
|
1820
1844
|
return err(resolved.error);
|
|
1821
1845
|
const session = resolved.session;
|
|
@@ -2852,6 +2876,7 @@ async function ensureToolSession(target, missingConnectionMessage = 'Not connect
|
|
|
2852
2876
|
width: target.width,
|
|
2853
2877
|
height: target.height,
|
|
2854
2878
|
slowMo: target.slowMo,
|
|
2879
|
+
...(target.stealth !== undefined && { stealth: target.stealth }),
|
|
2855
2880
|
awaitInitialFrame: target.awaitInitialFrame,
|
|
2856
2881
|
isolated: target.isolated,
|
|
2857
2882
|
});
|
package/dist/session.d.ts
CHANGED
|
@@ -488,6 +488,7 @@ export declare function prewarmProxy(options: {
|
|
|
488
488
|
pageUrl: string;
|
|
489
489
|
port?: number;
|
|
490
490
|
headless?: boolean;
|
|
491
|
+
stealth?: boolean;
|
|
491
492
|
width?: number;
|
|
492
493
|
height?: number;
|
|
493
494
|
slowMo?: number;
|
|
@@ -499,6 +500,7 @@ export declare function prewarmProxy(options: {
|
|
|
499
500
|
pageUrl: string;
|
|
500
501
|
wsUrl: string;
|
|
501
502
|
headless: boolean;
|
|
503
|
+
stealth: boolean;
|
|
502
504
|
width: number;
|
|
503
505
|
height: number;
|
|
504
506
|
}>;
|
|
@@ -524,6 +526,7 @@ export declare function connectThroughProxy(options: {
|
|
|
524
526
|
width?: number;
|
|
525
527
|
height?: number;
|
|
526
528
|
slowMo?: number;
|
|
529
|
+
stealth?: boolean;
|
|
527
530
|
awaitInitialFrame?: boolean;
|
|
528
531
|
eagerInitialExtract?: boolean;
|
|
529
532
|
/**
|
package/dist/session.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { performance } from 'node:perf_hooks';
|
|
3
3
|
import WebSocket from 'ws';
|
|
4
|
-
import { spawnGeometraProxy, startEmbeddedGeometraProxy } from './proxy-spawn.js';
|
|
4
|
+
import { resolveStealthMode, spawnGeometraProxy, startEmbeddedGeometraProxy, } from './proxy-spawn.js';
|
|
5
5
|
import { completeSessionLifecycle, failSessionLifecycle, heartbeatSessionLifecycle, initializeSessionLifecycle, recordSessionSnapshot, } from './session-state.js';
|
|
6
6
|
/**
|
|
7
7
|
* Stable identity for an outbound proxy config, used as the reusable-pool
|
|
@@ -143,10 +143,12 @@ function setReusableProxy(proxy, wsUrl, opts) {
|
|
|
143
143
|
clearReusableProxiesIfExited();
|
|
144
144
|
const now = Date.now();
|
|
145
145
|
const proxyKey = proxyKeyFor(opts.proxy);
|
|
146
|
+
const stealth = resolveStealthMode(opts.stealth);
|
|
146
147
|
const existing = reusableProxies.find(entry => sameReusableProxyEntry(entry, proxy));
|
|
147
148
|
if (existing) {
|
|
148
149
|
existing.wsUrl = wsUrl;
|
|
149
150
|
existing.headless = opts.headless === true;
|
|
151
|
+
existing.stealth = stealth;
|
|
150
152
|
existing.slowMo = opts.slowMo ?? 0;
|
|
151
153
|
existing.width = opts.width ?? 1280;
|
|
152
154
|
existing.height = opts.height ?? 720;
|
|
@@ -162,6 +164,7 @@ function setReusableProxy(proxy, wsUrl, opts) {
|
|
|
162
164
|
child,
|
|
163
165
|
wsUrl,
|
|
164
166
|
headless: opts.headless === true,
|
|
167
|
+
stealth,
|
|
165
168
|
slowMo: opts.slowMo ?? 0,
|
|
166
169
|
width: opts.width ?? 1280,
|
|
167
170
|
height: opts.height ?? 720,
|
|
@@ -188,6 +191,7 @@ function setReusableProxy(proxy, wsUrl, opts) {
|
|
|
188
191
|
runtime: proxy.runtime,
|
|
189
192
|
wsUrl,
|
|
190
193
|
headless: opts.headless === true,
|
|
194
|
+
stealth,
|
|
191
195
|
slowMo: opts.slowMo ?? 0,
|
|
192
196
|
width: opts.width ?? 1280,
|
|
193
197
|
height: opts.height ?? 720,
|
|
@@ -405,6 +409,7 @@ function stopSessionHeartbeat(session) {
|
|
|
405
409
|
function reusableProxyMatchesOptions(entry, options) {
|
|
406
410
|
return (entry.pageUrl === options.pageUrl &&
|
|
407
411
|
entry.headless === (options.headless === true) &&
|
|
412
|
+
entry.stealth === resolveStealthMode(options.stealth) &&
|
|
408
413
|
entry.slowMo === (options.slowMo ?? 0) &&
|
|
409
414
|
entry.width === (options.width ?? 1280) &&
|
|
410
415
|
entry.height === (options.height ?? 720) &&
|
|
@@ -422,12 +427,14 @@ function findExactReusableProxy(options) {
|
|
|
422
427
|
function findReusableProxy(options) {
|
|
423
428
|
clearReusableProxiesIfExited();
|
|
424
429
|
const desiredHeadless = options.headless === true;
|
|
430
|
+
const desiredStealth = resolveStealthMode(options.stealth);
|
|
425
431
|
const desiredSlowMo = options.slowMo ?? 0;
|
|
426
432
|
const desiredWidth = options.width ?? 1280;
|
|
427
433
|
const desiredHeight = options.height ?? 720;
|
|
428
434
|
const desiredProxyKey = proxyKeyFor(options.proxy);
|
|
429
435
|
return reusableProxies
|
|
430
436
|
.filter(entry => entry.headless === desiredHeadless
|
|
437
|
+
&& entry.stealth === desiredStealth
|
|
431
438
|
&& entry.slowMo === desiredSlowMo
|
|
432
439
|
// Proxy partition is hard — a session with residential proxy MUST NOT
|
|
433
440
|
// attach to a pooled direct-connection Chromium (and vice versa).
|
|
@@ -459,6 +466,7 @@ export async function prewarmProxy(options) {
|
|
|
459
466
|
pageUrl: options.pageUrl,
|
|
460
467
|
wsUrl: existing.wsUrl,
|
|
461
468
|
headless: options.headless === true,
|
|
469
|
+
stealth: resolveStealthMode(options.stealth),
|
|
462
470
|
width: options.width ?? 1280,
|
|
463
471
|
height: options.height ?? 720,
|
|
464
472
|
};
|
|
@@ -472,6 +480,7 @@ export async function prewarmProxy(options) {
|
|
|
472
480
|
width: options.width,
|
|
473
481
|
height: options.height,
|
|
474
482
|
slowMo: options.slowMo,
|
|
483
|
+
stealth: options.stealth,
|
|
475
484
|
proxy: options.proxy,
|
|
476
485
|
});
|
|
477
486
|
try {
|
|
@@ -484,6 +493,7 @@ export async function prewarmProxy(options) {
|
|
|
484
493
|
setReusableProxy({ runtime }, wsUrl, {
|
|
485
494
|
headless: options.headless,
|
|
486
495
|
slowMo: options.slowMo,
|
|
496
|
+
stealth: options.stealth,
|
|
487
497
|
width: options.width,
|
|
488
498
|
height: options.height,
|
|
489
499
|
pageUrl: options.pageUrl,
|
|
@@ -497,6 +507,7 @@ export async function prewarmProxy(options) {
|
|
|
497
507
|
pageUrl: options.pageUrl,
|
|
498
508
|
wsUrl,
|
|
499
509
|
headless: options.headless === true,
|
|
510
|
+
stealth: resolveStealthMode(options.stealth),
|
|
500
511
|
width: options.width ?? 1280,
|
|
501
512
|
height: options.height ?? 720,
|
|
502
513
|
};
|
|
@@ -512,11 +523,13 @@ export async function prewarmProxy(options) {
|
|
|
512
523
|
width: options.width,
|
|
513
524
|
height: options.height,
|
|
514
525
|
slowMo: options.slowMo,
|
|
526
|
+
stealth: options.stealth,
|
|
515
527
|
proxy: options.proxy,
|
|
516
528
|
});
|
|
517
529
|
setReusableProxy({ child }, wsUrl, {
|
|
518
530
|
headless: options.headless,
|
|
519
531
|
slowMo: options.slowMo,
|
|
532
|
+
stealth: options.stealth,
|
|
520
533
|
width: options.width,
|
|
521
534
|
height: options.height,
|
|
522
535
|
pageUrl: options.pageUrl,
|
|
@@ -529,6 +542,7 @@ export async function prewarmProxy(options) {
|
|
|
529
542
|
pageUrl: options.pageUrl,
|
|
530
543
|
wsUrl,
|
|
531
544
|
headless: options.headless === true,
|
|
545
|
+
stealth: resolveStealthMode(options.stealth),
|
|
532
546
|
width: options.width ?? 1280,
|
|
533
547
|
height: options.height ?? 720,
|
|
534
548
|
};
|
|
@@ -621,6 +635,7 @@ async function startFreshProxySession(options) {
|
|
|
621
635
|
width: options.width,
|
|
622
636
|
height: options.height,
|
|
623
637
|
slowMo: options.slowMo,
|
|
638
|
+
stealth: options.stealth,
|
|
624
639
|
eagerInitialExtract,
|
|
625
640
|
proxy: options.proxy,
|
|
626
641
|
});
|
|
@@ -649,6 +664,7 @@ async function startFreshProxySession(options) {
|
|
|
649
664
|
setReusableProxy({ runtime }, wsUrl, {
|
|
650
665
|
headless: options.headless,
|
|
651
666
|
slowMo: options.slowMo,
|
|
667
|
+
stealth: options.stealth,
|
|
652
668
|
width: options.width,
|
|
653
669
|
height: options.height,
|
|
654
670
|
pageUrl: options.pageUrl,
|
|
@@ -695,6 +711,7 @@ async function startFreshProxySession(options) {
|
|
|
695
711
|
width: options.width,
|
|
696
712
|
height: options.height,
|
|
697
713
|
slowMo: options.slowMo,
|
|
714
|
+
stealth: options.stealth,
|
|
698
715
|
eagerInitialExtract,
|
|
699
716
|
proxy: options.proxy,
|
|
700
717
|
});
|
|
@@ -717,6 +734,7 @@ async function startFreshProxySession(options) {
|
|
|
717
734
|
setReusableProxy({ child }, wsUrl, {
|
|
718
735
|
headless: options.headless,
|
|
719
736
|
slowMo: options.slowMo,
|
|
737
|
+
stealth: options.stealth,
|
|
720
738
|
width: options.width,
|
|
721
739
|
height: options.height,
|
|
722
740
|
pageUrl: options.pageUrl,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geometra/mcp",
|
|
3
|
-
"version": "1.61.
|
|
3
|
+
"version": "1.61.3",
|
|
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",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"ui-testing"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@geometra/proxy": "^1.61.
|
|
35
|
+
"@geometra/proxy": "^1.61.3",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
37
37
|
"@razroo/parallel-mcp": "^0.1.0",
|
|
38
38
|
"ws": "^8.18.0",
|