11import { getBrowser } from './browser.tool' ;
2+ import { getBrowserAccessibilityTree } from '../scripts/get-browser-accessibility-tree' ;
23import type { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp' ;
34import type { CallToolResult } from '@modelcontextprotocol/sdk/types' ;
45import type { ToolDefinition } from '../types/tool' ;
@@ -23,63 +24,6 @@ export const getAccessibilityToolDefinition: ToolDefinition = {
2324 } ,
2425} ;
2526
26- /**
27- * Flatten a hierarchical accessibility tree into a flat list
28- * Uses uniform fields (all nodes have same keys) to enable tabular format
29- * @param node - The accessibility node
30- * @param result - Accumulator array
31- */
32- function flattenAccessibilityTree ( node : any , result : any [ ] = [ ] ) : any [ ] {
33- if ( ! node ) return result ;
34-
35- // Add current node (excluding root WebArea unless it has meaningful content)
36- if ( node . role !== 'WebArea' || node . name ) {
37- // Build object with ALL fields for uniform schema (enables tabular format)
38- // Empty string '' used for missing values to keep schema consistent
39- const entry : Record < string , any > = {
40- // Primary identifiers (most useful)
41- role : node . role || '' ,
42- name : node . name || '' ,
43- value : node . value ?? '' ,
44- description : node . description || '' ,
45- // Boolean states (empty string = not applicable/false)
46- disabled : node . disabled ? 'true' : '' ,
47- focused : node . focused ? 'true' : '' ,
48- selected : node . selected ? 'true' : '' ,
49- checked : node . checked === true ? 'true' : node . checked === false ? 'false' : node . checked === 'mixed' ? 'mixed' : '' ,
50- expanded : node . expanded === true ? 'true' : node . expanded === false ? 'false' : '' ,
51- pressed : node . pressed === true ? 'true' : node . pressed === false ? 'false' : node . pressed === 'mixed' ? 'mixed' : '' ,
52- readonly : node . readonly ? 'true' : '' ,
53- required : node . required ? 'true' : '' ,
54- // Less common properties
55- level : node . level ?? '' ,
56- valuemin : node . valuemin ?? '' ,
57- valuemax : node . valuemax ?? '' ,
58- autocomplete : node . autocomplete || '' ,
59- haspopup : node . haspopup || '' ,
60- invalid : node . invalid ? 'true' : '' ,
61- modal : node . modal ? 'true' : '' ,
62- multiline : node . multiline ? 'true' : '' ,
63- multiselectable : node . multiselectable ? 'true' : '' ,
64- orientation : node . orientation || '' ,
65- keyshortcuts : node . keyshortcuts || '' ,
66- roledescription : node . roledescription || '' ,
67- valuetext : node . valuetext || '' ,
68- } ;
69-
70- result . push ( entry ) ;
71- }
72-
73- // Recursively process children
74- if ( node . children && Array . isArray ( node . children ) ) {
75- for ( const child of node . children ) {
76- flattenAccessibilityTree ( child , result ) ;
77- }
78- }
79-
80- return result ;
81- }
82-
8327export const getAccessibilityTreeTool : ToolCallback = async ( args : {
8428 limit ?: number ;
8529 offset ?: number ;
@@ -101,41 +45,23 @@ export const getAccessibilityTreeTool: ToolCallback = async (args: {
10145
10246 const { limit = 100 , offset = 0 , roles, namedOnly = true } = args || { } ;
10347
104- // Get Puppeteer instance for native accessibility API
105- const puppeteer = await browser . getPuppeteer ( ) ;
106- const pages = await puppeteer . pages ( ) ;
48+ let nodes = await getBrowserAccessibilityTree ( browser ) ;
10749
108- if ( pages . length === 0 ) {
109- return {
110- content : [ { type : 'text' , text : 'No active pages found' } ] ,
111- } ;
112- }
113-
114- const page = pages [ 0 ] ;
115-
116- // Get accessibility snapshot with interestingOnly filter
117- const snapshot = await page . accessibility . snapshot ( {
118- interestingOnly : true , // Filter to only interesting/semantic nodes
119- } ) ;
120-
121- if ( ! snapshot ) {
50+ if ( nodes . length === 0 ) {
12251 return {
12352 content : [ { type : 'text' , text : 'No accessibility tree available' } ] ,
12453 } ;
12554 }
12655
127- // Flatten the hierarchical tree into a flat list
128- let nodes = flattenAccessibilityTree ( snapshot ) ;
129-
13056 // Filter to named nodes only (removes anonymous containers, StaticText duplicates)
13157 if ( namedOnly ) {
132- nodes = nodes . filter ( n => n . name && n . name . trim ( ) !== '' ) ;
58+ nodes = nodes . filter ( ( n ) => n . name && n . name . trim ( ) !== '' ) ;
13359 }
13460
13561 // Filter to specific roles if provided
13662 if ( roles && roles . length > 0 ) {
137- const roleSet = new Set ( roles . map ( r => r . toLowerCase ( ) ) ) ;
138- nodes = nodes . filter ( n => n . role && roleSet . has ( n . role . toLowerCase ( ) ) ) ;
63+ const roleSet = new Set ( roles . map ( ( r ) => r . toLowerCase ( ) ) ) ;
64+ nodes = nodes . filter ( ( n ) => n . role && roleSet . has ( n . role . toLowerCase ( ) ) ) ;
13965 }
14066
14167 const total = nodes . length ;
0 commit comments