@geometra/mcp 1.59.0 → 1.60.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 +1 -75
- package/dist/session-state.js +2 -1
- package/dist/session.d.ts +1 -2
- package/dist/session.js +4 -25
- package/package.json +5 -3
- package/dist/__tests__/ats-integration.test.d.ts +0 -1
- package/dist/__tests__/ats-integration.test.js +0 -891
- package/dist/__tests__/connect-utils.test.d.ts +0 -1
- package/dist/__tests__/connect-utils.test.js +0 -255
- package/dist/__tests__/proxy-session-actions.test.d.ts +0 -1
- package/dist/__tests__/proxy-session-actions.test.js +0 -356
- package/dist/__tests__/proxy-session-recovery.test.d.ts +0 -1
- package/dist/__tests__/proxy-session-recovery.test.js +0 -262
- package/dist/__tests__/server-batch-results.test.d.ts +0 -1
- package/dist/__tests__/server-batch-results.test.js +0 -1777
- package/dist/__tests__/server-filters.test.d.ts +0 -1
- package/dist/__tests__/server-filters.test.js +0 -57
- package/dist/__tests__/server-session-resolution.test.d.ts +0 -1
- package/dist/__tests__/server-session-resolution.test.js +0 -88
- package/dist/__tests__/session-isolation.test.d.ts +0 -1
- package/dist/__tests__/session-isolation.test.js +0 -308
- package/dist/__tests__/session-model.test.d.ts +0 -1
- package/dist/__tests__/session-model.test.js +0 -815
- package/dist/__tests__/values-equivalent.test.d.ts +0 -1
- package/dist/__tests__/values-equivalent.test.js +0 -52
package/dist/server.js
CHANGED
|
@@ -2027,7 +2027,7 @@ Pass \`fieldLabel\` to open a labeled dropdown semantically instead of relying o
|
|
|
2027
2027
|
.describe('Optional action wait timeout for slow dropdowns / remote search results'),
|
|
2028
2028
|
detail: detailInput(),
|
|
2029
2029
|
sessionId: sessionIdInput,
|
|
2030
|
-
}, async ({ label, exact, openX, openY, fieldLabel, contextText, sectionText, query, timeoutMs, detail, sessionId }) => {
|
|
2030
|
+
}, async ({ label, exact, openX, openY, fieldLabel, contextText: _contextText, sectionText: _sectionText, query, timeoutMs, detail, sessionId }) => {
|
|
2031
2031
|
const sessionResult = resolveToolSession(sessionId);
|
|
2032
2032
|
if ('error' in sessionResult)
|
|
2033
2033
|
return sessionResult.error;
|
|
@@ -4279,80 +4279,6 @@ function sortA11yNodes(nodes) {
|
|
|
4279
4279
|
function clamp(value, min, max) {
|
|
4280
4280
|
return Math.min(Math.max(value, min), max);
|
|
4281
4281
|
}
|
|
4282
|
-
function pathStartsWith(path, prefix) {
|
|
4283
|
-
if (prefix.length > path.length)
|
|
4284
|
-
return false;
|
|
4285
|
-
for (let index = 0; index < prefix.length; index++) {
|
|
4286
|
-
if (path[index] !== prefix[index])
|
|
4287
|
-
return false;
|
|
4288
|
-
}
|
|
4289
|
-
return true;
|
|
4290
|
-
}
|
|
4291
|
-
function namedAncestors(root, path) {
|
|
4292
|
-
const out = [];
|
|
4293
|
-
let current = root;
|
|
4294
|
-
for (const index of path) {
|
|
4295
|
-
out.push(current);
|
|
4296
|
-
if (!current.children[index])
|
|
4297
|
-
break;
|
|
4298
|
-
current = current.children[index];
|
|
4299
|
-
}
|
|
4300
|
-
return out;
|
|
4301
|
-
}
|
|
4302
|
-
function collectDescendants(node, predicate) {
|
|
4303
|
-
const out = [];
|
|
4304
|
-
function walk(current) {
|
|
4305
|
-
for (const child of current.children) {
|
|
4306
|
-
if (predicate(child))
|
|
4307
|
-
out.push(child);
|
|
4308
|
-
walk(child);
|
|
4309
|
-
}
|
|
4310
|
-
}
|
|
4311
|
-
walk(node);
|
|
4312
|
-
return out;
|
|
4313
|
-
}
|
|
4314
|
-
function promptContext(root, node) {
|
|
4315
|
-
const ancestors = namedAncestors(root, node.path);
|
|
4316
|
-
const normalizedName = (node.name ?? '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
4317
|
-
for (let index = ancestors.length - 1; index >= 0; index--) {
|
|
4318
|
-
const ancestor = ancestors[index];
|
|
4319
|
-
const grouped = collectDescendants(ancestor, candidate => candidate.role === 'button' || candidate.role === 'radio' || candidate.role === 'checkbox').length >= 2;
|
|
4320
|
-
if (!grouped && ancestor.role !== 'group' && ancestor.role !== 'form' && ancestor.role !== 'dialog')
|
|
4321
|
-
continue;
|
|
4322
|
-
const best = collectDescendants(ancestor, candidate => (candidate.role === 'heading' || candidate.role === 'text') &&
|
|
4323
|
-
!!truncateInlineText(candidate.name, 120) &&
|
|
4324
|
-
!pathStartsWith(candidate.path, node.path))
|
|
4325
|
-
.filter(candidate => candidate.bounds.y <= node.bounds.y + 8)
|
|
4326
|
-
.map(candidate => {
|
|
4327
|
-
const text = truncateInlineText(candidate.name, 120);
|
|
4328
|
-
if (!text)
|
|
4329
|
-
return null;
|
|
4330
|
-
if (text.toLowerCase() === normalizedName)
|
|
4331
|
-
return null;
|
|
4332
|
-
const dy = Math.max(0, node.bounds.y - candidate.bounds.y);
|
|
4333
|
-
const dx = Math.abs(node.bounds.x - candidate.bounds.x);
|
|
4334
|
-
const headingBonus = candidate.role === 'heading' ? -32 : 0;
|
|
4335
|
-
return { text, score: dy * 4 + dx + headingBonus };
|
|
4336
|
-
})
|
|
4337
|
-
.filter((candidate) => !!candidate)
|
|
4338
|
-
.sort((a, b) => a.score - b.score)[0];
|
|
4339
|
-
if (best?.text)
|
|
4340
|
-
return best.text;
|
|
4341
|
-
}
|
|
4342
|
-
return undefined;
|
|
4343
|
-
}
|
|
4344
|
-
function sectionContext(root, node) {
|
|
4345
|
-
const ancestors = namedAncestors(root, node.path);
|
|
4346
|
-
for (let index = ancestors.length - 1; index >= 0; index--) {
|
|
4347
|
-
const ancestor = ancestors[index];
|
|
4348
|
-
if (ancestor.role === 'form' || ancestor.role === 'dialog' || ancestor.role === 'main' || ancestor.role === 'navigation' || ancestor.role === 'region') {
|
|
4349
|
-
const name = truncateInlineText(ancestor.name, 80);
|
|
4350
|
-
if (name)
|
|
4351
|
-
return name;
|
|
4352
|
-
}
|
|
4353
|
-
}
|
|
4354
|
-
return undefined;
|
|
4355
|
-
}
|
|
4356
4282
|
function nodeContextText(context) {
|
|
4357
4283
|
return [context?.prompt, context?.section, context?.item].filter(Boolean).join(' | ') || undefined;
|
|
4358
4284
|
}
|
package/dist/session-state.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { threadId } from 'node:worker_threads';
|
|
4
5
|
import { ParallelMcpOrchestrator, SqliteParallelMcpStore, } from '@razroo/parallel-mcp';
|
|
5
6
|
const SESSION_NAMESPACE = 'geometra-mcp-session';
|
|
6
7
|
const SESSION_TASK_KEY = 'session.live';
|
|
@@ -15,7 +16,7 @@ function resolveSessionStateFile() {
|
|
|
15
16
|
}
|
|
16
17
|
const dir = path.join(homedir(), '.geometra-mcp');
|
|
17
18
|
mkdirSync(dir, { recursive: true });
|
|
18
|
-
return path.join(dir, `parallel-mcp-${process.pid}.sqlite`);
|
|
19
|
+
return path.join(dir, `parallel-mcp-${process.pid}-${threadId}.sqlite`);
|
|
19
20
|
}
|
|
20
21
|
const orchestrator = new ParallelMcpOrchestrator(new SqliteParallelMcpStore({ filename: resolveSessionStateFile() }), { defaultLeaseMs: SESSION_LEASE_MS });
|
|
21
22
|
const leaseSweep = setInterval(() => {
|
package/dist/session.d.ts
CHANGED
|
@@ -112,8 +112,7 @@ interface PageSectionSummaryBase {
|
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
/** Higher-level webpage structures extracted from the a11y tree. */
|
|
115
|
-
export
|
|
116
|
-
}
|
|
115
|
+
export type PageLandmark = PageSectionSummaryBase;
|
|
117
116
|
export interface PagePrimaryAction {
|
|
118
117
|
id: string;
|
|
119
118
|
role: string;
|
package/dist/session.js
CHANGED
|
@@ -534,7 +534,7 @@ export async function prewarmProxy(options) {
|
|
|
534
534
|
};
|
|
535
535
|
}
|
|
536
536
|
catch (spawnFailure) {
|
|
537
|
-
throw new Error(`Failed to prewarm embedded browser session: ${formatUnknownError(embeddedFailure)}\nChild-process proxy prewarm also failed: ${formatUnknownError(spawnFailure)}
|
|
537
|
+
throw new Error(`Failed to prewarm embedded browser session: ${formatUnknownError(embeddedFailure)}\nChild-process proxy prewarm also failed: ${formatUnknownError(spawnFailure)}`, { cause: spawnFailure });
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
async function attachToReusableProxy(proxy, options) {
|
|
@@ -685,7 +685,6 @@ async function startFreshProxySession(options) {
|
|
|
685
685
|
// every time we fall through to the child-process fallback path.
|
|
686
686
|
if (pendingEmbeddedRuntime) {
|
|
687
687
|
const leaked = pendingEmbeddedRuntime;
|
|
688
|
-
pendingEmbeddedRuntime = undefined;
|
|
689
688
|
void leaked.close().catch(() => { });
|
|
690
689
|
}
|
|
691
690
|
const proxyStartStartedAt = performance.now();
|
|
@@ -971,7 +970,7 @@ export async function connectThroughProxy(options) {
|
|
|
971
970
|
}
|
|
972
971
|
catch (e) {
|
|
973
972
|
if (reuseFailure) {
|
|
974
|
-
throw new Error(`Failed to recover reusable browser session after it became stale: ${formatUnknownError(reuseFailure)}\nFresh proxy start also failed: ${formatUnknownError(e)}
|
|
973
|
+
throw new Error(`Failed to recover reusable browser session after it became stale: ${formatUnknownError(reuseFailure)}\nFresh proxy start also failed: ${formatUnknownError(e)}`, { cause: e });
|
|
975
974
|
}
|
|
976
975
|
throw e;
|
|
977
976
|
}
|
|
@@ -1076,7 +1075,7 @@ function estimateFillBatchTimeout(fields) {
|
|
|
1076
1075
|
export function waitForUiCondition(session, predicate, timeoutMs) {
|
|
1077
1076
|
return new Promise((resolve) => {
|
|
1078
1077
|
const check = () => {
|
|
1079
|
-
let matched
|
|
1078
|
+
let matched;
|
|
1080
1079
|
try {
|
|
1081
1080
|
matched = predicate();
|
|
1082
1081
|
}
|
|
@@ -1240,7 +1239,7 @@ async function ensureSessionConnected(session) {
|
|
|
1240
1239
|
}
|
|
1241
1240
|
})();
|
|
1242
1241
|
session.reconnectInFlight = reconnectPromise;
|
|
1243
|
-
let recovered
|
|
1242
|
+
let recovered;
|
|
1244
1243
|
try {
|
|
1245
1244
|
recovered = await reconnectPromise;
|
|
1246
1245
|
}
|
|
@@ -1520,7 +1519,6 @@ const DIALOG_ROLES = new Set([
|
|
|
1520
1519
|
'dialog',
|
|
1521
1520
|
'alertdialog',
|
|
1522
1521
|
]);
|
|
1523
|
-
const FIELD_LABEL_ROLES = new Set(['textbox', 'combobox', 'checkbox', 'radio']);
|
|
1524
1522
|
const CONTENT_NAME_ROLES = new Set(['heading', 'text']);
|
|
1525
1523
|
function encodePath(path) {
|
|
1526
1524
|
return path.length === 0 ? 'root' : path.map(part => part.toString(36)).join('.');
|
|
@@ -1797,9 +1795,6 @@ function cloneValidation(validation) {
|
|
|
1797
1795
|
next.error = validation.error;
|
|
1798
1796
|
return Object.keys(next).length > 0 ? next : undefined;
|
|
1799
1797
|
}
|
|
1800
|
-
function clonePath(path) {
|
|
1801
|
-
return [...path];
|
|
1802
|
-
}
|
|
1803
1798
|
function sortByBounds(items) {
|
|
1804
1799
|
return items.sort((a, b) => {
|
|
1805
1800
|
if (a.bounds.y !== b.bounds.y)
|
|
@@ -2388,9 +2383,6 @@ function buildFormSchemaForNode(root, formNode, options) {
|
|
|
2388
2383
|
})(),
|
|
2389
2384
|
};
|
|
2390
2385
|
}
|
|
2391
|
-
function trimSchemaFieldContexts(fields) {
|
|
2392
|
-
return presentFormSchemaFields(fields, { includeOptions: true, includeContext: 'auto' });
|
|
2393
|
-
}
|
|
2394
2386
|
function presentFormSchemaFields(fields, options) {
|
|
2395
2387
|
const includeOptions = options?.includeOptions ?? false;
|
|
2396
2388
|
const includeContext = options?.includeContext ?? 'auto';
|
|
@@ -2704,16 +2696,6 @@ function headingModels(node, maxHeadings, includeBounds) {
|
|
|
2704
2696
|
...(includeBounds ? { bounds: cloneBounds(heading.bounds) } : {}),
|
|
2705
2697
|
}));
|
|
2706
2698
|
}
|
|
2707
|
-
function nestedListSummaries(node, maxLists, selfPath) {
|
|
2708
|
-
const nestedLists = sortByBounds(collectDescendants(node, candidate => candidate.role === 'list' && pathKey(candidate.path) !== pathKey(selfPath)));
|
|
2709
|
-
return nestedLists.slice(0, maxLists).map(list => ({
|
|
2710
|
-
id: sectionIdForPath('list', list.path),
|
|
2711
|
-
role: list.role,
|
|
2712
|
-
...(sectionDisplayName(list, 'list') ? { name: sectionDisplayName(list, 'list') } : {}),
|
|
2713
|
-
bounds: cloneBounds(list.bounds),
|
|
2714
|
-
itemCount: collectDescendants(list, candidate => candidate.role === 'listitem').length,
|
|
2715
|
-
}));
|
|
2716
|
-
}
|
|
2717
2699
|
function sectionKindForNode(node) {
|
|
2718
2700
|
if (node.role === 'form')
|
|
2719
2701
|
return 'form';
|
|
@@ -2901,9 +2883,6 @@ function diffCompactNodes(before, after) {
|
|
|
2901
2883
|
}
|
|
2902
2884
|
return changes;
|
|
2903
2885
|
}
|
|
2904
|
-
function pageContainerKey(value) {
|
|
2905
|
-
return `${pathKey(value.path)}|${value.name ?? ''}`;
|
|
2906
|
-
}
|
|
2907
2886
|
/**
|
|
2908
2887
|
* Compare two accessibility trees at the compact viewport layer plus a few
|
|
2909
2888
|
* higher-level structures (dialogs, forms, lists).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geometra/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.60.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",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "tsc",
|
|
20
|
+
"build": "rm -rf dist && tsc",
|
|
21
|
+
"check": "tsc --noEmit",
|
|
22
|
+
"test": "npm run check",
|
|
21
23
|
"dev": "node --loader ts-node/esm src/index.ts",
|
|
22
24
|
"prepublishOnly": "npm run build"
|
|
23
25
|
},
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
"ui-testing"
|
|
31
33
|
],
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"@geometra/proxy": "^1.
|
|
35
|
+
"@geometra/proxy": "^1.60.0",
|
|
34
36
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
35
37
|
"@razroo/parallel-mcp": "^0.1.0",
|
|
36
38
|
"ws": "^8.18.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|