@geometra/mcp 1.19.10 → 1.19.12
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 +156 -12
- package/dist/__tests__/server-batch-results.test.d.ts +1 -0
- package/dist/__tests__/server-batch-results.test.js +311 -0
- package/dist/__tests__/session-model.test.js +100 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +459 -78
- package/dist/session.d.ts +57 -0
- package/dist/session.js +163 -6
- package/package.json +2 -2
package/dist/session.d.ts
CHANGED
|
@@ -61,6 +61,23 @@ export interface CompactUiContext {
|
|
|
61
61
|
scrollY?: number;
|
|
62
62
|
focusedNode?: CompactUiNode;
|
|
63
63
|
}
|
|
64
|
+
export interface NodeContextModel {
|
|
65
|
+
prompt?: string;
|
|
66
|
+
section?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface NodeVisibilityModel {
|
|
69
|
+
intersectsViewport: boolean;
|
|
70
|
+
fullyVisible: boolean;
|
|
71
|
+
offscreenAbove: boolean;
|
|
72
|
+
offscreenBelow: boolean;
|
|
73
|
+
offscreenLeft: boolean;
|
|
74
|
+
offscreenRight: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface NodeScrollHintModel {
|
|
77
|
+
status: 'visible' | 'partial' | 'offscreen';
|
|
78
|
+
revealDeltaX: number;
|
|
79
|
+
revealDeltaY: number;
|
|
80
|
+
}
|
|
64
81
|
export type PageSectionKind = 'landmark' | 'form' | 'dialog' | 'list';
|
|
65
82
|
export type PageArchetype = 'shell' | 'form' | 'dialog' | 'results' | 'content' | 'dashboard';
|
|
66
83
|
interface PageSectionSummaryBase {
|
|
@@ -136,6 +153,9 @@ export interface PageFieldModel {
|
|
|
136
153
|
value?: string;
|
|
137
154
|
state?: A11yNode['state'];
|
|
138
155
|
validation?: A11yNode['validation'];
|
|
156
|
+
context?: NodeContextModel;
|
|
157
|
+
visibility?: NodeVisibilityModel;
|
|
158
|
+
scrollHint?: NodeScrollHintModel;
|
|
139
159
|
bounds?: {
|
|
140
160
|
x: number;
|
|
141
161
|
y: number;
|
|
@@ -148,6 +168,9 @@ export interface PageActionModel {
|
|
|
148
168
|
role: string;
|
|
149
169
|
name?: string;
|
|
150
170
|
state?: A11yNode['state'];
|
|
171
|
+
context?: NodeContextModel;
|
|
172
|
+
visibility?: NodeVisibilityModel;
|
|
173
|
+
scrollHint?: NodeScrollHintModel;
|
|
151
174
|
bounds?: {
|
|
152
175
|
x: number;
|
|
153
176
|
y: number;
|
|
@@ -179,10 +202,38 @@ export interface PageSectionDetail {
|
|
|
179
202
|
summary: {
|
|
180
203
|
headingCount: number;
|
|
181
204
|
fieldCount: number;
|
|
205
|
+
requiredFieldCount: number;
|
|
206
|
+
invalidFieldCount: number;
|
|
182
207
|
actionCount: number;
|
|
183
208
|
listCount: number;
|
|
184
209
|
itemCount: number;
|
|
185
210
|
};
|
|
211
|
+
page: {
|
|
212
|
+
fields: {
|
|
213
|
+
offset: number;
|
|
214
|
+
returned: number;
|
|
215
|
+
total: number;
|
|
216
|
+
hasMore: boolean;
|
|
217
|
+
};
|
|
218
|
+
actions: {
|
|
219
|
+
offset: number;
|
|
220
|
+
returned: number;
|
|
221
|
+
total: number;
|
|
222
|
+
hasMore: boolean;
|
|
223
|
+
};
|
|
224
|
+
lists: {
|
|
225
|
+
offset: number;
|
|
226
|
+
returned: number;
|
|
227
|
+
total: number;
|
|
228
|
+
hasMore: boolean;
|
|
229
|
+
};
|
|
230
|
+
items: {
|
|
231
|
+
offset: number;
|
|
232
|
+
returned: number;
|
|
233
|
+
total: number;
|
|
234
|
+
hasMore: boolean;
|
|
235
|
+
};
|
|
236
|
+
};
|
|
186
237
|
headings: PageHeadingModel[];
|
|
187
238
|
fields: PageFieldModel[];
|
|
188
239
|
actions: PageActionModel[];
|
|
@@ -368,9 +419,15 @@ export declare function buildPageModel(root: A11yNode, options?: {
|
|
|
368
419
|
export declare function expandPageSection(root: A11yNode, id: string, options?: {
|
|
369
420
|
maxHeadings?: number;
|
|
370
421
|
maxFields?: number;
|
|
422
|
+
fieldOffset?: number;
|
|
423
|
+
onlyRequiredFields?: boolean;
|
|
424
|
+
onlyInvalidFields?: boolean;
|
|
371
425
|
maxActions?: number;
|
|
426
|
+
actionOffset?: number;
|
|
372
427
|
maxLists?: number;
|
|
428
|
+
listOffset?: number;
|
|
373
429
|
maxItems?: number;
|
|
430
|
+
itemOffset?: number;
|
|
374
431
|
maxTextPreview?: number;
|
|
375
432
|
includeBounds?: boolean;
|
|
376
433
|
}): PageSectionDetail | null;
|
package/dist/session.js
CHANGED
|
@@ -743,8 +743,103 @@ function primaryAction(node) {
|
|
|
743
743
|
bounds: cloneBounds(node.bounds),
|
|
744
744
|
};
|
|
745
745
|
}
|
|
746
|
-
function
|
|
746
|
+
function buildVisibility(bounds, viewport) {
|
|
747
|
+
const visibleLeft = Math.max(0, bounds.x);
|
|
748
|
+
const visibleTop = Math.max(0, bounds.y);
|
|
749
|
+
const visibleRight = Math.min(viewport.width, bounds.x + bounds.width);
|
|
750
|
+
const visibleBottom = Math.min(viewport.height, bounds.y + bounds.height);
|
|
751
|
+
const hasVisibleIntersection = visibleRight > visibleLeft && visibleBottom > visibleTop;
|
|
752
|
+
const fullyVisible = bounds.x >= 0 &&
|
|
753
|
+
bounds.y >= 0 &&
|
|
754
|
+
bounds.x + bounds.width <= viewport.width &&
|
|
755
|
+
bounds.y + bounds.height <= viewport.height;
|
|
756
|
+
return {
|
|
757
|
+
intersectsViewport: hasVisibleIntersection,
|
|
758
|
+
fullyVisible,
|
|
759
|
+
offscreenAbove: bounds.y + bounds.height <= 0,
|
|
760
|
+
offscreenBelow: bounds.y >= viewport.height,
|
|
761
|
+
offscreenLeft: bounds.x + bounds.width <= 0,
|
|
762
|
+
offscreenRight: bounds.x >= viewport.width,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function buildScrollHint(bounds, viewport) {
|
|
766
|
+
const visibility = buildVisibility(bounds, viewport);
|
|
767
|
+
return {
|
|
768
|
+
status: visibility.fullyVisible ? 'visible' : visibility.intersectsViewport ? 'partial' : 'offscreen',
|
|
769
|
+
revealDeltaX: Math.round(bounds.x + bounds.width / 2 - viewport.width / 2),
|
|
770
|
+
revealDeltaY: Math.round(bounds.y + bounds.height / 2 - viewport.height / 2),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function ancestorNodes(root, path) {
|
|
774
|
+
const out = [];
|
|
775
|
+
let current = root;
|
|
776
|
+
for (const index of path) {
|
|
777
|
+
out.push(current);
|
|
778
|
+
if (!current.children[index])
|
|
779
|
+
break;
|
|
780
|
+
current = current.children[index];
|
|
781
|
+
}
|
|
782
|
+
return out;
|
|
783
|
+
}
|
|
784
|
+
function countGroupedChoiceControls(node) {
|
|
785
|
+
return collectDescendants(node, candidate => candidate.role === 'radio' || candidate.role === 'checkbox' || candidate.role === 'button').length;
|
|
786
|
+
}
|
|
787
|
+
function nearestPromptText(container, target) {
|
|
788
|
+
const candidates = collectDescendants(container, candidate => (candidate.role === 'heading' || candidate.role === 'text') &&
|
|
789
|
+
!!sanitizeInlineName(candidate.name, 120) &&
|
|
790
|
+
pathKey(candidate.path) !== pathKey(target.path));
|
|
791
|
+
const normalizedTarget = normalizeUiText(target.name ?? '');
|
|
792
|
+
const best = candidates
|
|
793
|
+
.filter(candidate => candidate.bounds.y <= target.bounds.y + 8)
|
|
794
|
+
.map(candidate => {
|
|
795
|
+
const text = sanitizeInlineName(candidate.name, 120);
|
|
796
|
+
if (!text)
|
|
797
|
+
return null;
|
|
798
|
+
if (normalizeUiText(text) === normalizedTarget)
|
|
799
|
+
return null;
|
|
800
|
+
const dy = Math.max(0, target.bounds.y - candidate.bounds.y);
|
|
801
|
+
const dx = Math.abs(target.bounds.x - candidate.bounds.x);
|
|
802
|
+
const headingBonus = candidate.role === 'heading' ? -32 : 0;
|
|
803
|
+
return { text, score: dy * 4 + dx + headingBonus };
|
|
804
|
+
})
|
|
805
|
+
.filter((candidate) => !!candidate)
|
|
806
|
+
.sort((a, b) => a.score - b.score)[0];
|
|
807
|
+
return best?.text;
|
|
808
|
+
}
|
|
809
|
+
function nodeContext(root, node) {
|
|
810
|
+
const ancestors = ancestorNodes(root, node.path);
|
|
811
|
+
let prompt;
|
|
812
|
+
for (let index = ancestors.length - 1; index >= 0; index--) {
|
|
813
|
+
const ancestor = ancestors[index];
|
|
814
|
+
const grouped = countGroupedChoiceControls(ancestor) >= 2;
|
|
815
|
+
if (grouped || ancestor.role === 'group' || ancestor.role === 'form' || ancestor.role === 'dialog') {
|
|
816
|
+
prompt = nearestPromptText(ancestor, node);
|
|
817
|
+
if (prompt)
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
let section;
|
|
822
|
+
for (let index = ancestors.length - 1; index >= 0; index--) {
|
|
823
|
+
const ancestor = ancestors[index];
|
|
824
|
+
const kind = sectionKindForNode(ancestor);
|
|
825
|
+
if (!kind)
|
|
826
|
+
continue;
|
|
827
|
+
section = sectionDisplayName(ancestor, kind);
|
|
828
|
+
if (section)
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
if (!prompt && !section)
|
|
832
|
+
return undefined;
|
|
833
|
+
return {
|
|
834
|
+
...(prompt ? { prompt } : {}),
|
|
835
|
+
...(section ? { section } : {}),
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function toFieldModel(root, node, includeBounds = true) {
|
|
747
839
|
const value = sanitizeInlineName(node.value, 120);
|
|
840
|
+
const context = nodeContext(root, node);
|
|
841
|
+
const visibility = buildVisibility(node.bounds, root.bounds);
|
|
842
|
+
const scrollHint = buildScrollHint(node.bounds, root.bounds);
|
|
748
843
|
return {
|
|
749
844
|
id: nodeIdForPath(node.path),
|
|
750
845
|
role: node.role,
|
|
@@ -752,15 +847,24 @@ function toFieldModel(node, includeBounds = true) {
|
|
|
752
847
|
...(value ? { value } : {}),
|
|
753
848
|
...(cloneState(node.state) ? { state: cloneState(node.state) } : {}),
|
|
754
849
|
...(cloneValidation(node.validation) ? { validation: cloneValidation(node.validation) } : {}),
|
|
850
|
+
...(context ? { context } : {}),
|
|
851
|
+
visibility,
|
|
852
|
+
scrollHint,
|
|
755
853
|
...(includeBounds ? { bounds: cloneBounds(node.bounds) } : {}),
|
|
756
854
|
};
|
|
757
855
|
}
|
|
758
|
-
function toActionModel(node, includeBounds = true) {
|
|
856
|
+
function toActionModel(root, node, includeBounds = true) {
|
|
857
|
+
const context = nodeContext(root, node);
|
|
858
|
+
const visibility = buildVisibility(node.bounds, root.bounds);
|
|
859
|
+
const scrollHint = buildScrollHint(node.bounds, root.bounds);
|
|
759
860
|
return {
|
|
760
861
|
id: nodeIdForPath(node.path),
|
|
761
862
|
role: node.role,
|
|
762
863
|
...(sanitizeInlineName(node.name, 80) ? { name: sanitizeInlineName(node.name, 80) } : {}),
|
|
763
864
|
...(cloneState(node.state) ? { state: cloneState(node.state) } : {}),
|
|
865
|
+
...(context ? { context } : {}),
|
|
866
|
+
visibility,
|
|
867
|
+
scrollHint,
|
|
764
868
|
...(includeBounds ? { bounds: cloneBounds(node.bounds) } : {}),
|
|
765
869
|
};
|
|
766
870
|
}
|
|
@@ -931,9 +1035,15 @@ export function expandPageSection(root, id, options) {
|
|
|
931
1035
|
return null;
|
|
932
1036
|
const maxHeadings = options?.maxHeadings ?? 6;
|
|
933
1037
|
const maxFields = options?.maxFields ?? 18;
|
|
1038
|
+
const fieldOffset = Math.max(0, options?.fieldOffset ?? 0);
|
|
1039
|
+
const onlyRequiredFields = options?.onlyRequiredFields ?? false;
|
|
1040
|
+
const onlyInvalidFields = options?.onlyInvalidFields ?? false;
|
|
934
1041
|
const maxActions = options?.maxActions ?? 12;
|
|
1042
|
+
const actionOffset = Math.max(0, options?.actionOffset ?? 0);
|
|
935
1043
|
const maxLists = options?.maxLists ?? 8;
|
|
1044
|
+
const listOffset = Math.max(0, options?.listOffset ?? 0);
|
|
936
1045
|
const maxItems = options?.maxItems ?? 20;
|
|
1046
|
+
const itemOffset = Math.max(0, options?.itemOffset ?? 0);
|
|
937
1047
|
const maxTextPreview = options?.maxTextPreview ?? 6;
|
|
938
1048
|
const includeBounds = options?.includeBounds ?? false;
|
|
939
1049
|
const headingsAll = sortByBounds(collectDescendants(node, candidate => candidate.role === 'heading' && !!sanitizeInlineName(candidate.name, 80)));
|
|
@@ -943,6 +1053,19 @@ export function expandPageSection(root, id, options) {
|
|
|
943
1053
|
const itemsAll = actualKind === 'list'
|
|
944
1054
|
? sortByBounds(collectDescendants(node, candidate => candidate.role === 'listitem'))
|
|
945
1055
|
: [];
|
|
1056
|
+
const requiredFieldCount = fieldsAll.filter(field => field.state?.required).length;
|
|
1057
|
+
const invalidFieldCount = fieldsAll.filter(field => field.state?.invalid).length;
|
|
1058
|
+
const filteredFields = fieldsAll.filter(field => {
|
|
1059
|
+
if (onlyRequiredFields && !field.state?.required)
|
|
1060
|
+
return false;
|
|
1061
|
+
if (onlyInvalidFields && !field.state?.invalid)
|
|
1062
|
+
return false;
|
|
1063
|
+
return true;
|
|
1064
|
+
});
|
|
1065
|
+
const pageFields = filteredFields.slice(fieldOffset, fieldOffset + maxFields);
|
|
1066
|
+
const pageActions = actionsAll.slice(actionOffset, actionOffset + maxActions);
|
|
1067
|
+
const pageLists = nestedListsAll.slice(listOffset, listOffset + maxLists);
|
|
1068
|
+
const pageItems = itemsAll.slice(itemOffset, itemOffset + maxItems);
|
|
946
1069
|
const name = sectionDisplayName(node, actualKind);
|
|
947
1070
|
return {
|
|
948
1071
|
id: sectionIdForPath(actualKind, node.path),
|
|
@@ -953,15 +1076,49 @@ export function expandPageSection(root, id, options) {
|
|
|
953
1076
|
summary: {
|
|
954
1077
|
headingCount: headingsAll.length,
|
|
955
1078
|
fieldCount: fieldsAll.length,
|
|
1079
|
+
requiredFieldCount,
|
|
1080
|
+
invalidFieldCount,
|
|
956
1081
|
actionCount: actionsAll.length,
|
|
957
1082
|
listCount: nestedListsAll.length,
|
|
958
1083
|
itemCount: itemsAll.length,
|
|
959
1084
|
},
|
|
1085
|
+
page: {
|
|
1086
|
+
fields: {
|
|
1087
|
+
offset: fieldOffset,
|
|
1088
|
+
returned: pageFields.length,
|
|
1089
|
+
total: filteredFields.length,
|
|
1090
|
+
hasMore: fieldOffset + pageFields.length < filteredFields.length,
|
|
1091
|
+
},
|
|
1092
|
+
actions: {
|
|
1093
|
+
offset: actionOffset,
|
|
1094
|
+
returned: pageActions.length,
|
|
1095
|
+
total: actionsAll.length,
|
|
1096
|
+
hasMore: actionOffset + pageActions.length < actionsAll.length,
|
|
1097
|
+
},
|
|
1098
|
+
lists: {
|
|
1099
|
+
offset: listOffset,
|
|
1100
|
+
returned: pageLists.length,
|
|
1101
|
+
total: nestedListsAll.length,
|
|
1102
|
+
hasMore: listOffset + pageLists.length < nestedListsAll.length,
|
|
1103
|
+
},
|
|
1104
|
+
items: {
|
|
1105
|
+
offset: itemOffset,
|
|
1106
|
+
returned: pageItems.length,
|
|
1107
|
+
total: itemsAll.length,
|
|
1108
|
+
hasMore: itemOffset + pageItems.length < itemsAll.length,
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
960
1111
|
headings: headingModels(node, maxHeadings, includeBounds),
|
|
961
|
-
fields:
|
|
962
|
-
actions:
|
|
963
|
-
lists:
|
|
964
|
-
|
|
1112
|
+
fields: pageFields.map(field => toFieldModel(root, field, includeBounds)),
|
|
1113
|
+
actions: pageActions.map(action => toActionModel(root, action, includeBounds)),
|
|
1114
|
+
lists: pageLists.map(list => ({
|
|
1115
|
+
id: sectionIdForPath('list', list.path),
|
|
1116
|
+
role: list.role,
|
|
1117
|
+
...(sectionDisplayName(list, 'list') ? { name: sectionDisplayName(list, 'list') } : {}),
|
|
1118
|
+
bounds: cloneBounds(list.bounds),
|
|
1119
|
+
itemCount: collectDescendants(list, candidate => candidate.role === 'listitem').length,
|
|
1120
|
+
})),
|
|
1121
|
+
items: pageItems.map(item => ({
|
|
965
1122
|
id: nodeIdForPath(item.path),
|
|
966
1123
|
...(listItemName(item) ? { name: listItemName(item) } : {}),
|
|
967
1124
|
...(includeBounds ? { bounds: cloneBounds(item.bounds) } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geometra/mcp",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.12",
|
|
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.12",
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
35
35
|
"ws": "^8.18.0",
|
|
36
36
|
"zod": "^3.23.0"
|