# Mcp Accessibility Bridge

> MCP server that exposes Chrome's accessibility tree to Claude — generate Playwright/Selenium/Cypress/WebdriverIO selectors from natural language

- **Type:** MCP server
- **Install:** `agentstack add mcp-yashpreetbathla-mcp-accessibility-bridge`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [yashpreetbathla](https://agentstack.voostack.com/s/yashpreetbathla)
- **Installs:** 0
- **Category:** [Web & Browser](https://agentstack.voostack.com/c/web-and-browser)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [yashpreetbathla](https://github.com/yashpreetbathla)
- **Source:** https://github.com/yashpreetbathla/mcp-accessibility-bridge
- **Website:** https://www.npmjs.com/package/mcp-accessibility-bridge

## Install

```sh
agentstack add mcp-yashpreetbathla-mcp-accessibility-bridge
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# MCP Accessibility Bridge

**Expose any live webpage's accessibility tree to Claude — generate rock-solid, framework-agnostic test selectors in seconds.**

[](https://www.npmjs.com/package/mcp-accessibility-bridge)
[](https://www.npmjs.com/package/mcp-accessibility-bridge)
[](LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org)
[](https://github.com/modelcontextprotocol/sdk)

---

## Quick Start

No cloning. No building. Paste this into your Claude Desktop config and you're done:

```json
{
  "mcpServers": {
    "accessibility-bridge": {
      "command": "npx",
      "args": ["-y", "mcp-accessibility-bridge"]
    }
  }
}
```

---

## What Is This?

MCP Accessibility Bridge is a **stdio MCP server** that connects Claude Desktop to a live Chrome browser via the [Chrome DevTools Protocol (CDP)](https://chromedevtools.github.io/devtools-protocol/). It exposes the browser's full ARIA accessibility tree so Claude can:

- Read every element's role, name, state, and relationships exactly as a screen reader would
- Generate reliable, stable test selectors for **Playwright, Selenium, Cypress, and WebdriverIO** — without ever opening DevTools
- Audit pages for accessibility issues
- Write, debug, and migrate test suites using natural language

> **No bundled Chromium.** Uses your existing Chrome installation via `puppeteer-core`.

---

## Table of Contents

- [Quick Start](#quick-start)
- [Architecture](#architecture)
- [Why the Accessibility Tree?](#why-the-accessibility-tree)
- [8 MCP Tools](#8-mcp-tools)
- [Selector Priority Engine](#selector-priority-engine)
- [Real-World Use Cases](#real-world-use-cases)
- [Chrome Setup](#chrome-setup)
- [Claude Desktop Configuration](#claude-desktop-configuration)
- [Usage Examples](#usage-examples)
- [Example Project](#example-project)
- [Project Structure](#project-structure)
- [How It Works (Deep Dive)](#how-it-works-deep-dive)
- [Contributing](#contributing)
- [License](#license)

---

## Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                        Claude Desktop                               │
│                                                                     │
│   ┌─────────────┐    MCP (stdio)    ┌──────────────────────────┐   │
│   │   Claude    │◄─────────────────►│  MCP Accessibility       │   │
│   │   LLM       │   JSON-RPC 2.0    │  Bridge (Node.js)        │   │
│   └─────────────┘                   │                          │   │
│                                     │  ┌────────────────────┐  │   │
│                                     │  │  8 MCP Tools       │  │   │
│                                     │  │  browser_connect   │  │   │
│                                     │  │  browser_navigate  │  │   │
│                                     │  │  get_ax_tree       │  │   │
│                                     │  │  query_ax_tree     │  │   │
│                                     │  │  get_element_props │  │   │
│                                     │  │  get_interactive   │  │   │
│                                     │  │  get_focused       │  │   │
│                                     │  │  browser_disconnect│  │   │
│                                     │  └────────┬───────────┘  │   │
│                                     │           │               │   │
│                                     │  ┌────────▼───────────┐  │   │
│                                     │  │  BrowserManager    │  │   │
│                                     │  │  (Singleton)       │  │   │
│                                     │  │  Browser + Page +  │  │   │
│                                     │  │  CDPSession        │  │   │
│                                     │  └────────┬───────────┘  │   │
│                                     │           │               │   │
│                                     │  ┌────────▼───────────┐  │   │
│                                     │  │  Utilities         │  │   │
│                                     │  │  axTree.ts         │  │   │
│                                     │  │  selectorGen.ts    │  │   │
│                                     │  │  errors.ts         │  │   │
│                                     │  └────────────────────┘  │   │
│                                     └────────────┬─────────────┘   │
└──────────────────────────────────────────────────┼─────────────────┘
                                                   │
                                     CDP WebSocket │
                                     (port 9222)   │
                                                   │
                              ┌────────────────────▼──────────────────┐
                              │          Google Chrome                 │
                              │                                        │
                              │  ┌──────────────────────────────────┐  │
                              │  │  Active Tab                      │  │
                              │  │                                  │  │
                              │  │  DOM Tree ──► AX Tree            │  │
                              │  │                ▲                 │  │
                              │  │  Computed      │                 │  │
                              │  │  Accessibility │                 │  │
                              │  │  Object Model  │                 │  │
                              │  └────────────────┼─────────────────┘  │
                              │                   │                    │
                              │   CDP Domains Used:                    │
                              │   • Accessibility.enable               │
                              │   • Accessibility.getFullAXTree        │
                              │   • Accessibility.getPartialAXTree     │
                              │   • Accessibility.queryAXTree          │
                              │   • DOM.describeNode                   │
                              │   • DOM.getOuterHTML                   │
                              └────────────────────────────────────────┘
```

### Data Flow

```
User prompt in Claude Desktop
         │
         ▼
Claude decides which tool to call
         │
         ▼
MCP SDK dispatches tool call (stdio JSON-RPC)
         │
         ▼
Tool handler in src/tools/
         │
         ▼
BrowserManager.requireConnection()
     returns { browser, page, cdpSession }
         │
         ▼
CDP commands sent over WebSocket to Chrome
         │
         ▼
Chrome computes AX tree from live DOM
         │
         ▼
Raw CDP response parsed + transformed
         │
         ▼
selectorGenerator.ts builds multi-framework selectors
         │
         ▼
toolSuccess({ ... }) → JSON returned to Claude
         │
         ▼
Claude reads selectors, names, roles → responds to user
```

---

## Why the Accessibility Tree?

Most test automation targets the **DOM** — brittle class names, nested div soup, hashed CSS modules. The accessibility tree is different:

| Property | DOM Selectors | AX Tree Selectors |
|---|---|---|
| **Stability** | Break on CSS refactors | Stable across visual redesigns |
| **Dynamic state** | Miss `disabled`, `expanded`, `checked` | Reflect real runtime state |
| **Semantic meaning** | Target implementation details | Target user-facing intent |
| **Framework coupling** | Often framework-specific | Framework-agnostic |
| **Accessibility signal** | Silent on a11y problems | Surface a11y bugs automatically |
| **Screen reader parity** | Unknown | Exactly what a screen reader announces |

The AX tree is Chrome's **computed semantic model** — what the browser exposes to assistive technology. Selectors built from it use `role` and `name` attributes that are:

1. **Immune to visual redesigns** — renaming a CSS class doesn't change `role=button[name='Submit']`
2. **Semantically verified** — if Claude can select it, a screen reader can reach it
3. **Framework-neutral** — the same ARIA semantics translate to any test framework

---

## 8 MCP Tools

### `browser_connect`

Connect Claude to a running Chrome instance via CDP.

```
Input:  debugUrl (string, default: "http://localhost:9222")
Output: { connected, debugUrl, currentUrl, pageTitle }
```

Chrome must be started with `--remote-debugging-port=9222`. The tool creates a single shared `CDPSession` that all other tools reuse, enabling `Accessibility.*` CDP domain calls.

---

### `browser_navigate`

Navigate the connected tab to any URL.

```
Input:  url (string), waitUntil (load|domcontentloaded|networkidle0|networkidle2), timeout (ms)
Output: { navigated, url, finalUrl, title, status }
```

Returns the HTTP status code and final URL (after redirects).

---

### `browser_disconnect`

Close the CDP connection cleanly. Does **not** kill Chrome.

```
Input:  (none)
Output: { disconnected, message }
```

---

### `get_accessibility_tree`

Snapshot the full accessibility tree of the current page.

```
Input:  interestingOnly (bool), maxDepth (int), useFullTree (bool)
Output: { url, title, nodeCount, tree }
```

Two modes:
- **Default:** `page.accessibility.snapshot()` — fast, filters noise, ideal for most pages
- **Full CDP:** `Accessibility.getFullAXTree` — raw, complete, slower — use when the default misses nodes

---

### `query_accessibility_tree`

Search the tree by ARIA role and/or accessible name.

```
Input:  role (string), accessibleName (string), backendNodeId (int)
Output: { query, count, nodes[] }
```

Uses CDP `Accessibility.queryAXTree` — targeted and fast. Example: find all unchecked checkboxes, all level-2 headings, all disabled buttons.

---

### `get_element_properties`

Given a CSS selector, return the element's full AX profile and multi-framework selectors.

```
Input:  selector (string), includeHtml (bool)
Output: { selector, tagName, domAttributes, backendNodeId, accessibility, suggestedSelectors }
```

Resolves: `CSS selector → backendNodeId → Accessibility.getPartialAXTree → selectorGenerator`.

---

### `get_interactive_elements`

Find every interactive element on the page — buttons, inputs, links, tabs, etc.

```
Input:  roles (string[]), includeDisabled (bool), maxElements (int)
Output: { totalFound, returned, elements[] }
```

Filters `Accessibility.getFullAXTree` by **20 interactive ARIA roles**, then calls `DOM.describeNode` in parallel for each to retrieve DOM attributes for selector generation.

**20 covered roles:**
`button` · `link` · `textbox` · `searchbox` · `combobox` · `listbox` · `option` · `checkbox` · `radio` · `switch` · `slider` · `spinbutton` · `menuitem` · `tab` · `treeitem` · `gridcell` · `rowheader` · `columnheader` · `progressbar` · `scrollbar`

---

### `get_focused_element`

Return the currently keyboard-focused element's AX info and selectors.

```
Input:  (none)
Output: { focused: { role, name, value, tagName, domAttributes, suggestedSelectors } }
```

Uses `document.activeElement` + `Accessibility.getPartialAXTree` to report what a keyboard user is currently on.

---

## Selector Priority Engine

`src/utils/selectorGenerator.ts` implements a 4-tier priority system:

```
Priority 1 — Test ID attributes (HIGHEST STABILITY)
──────────────────────────────────────────────────
Checks: data-testid, data-cy, data-test, data-qa
Output: [data-testid="submit-btn"]
Playwright: page.getByTestId('submit-btn')

Priority 2 — Stable element ID
──────────────────────────────────────────────────
Checks: id attribute
Skips:  UUIDs, numeric IDs, mat-* / ng-* prefixes
Output: #email-input
Playwright: page.locator('#email-input')

Priority 3 — ARIA role + accessible name (MEDIUM STABILITY)
──────────────────────────────────────────────────
Uses: role + computed accessible name from AX tree
Output: role=button[name='Submit']
Playwright: page.getByRole('button', { name: 'Submit' })

Priority 4 — Semantic CSS (FALLBACK)
──────────────────────────────────────────────────
Uses: tagName + type/name/role/placeholder attributes
Output: input[type="email"][name="email"]
Playwright: page.locator('input[type="email"][name="email"]')
```

Every element returns selectors for all four frameworks:

```json
{
  "testId": "[data-testid=\"search-input\"]",
  "aria": "role=searchbox[name='Search']",
  "css": "input[type=\"search\"]",
  "playwright": "page.getByRole('searchbox', { name: 'Search' })",
  "selenium": "driver.find_element(By.CSS_SELECTOR, '[data-testid=\"search-input\"]')",
  "cypress": "cy.get('[data-testid=\"search-input\"]')",
  "webdriverio": "$('[data-testid=\"search-input\"]')",
  "stability": "high",
  "recommended": "page.getByTestId('search-input')"
}
```

---

## Real-World Use Cases

### 1. Instant Test Suite from Zero

A legacy app with no tests. Navigate to any page and ask:
> *"Generate a Playwright test that fills the checkout form and submits it."*

Claude calls `get_interactive_elements`, receives all inputs and buttons with selectors, and writes the full test — in minutes, not days.

### 2. Cross-Framework Selector Migration

Moving from Selenium to Playwright? Hundreds of brittle XPath selectors?
> *"Give me the Playwright equivalent of every interactive element on this page."*

Claude maps `driver.find_element(By.XPATH, ...)` → `page.getByRole(...)` using the live AX tree as ground truth.

### 3. Accessibility Audit

> *"Get the full accessibility tree for /checkout and identify elements missing accessible names, wrong roles, or bad focus order."*

Claude reads the AX tree and reports:
- Buttons with `name: ""` (icon buttons missing `aria-label`)
- `` acting as buttons (`role: generic`, no keyboard access)
- Form fields missing `required` or `aria-describedby`

### 4. Debugging Flaky Tests

> *"The test can't find #submit-btn. Navigate to the page and check if it exists in the AX tree, and if it's enabled."*

Claude checks: is the element ignored? Is `disabled: true`? Is a modal trapping focus? All invisible to raw DOM queries, visible here.

### 5. Component Library Selector Documentation

> *"Open Storybook at localhost:6006 and document the recommended selector for every interactive element in every story."*

Claude iterates stories, calls `get_interactive_elements`, and outputs a complete selector reference.

### 6. Natural Language → Selector (for Non-Technical QA)

> *"Find the selector for the blue submit button at the bottom of the registration form."*

No DevTools needed. Claude queries the AX tree, identifies the button by its accessible name, and returns all four framework selectors.

### 7. Dynamic SPA Testing

React/Vue/Angular apps with hashed class names (`sc-abc123`) break CSS selectors on every build. AX tree selectors (`page.getByRole('button', { name: 'Subscribe' })`) are permanently stable — hashing class names never changes semantic meaning.

### 8. Pre-Merge Selector Validation

> *"Before merging this PR, verify these 10 selectors still resolve correctly on staging."*

Claude calls `get_element_properties` for each selector and confirms role + name still match expected values. Catches regressions before CI runs.

---

## Chrome Setup

Chrome must be running with the remote debugging port open **before** calling `browser_connect`.

### macOS

```bash
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=/tmp/chrome-debug-profile
```

### Linux

```bash
google-chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=/tmp/chrome-debug-profile
```

### Windows (PowerShell)

```powershell
& "C:\Program Files\Google\Chrome\Application\chrome.exe" `
  --remote-debugging-port=9222 `
  --user-data-dir="$env:TEMP\chrome-debug-profile"
```

### Verify Chrome is Ready

```bash
curl http://localhost:9222/json/version
```

Expected response:
```json
{
  "Browser": "Chrome/124.0.0.0",
  "Protocol-Version": "1.3",
  "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..."
}
```

> **Why a separate `--user-data-dir`?** Chrome requires

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [yashpreetbathla](https://github.com/yashpreetbathla)
- **Source:** [yashpreetbathla/mcp-accessibility-bridge](https://github.com/yashpreetbathla/mcp-accessibility-bridge)
- **License:** MIT
- **Homepage:** https://www.npmjs.com/package/mcp-accessibility-bridge

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-yashpreetbathla-mcp-accessibility-bridge
- Seller: https://agentstack.voostack.com/s/yashpreetbathla
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
