@geometra/mcp 1.25.0 → 1.26.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 CHANGED
@@ -3,7 +3,7 @@ import { performance } from 'node:perf_hooks';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { z } from 'zod';
5
5
  import { formatConnectFailureMessage, isHttpUrl, normalizeConnectTarget } from './connect-utils.js';
6
- import { connect, connectThroughProxy, disconnect, getSession, listSessions, getDefaultSessionId, prewarmProxy, sendClick, sendFillFields, sendType, sendKey, sendFileUpload, sendFieldText, sendFieldChoice, sendListboxPick, sendSelectOption, sendSetChecked, sendWheel, sendScreenshot, sendPdfGenerate, buildA11yTree, buildCompactUiIndex, buildFormRequiredSnapshot, buildPageModel, buildFormSchemas, expandPageSection, buildUiDelta, hasUiDelta, nodeIdForPath, nodeContextForNode, summarizeCompactIndex, summarizePageModel, summarizeUiDelta, waitForUiCondition, } from './session.js';
6
+ import { connect, connectThroughProxy, disconnect, getSession, listSessions, getDefaultSessionId, prewarmProxy, sendClick, sendFillFields, sendType, sendKey, sendFileUpload, sendFieldText, sendFieldChoice, sendListboxPick, sendSelectOption, sendSetChecked, sendWheel, sendScreenshot, sendPdfGenerate, buildA11yTree, buildCompactUiIndex, buildFormRequiredSnapshot, buildPageModel, buildFormSchemas, expandPageSection, buildUiDelta, hasUiDelta, nodeIdForPath, nodeContextForNode, parseSectionId, findNodeByPath, summarizeCompactIndex, summarizePageModel, summarizeUiDelta, waitForUiCondition, } from './session.js';
7
7
  function checkedStateInput() {
8
8
  return z
9
9
  .union([z.boolean(), z.literal('mixed')])
@@ -1755,7 +1755,7 @@ Use this for dropdowns, location pickers, or any scrollable list where items are
1755
1755
  maxScrollSteps: z.number().int().min(1).max(50).optional().default(20).describe('Max scroll steps before stopping (default 20)'),
1756
1756
  scrollDelta: z.number().optional().default(300).describe('Vertical scroll delta per step (default 300)'),
1757
1757
  sessionId: sessionIdInput,
1758
- }, async ({ listId: _listId, role, scrollX, scrollY, maxItems, maxScrollSteps, scrollDelta, sessionId }) => {
1758
+ }, async ({ listId, role, scrollX, scrollY, maxItems, maxScrollSteps, scrollDelta, sessionId }) => {
1759
1759
  const session = getSession(sessionId);
1760
1760
  if (!session)
1761
1761
  return err('Not connected. Call geometra_connect first.');
@@ -1767,7 +1767,17 @@ Use this for dropdowns, location pickers, or any scrollable list where items are
1767
1767
  const a11y = await sessionA11yWhenReady(session);
1768
1768
  if (!a11y)
1769
1769
  break;
1770
- const items = findNodes(a11y, { role: itemRole });
1770
+ // Scope to subtree if listId is provided
1771
+ let searchRoot = a11y;
1772
+ if (listId) {
1773
+ const parsed = parseSectionId(listId);
1774
+ if (parsed) {
1775
+ const node = findNodeByPath(a11y, parsed.path);
1776
+ if (node)
1777
+ searchRoot = node;
1778
+ }
1779
+ }
1780
+ const items = findNodes(searchRoot, { role: itemRole });
1771
1781
  let newCount = 0;
1772
1782
  for (const item of items) {
1773
1783
  const id = nodeIdForPath(item.path);
package/dist/session.d.ts CHANGED
@@ -594,6 +594,10 @@ export declare function sendNavigate(session: Session, url: string, timeoutMs?:
594
594
  */
595
595
  export declare function buildA11yTree(tree: Record<string, unknown>, layout: Record<string, unknown>): A11yNode;
596
596
  export declare function nodeIdForPath(path: number[]): string;
597
+ export declare function parseSectionId(id: string): {
598
+ kind: PageSectionKind;
599
+ path: number[];
600
+ } | null;
597
601
  /**
598
602
  * Flat list of actionable / semantic nodes in the viewport, sorted with focusable first
599
603
  * then top-to-bottom reading order. Intended to minimize LLM tokens vs a full nested tree.
@@ -608,6 +612,7 @@ export declare function buildCompactUiIndex(root: A11yNode, options?: {
608
612
  context: CompactUiContext;
609
613
  };
610
614
  export declare function summarizeCompactIndex(nodes: CompactUiNode[], maxLines?: number): string;
615
+ export declare function findNodeByPath(root: A11yNode, path: number[]): A11yNode | null;
611
616
  export declare function nodeContextForNode(root: A11yNode, node: A11yNode): NodeContextModel | undefined;
612
617
  export declare function buildPageModel(root: A11yNode, options?: {
613
618
  maxPrimaryActions?: number;
package/dist/session.js CHANGED
@@ -1046,7 +1046,7 @@ function sectionPrefix(kind) {
1046
1046
  function sectionIdForPath(kind, path) {
1047
1047
  return `${sectionPrefix(kind)}:${encodePath(path)}`;
1048
1048
  }
1049
- function parseSectionId(id) {
1049
+ export function parseSectionId(id) {
1050
1050
  const [prefix, encoded] = id.split(':', 2);
1051
1051
  if (!prefix || !encoded)
1052
1052
  return null;
@@ -1314,7 +1314,7 @@ function firstNamedDescendant(node, allowedRoles) {
1314
1314
  }
1315
1315
  return undefined;
1316
1316
  }
1317
- function findNodeByPath(root, path) {
1317
+ export function findNodeByPath(root, path) {
1318
1318
  let current = root;
1319
1319
  for (const index of path) {
1320
1320
  if (!current.children[index])
@@ -2054,10 +2054,25 @@ export function buildPageModel(root, options) {
2054
2054
  };
2055
2055
  }
2056
2056
  export function buildFormSchemas(root, options) {
2057
- const forms = sortByBounds([
2057
+ const explicitForms = [
2058
2058
  ...(root.role === 'form' ? [root] : []),
2059
2059
  ...collectDescendants(root, candidate => candidate.role === 'form'),
2060
- ]);
2060
+ ];
2061
+ // Infer forms from group/region containers with 2+ form fields (e.g. Ashby-style UIs without <form>)
2062
+ const inferredForms = collectDescendants(root, candidate => {
2063
+ if (candidate.role !== 'group' && candidate.role !== 'region')
2064
+ return false;
2065
+ // Skip descendants of explicit forms
2066
+ for (const form of explicitForms) {
2067
+ if (candidate.path.length > form.path.length &&
2068
+ form.path.every((v, i) => candidate.path[i] === v)) {
2069
+ return false;
2070
+ }
2071
+ }
2072
+ const fields = collectDescendants(candidate, child => FORM_FIELD_ROLES.has(child.role));
2073
+ return fields.length >= 2;
2074
+ });
2075
+ const forms = sortByBounds([...explicitForms, ...inferredForms]);
2061
2076
  return forms
2062
2077
  .filter(form => !options?.formId || sectionIdForPath('form', form.path) === options.formId)
2063
2078
  .map(form => buildFormSchemaForNode(root, form, options));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geometra/mcp",
3
- "version": "1.25.0",
3
+ "version": "1.26.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",