@geometra/mcp 1.19.19 → 1.19.20
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 +5 -1
- package/dist/server.js +42 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Proxy-backed sessions stay warm by default on disconnect, and MCP now keeps a sm
|
|
|
26
26
|
|---|---|
|
|
27
27
|
| `geometra_connect` | Connect with `url` (ws://…) **or** `pageUrl` (https://…) to auto-start geometra-proxy; can inline `formSchema` and/or `pageModel` for lower-turn starts |
|
|
28
28
|
| `geometra_query` | Find elements by stable id, role, name, text content, ancestor/prompt context, current value, or semantic state such as `invalid`, `required`, or `busy` |
|
|
29
|
-
| `geometra_wait_for` | Wait for a semantic condition instead of guessing sleeps (`busy`, `disabled`, alerts, values, etc.) |
|
|
29
|
+
| `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 |
|
|
30
30
|
| `geometra_form_schema` | Compact, fill-oriented form schema with stable field ids and collapsed radio/button groups; can auto-connect from `pageUrl` / `url` |
|
|
31
31
|
| `geometra_fill_form` | Fill a form from `valuesById` / `valuesByLabel` in one MCP call; can auto-connect from `pageUrl` / `url` for the lowest-token known-form path |
|
|
32
32
|
| `geometra_fill_fields` | Fill labeled text/choice/toggle/file fields in one MCP call; can return final-only status for the smallest responses |
|
|
@@ -46,6 +46,10 @@ Proxy-backed sessions stay warm by default on disconnect, and MCP now keeps a sm
|
|
|
46
46
|
| `geometra_layout` | Raw computed geometry for every node |
|
|
47
47
|
| `geometra_disconnect` | Close the connection |
|
|
48
48
|
|
|
49
|
+
### `geometra_wait_for` and loading banners
|
|
50
|
+
|
|
51
|
+
The tool input is **strict**: unknown keys are rejected (so mistaken names like `textGone` fail fast instead of being ignored). To wait until copy such as “Parsing…” or “Parsing your resume” is **gone**, pass a substring that matches the banner in **`text`** and set **`present`** to **`false`**. Example: `{ "text": "Parsing", "present": false }` (adjust the substring per site).
|
|
52
|
+
|
|
49
53
|
## Setup
|
|
50
54
|
|
|
51
55
|
<details>
|
package/dist/server.js
CHANGED
|
@@ -51,7 +51,11 @@ function nodeFilterShape() {
|
|
|
51
51
|
function waitConditionShape() {
|
|
52
52
|
return {
|
|
53
53
|
...nodeFilterShape(),
|
|
54
|
-
present: z
|
|
54
|
+
present: z
|
|
55
|
+
.boolean()
|
|
56
|
+
.optional()
|
|
57
|
+
.default(true)
|
|
58
|
+
.describe('Wait until at least one node matches the filter (default true), or until no node matches (set false to wait out loading/parsing banners like “Parsing…” or “Parsing your resume”)'),
|
|
55
59
|
timeoutMs: z
|
|
56
60
|
.number()
|
|
57
61
|
.int()
|
|
@@ -62,6 +66,15 @@ function waitConditionShape() {
|
|
|
62
66
|
.describe('Maximum time to wait before returning an error (default 10000ms)'),
|
|
63
67
|
};
|
|
64
68
|
}
|
|
69
|
+
const GEOMETRA_QUERY_FILTER_REQUIRED_MESSAGE = 'Provide at least one filter (id, role, name, text, contextText, value, checked, disabled, focused, selected, expanded, invalid, required, or busy). ' +
|
|
70
|
+
'This tool uses a strict schema: unknown keys are rejected. There is no textGone parameter — use text for substring matching. ' +
|
|
71
|
+
'To wait until text disappears from the UI, use geometra_wait_for with text and present: false.';
|
|
72
|
+
const GEOMETRA_WAIT_FILTER_REQUIRED_MESSAGE = 'Provide at least one semantic filter (id, role, name, text, contextText, value, checked, disabled, focused, selected, expanded, invalid, required, or busy). ' +
|
|
73
|
+
'This tool uses a strict schema: unknown keys are rejected. There is no textGone parameter — use text with a distinctive substring and present: false to wait until that text is gone ' +
|
|
74
|
+
'(common for “Parsing…”, “Parsing your resume”, or similar). Passing only present/timeoutMs is not enough without a filter.';
|
|
75
|
+
/** Strict input so unknown keys (e.g. textGone) fail parse; empty-filter checks happen in handlers / waitForSemanticCondition. */
|
|
76
|
+
const geometraQueryInputSchema = z.object(nodeFilterShape()).strict();
|
|
77
|
+
const geometraWaitForInputSchema = z.object(waitConditionShape()).strict();
|
|
65
78
|
const timeoutMsInput = z.number().int().min(50).max(60_000).optional();
|
|
66
79
|
const fillFieldSchema = z.discriminatedUnion('kind', [
|
|
67
80
|
z.object({
|
|
@@ -195,7 +208,7 @@ const batchActionSchema = z.discriminatedUnion('type', [
|
|
|
195
208
|
}),
|
|
196
209
|
]);
|
|
197
210
|
export function createServer() {
|
|
198
|
-
const server = new McpServer({ name: 'geometra', version: '1.19.
|
|
211
|
+
const server = new McpServer({ name: 'geometra', version: '1.19.20' }, { capabilities: { tools: {} } });
|
|
199
212
|
// ── connect ──────────────────────────────────────────────────
|
|
200
213
|
server.tool('geometra_connect', `Connect to a Geometra WebSocket peer, or start \`geometra-proxy\` automatically for a normal web page.
|
|
201
214
|
|
|
@@ -322,7 +335,11 @@ Chromium opens **visible** by default unless \`headless: true\`. File upload / w
|
|
|
322
335
|
// ── query ────────────────────────────────────────────────────
|
|
323
336
|
server.tool('geometra_query', `Find elements in the current Geometra UI by stable id, role, name, text content, current value, or semantic state. Returns matching elements with their exact pixel bounds {x, y, width, height}, visible in-viewport bounds, an on-screen center point, visibility / scroll-reveal hints, role, name, value, state, and tree path.
|
|
324
337
|
|
|
325
|
-
This is the Geometra equivalent of Playwright's locator — but instant, structured, and with no browser. Use the returned bounds to click elements or assert on layout
|
|
338
|
+
This is the Geometra equivalent of Playwright's locator — but instant, structured, and with no browser. Use the returned bounds to click elements or assert on layout.
|
|
339
|
+
|
|
340
|
+
Unknown parameter names are rejected (strict schema). To wait until visible text goes away (e.g. a parsing banner), use geometra_wait_for with that substring in text and present: false — there is no textGone field.`,
|
|
341
|
+
// SDK overload typings only list raw shapes; runtime accepts ZodObject via getZodSchemaObject().
|
|
342
|
+
geometraQueryInputSchema, async ({ id, role, name, text, contextText, value, checked, disabled, focused, selected, expanded, invalid, required, busy }) => {
|
|
326
343
|
const session = getSession();
|
|
327
344
|
if (!session?.tree || !session?.layout)
|
|
328
345
|
return err('Not connected. Call geometra_connect first.');
|
|
@@ -346,7 +363,7 @@ This is the Geometra equivalent of Playwright's locator — but instant, structu
|
|
|
346
363
|
busy,
|
|
347
364
|
};
|
|
348
365
|
if (!hasNodeFilter(filter))
|
|
349
|
-
return err(
|
|
366
|
+
return err(GEOMETRA_QUERY_FILTER_REQUIRED_MESSAGE);
|
|
350
367
|
const matches = findNodes(a11y, filter);
|
|
351
368
|
if (matches.length === 0) {
|
|
352
369
|
return ok(`No elements found matching ${JSON.stringify(filter)}`);
|
|
@@ -356,27 +373,30 @@ This is the Geometra equivalent of Playwright's locator — but instant, structu
|
|
|
356
373
|
});
|
|
357
374
|
server.tool('geometra_wait_for', `Wait for a semantic UI condition without guessing sleep durations. Use this for slow SPA transitions, resume parsing, custom validation alerts, disabled submit buttons, and value/state confirmation before submit.
|
|
358
375
|
|
|
359
|
-
The filter matches the same fields as geometra_query. Set \`present: false\` to wait
|
|
376
|
+
The filter matches the same fields as geometra_query (strict schema — unknown keys error). Set \`present: false\` to wait until **no** node matches — for example Ashby/Lever-style “Parsing your resume” or any “Parsing…” banner: \`{ "text": "Parsing", "present": false }\` (tune the substring to the site). Do not use a textGone parameter; use \`text\` + \`present: false\`.`, geometraWaitForInputSchema, async ({ id, role, name, text, contextText, value, checked, disabled, focused, selected, expanded, invalid, required, busy, present, timeoutMs }) => {
|
|
360
377
|
const session = getSession();
|
|
361
378
|
if (!session?.tree || !session?.layout)
|
|
362
379
|
return err('Not connected. Call geometra_connect first.');
|
|
380
|
+
const filterProbe = {
|
|
381
|
+
id,
|
|
382
|
+
role,
|
|
383
|
+
name,
|
|
384
|
+
text,
|
|
385
|
+
contextText,
|
|
386
|
+
value,
|
|
387
|
+
checked,
|
|
388
|
+
disabled,
|
|
389
|
+
focused,
|
|
390
|
+
selected,
|
|
391
|
+
expanded,
|
|
392
|
+
invalid,
|
|
393
|
+
required,
|
|
394
|
+
busy,
|
|
395
|
+
};
|
|
396
|
+
if (!hasNodeFilter(filterProbe))
|
|
397
|
+
return err(GEOMETRA_WAIT_FILTER_REQUIRED_MESSAGE);
|
|
363
398
|
const waited = await waitForSemanticCondition(session, {
|
|
364
|
-
filter:
|
|
365
|
-
id,
|
|
366
|
-
role,
|
|
367
|
-
name,
|
|
368
|
-
text,
|
|
369
|
-
contextText,
|
|
370
|
-
value,
|
|
371
|
-
checked,
|
|
372
|
-
disabled,
|
|
373
|
-
focused,
|
|
374
|
-
selected,
|
|
375
|
-
expanded,
|
|
376
|
-
invalid,
|
|
377
|
-
required,
|
|
378
|
-
busy,
|
|
379
|
-
},
|
|
399
|
+
filter: filterProbe,
|
|
380
400
|
present: present ?? true,
|
|
381
401
|
timeoutMs: timeoutMs ?? 10_000,
|
|
382
402
|
});
|
|
@@ -1665,7 +1685,7 @@ function compactFilterPayload(filter) {
|
|
|
1665
1685
|
}
|
|
1666
1686
|
async function waitForSemanticCondition(session, options) {
|
|
1667
1687
|
if (!hasNodeFilter(options.filter)) {
|
|
1668
|
-
return { ok: false, error:
|
|
1688
|
+
return { ok: false, error: GEOMETRA_WAIT_FILTER_REQUIRED_MESSAGE };
|
|
1669
1689
|
}
|
|
1670
1690
|
const startedAt = Date.now();
|
|
1671
1691
|
const matched = await waitForUiCondition(session, () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geometra/mcp",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.20",
|
|
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",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"ui-testing"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@geometra/proxy": "^1.19.
|
|
33
|
+
"@geometra/proxy": "^1.19.20",
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
35
35
|
"ws": "^8.18.0",
|
|
36
36
|
"zod": "^3.23.0"
|