get_accessibility_tree
Capture a page's accessibility tree to analyze ARIA roles, names, and properties for testing and audits.
Instructions
Capture a snapshot of the current page's accessibility tree. Returns a hierarchical tree of ARIA roles, names, and properties. Use interestingOnly=false for the complete raw tree. Use useFullTree=true for the CDP-level complete tree (slower but more accurate). Use maxDepth to control how deep the tree goes.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| interestingOnly | No | If true, prunes nodes that are not interesting (hidden, presentational). Set false to get the raw full tree. Default: true | |
| maxDepth | No | Maximum tree depth to return. Default: 10 | |
| useFullTree | No | If true, uses CDP Accessibility.getFullAXTree (more complete but slower). If false, uses page.accessibility.snapshot(). Default: false |
Implementation Reference
- src/tools/getAccessibilityTree.ts:39-115 (handler)Main handler function getAccessibilityTreeHandler that executes the tool logic. It captures the accessibility tree from the current page using either CDP's getFullAXTree or Puppeteer's accessibility.snapshot, filters based on interestingOnly, prunes to maxDepth, and returns the tree with metadata (url, title, nodeCount).export async function getAccessibilityTreeHandler(args: { interestingOnly?: boolean; maxDepth?: number; useFullTree?: boolean; }): Promise<ReturnType<typeof toolSuccess | typeof toolError>> { try { const { page, cdpSession } = browserManager.requireConnection(); const interestingOnly = args.interestingOnly ?? true; const maxDepth = args.maxDepth ?? 10; const useFullTree = args.useFullTree ?? false; let tree: unknown; let nodeCount = 0; if (useFullTree) { // Use CDP Accessibility.getFullAXTree for the raw complete tree const result = await cdpSession.send( 'Accessibility.getFullAXTree', {} ) as GetFullAXTreeResponse; const nodes: CdpAXNode[] = result.nodes; const filtered = interestingOnly ? nodes.filter((n) => !n.ignored) : nodes; // Find root node (no parentId or parentId not in set) const nodeIds = new Set(filtered.map((n) => n.nodeId)); const root = filtered.find((n) => !n.parentId || !nodeIds.has(n.parentId)); if (!root) { return toolSuccess({ tree: null, nodeCount: 0, message: 'No root node found in accessibility tree.', }); } const index = buildTreeIndex(filtered); const assembled = assembleTree(root, index, 0, maxDepth); tree = pruneToDepth(assembled, maxDepth); nodeCount = countNodes(assembled); } else { // Use Puppeteer's higher-level accessibility snapshot const snapshot = await page.accessibility.snapshot({ interestingOnly, }); if (!snapshot) { return toolSuccess({ tree: null, nodeCount: 0, message: 'Accessibility snapshot returned null. The page may not have loaded.', }); } tree = snapshot; // Count nodes in the snapshot nodeCount = countSnapshotNodes(snapshot as unknown as Record<string, unknown>); } const title = await page.title().catch(() => ''); const url = page.url(); return toolSuccess({ url, title, nodeCount, maxDepth, interestingOnly, tree, }); } catch (error) { return toolError(error); } }
- Zod schema definition getAccessibilityTreeSchema that defines input parameters: interestingOnly (boolean, default true) to filter uninteresting nodes, maxDepth (number, default 10) for tree depth control, and useFullTree (boolean, default false) to choose between CDP's getFullAXTree or Puppeteer's accessibility.snapshot.export const getAccessibilityTreeSchema = { interestingOnly: z .boolean() .optional() .default(true) .describe( 'If true, prunes nodes that are not interesting (hidden, presentational). ' + 'Set false to get the raw full tree. Default: true' ), maxDepth: z .number() .int() .positive() .optional() .default(10) .describe('Maximum tree depth to return. Default: 10'), useFullTree: z .boolean() .optional() .default(false) .describe( 'If true, uses CDP Accessibility.getFullAXTree (more complete but slower). ' + 'If false, uses page.accessibility.snapshot(). Default: false' ), };
- src/index.ts:60-74 (registration)Tool registration in src/index.ts where get_accessibility_tree is registered with the MCP server, including title, description, inputSchema, and the handler function reference.// ── get_accessibility_tree ─────────────────────────────────────────────────── server.registerTool( 'get_accessibility_tree', { title: 'Get Accessibility Tree', description: 'Capture a snapshot of the current page\'s accessibility tree. ' + 'Returns a hierarchical tree of ARIA roles, names, and properties. ' + 'Use interestingOnly=false for the complete raw tree. ' + 'Use useFullTree=true for the CDP-level complete tree (slower but more accurate). ' + 'Use maxDepth to control how deep the tree goes.', inputSchema: getAccessibilityTreeSchema, }, getAccessibilityTreeHandler );
- src/utils/axTree.ts:60-85 (helper)Helper function assembleTree that recursively builds an AXNodeSummary tree from a flat list of CDP AX nodes, attaching children up to maxDepth and filtering out ignored nodes.export function assembleTree( node: CdpAXNode, index: Map<string, CdpAXNode>, depth: number, maxDepth: number, ): AXNodeSummary { const summary = cdpAXNodeToSummary(node); if (depth >= maxDepth || !node.childIds || node.childIds.length === 0) { return summary; } const children: AXNodeSummary[] = []; for (const childId of node.childIds) { const child = index.get(childId); if (child && !child.ignored) { children.push(assembleTree(child, index, depth + 1, maxDepth)); } } if (children.length > 0) { summary.children = children; } return summary; }
- src/utils/axTree.ts:15-43 (helper)Helper function cdpAXNodeToSummary that converts raw CDP AX node data into a cleaner AXNodeSummary object, extracting role, name, description, value, and various ARIA properties.export function cdpAXNodeToSummary(node: CdpAXNode): AXNodeSummary { const role = (node.role?.value as string) ?? 'unknown'; const name = (node.name?.value as string) ?? ''; const description = node.description?.value as string | undefined; const value = node.value?.value as string | undefined; const props = node.properties ?? []; const focused = getPropertyValue(props, 'focused') as boolean | undefined; const disabled = getPropertyValue(props, 'disabled') as boolean | undefined; const checked = getPropertyValue(props, 'checked') as boolean | 'mixed' | undefined; const expanded = getPropertyValue(props, 'expanded') as boolean | undefined; const required = getPropertyValue(props, 'required') as boolean | undefined; const level = getPropertyValue(props, 'level') as number | undefined; return { role, name, ...(description !== undefined && { description }), ...(value !== undefined && { value }), ...(focused !== undefined && { focused }), ...(disabled !== undefined && { disabled }), ...(checked !== undefined && { checked }), ...(expanded !== undefined && { expanded }), ...(required !== undefined && { required }), ...(level !== undefined && { level }), ...(node.backendDOMNodeId !== undefined && { backendDOMNodeId: node.backendDOMNodeId }), nodeId: node.nodeId, }; }