
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
@audioeye/testing-sdk-mcp
Advanced tools
Scans live pages with the AudioEye rules engine and maps accessibility issues to JSX source.
A Model Context Protocol server that drives a live browser to scan a URL with the AudioEye testing SDK, attaches JSX source locations to each issue, and exposes the results as MCP tools so AI coding agents can propose source-level fixes.
Getting started is three commands:
npm install -g @audioeye/testing-sdk-mcp
audioeye-mcp setup # register with every detected MCP host
audioeye-mcp login # sign in via your browser
…then ask your agent to /mcp__audioeye__scan-and-fix your dev server.
.mcpb bundle don't need Node at all — Claude Desktop bundles its own runtime.audioeye-mcp login (see Sign in); without credentials, every tool that touches the AudioEye engine — opening the controlled browser (its page setup injects the engine), scans, verify-fix, a11y facts — fails closed with SdkLicenseError before any browser work. Rule metadata lookups work without credentials (their cache is only populated by licensed scans).@audioeye/testing-sdk-mcp is published to the public npm registry — no registry configuration or install token needed. Install it globally so the audioeye-mcp bin lands on your PATH:
npm install -g @audioeye/testing-sdk-mcp
The server also runs over stdio via npx -y @audioeye/testing-sdk-mcp, but prefer the global install for host registration: npx -y re-resolves the package against whatever directory the MCP host launches from, and a workspace whose package.json carries a conflicting npm overrides block kills the server at startup (EOVERRIDE — hosts just show "Connection closed"). setup registers the installed bin's absolute path to sidestep this.
Register it under the key audioeye — hosts prefix tool and prompt names with it (mcp__audioeye__scan, …). The setup command does this for you; manual per-host options are listed there too.
audioeye-mcp login
login prints a short pairing code, opens your browser to the AudioEye portal's device-approval page, and waits. You confirm the code and click Approve in the portal (signed in as your AudioEye user); the credentials then land on your machine at ~/.config/audioeye/credentials.json (%APPDATA%\audioeye\credentials.json on Windows), permissions 0600. No secret is ever printed to the terminal or pasted by hand.
audioeye-mcp whoami — shows the active identity, and which source it came from (environment variables win over the stored credentials).audioeye-mcp logout — deletes the stored credentials. Environment credentials are untouched.If you also use the AudioEye CLI (aetest), aetest login is the same flow writing the same store — sign in once, both work.
On hosts with no terminal (Claude Desktop's .mcpb install), sign in from the chat with the audioeye_login tool instead — same flow, same credential store, no Node or terminal required.
For non-interactive environments (CI), skip login and provide credentials as environment variables — see CI.
Run the bundled setup command once per machine:
audioeye-mcp setup
With no flags this is setup --all-detected --global: it registers the server into every detected host at machine-wide scope — for VS Code that means the user-profile mcp.json (read by GitHub Copilot and every other native-MCP consumer), never a file inside your repo. To configure only specific hosts, pass a flag per host — host flags switch off the all-detected default:
audioeye-mcp setup --claude --global # Claude Code only, machine-wide (`claude mcp add --scope user` + ~/.claude permissions.allow)
audioeye-mcp setup --claude # Claude Code only, project-scoped (`claude mcp add` + ./.claude permissions.allow)
audioeye-mcp setup --cursor --vscode --windsurf --zed --codex --gemini
--global affects Claude Code (user-scope registration + ~/.claude/settings.json), VS Code (user-profile mcp.json instead of the project ./.vscode/mcp.json), and gemini (mcp add --scope user instead of the CLI's project-scope default); the other hosts' config files are inherently user-scoped.
Not sure what it will touch? audioeye-mcp setup --dry-run prints every host's detection result (including which binary a vendor CLI resolved to) and exactly what would be registered, without writing anything.
The registered server command is the absolute path of the audioeye-mcp bin that ran setup (via node on Windows). When setup itself runs from the npx cache (npx -y @audioeye/testing-sdk-mcp setup), that path is evictable, so it registers the npx -y @audioeye/testing-sdk-mcp form instead — which breaks in workspaces with a conflicting npm overrides block (see Install). Install globally first.
Two registration strategies, per host:
--claude, --codex, --gemini): shells out to the host's own mcp add command — preferred, because that CLI owns the shape of its config. When the CLI isn't on PATH, a standalone install is tried next (claude's native-installer paths ~/.local/bin/claude, ~/.claude/local/claude); a CLI that ships only inside another app's bundle is never spawned — running a bundled binary standalone makes macOS Gatekeeper flag it as malware and quarantine it. If that still finds nothing, the host's config file is written directly instead, into the same file the CLI would have:
~/.claude.json when it (or ~/.claude) exists: a non-destructive merge into mcpServers (with --global) or the per-project projects[<cwd>].mcpServers map, leaving the rest of that managed file untouched.~/.codex/config.toml when ~/.codex exists (the same file the ChatGPT app, the VS Code extension, and the CLI all read): a surgical edit of its [mcp_servers.audioeye] table that leaves every other table, comment, and formatting choice untouched.settings.json (also read by Gemini Code Assist's agent mode) at the scope the CLI would use — ~/.gemini/ with --global, else the project's ./.gemini/.--cursor, --vscode, --windsurf, --zed): a non-destructive JSON merge into the host's own config file (~/.cursor/mcp.json; ./.vscode/mcp.json, or the user-profile mcp.json with --global; ~/.codeium/windsurf/mcp_config.json; ~/.config/zed/settings.json). Never writes an env block — credentials come from the SDK credential store, not host configs. If our audioeye entry has drifted from what we'd write, it's overwritten and reported updated — that key is ours to own.Claude Code additionally appends mcp__audioeye__* to permissions.allow in settings.json, so AudioEye's tools stop prompting for permission on every call.
--all-detected includes VS Code only together with --global (the bare-setup default), where the write goes to the user profile. Without --global the vscode write would land inside the project tree (committable) off a weak signal — a ./.vscode directory exists in most repos regardless of editor — so pass --vscode explicitly for a project-scoped install.
Safe to re-run: config-write hosts (including claude/codex/gemini via their fallback) report already-present/updated with no duplicate entries; when a vendor CLI is on PATH, re-running its own idempotent mcp add may just report success again rather than already-present (codex/gemini don't signal "already registered" — they just silently succeed). Reload your MCP client (Claude Code: Developer → Reload Window) after running it. If any host fails, setupCli exits non-zero while still leaving successfully-configured hosts in place.
Read-only AudioEye tools (audioeye_scan, audioeye_get_a11y_facts, audioeye_get_rule_metadata, audioeye_get_source_context) also carry readOnlyHint: true annotations per the MCP spec — hosts that auto-approve read-only tools (some IDE integrations, future Claude Code versions) skip the prompt without needing the allowlist at all.
If you'd rather register by hand (or your host isn't covered by setup), docs/install-links.md has the generated one-liners and deeplinks:
Cursor / VS Code — one-click install deeplinks.
Claude Code / Codex / Gemini CLIs:
claude mcp add audioeye -- npx -y @audioeye/testing-sdk-mcp
codex mcp add audioeye -- npx -y @audioeye/testing-sdk-mcp
# gemini's grammar differs: the command goes before `--` (`mcp add <name> <command> -- <args>`)
gemini mcp add audioeye npx -- -y @audioeye/testing-sdk-mcp
With a global install, registering the bin (claude mcp add audioeye -- audioeye-mcp) is sturdier than the npx form — see Install.
Claude Desktop — install the audioeye-mcp-<version>.mcpb bundle (Settings → Extensions; drag the file in). The bundle ships its own dependencies and runs on Claude Desktop's bundled Node.js.
Keep the registration key audioeye — it prefixes the tool and prompt names hosts display (/mcp__audioeye__scan, …).
In CI (and any non-interactive environment), provide the license credentials as environment variables instead of the browser sign-in. A stored browser sign-in takes precedence when present; the env vars are the fallback (and also kick in when the stored credentials fail validation):
AUDIOEYE_TESTING_SDK_CLIENT_ID — your AudioEye testing-SDK client id.AUDIOEYE_TESTING_SDK_CLIENT_TOKEN — the matching token.Both come from the AudioEye portal (My Account → Testing SDK → CI credentials). Store them in your CI secret store and export them into the job environment. When a host config needs them inline, use the host's variable expansion so the committed file references only the variable name (Claude Code's .mcp.json variable expansion shown):
{
"mcpServers": {
"audioeye": {
"command": "audioeye-mcp",
"env": {
"AUDIOEYE_TESTING_SDK_CLIENT_ID": "${AUDIOEYE_TESTING_SDK_CLIENT_ID}",
"AUDIOEYE_TESTING_SDK_CLIENT_TOKEN": "${AUDIOEYE_TESTING_SDK_CLIENT_TOKEN}"
}
}
}
}
Don't commit the raw token, and don't pass secrets as CLI flags — arguments are visible to any local process via ps and leak into shell history; a child process's environment is only readable by the same user.
audioeye_open_browser({ url? }) — launch (or focus) a headed Chrome window with a persistent profile at ~/.cache/audioeye-mcp/profile/. Login state survives between scans. Idempotent.audioeye_scan({ url?, runOptions?, waitForReadyMs?, persistTo? }) — run the SDK scan in-page, walk the framework's component tree to attach a JSX source location (fileName:lineNumber:columnNumber) to each failing element, and return a slim grouped JSON result. Trimmed rule metadata (title, description, sourceFixGuidance, severity, …) is embedded in the response under metadata[ruleCode] for every rule that failed — no follow-up audioeye_get_rule_metadata call needed. Pass persistTo: ".agent/a11y-scan.json" (or any workspace-relative path) to have the server write the response to disk and return persistedTo so the agent can read it back later without re-emitting the JSON.audioeye_get_rule_metadata({ ruleCodes }) — look up rule metadata for codes from a previous scan whose response is no longer in your context. New scans embed metadata directly; this tool is only needed to backfill from earlier turns.audioeye_get_source_context({ source, contextLines? }) — read ±N lines around a DebugSource from a previous scan. Workspace-bounded (rejects paths outside AUDIOEYE_MCP_WORKSPACE / process.cwd()).audioeye_get_a11y_facts({ cssSelector }) — compute the assistive-tech facts (accessible name, role, resolved aria-* attrs, nearest landmark, nearest focusable ancestor, inheritedAtHidden) for an element on the currently-open page.audioeye_verify_fix({ ruleCode, source, url? }) — re-scan after applying a fix and report whether the targeted (ruleCode, fileName, lineNumber) failure is gone within ±15 lines (tolerant to JSX shifts).audioeye_close_browser() — graceful shutdown; profile preserved.audioeye_login() — sign in without a terminal, for hosts that have none (Claude Desktop's .mcpb install). The first call returns a pairing code and the portal's device-approval link; approve it in the browser and call again to check progress. A background poll writes the shared credential store on success, so the CLI and every other host are signed in too. The entitlement token is never returned to the host.At boot the server checks the npm registry (channel-aware: an alpha install compares against the alpha dist-tag, a release against latest) at most once every 24h, cached under the SDK cache dir. The check is best-effort — it never blocks the server, and a failed check simply reports no update.
When a newer version is published, the "newer version available" note rides along on every tool result until you update (it never corrupts the JSON — it's an extra markdown block). Update with npm install -g @audioeye/testing-sdk-mcp@<channel>.
Three composable prompts ship with the server, registered via the standard MCP prompts/list API — every host that surfaces MCP prompts exposes them under its own naming with no setup step (Claude Code: /mcp__audioeye__scan, /mcp__audioeye__fix, /mcp__audioeye__scan-and-fix):
scan (takes an optional url argument) — scans the live URL with the AudioEye testing SDK and summarizes failures (with JSX source locations where available). The summary tables are server-rendered and deterministic — the agent presents the response's summaryMarkdown verbatim. No code edits. Persists the scan to .agent/a11y-scan.json for the fix flow to read back.fix — proposes source-level fixes against an EXISTING scan response — either in conversation context, or persisted at .agent/a11y-scan.json from a prior session. Re-displays the persisted scan's server-rendered summaryMarkdown instead of rebuilding tables. Does not run a scan.scan-and-fix (takes an optional url argument) — full end-to-end flow: open browser → wait for login → scan → summarize → propose source-level fixes (only on user approval) → verify. The scan summary shown to the user is the server-rendered, deterministic summaryMarkdown from the scan response. This replaces the previous /fix-a11y slash command.The prompt bodies compose from the Markdown chunks under prompts/_partials/ — single source of truth, no drift between commands. Customers do not need to copy a SKILL.md into every repo — updates to the prompts land via npm update, not per-repo hand edits.
A project-local .audioeye-mcp.json (discovered by walking up from the workspace root — AUDIOEYE_MCP_WORKSPACE when set, otherwise process.cwd()) suppresses known-irrelevant failures:
{
"ignore": [
{
"cssSelector": "footer.TanStackRouterDevtools",
"comment": "Dev-only widget; not shipped to prod."
},
{
"ruleCode": "Iframe_Name_Missing",
"cssSelector": "iframe#cb-master-frame",
"comment": "Vendor SDK iframe — vendor-owned."
},
{
"fileNameContains": "node_modules/",
"comment": "Don't try to patch dependencies."
}
]
}
Each entry's fields are AND'd; multiple entries are OR'd. The scan response includes the resolved config path and per-rule hit counts so reviewers can audit what's filtered.
cssSelector is evaluated in the page against the live DOM (element.closest), so one field covers both the failing element itself and anything nested inside a matching container.
License (CI fallback — interactive machines should use audioeye-mcp login instead):
AUDIOEYE_TESTING_SDK_CLIENT_ID / AUDIOEYE_TESTING_SDK_CLIENT_TOKEN — see CI. Stored device credentials take precedence when present; the env vars are used when there is no sign-in or the stored credentials fail validation. With neither the env vars nor a device sign-in present, every browser-opening, scan, verify-fix, and a11y-facts call fails closed with SdkLicenseError.Optional (behavior):
AUDIOEYE_MCP_PROFILE_DIR — override the persistent profile location. Defaults to ~/.cache/audioeye-mcp/profile.AUDIOEYE_MCP_HEADLESS — set to 1 or true to run Chrome headless. Defaults to headed (so the user can authenticate interactively).AUDIOEYE_MCP_WORKSPACE — repo root used by audioeye_scan's persistTo, audioeye_get_source_context, and .audioeye-mcp.json discovery. Defaults to process.cwd(). Set this when the host's working directory differs from the repo root.AUDIOEYE_MCP_CONFIG_PATH — explicit absolute path to a config file, bypassing the workspace-root walk.Symlink containment for audioeye_scan's persistTo writes and audioeye_get_source_context reads is enforced via POSIX O_NOFOLLOW (and a realpath containment check on the parent dir). On Windows hosts the O_NOFOLLOW flag is a no-op in some Node versions, so symlink containment there is best-effort — the realpath check still applies, but the final-segment open will follow a symlink. Run the MCP server on macOS or Linux for full containment.
The scan resolves JSX source for failures via seven fallback paths (in order, per fiber):
fiber._debugSource — React 16 / 17 (with @babel/plugin-transform-react-jsx-source) and React 18.0–18.2 stable.fiber._debugStack — React 19 (the dev runtime captures new Error() at the JSX call site; the SDK parses the first user frame).jsxDEV/jsxsDEV wrapper installed on webpack chunk globals — covers React canaries that drop both _debugSource and _debugStack (notably the build Next.js 14 pins).React.cloneElement source-preservation hook — propagates the WeakMap entry from the original element's props to the cloned props, so wrapper / slot-composition patterns (MUI / Joy <IconButton component={NextLink}>, Storyblok link wrappers) keep their source.props.__source directly when present.[[FunctionLocation]] + source-map translation — when the in-page walk captures a component function reference but no source location, the Node-side server queries CDP for the function's compiled position, fetches the bundle's source map, and VLQ-decodes the mapping. Matches via this path are tagged confidence: 'passthrough' with passthroughReason: 'component-function-location'. Lets prod builds work as long as source maps ship.Function.prototype.toString() on the component type. The dev JSX transform (SWC and Babel both) stamps the literal {fileName, lineNumber, columnNumber} source object inside the compiled component body; we regex it out. File-level precision only (the lineNumber is the first JSX call in the body, not the specific failing element), so matches via this path are tagged confidence: 'passthrough' with passthroughReason: 'function-body-source'. The agent reads the file to locate the precise line.JSX rendered by React Server Components — that is, files in a Next.js App Router project that do not opt into 'use client' — runs server-side and is serialized into the React Flight payload stripped of __source info. The client-side fiber tree only sees the 'use client' boundary; everything upstream is opaque from the browser.
As a result, the SDK cannot map failures in server-rendered DOM back to their authoring site. The fiber walk will find a "nearest" source, but it's typically the wrapper boundary (e.g. a top-level <ThemeProvider> or <JoyCssVarsProvider>), not the file that authored the failing element.
To detect this, the SDK classifies each sourceMapped match with a confidence field:
confidence: 'high' — the match is the real authoring site. Propose normally.confidence: 'passthrough' — the match is a wrapper / provider / cloneElement shim and probably not the real authoring site. The scan response includes a passthroughReason label (no-named-component-in-walk, children-passthrough-line, context-provider-line, cloneElement-call-line) so the agent can warn the user precisely.To restore precise source mapping for an RSC-rendered component, mark its file with 'use client' so it executes client-side; the JSX dev runtime will then run in the browser and the SDK's interception path captures source normally.
The summary.failuresWithSourceHighConfidence metric is the one to gate auto-propose flows on — summary.failuresWithSource includes passthrough matches and can read healthy even when most failures point at the wrong file.
A pre-flight check — if your project sits mostly in one of these buckets, expect few actionable source-level diffs.
audioeye_scan still runs and reports failures, but every failure lands in unmapped because there's no JSX fiber tree to walk — which also means we never capture a compiled position to feed the existing source-map resolver. Even when your prod bundle ships source maps, we have no anchor to translate them from. Pluggable framework adapters (tracked under Roadmap) are the path to unlocking source-map recovery here.confidence: 'passthrough' and passthroughReason: 'cms-rendered-content:<CMS>', and the fix flow directs you to fix in the CMS admin. No code diff is proposed._debugSource / _debugStack and friends) is dev-only. In prod builds, those signals are gone. If your prod bundle ships source maps, the MCP's Node-side V8 [[FunctionLocation]] + source-map translation (see componentFunctionLookup.ts) recovers the original source — failures land with confidence: 'passthrough' and passthroughReason: 'component-function-location', which the agent surfaces as a per-row warning. If source maps are absent, every prod failure goes to unmapped and sourceMappingDiagnostics flags reactDevTransformMissing so the agent can tell the user to either re-enable the dev transform or ship source maps.thirdPartyIframe; the fix is a vendor change or runtime patching via AudioEye's overlay, not a source diff.shadowDom.'use client'. See the RSC caveat. On App Router projects that don't opt components into 'use client', most failures surface as passthrough with no-named-component-in-walk — useful as a signal, not actionable as a code diff. Source maps don't help here either — the missing data is the server-side fiber tree, not source-map translation.docs/framework-support.md. The package currently only resolves source locations for React (via fiber _debugSource). Preact, SolidJS, Inferno, Vue, Svelte, and Angular are planned via a pluggable adapter API with per-project framework caching.The AudioEye-controlled Chrome window is parented to the MCP server process the host (Claude Code, VS Code, etc.) spawned. When that process exits, Chrome exits with it — by design. The MCP server is a single-session tool, not a long-running service, and shutting Chrome down with the host avoids leaving an orphaned browser running across editor restarts. The persistent profile at ~/.cache/audioeye-mcp/profile/ keeps your login state for the next session.
Proprietary — see LICENSE. The SDK and any accompanying source code are not open source; use is subject to AudioEye's terms of service or your separate agreement with AudioEye.
FAQs
Scans live pages with the AudioEye rules engine and maps accessibility issues to JSX source.
The npm package @audioeye/testing-sdk-mcp receives a total of 26 weekly downloads. As such, @audioeye/testing-sdk-mcp popularity was classified as not popular.
We found that @audioeye/testing-sdk-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.