tactual-mcp
OfficialThe tactual-mcp server provides tools for analyzing web page accessibility from a screen-reader navigation cost perspective — measuring keystrokes/actions needed to discover, reach, and operate interactive targets.
analyze_url— Analyze a single page for screen-reader navigation cost, returning scored findings (discoverability, reachability, operability, recovery, interop risk) per target. Supports multiple output formats (SARIF, JSON, Markdown), AT profile selection, optional exploration of hidden UI (menus, dialogs, accordions), keyboard/widget/form probes, filtering by landmark/severity/CSS selector, SPA support, authenticated analysis viastorageState, and summary-only mode.analyze_pages— Analyze 2–20 URLs in a single session, producing an aggregated site-level report with per-page breakdown and repeated navigation-cost groups.trace_path— Trace the exact step-by-step screen-reader navigation path to a specific target, showing modeled AT announcements, cumulative cost, and role/name at each hop. Can reuse pre-captured states fromanalyze_urlto skip browser launch.diff_results— Compare two analysis results (before/after) to identify improvements, regressions, resolved/added penalties, and severity changes per target.suggest_remediations— Extract and rank the top unique remediation suggestions from a JSON analysis result by severity, providing a prioritized fix list.list_profiles— List available AT profiles (NVDA, JAWS, VoiceOver iOS, TalkBack Android, generic mobile) with their IDs, platforms, and descriptions.save_auth— Authenticate with a web application by executing login steps (clicks, form fills, waits) and save the session state (cookies + localStorage) for use in subsequent protected-page analyses.
Provides accessibility analysis targeting Android devices via the TalkBack screen reader profile (talkback-android-v0), measuring navigation costs and discoverability for TalkBack users.
Provides continuous integration support via GitHub Actions, including SARIF output format for GitHub code scanning and reusable workflows for automated accessibility analysis in CI pipelines.
Supports configuration as an MCP server for GitHub Copilot, enabling AI-assisted accessibility analysis and navigation cost measurement directly within the development workflow.
Provides accessibility analysis targeting iOS devices via the VoiceOver screen reader profile (voiceover-ios-v0), including rotor-based navigation cost measurement for Safari on iOS.
Tactual
Screen-reader navigation cost analyzer. Measures how many keystrokes a screen-reader user needs to discover, reach, and operate every interactive target on your page — under a specific AT profile (NVDA, JAWS, VoiceOver).
What it does
Existing accessibility tools check conformance — is the ARIA correct? Is the contrast ratio sufficient?
Tactual measures navigation cost — how many actions does it take a screen-reader user to reach the checkout button? What happens if they overshoot? Can they even discover it exists? Does the menu actually open on Enter, or only on click? Does focus land on the first menuitem or stay stuck on the trigger?
How it works:
Captures Playwright accessibility snapshots + screen-reader announcement simulation
Optionally explores hidden branches (menus, dialogs, tabs, disclosures) and probes them with real keyboard events, including APG-style widget contracts and form-error flows
Builds a navigation graph with entry points (landmarks, headings, linear Tab) and scores every target
Optionally validates predicted paths against
@guidepup/virtual-screen-readerfor calibration
Tactual is a developer tool for analyzing your own sites and staging environments. Run it locally, in CI, or via the MCP server in your editor. It is not a public scanning service.
Related MCP server: Playwright MCP Server
How it fits
Tactual complements conformance scanners such as axe-core, Lighthouse, and Pa11y. Those tools are still the right first pass for broad WCAG and ARIA rule coverage. Tactual is aimed at the next question: after a page has valid markup, how expensive is it for an AT user to discover, reach, and operate the important targets?
Use Tactual for screen-reader navigation-cost triage, path tracing, measured keyboard/widget evidence, before/after diffs, CI prioritization, and MCP workflows where an agent needs compact findings with source selectors and remediation candidates. Use real screen readers and manual testing for final validation of critical journeys, timing-sensitive flows, browser/AT settings, and implementation patterns that intentionally differ from a common APG example.
Agent quick path: this README is a product overview plus reference. Agents should start with docs/AGENT-RECIPES.md for task patterns and docs/MCP-TOOLS.md for full MCP schemas, then come back here only for product context, install notes, and release-surface examples.
Install
Requires Node.js 20 or later.
npm install tactualTactual installs Playwright as a runtime dependency so one-off npx tactual@latest ... commands work without separately installing Playwright into the npx cache. The MCP SDK also ships as a runtime dependency, so tactual-mcp works from an installed tactual package without a separate SDK install.
Quick start
CLI
# Analyze a URL (default profile: generic-mobile-web-sr-v0)
npx tactual analyze-url https://example.com
# Analyze with a specific AT profile
npx tactual analyze-url https://example.com --profile voiceover-ios-v0
# Explore hidden UI (menus, tabs, dialogs, disclosures)
npx tactual analyze-url https://example.com --explore
# Use a scoring preset for your use case
npx tactual analyze-url https://shop.com --preset ecommerce-checkout
npx tactual analyze-url https://docs.example.com --preset docs-site
# Output as JSON, Markdown, or SARIF
npx tactual analyze-url https://example.com --format json --output report.json
npx tactual analyze-url https://example.com --format sarif --output report.sarif
# Compare two analysis runs
npx tactual diff-results baseline.json candidate.json
npx tactual diff-results baseline.json candidate.json --format json
# Print what NVDA would say as you Tab through the page
npx tactual transcript https://example.com
npx tactual transcript https://example.com --at voiceover
# List available AT profiles and scoring presets
npx tactual profiles
npx tactual presets
# Run benchmark suites
npx tactual benchmark
npx tactual benchmark --suite all
Benchmark fixtures ship with the npm package, so the benchmark command works from a fresh install and does not require cloning the repository fixtures into your current directory.
# Validate predicted paths against a virtual screen reader (reachability + step count)
# Requires optional deps, installed by default with tactual
npx tactual validate-url https://example.com --max-targets 10 --strategy semantic
# Initialize a tactual.json config file
npx tactual init
# Analyze a bot-protected site with stealth + real Chrome
npx tactual analyze-url https://www.npmjs.com/ --stealth --channel chrome
# Deep keyboard probing including revealed widgets and form-error flows
npx tactual analyze-url https://docs.example.com --probe --explore --probe-mode deep
# Focus probing on one opened branch, such as a dialog trigger
npx tactual analyze-url https://app.example.com/settings \
--probe \
--entry-selector "[aria-controls='profile-dialog']" \
--probe-strategy modal-return-focus
# Analyze + inline virtual-SR validation in one command (predicted vs validated steps)
npx tactual analyze-url https://example.com --validate --validate-max-targets 10Console output includes a compacted path line for each finding showing how a screen-reader user reaches it:
██████░░ 70 link:reference structural
D:47 R:71 O:100 Rec:100
getByRole('link', { name: 'Reference' })
↪ Tab ×2 "v19.2" → K "Learn" → Tab "Reference"
→ Target is not efficiently reachable via heading or landmark navigationWhere Tab = nextItem, H = nextHeading, ; = nextLandmark, K = nextLink, B = nextButton, Enter = activate. Consecutive same-action steps collapse (Tab ×2).
From Audit to Fix
For accessibility work in a local app or preview environment, Tactual supplies evidence for small, reviewable changes:
selector,penalties,suggestedFixes, and evidence summaries on each findinggrouped
issueGroupsand remediation candidates in summarized outputanalyze_pages.site.repeatedNavigationfor repeated navigation cost across routesdiff-results/diff_resultsfor before-and-after verification
Start with broad triage, then deepen one route before changing code:
# Site-level triage. Redirect JSON for tool consumption.
npx tactual analyze-pages \
https://app.example.com/ \
https://app.example.com/docs \
https://app.example.com/settings \
--profile nvda-desktop-v0 \
--format json > tactual-site.json
# Deepen one route and produce a reviewable markdown report.
npx tactual analyze-url https://app.example.com/docs \
--profile nvda-desktop-v0 \
--explore --probe --probe-mode standard \
--format markdown --output tactual-report.md
# When one branch is the target, open it first and spend probe budget there.
npx tactual analyze-url https://app.example.com/docs \
--profile nvda-desktop-v0 \
--probe \
--entry-selector "[aria-controls='search-panel']" \
--probe-selector "#search-panel" \
--probe-strategy composite-widget \
--format markdown --output tactual-search-panel.md
# Save a baseline before editing, then verify the patch.
npx tactual analyze-url https://app.example.com/docs --explore --probe --format json --output baseline.json
# Edit one root cause in the local repo, rebuild/restart the preview, then re-run:
npx tactual analyze-url https://app.example.com/docs --explore --probe --format json --output candidate.json
npx tactual diff-results baseline.json candidate.jsonUse the candidate section as a starting point for repeated root causes such as a shared component, navigation pattern, or widget contract. Confirm the source component and include the route, command, finding evidence, user impact, code change, and verification in whatever issue or PR format the project expects. Score movement is useful supporting evidence, but the change should lead with the accessibility behavior that changed.
MCP clients can consume the same compact output and keep the review loop grounded in routes, selectors, evidence, source changes, and before/after verification.
Library API
import { analyze, getProfile } from "tactual";
import { captureState } from "tactual/playwright";
import { chromium } from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
const state = await captureState(page);
await browser.close();
const profile = getProfile("generic-mobile-web-sr-v0");
const result = analyze([state], profile);
for (const finding of result.findings) {
console.log(finding.targetId, finding.scores.overall, finding.severity);
}Screen-reader announcement simulator — predict what NVDA, JAWS, or VoiceOver would announce for every target, with state info (checked, expanded, selected, modal, value, required, invalid, etc.):
import {
simulateScreenReader,
buildAnnouncement,
buildMultiATAnnouncement,
buildTranscript,
} from "tactual/playwright";
const report = await simulateScreenReader(page, state.targets);
for (const a of report.formFields) {
console.log(a.announcement);
// → "Subscribe, check box, checked"
// → "Country, combo box, collapsed"
// → "Email, edit, invalid entry, required, you must use a work address"
}
// Compare across screen readers
const tx = state.targets[5];
buildAnnouncement(tx, "nvda"); // → "Country, combo box, collapsed"
buildAnnouncement(tx, "voiceover"); // → "Country, popup button"
// All three at once
buildMultiATAnnouncement(tx);
// → { nvda: "...", jaws: "...", voiceover: "..." }
// Linear navigation transcript — what an SR user hears Tabbing through
const transcript = buildTranscript(state.targets, "nvda");
// → [{ step: 1, kind: "landmark", announcement: "Main, main landmark" }, ...]
// Multi-target navigation modes (linear, by-heading, by-landmark, by-form-control)
import { buildNavigationTranscript } from "tactual/playwright";
// Heading-only navigation (NVDA: H key)
const headings = buildNavigationTranscript(state.targets, { mode: "by-heading" });
// Navigate from one element to another
const path = buildNavigationTranscript(state.targets, {
from: "link:before-main",
to: "heading:welcome",
mode: "linear",
});
// Demoted landmarks (in DOM but stripped by HTML rules, e.g. <header> in <section>)
for (const d of report.demotedLandmarks) {
console.warn(d.demotionReason);
}Validation and calibration APIs — compare model output against virtual-SR validation runs or human-observation datasets:
import { validateFindingsInJsdom } from "tactual/validation";
import { runCalibration, formatCalibrationReport } from "tactual/calibration";
// Given a JSDOM instance, PageState, AnalysisResult, and calibration dataset:
const validation = await validateFindingsInJsdom(dom, state, result.findings, {
maxTargets: 10,
strategy: "semantic",
});
const calibration = runCalibration(dataset, new Map([[state.url, result]]));
console.log(validation, formatCalibrationReport(calibration));Or from the CLI:
npx tactual transcript https://example.com --at voiceover
npx tactual calibration-report my-calibration.json --analysis example-nvda.jsonThe simulator is heuristic prediction, not real screen-reader output. The simulator itself is fast (pure JavaScript over captured targets — sub-second once targets are in memory), but a full analyze-url run includes browser launch + page capture + scoring and takes seconds on small pages, longer with --probe (~30s+) and --explore (~1–5 min on complex SPAs). Analysis runs in a headless browser by default, so nothing pops up while you work. (Use --no-headless or --channel chrome --stealth for visible/bot-protected sites.)
Data quality. Calibrated against token-level assertions from the W3C ARIA-AT project: 77/77 role/name/state-token assertions pass at 100% across all three ATs (NVDA, JAWS, VoiceOver), covering role/name/state phrasing for 36 single-target patterns (button, toggle button, all menu button variants, disclosure, accordion, checkbox/tri-state, switch, sliders, dialog, alert, links, tabs, comboboxes, radiogroups, spin button, menubar) plus 4 multi-target landmark scenarios. Run npm run calibrate after npm run build to verify against the latest upstream assertions. This is simulator calibration, not proof of full screen-reader fidelity across browse modes, verbosity settings, timing, or every valid widget variant. AT-specific overrides outside the calibrated set are labeled HIGH/MEDIUM/LOW confidence in the source.
MCP Server
Tactual includes an MCP server for AI agent consumption:
# Start the MCP server (stdio transport — default)
npx tactual-mcp
# Start with HTTP transport (for hosted platforms, remote clients)
npx tactual-mcp --http # listens on http://127.0.0.1:8787/mcp
npx tactual-mcp --http --port=3000 # custom port (or set PORT env var)
npx tactual-mcp --http --port 3000 # space-separated form is also supported
npx tactual-mcp --http --host=0.0.0.0 # bind to all interfaces (default: 127.0.0.1)For network-facing MCP deployments, put the HTTP transport behind an authenticated TLS proxy and keep it scoped to trusted clients. See SECURITY.md for the hosted checklist and threat model.
MCP tools available:
Tool | Description |
| Analyze a page for SR navigation cost (SARIF default). Supports opt-in exploration, keyboard/widget/form probes, scoped/goal-directed probing, stealth/channel for bot-protected sites, and filtering. |
| Step-by-step navigation path to a target with modeled SR announcements. |
| Validate predicted paths against |
| Run observed calibration datasets against saved full analysis JSON and return structured scoring signals for tuning/review workflows. |
| List available AT profiles. |
| Compare two analysis results — improvements, regressions, severity changes. |
| Ranked fix suggestions by impact. |
| Authenticate and save session state for analyzing protected content. |
| Multi-page site triage with aggregated stats and repeated navigation-cost groups across pages. |
Full parameter reference: docs/MCP-TOOLS.md
Setup by AI tool
First install the required packages in your project:
npm install tactualClaude Code — add to .mcp.json in your project root:
{
"mcpServers": {
"tactual": {
"type": "stdio",
"command": "npx",
"args": ["tactual-mcp"]
}
}
}GitHub Copilot — add to .copilot/mcp.json or ~/.copilot/mcp-config.json:
{
"mcpServers": {
"tactual": {
"type": "stdio",
"command": "npx",
"args": ["tactual-mcp"]
}
}
}Cursor / Windsurf / Cline — same format in your editor's MCP config:
{
"mcpServers": {
"tactual": {
"command": "npx",
"args": ["tactual-mcp"]
}
}
}Direct (global install) — if you prefer not to use npx:
npm install -g tactual
tactual-mcp # starts the MCP server on stdioGitHub Actions
Use the composite action from the GitHub Actions Marketplace:
jobs:
a11y:
runs-on: ubuntu-latest
permissions:
security-events: write # for SARIF upload
pull-requests: write # for comment-on-pr
steps:
- name: Analyze accessibility
uses: tactual-dev/tactual@v0.5.0
with:
url: https://your-app.com
profile: nvda-desktop-v0
explore: "true"
probe: "true"
probe-mode: standard
fail-below: "70"
comment-on-pr: "true"The action installs Tactual and Chromium browser binaries, runs the analysis, uploads SARIF to GitHub Code Scanning, and fails the build if the average score is below the threshold. Set comment-on-pr: "true" to post a summary comment on pull requests (updates on re-run). Outputs average-score and result-file for downstream steps. Action version tracks Tactual version — bump the uses: line to pick up patches.
Defaults are conservative: probe is off unless enabled because it sends real keyboard events, and forced-colors icon checks run only for profiles that declare visualModes such as nvda-desktop-v0 and jaws-desktop-v0.
Or use the CLI directly for more control:
- name: Install Tactual
run: npm install tactual
- name: Install browsers
run: npx playwright install chromium --with-deps
- name: Run accessibility analysis
run: npx tactual analyze-url https://your-app.com --format sarif --output results.sarif --threshold 70
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarifRegression gate (CI fail on worse-than-baseline)
Pair --baseline with --fail-on-regression to turn Tactual into a strict CI gate: save a baseline from a known-good build, then fail PR checks whenever a change regresses N+ findings vs the baseline. The diff-results command can also be run separately for human-readable before/after reports.
# One-time: snapshot main as the baseline
- name: Snapshot baseline
if: github.ref == 'refs/heads/main'
run: |
npx tactual analyze-url https://preview.your-app.com \
--format json --output tactual-baseline.json
- name: Upload baseline
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v4
with:
name: tactual-baseline
path: tactual-baseline.json
# On PRs: compare against the baseline, fail on regressions
- name: Fetch baseline
uses: actions/download-artifact@v4
with:
name: tactual-baseline
- name: Analyze + gate on regressions
run: |
npx tactual analyze-url https://pr-preview-${{ github.event.number }}.your-app.com \
--format sarif --output results.sarif \
--baseline tactual-baseline.json \
--fail-on-regression 3 # fail if 3+ findings regressedOr via the action:
- uses: tactual-dev/tactual@v0.5.0
with:
url: https://pr-preview.your-app.com
baseline: tactual-baseline.json
fail-on-regression: "3"The action mirrors the analyze-url CLI surface for analysis inputs, and a CI-to-CLI contract test keeps those fields aligned. Some workflow controls are Action orchestration rather than direct CLI flags: fail-below wraps CLI --threshold, comment-on-pr controls the PR comment step, and SARIF upload is handled by the workflow. The common inputs you'll set include profile, explore, explore-depth, explore-budget, explore-timeout, probe, probe-mode, probe-strategy, scope-selector, probe-selector, entry-selector, goal-target, goal-pattern, stealth, channel, wait-for-selector, exclude, exclude-selector, focus, min-severity, max-findings, baseline, fail-on-regression, fail-below, validate, storage-state, summary-only. The direct-CLI invocation pattern above is still the recommended path when you want a different Tactual version than the action pins.
Surface | Naming convention | Example |
CLI | kebab-case flags |
|
MCP and library options | camelCase fields |
|
GitHub Action | kebab-case inputs |
|
Configuration
CLI flags
Options:
-p, --profile <id> AT profile (default: generic-mobile-web-sr-v0)
-f, --format <format> json | markdown | console | sarif (default: console)
-o, --output <path> Write to file instead of stdout
-d, --device <name> Playwright device emulation
-e, --explore Explore hidden branches
--explore-depth <n> Max exploration depth (default: 3)
--explore-budget <n> Max exploration actions (default: 50)
--explore-timeout <ms> Total exploration timeout; includes probe time when combined with --probe (default: 60000)
--explore-max-targets <n> Max accumulated targets before stopping (default: 2000)
--allow-action <patterns...> Allow exploring controls matching these patterns (overrides safety)
--exclude <patterns...> Exclude targets by name/role glob
--exclude-selector <css...> Exclude elements by CSS selector
--scope-selector <css...> Capture, score, and probe only these subtrees
--focus <landmarks...> Only analyze within these landmarks
--suppress <codes...> Suppress diagnostic codes
--top <n> Show only worst N findings
--min-severity <level> Minimum severity to report
--threshold <n> Exit non-zero if avg score < N
--preset <name> Scoring preset (ecommerce-checkout, docs-site, dashboard, form-heavy)
--config <path> Path to tactual.json
--no-headless Headed browser (for bot-blocked sites)
--channel <name> Browser channel: chrome, chrome-beta, msedge (uses installed browser; bypasses most bot detection)
--stealth Anti-detection defaults: realistic UA, override navigator.webdriver, spoof plugins/languages
--user-agent <ua> Override User-Agent string
--timeout <ms> Page load timeout (default: 30000)
--probe Opt-in runtime keyboard probes for interactive targets
(focus, activation, Escape, Tab).
Also probes menu, dialog, tab, disclosure, combobox/listbox,
and form-error patterns.
When combined with --explore, probes revealed-state targets too
(menu items, dialog bodies, expanded widgets).
--probe-budget <n> Override generic-probe budget (default: per --probe-mode)
--probe-mode <mode> fast | standard (default) | deep.
fast=5 generic/5 menu/3 modal/5 widget;
standard=20/20/10/20; deep=50/40/20/40.
Budget is shared across initial + all revealed states.
--probe-selector <css...> Probe only these subtrees without changing capture/scoring
--entry-selector <css> Activate this trigger before capture/probe
--goal-target <target> Exact-ish target id/name/role/kind/selector hint
--goal-pattern <pattern> Glob target id/name/role/kind/selector hint
--probe-strategy <strategy> all | overlay | composite-widget | form |
navigation | modal-return-focus | menu-pattern
--validate Run the virtual screen reader over the captured DOM and include
a predicted-vs-validated step comparison in the output.
Requires optional deps: jsdom + @guidepup/virtual-screen-reader.
Installed by default unless optional deps were omitted.
--validate-max-targets <n> Max findings to validate (default: 10)
--validate-strategy <mode> Virtual-SR nav strategy: linear | semantic (default: semantic)
--check-visibility Force per-icon contrast check across the profile's visualModes
--no-check-visibility Disable per-icon contrast check even if profile declares modes
--detect-routes Record SPA route changes during analysis
--descend-frames Include iframe accessibility targets; Chromium can recover many cross-origin OOPIFs via CDP
--auto-scroll Scroll before capture to surface lazy/infinite-scroll content
--dismiss-banners Best-effort dismissal of safe cookie/consent banners
--probe-hover Hover likely triggers to expose hover-only popup content
--walk-tab-order Record Tab traversal to detect focus-order/focus-trap issues
--diff-viewports Compare desktop and mobile captures for hidden content
--wait-for-selector <css> Wait for selector before capturing (for SPAs)
--wait-time <ms> Additional wait after page load
--storage-state <path> Playwright storageState JSON for authenticated pages
--also-json <path> Also write JSON to this path (single analysis run for CI)
--summary-only Return only summary stats, no individual findings
-q, --quiet Suppress info diagnosticstactual.json
Create with tactual init or manually:
{
"preset": "ecommerce-checkout",
"profile": "voiceover-ios-v0",
"exclude": ["easter*", "admin*", "debug*"],
"excludeSelectors": ["#easter-egg", ".admin-only", ".third-party-widget"],
"scopeSelectors": ["main"],
"probeSelectors": [".checkout-dialog"],
"probeStrategy": "modal-return-focus",
"focus": ["main"],
"suppress": ["possible-cookie-wall"],
"threshold": 70,
"priority": {
"checkout*": "critical",
"footer*": "low",
"analytics*": "ignore"
}
}Config is auto-detected from the working directory (tactual.json or .tactualrc.json). CLI flags merge with and override config settings.
AT Profiles
Profile | Platform | Description |
| Mobile | Normalized mobile SR primitives (default) |
| Mobile | VoiceOver on iOS Safari — rotor-based navigation |
| Mobile | TalkBack on Android Chrome — reading controls |
| Desktop | NVDA on Windows — browse mode quick keys |
| Desktop | JAWS on Windows — virtual cursor with auto forms mode |
Profiles define the cost of each navigation action, score dimension weights, costSensitivity (scales the reachability decay curve), and context-dependent modifiers. See src/profiles/ for implementation details.
Mobile profile limitation. The voiceover-ios-v0 and talkback-android-v0 profiles model action costs and SR announcement phrasing accurately, but Tactual's keyboard probes (--probe) only test desktop interactions (Tab, Enter, Escape). They do NOT simulate touch gestures (single-tap, double-tap, swipe-right, three-finger swipe, rotor rotation, etc.). For mobile profiles, score dimensions reflect predicted cost from the profile model — not measured behavior. Real device testing remains necessary to verify mobile a11y.
Visual modes. The nvda-desktop-v0 and jaws-desktop-v0 profiles declare a visualModes matrix (light/dark × forced-colors on/off) so the analyzer captures per-icon contrast under each combination. Mobile and generic profiles omit this — Windows High Contrast Mode isn't a realistic mobile concern. See Visibility checks below.
Visibility checks
When the active profile declares a visualModes matrix, Tactual re-emulates each (colorScheme, forcedColors) combination after the initial capture and samples per-icon computed styles. The finding builder compares each icon's computed fill against the nearest non-transparent ancestor background-color and emits a penalty when contrast falls below the WCAG 1.4.11 non-text threshold (3:1).
Four penalty wordings, three scoring tiers:
Penalty | Trigger | Operability impact |
| Contrast < 1.5:1, no adjacent text label | Operability capped at 60 |
| Contrast < 1.5:1, control has visible text label | Operability −5 |
| Contrast 1.5–3.0:1, no adjacent text label | Operability −5 |
| Contrast OK in Playwright (≥3:1) but mode is | Operability −2 |
The check skips icons that are already HCM-safe: fill="currentColor", fill: ButtonText (or any system color), forced-color-adjust: none (author opt-out), or computed fill === color (CSS-applied currentColor). Low-contrast icons next to a visible text label are suppressed entirely — the label identifies the control and the icon is reinforcement.
Why the substitution-risk tier exists. Different user HCM themes have different Canvas/ButtonText/system-color values. An author literal fill (e.g. svg { fill: #e4e6e6 }) may contrast well against Chromium's default HCM palette but poorly against a specific user theme. Browser rendering of the literal itself is consistent across Playwright, Chrome, and Edge for forced-color-adjust: preserve-parent-color (the default for SVG paths) — the concrete concern is theme variability, not a hidden OS-paint substitution. Tactual flags the pattern so you know to verify in real Edge with a representative HCM theme, not because Playwright's contrast measurement is misleading.
Disable explicitly via --no-check-visibility, checkVisibility: false in tactual.json, or checkVisibility: false on the MCP analyze_url tool. Force-enable via --check-visibility even when a profile doesn't declare modes (no-op without modes).
The check adds roughly +50–200ms per declared mode per page — re-emulating media is cheap; there's no new browser context per mode.
Scoring Presets
Presets bundle focus filters and priority mappings for common use cases. They layer under config files and CLI flags (preset → tactual.json → CLI flags).
Preset | Use case | Focus | Critical targets |
| Shopping flows | main | checkout, cart, payment, buy |
| Documentation | main, navigation | search, nav |
| Web apps | main, navigation | save, submit, create, delete, search |
| Form pages | main | submit, save, next, continue, error |
npx tactual analyze-url https://shop.com --preset ecommerce-checkout
npx tactual presets # list all presets with detailsPresets suppress cookie banners and analytics targets by default. To override, use --exclude or set priority in tactual.json. Presets do not compose — only one --preset can be active.
Scoring
Each target receives a 5-dimension score vector:
Dimension | What it measures |
Discoverability | Can the user tell the target exists? |
Reachability | What is the navigation cost to get there? |
Operability | Does the control behave predictably? |
Recovery | How hard is it to recover from overshooting? |
Interop Risk | How likely is AT/browser support variance? (penalty) |
Dimension weights vary by profile:
Profile | D | R | O | Rec | costSensitivity |
generic-mobile-web-sr-v0 | 0.30 | 0.40 | 0.20 | 0.10 | 1.0 |
voiceover-ios-v0 | 0.30 | 0.35 | 0.20 | 0.15 | 1.1 |
talkback-android-v0 | 0.25 | 0.45 | 0.20 | 0.10 | 1.3 |
nvda-desktop-v0 | 0.35 | 0.25 | 0.30 | 0.10 | 0.7 |
jaws-desktop-v0 | 0.30 | 0.25 | 0.35 | 0.10 | 0.6 |
Composite: Weighted geometric mean: overall = exp(sum(w_i * ln(score_i)) / sum(w_i)) - interopRisk. Each dimension is floored at 1 before the log to avoid log(0). A zero in any dimension eliminates that dimension's contribution to the geometric mean, significantly dragging the overall score down -- you cannot operate what you cannot reach.
Severity bands:
Score | Band | Meaning |
90-100 | Strong | Low concern |
75-89 | Acceptable | Improvable |
60-74 | Moderate | Should be triaged |
40-59 | High | Likely meaningful friction |
0-39 | Severe | Likely blocking |
Diagnostics
Tactual emits diagnostics for capture reliability, page structure, visual access, runtime evidence, ARIA validity, and repeated cost patterns. Warnings are review prompts, not automatic conformance failures. Many visual/content checks are heuristic and should be confirmed in context before filing a defect.
Code | Level | Meaning |
| error | Bot/challenge page detected; captured content is not the intended page. |
| error | No targets found at all. |
| info | Capture produced a target set without reliability warnings. |
| warning | Suspiciously few targets for an HTTP page. |
| warning | Only 1-4 targets found. |
| warning | Auth-gated content or login redirect suspected. |
| info | Cookie consent may obscure content. |
| info/warning | Capture landed on a different URL or domain. |
| warning | A requested render wait did not complete before capture. |
| info | Frontend framework signals were detected during capture. |
| info | SPA route changes happened during analysis. |
| warning |
|
| info | Iframe descent captured or skipped child frames. |
| info | Auto-scroll ran before capture and reports what it surfaced. |
| info | Cookie/consent banner dismissal was attempted. |
| info/warning | Tab-order walk recorded focus stops; warns on positive |
| warning | Desktop/mobile viewport diff found missing targets, landmarks, or headings. |
| warning | No heading elements found. |
| warning | Heading hierarchy skips a level, such as |
| warning | Heading exists but has no text. |
| warning | Heading text is only digits, punctuation, or trivial single-character content. |
| info/warning | Page has no useful single H1, or has multiple H1s worth reviewing. |
| warning | No landmark regions found. |
| warning | Missing |
| info | Missing |
| info | Missing |
| info | Missing |
| warning | HTML landmark exists but is demoted by nesting context. |
| info | One-line structural overview. |
| warning | No skip-to-content link on pages with 5+ targets. |
| warning | Skip-style link points to a missing fragment target. |
| warning | A skip link exists but is not reachable in the first two Tab stops. |
| warning | Visual order appears to diverge from DOM/SR navigation order. |
| warning | A penalty affecting >50% of targets is promoted to page level. |
| warning | Multiple link targets create repeated Tab stops to the same destination. |
| info | Explored states reveal controls that become enabled only after prior action. |
| info/warning | Summarizes forms and warns when a form appears to lack a submit control. |
| warning | Standard form fields lack useful |
| warning | Interactive target has no accessible name. |
| warning | Clickable non-semantic elements are not keyboard/SR reachable. |
| warning | CDP found click-like listeners on non-interactive elements. |
| warning | Links with the same accessible name point to different destinations. |
| warning | Audio/video lacks controls and is not hidden. |
| warning | Duplicate |
| warning | Interactive controls are nested inside other interactive controls. |
| warning | Page auto-refreshes or redirects via meta refresh. |
| warning | Images lack |
| warning | Image alt text looks like filler or a filename-like placeholder. |
| warning | Iframes lack a |
| warning |
|
| warning | Document title is missing, empty, too short, or generic. |
| warning | Viewport meta settings restrict user zoom. |
| warning | Interactive text or headings fail WCAG-style text contrast thresholds. |
| warning | Text appears to rely on color alone to convey meaning. |
| warning | Text loses contrast under simulated color-vision deficiency. |
| warning | Text language appears to change without a |
| warning | Non-standard ARIA role is present. |
| warning | Unknown |
| warning | ARIA attribute value is outside the allowed value set. |
| warning | ARIA role is missing a required state or property. |
| warning | Name is applied to a role that prohibits naming. |
| warning | ARIA attribute is not supported on the element's role. |
Exploration
The --explore flag activates bounded branch exploration:
Opens menus, tabs, disclosures, accordions, and dialogs
Captures new accessibility states from hidden UI
Marks discovered targets as
requiresBranchOpenRespects depth, action count, target count, and novelty budgets
Safe-action policy blocks destructive interactions
Exploration is useful for pages with significant hidden UI (e.g., dropdown menus, tabbed interfaces, modal dialogs).
Exploration candidates are sorted by a stable key (role + name) before iterating, so the same page content produces the same exploration order across runs.
Probes
The --probe flag measures whether important interactive patterns work after they appear in the accessibility tree. Probes are opt-in because they send real keyboard events and add runtime. Since 0.4.0 this includes generic focus/activation checks, menu contracts, modal dialog contracts, trigger-to-dialog flows, tabs, disclosures, comboboxes, listboxes, and required-field error flows. Probe findings include evidence summaries so reports distinguish measured failures from modeled or heuristic scoring.
Goal-directed controls keep deep probes useful on complex SPAs:
Need | CLI | MCP/Action field | Effect |
Analyze one subtree |
|
| Captures, scores, and probes only the selected subtree(s). |
Probe one subtree |
|
| Keeps page-wide scoring but spends probe budget only inside the selected subtree(s). |
Open one branch first |
|
| Activates the trigger before capture/probe and prioritizes newly revealed targets. |
Aim at a known target |
|
| Narrows probing to matching target ids, names, roles, kinds, or selectors. |
Aim by glob |
|
| Same as goal target, with glob matching. |
Spend budget by intent |
|
| Runs the probe families relevant to |
For example, to evaluate a modal branch without crawling unrelated menus:
npx tactual analyze-url https://app.example.com/settings \
--profile nvda-desktop-v0 \
--probe \
--entry-selector "[aria-controls='profile-dialog']" \
--probe-strategy modal-return-focus \
--format markdownExploration budgets
Budget | CLI flag | Default | Purpose |
Depth |
| 3 | Max recursion depth |
Actions |
| 50 | Total click budget across all branches |
Targets |
| 2000 | Stop if accumulated targets exceed this |
Time |
| 60000 ms | Bound total exploration time, including initial probes, branch captures, and revealed-state probes |
Sizing guidance:
Page type | Suggested settings | Why |
Marketing site, docs page, blog | defaults | Small surface, defaults rarely hit |
Dashboard with sidebar/menu |
| Captures one level of menu opens |
Complex app (Figma, Notion, etc.) |
| Deeper menus, more state |
Pages with very large hidden UI (emoji pickers, color grids) |
| Cap or filter out the firehose |
Quick triage of unknown page |
| Just open obvious branches, fast |
If exploration hits the timeout before opening useful branches, raise --explore-timeout and --explore-budget slowly, or use --entry-selector, --probe-selector, and --probe-strategy to spend the same budget on the branch you care about. If output has duplicate-looking targets, lower --explore-depth (deep recursion can re-discover the same elements through different paths).
SPA framework detection
Tactual detects when SPA content has rendered before capturing the accessibility tree. Detected frameworks: React, Next.js, Vue, Nuxt, Angular, Svelte, and SvelteKit. Generic HTML5 content signals (landmarks, headings, navigation, links) are also checked. For SPAs not covered by auto-detection, use --wait-for-selector (CLI) or waitForSelector (MCP/API) to specify a CSS selector that indicates your app has hydrated.
After initial framework detection, Tactual uses convergence-based polling — repeatedly snapshotting the accessibility tree until the target count stabilizes — which works regardless of framework.
For SPA-heavy apps, these opt-in capture helpers are useful:
npx tactual analyze-url https://app.example.com \
--wait-for-selector "main" \
--detect-routes \
--auto-scroll \
--descend-frames \
--diff-viewports--detect-routesrecordspushState,replaceState,popstate, andhashchangeevents that happen during analysis.--auto-scrollsurfaces IntersectionObserver-driven lazy content before capture.--descend-framesappends iframe targets with frame URL attribution. Same-origin frames use Playwright's frame-scoped accessibility snapshot; Chromium falls back to CDP for cross-origin OOPIFs when the normal snapshot path is inaccessible. Firefox/WebKit keep the existing skip behavior for inaccessible frames.--diff-viewportscatches target, landmark, or heading content that disappears between desktop and mobile viewports.--dismiss-banners,--probe-hover, and--walk-tab-orderadd targeted runtime evidence for common SPA overlays and focus-order bugs.
Known-pages benchmark
For release evidence against complex public SPA/component-library pages, run:
npm run benchmark:known-pagesThe script builds the package, runs analyze-url with the SPA helper stack
enabled, and writes run-results.json, summary.json, and REPORT.md under
build/known-pages-*. It is intentionally not a CI gate: public sites change,
block automation, and serve different content over time. Use the report to spot
drift and category-level surprises, then use local fixtures or project-owned
pages for deterministic regression gates. For a bounded smoke run or APG/W3C
capture-quality probes, call the script directly, for example
node scripts/known-pages-corpus.mjs --build --limit 1 or
node scripts/known-pages-corpus.mjs --build --include-capture-probes.
Regression Tracking
Compare two analysis runs to catch regressions:
# Save a baseline
npx tactual analyze-url https://your-app.com --format json --output baseline.json
# After changes, run again and diff
npx tactual analyze-url https://your-app.com --format json --output candidate.json
npx tactual diff-results baseline.json candidate.jsonThe diff shows targets that improved, regressed, or changed severity, plus penalties resolved and added. In CI, use the comment-on-pr action input to post results on every pull request automatically.
Interop Risk
Tactual includes a static snapshot of ARIA role/attribute support data derived from a11ysupport.io and the ARIA-AT project. Roles with known cross-AT/browser support gaps receive an interop risk penalty.
Role | Risk | Note |
| 0 | Well-supported |
| 5 | Focus management varies |
| 8 | Most interop-problematic pattern |
| 10 | Poorly supported outside JAWS |
| 15 | Dangerous if misused |
Interpreting Findings
Tactual findings intentionally mix several evidence domains:
SR navigation: landmarks, headings, labels, branch discovery, sequential traversal cost, and modeled announcements.
Keyboard operability: focus movement, activation, Escape recovery, Tab trapping, and runtime widget probes.
Structural semantics: missing names, heading/landmark structure, demoted landmarks, repeated shared causes.
Interop risk: roles and states with known cross-AT/browser support gaps.
Pointer-adjacent checks: target-size and icon visibility issues that can affect users outside the screen-reader navigation model.
That means a page can have a strong screen-reader navigation score and still receive skip-link, target-size, or visibility warnings. Treat those as separate fix categories rather than contradictions.
Probe-derived APG findings are measured consistency warnings. Many widgets have valid implementation variants, especially comboboxes and disclosure-like patterns, so verify the warning against the intended pattern before treating it as a mandatory replacement. Critical flows should still be checked with the target browser/AT combination.
Output Format Recommendations
Format | Typical size | Best for |
| ~8KB | Human review in terminal |
| ~11KB | PRs and issue comments |
| ~18KB | Programmatic consumption |
| ~4-40KB | GitHub Code Scanning / CI |
All non-SARIF reporter formats emit summarized output by default: stats, grouped issues, remediation candidates, evidence summaries, and worst findings (capped at 15). SARIF caps at 25 results. When output is truncated, a note appears at the top. The library API exposes the full AnalysisResult; CLI and MCP reporter output is intentionally compact unless a specific field such as includeStates is requested.
For MCP usage, sarif is the default and recommended format. Use summaryOnly: true for a compact health check with stats, severity counts, diagnostics, and the top 3 issues.
Calibration
Tactual includes a calibration framework (src/calibration/, exported as tactual/calibration) for tuning scoring parameters against ground-truth datasets. See docs/CALIBRATION.md for details.
Calibration observations can also include deterministic announcement feedback from OSS review work: record observedAnnouncement when you know the tested output, or observedAnnouncementTokens when exact phrasing is noisy but role/name/state tokens are clear. Tactual compares those against its modeled announcement for the matched target and reports missing or unexpected tokens. Use tactual calibration-report or MCP calibration_report to run a dataset against saved analyze-url --full-json output and emit scoringSignals. Use tactual observe-announcement to generate or append announcement-only observations from a saved analysis, or npm run -- nvda:vm:observe -- ... in this repo to organize a controlled NVDA VM capture folder. The repo's versioned corpus lives under calibration/corpus/; run npm run calibration:corpus to audit coverage gates and npm run calibration:matrix after npm run build to rank reachability tuning work by MAE, bias, variance, and stale sequence-plan drift.
Release readiness and known boundaries are documented in docs/RELEASE_TEST_MATRIX.md, docs/LIMITATIONS.md, and docs/NVDA_VM_OBSERVER.md.
Development
npm install # Install dependencies
npm run build # Build with tsup
npm run test # Run unit + integration tests
npm run test:shard -- --list # List bounded Vitest release shards
npm run test:shard -- capture # Run one bounded Vitest shard
npm run test:shards # Run all bounded Vitest shards
npm run test:benchmark # Run benchmark suites
npm run typecheck # TypeScript type checking
npm run lint # ESLint
npm run test:release # Full split release gateSecurity
Browser sandboxing
Tactual always runs Playwright with default Chromium sandboxing enabled. It never disables web security or modifies the browser's security model. All page interactions happen within the standard Chromium process sandbox.
Safe-action policy
When exploration is enabled (--explore), Tactual classifies interactive elements into three tiers before activating them:
Tier | Action | Examples |
Safe | Activated | Tabs, menu items, disclosures, accordions, same-page anchors |
Caution | Activated with care | External links, ambiguous buttons |
Unsafe | Skipped | Submit buttons (outside search forms), Delete, sign out, purchase, deploy, unsubscribe |
This is a keyword-based heuristic — it cannot detect semantic deception (e.g., a "Save" button that actually deletes data) or inspect server-side behavior. For production use, always run exploration against trusted or sandboxed environments.
URL validation
All URLs are validated before navigation. The CLI accepts http:, https:, and file: schemes so local fixtures work; MCP URL-taking tools accept only http: and https: to avoid exposing local files through agent-controlled browser navigation. javascript:, data:, blob:, and vbscript: are rejected. URLs with embedded credentials (e.g. https://user:pass@host/) are also rejected. Private/internal IP ranges are not filtered — running Tactual in an environment with access to internal services is equivalent to letting any other Playwright-driven tool reach them, so treat the URL input as trusted input.
License
Apache-2.0
Attribution
The simulator's role/state phrasing is calibrated against the W3C ARIA-AT project, which is licensed under CC-BY 4.0. Tactual does not bundle ARIA-AT data; the calibration script (npm run calibrate) fetches assertions from the upstream repository at run time. If you publish Tactual calibration results, please attribute the W3C ARIA-AT project as the source of the ground-truth assertions.
ARIA role/attribute support data referenced in interop risk scoring is derived from a11ysupport.io and the same ARIA-AT project.
Available Tools
9 toolsanalyze_pagesA
Analyze multiple pages and produce an aggregated site-level report. Runs analyze_url on each URL in a single browser session and combines results into a site score with per-page breakdown. Read-only — navigates to each URL but does not modify pages.
Use this instead of calling analyze_url repeatedly when you need a site-level assessment. Returns ~200 bytes per page plus a site-level summary. If a single URL fails (timeout, bot protection), its entry shows the error and remaining URLs still complete.
| Name | Required | Description | Default |
|---|---|---|---|
| urls | Yes | URLs to analyze (1-20 pages) | |
| profile | No | AT profile ID | generic-mobile-web-sr-v0 |
| timeout | No | Page load timeout per URL | |
| waitTime | No | Additional wait per page in ms | |
| storageState | No | Path to Playwright storageState JSON for authenticated pages. Use save_auth to create. Must be within cwd. | |
| waitForSelector | No | CSS selector to wait for on each page (for SPAs) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description explicitly states the tool is read-only (does not modify pages) and describes the behavior of running analyze_url in a single browser session. With no annotations provided, the description carries the full burden and addresses key behavioral aspects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two paragraphs, front-loaded with the verb and purpose, and every sentence adds value. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has no annotations and no output schema, the description explains the return format (approximate size per page) and error handling. It could be more explicit about the site-level summary structure, but it is adequate for a tool of moderate complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description adds overall context but does not provide additional meaning beyond the schema's parameter descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool analyzes multiple pages and produces an aggregated site-level report, and distinguishes it from the sibling tool analyze_url by explaining it runs analyze_url on each URL and combines results.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly advises to use this tool instead of calling analyze_url repeatedly for a site-level assessment, and describes error handling behavior where failed URLs produce errors while others continue.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
analyze_urlA
Analyze a web page for screen-reader navigation cost. Returns scored findings showing how hard it is for AT users to discover, reach, and operate interactive targets. Navigates to the URL in a sandboxed browser. Probes test keyboard behavior but do not submit forms or modify data.
Recommended: Use format='sarif' for concise, actionable output (~4KB). SARIF auto-filters to findings that need attention (moderate and worse). JSON/markdown include every target and can be 100x larger.
SPAs (React, Next.js, etc.): Pass waitForSelector (e.g., '[data-testid="app"]' or 'main') so Tactual waits for the app to hydrate before capturing.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL to analyze | |
| focus | No | Only analyze targets within these landmarks. | |
| probe | No | Run keyboard probes on interactive targets. Adds ~30-60s. | |
| device | No | Playwright device name for emulation (e.g., 'iPhone 14') | |
| format | No | Output format. 'sarif' (recommended) filters to actionable findings only. | sarif |
| channel | No | Browser channel: chrome, chrome-beta, msedge. Bypasses shared pool. | |
| exclude | No | Glob patterns to exclude targets by name/role/kind. | |
| explore | No | Explore hidden branches (menus, tabs, dialogs). Use with format='sarif' to avoid output overflow. | |
| profile | No | AT profile ID (generic-mobile-web-sr-v0, nvda-desktop-v0, jaws-desktop-v0, voiceover-ios-v0, talkback-android-v0) | generic-mobile-web-sr-v0 |
| stealth | No | Apply anti-bot-detection defaults. Pair with channel for Cloudflare-protected sites. | |
| timeout | No | Page load timeout in ms | |
| waitTime | No | Additional ms to wait after page load. | |
| probeMode | No | Probe depth preset: fast=5/5/3/5, standard=20/20/10/20 (default), deep=50/40/20/40. Budgets are generic/menu/modal/widget. | standard |
| autoScroll | No | Scroll to the bottom of the page in steps before capture so IntersectionObserver-driven lazy content materializes. Capped at 20 scrolls / 30 s; emits an auto-scrolled info diagnostic. | |
| goalTarget | No | Exact-ish target id, name, role, kind, or selector hint for goal-directed probing. | |
| probeHover | No | Hover candidate triggers to surface hover-only popups/tooltips without attribute hints. Diffs ariaSnapshot before/after each hover; new content becomes _hoverContent enrichment. Default budget 10 candidates; adds ~7-8 s. | |
| allowAction | No | Glob patterns for controls that should be explorable despite the safety policy. | |
| goalPattern | No | Glob pattern matched against target id/name/role/kind/selector for goal-directed probing. | |
| maxFindings | No | Maximum detailed findings to return. | |
| minSeverity | No | Only include findings at this severity or worse. Reduces output size. | |
| probeBudget | No | Maximum number of targets for the generic probe. Overrides probeMode's generic budget. | |
| summaryOnly | No | Return compact summary stats. Use for quick page health checks. | |
| detectRoutes | No | Record SPA route changes (history.pushState/replaceState, popstate, hashchange) that fire during analysis. Surfaces an spa-route-changes info diagnostic when any are observed. | |
| exploreDepth | No | Max exploration depth (default: 2). How many levels of branches to walk when explore=true. Higher = more thorough, but each level multiplies action count. CLI defaults to 3 for power-user runs; MCP defaults to 2 for tighter agent-loop latency. | |
| storageState | No | Path to Playwright storageState JSON (cookies + localStorage). Must be within cwd. | |
| walkTabOrder | No | Press Tab up to 30 times and record the focused-element sequence so the analyzer can flag positive-tabindex anti-patterns and focus traps. Adds ~1-3 s. | |
| descendFrames | No | Descend into child iframes during capture (capped at 20 frames). Appends frame targets with frame-URL attribution; Chromium can recover many inaccessible cross-origin OOPIF trees via CDP. Surfaces a frames-descended info diagnostic when any frame content is captured. | |
| diffViewports | No | Capture the URL at desktop (1280×800) and mobile (375×667) viewports and diff the resulting target / landmark / heading lists. Surfaces a viewport-divergence diagnostic when content is set to display:none on small screens. Adds ~3-5 s. | |
| entrySelector | No | Activate this trigger before capture/probe, then prioritize newly revealed targets. | |
| exploreBudget | No | Max total actions during exploration across all branches (default: 30). Prevents pathological pages from exploding probe time. CLI default is 50; MCP default is 30 for tighter agent-loop latency. | |
| includeStates | No | Include captured states in JSON output for passing to trace_path's statesJson parameter. Uses compact format (~5KB). | |
| probeSelector | No | CSS selectors that narrow probes without changing capture/scoring. | |
| probeStrategy | No | Probe family intent preset. Default all; use overlay, form, composite-widget, navigation, modal-return-focus, or menu-pattern to spend budget on one class of behavior. | |
| scopeSelector | No | CSS selectors that define the subtree(s) to capture, score, and probe. | |
| dismissBanners | No | Best-effort dismiss of cookie/consent/GDPR banners. Clicks safe-accept buttons (Accept / OK / Got it / Allow all); explicitly skips Decline/Manage/Customize. Emits a banners-dismissed info diagnostic. | |
| exploreTimeout | No | Total exploration timeout in ms; includes initial/revealed probe time when probe is enabled (default: 60000). | |
| checkVisibility | No | Run the per-icon visibility probe across profile-declared (colorScheme × forcedColors) modes. Emits hcm-icon-invisible, low-contrast-icon, and hcm-substitution-risk findings. Undefined defers to the profile default (desktop AT profiles declare the full matrix; mobile/generic do not). | |
| excludeSelector | No | CSS selectors to hide from analysis (set aria-hidden before capture). | |
| waitForSelector | No | CSS selector to wait for before capturing (essential for SPAs). | |
| exploreMaxTargets | No | Max accumulated targets before exploration stops early (default: 2000). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully discloses that the tool navigates in a sandboxed browser, probes keyboards, does not submit forms, and explains output sizes, exploration budgets, and timeouts in detail.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with bolded recommendations and clear sections. Every sentence adds useful information, and it efficiently covers the tool's purpose, key parameters, and usage tips without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite 40 parameters and no output schema, the description thoroughly explains return formats, behavioral aspects like exploration and probing, and practical considerations such as SPA handling, anti-bot measures, and output size. It is complete for effective tool use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description adds significant value beyond the schema: it recommends default profiles, explains the impact of exploreDepth and exploreBudget on latency, and clarifies the necessity of waitForSelector for SPAs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states the tool analyzes a web page for screen-reader navigation cost and returns scored findings. It clearly distinguishes from siblings like analyze_pages and validate_url by specifying the focus on accessibility testing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description recommends using SARIF format for concise output, warns about SPAs needing waitForSelector, and mentions that probes test keyboard behavior without modifying data. While it doesn't explicitly exclude use cases, it provides sufficient context for appropriate use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
calibration_reportA
Run a calibration dataset against saved full Tactual analysis JSON and return structured scoring signals. Use this after analyze_url --full-json/format=json artifacts or VM/manual screen-reader observations have been collected. Read-only; file inputs must be inside the current working directory. Returns JSON by default so agents can inspect scoringSignals, announcement drift, and per-target calibration errors.
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | Output format. JSON is best for agent workflows; markdown is for human review. | json |
| analysisDir | No | Directory of full analysis JSON files, within the current working directory | |
| datasetPath | Yes | Path to calibration dataset JSON, within the current working directory | |
| allowMissing | No | Allow observations whose URLs have no matching analysis JSON | |
| analysisPaths | No | Full analysis JSON files produced by analyze_url/analyze-url |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the tool is 'Read-only' and describes return values: 'scoringSignals, announcement drift, and per-target calibration errors.' It also mentions file location constraints. This fully informs the agent of behavior beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description consists of three well-organized sentences: purpose, usage context, and output details. Every sentence adds value without redundancy. It is front-loaded, allowing quick comprehension.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with five parameters, one required, and no output schema, the description adequately covers inputs, constraints, and output structure. It mentions specific return fields (scoringSignals, etc.) but does not detail the full output. Given the complexity, it is sufficiently complete for agents to understand the tool's role and usage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 100% description coverage, meaning the schema already explains each parameter. The description adds minimal extra semantics, reinforcing that file inputs must be in the current working directory and noting that JSON is best for agents. This meets the baseline for high schema coverage but does not significantly enhance parameter understanding.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Run a calibration dataset against saved full Tactual analysis JSON and return structured scoring signals.' The verb 'run' and specific resources (calibration dataset, analysis JSON) make the action and target unambiguous. It distinguishes from sibling tools like analyze_url and diff_results by focusing on calibration, a different workflow stage.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Use this after analyze_url --full-json/format=json artifacts or VM/manual screen-reader observations have been collected.' It also notes that inputs must be inside the current working directory, providing clear usage context. It does not explicitly list alternative tools or when not to use, but the guidance is specific and actionable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
diff_resultsA
Compare two Tactual analysis results (before/after). Shows what improved, regressed, which penalties were resolved or added, and severity band changes per target. Returns a JSON array of {targetId, baselineScore, candidateScore, status, penalties}.
Read-only, no side effects. Use after fixing accessibility issues to verify improvements. Both inputs must be JSON strings from analyze_url (format='json'). Not useful for SARIF output — use analyze_url directly for before/after SARIF comparisons.
| Name | Required | Description | Default |
|---|---|---|---|
| baseline | Yes | Baseline analysis result as JSON string | |
| candidate | Yes | Candidate analysis result as JSON string |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Declares read-only, no side effects. Describes return format (JSON array). Adequately discloses behavior beyond any annotations (none provided).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Concise, front-loaded with purpose, then usage guidance, then constraints. Every sentence is necessary and informative.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 2 parameters and no output schema, the description fully covers usage, constraints, return format, and when to use. No gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has 100% coverage, but description adds critical context: inputs must be JSON strings from analyze_url (format='json'), which is not in schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states it compares two Tactual analysis results (before/after) and lists what it shows. Differentiates itself from analyze_url by specifying it's not for SARIF output.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly advises using after fixing accessibility issues and requires inputs from analyze_url. Provides a clear when-not-to-use case for SARIF output.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_profilesA
List the assistive-technology (AT) profiles available for scoring. Each profile models a specific screen reader and platform — e.g., NVDA on Windows, VoiceOver on iOS — with its own navigation cost weights and action vocabulary. Returns an array of {id, name, platform, description} for each profile.
Read-only, no parameters, static data. Call once to discover valid profile IDs, then pass a profile ID to analyze_url, trace_path, or analyze_pages. Default profile for all analysis tools is 'generic-mobile-web-sr-v0' if none is specified.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, but the description fully discloses behavior: read-only, no parameters, static data. No contradictions or omissions for this simple tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Concise and front-loaded: first sentence states purpose, second provides usage guidance. Every sentence adds necessary detail without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers purpose, content, usage pattern, default behavior, and return format. No output schema exists, but the description compensates fully.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
No parameters exist, so schema coverage is 100%. The description adds value by detailing the return array structure ({id, name, platform, description}), exceeding the baseline for 0-param tools.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists assistive-technology profiles and explains what each profile contains (e.g., NVDA on Windows, VoiceOver on iOS). It distinguishes from sibling tools by noting the profile IDs are used in analysis tools like analyze_url.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use: call once to discover profile IDs, then pass to analysis tools. Mentions the default profile if none specified, providing clear context and alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
save_authA
Authenticate with a web application and save the session for subsequent analysis. Navigates to the URL, executes login steps (click a button, fill a form, etc.), waits for the authenticated page to load, then saves cookies and localStorage to a JSON file. Overwrites the output file if it already exists.
Side effects: Writes a storageState JSON file to disk at outputPath. Launches a headed browser that interacts with the page (clicks, fills inputs). Not needed for public pages — only use when content is behind authentication.
Pass the output file path as storageState to analyze_url, trace_path, or analyze_pages to analyze authenticated content.
Steps format: Array of actions to perform in order. Each step is an object:
{ click: 'button text or selector' }— click a button/link{ fill: ['input selector', 'value'] }— fill an input field{ wait: 2000 }— wait N milliseconds{ waitForUrl: '/dashboard' }— wait until URL contains this string
Example for a dev login: steps: [{ click: 'Dev Login' }, { waitForUrl: '/workspace' }]
Example for form login: steps: [{ fill: ['#email', 'user@test.com'] }, { fill: ['#password', 'pass'] }, { click: 'Sign In' }, { waitForUrl: '/dashboard' }]
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Login page URL | |
| steps | Yes | Login steps to execute (see description for format) | |
| timeout | No | Timeout per step in ms | |
| outputPath | No | File path to save the storageState JSON (must be within cwd) | tactual-auth.json |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, but the description thoroughly discloses side effects: file writing, browser launching, interaction, and overwriting behavior. This fully covers behavioral transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-structured with sections and examples. Slightly lengthy due to detailed examples, but each part serves a purpose. Could be trimmed slightly without losing clarity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Complete coverage: explains operation, parameters, side effects, integration with sibling tools, and provides examples. No output schema needed as tool writes to disk.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but description adds substantial value: explains steps format in detail, provides examples, constrains outputPath to cwd, and clarifies timeout usage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: authenticate and save session for subsequent analysis. It differentiates from siblings (analyze_pages, analyze_url, etc.) by focusing on login and session capture.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Description explicitly states when not to use ('Not needed for public pages') and how to use output with other tools. Though it could mention alternatives, it provides clear context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
suggest_remediationsA
Extract the top unique remediation suggestions from a Tactual analysis result, ranked by severity. Returns a JSON array of {targetId, severity, score, fix, penalties}.
Read-only, no side effects. Most useful with large JSON results where you want a prioritized shortlist of what to fix first. For SARIF results, the findings already contain fix suggestions inline — this tool is redundant in that case. Input must be a JSON string from analyze_url (format='json').
| Name | Required | Description | Default |
|---|---|---|---|
| analysis | Yes | Analysis result as JSON string | |
| maxSuggestions | No | Maximum number of suggestions to return |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Declares read-only and no side effects. Describes output format. Warns about redundancy for SARIF. Lacks error handling details, but given no annotations, it provides sufficient behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four concise sentences, each adding value. Front-loaded with core purpose. No redundant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, input/output format, usage guidelines, and exclusion criteria. Sufficient for an agent to correctly invoke the tool without additional context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers both parameters with descriptions. Description adds extra semantics for the 'analysis' parameter (must be from analyze_url in JSON format). The maxSuggestions parameter is clear from schema alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states it extracts top unique remediation suggestions from a Tactual analysis result, ranked by severity, and returns a JSON array. Differentiates from sibling tools by specifying it is a post-processing step and redundant for SARIF results.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use (with large JSON results needing prioritized shortlist) and when-not-to-use (SARIF results already have suggestions). Also specifies input must come from analyze_url with format='json'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
trace_pathA
Trace the exact screen-reader navigation path to a specific interactive target. Returns step-by-step actions a screen-reader user would perform, with modeled announcements, cumulative cost, and the target's role/name at each hop. Read-only — navigates to the URL but does not modify the page.
Use this after analyze_url to understand why a target scored poorly.
For auth-gated or explored targets: Pass statesJson from a prior analyze_url (use includeStates=true). This skips browser launch entirely and traces against the captured state, including any explored states discovered behind auth boundaries.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL of the page to trace | |
| device | No | Playwright device name for emulation (e.g., 'iPhone 14') | |
| target | Yes | Target to trace to. Exact target ID or glob pattern (e.g., '*search*', 'Submit*'). Case-insensitive. | |
| explore | No | Explore hidden branches (menus, tabs, dialogs) before tracing | |
| profile | No | AT profile ID | generic-mobile-web-sr-v0 |
| timeout | No | Page load timeout in milliseconds | |
| statesJson | No | Pre-captured states from a prior analyze_url (use includeStates=true). When provided, trace_path skips browser launch. | |
| storageState | No | Path to Playwright storageState JSON for authenticated pages. Must be within cwd. | |
| waitForSelector | No | CSS selector to wait for before capturing (essential for SPAs) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Explicitly states it is read-only and does not modify the page. Also describes behavior when statesJson is provided (skips browser launch). No annotations exist, so description carries full burden and does well.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise paragraphs. First paragraph delivers the core purpose and output format. Second paragraph provides essential usage tips. No wasted words, front-loaded with action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema, the description explains the return format (step-by-step actions, announcements, cost, target role/name). Provides enough context for a complex tool with 9 parameters. Could mention error handling but sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. Description adds value for key parameters: explains target accepts glob patterns and is case-insensitive, statesJson usage, and storageState path. Adds context beyond schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool traces the screen-reader navigation path to a specific target, returning step-by-step actions. It distinguishes itself from sibling tools by specifying it is used after analyze_url to understand poor scores.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says to use this after analyze_url, and provides guidance for auth-gated targets using statesJson. While not exhaustive on when not to use, it gives clear context for appropriate usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
validate_urlA
Validate Tactual's predicted navigation paths against a virtual screen reader. Runs analyze_url internally, then for each worst finding drives @guidepup/virtual-screen-reader over the captured DOM (via jsdom) to check: (a) is the target reachable at all, and (b) how many virtual SR announcements does it take to reach it? Compares to Tactual's predicted step count. Returns an accuracy ratio per target and a mean across all validated targets — closer to 1.0 means Tactual's predictions match this virtual-screen-reader run, not a guarantee of full real-AT fidelity.
Requires (optional deps): jsdom + @guidepup/virtual-screen-reader. Installed with tactual if optionalDependencies were honored; otherwise run npm install jsdom @guidepup/virtual-screen-reader in your project.
When to use: closing the modeled-vs-virtual loop. If Tactual's predictions diverge a lot from the virtual SR, either the profile weights need calibration or the page has structural patterns the analyzer doesn't model. Use sparingly — this adds the analyze_url cost plus jsdom parsing + virtual SR navigation time.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL to analyze and validate | |
| channel | No | Browser channel: chrome, chrome-beta, msedge | |
| profile | No | AT profile ID (default: nvda-desktop-v0). Use list_profiles to see options. | |
| stealth | No | Apply anti-bot-detection defaults | |
| timeout | No | Page load timeout in ms | |
| strategy | No | Navigation strategy for the virtual SR. 'linear' uses Tab/Shift-Tab (keyboard flow); 'semantic' uses heading/landmark skip commands (screen-reader flow). Semantic is more representative for NVDA/JAWS/VoiceOver users. | semantic |
| waitTime | No | Additional wait after load (ms) | |
| maxTargets | No | Maximum findings to validate (worst-first). Higher = slower but more signal. | |
| storageState | No | Path to a Playwright storageState JSON (for authenticated pages). Must be within the current working directory. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully explains behavior: it runs analyze_url internally, drives @guidepup/virtual-screen-reader, checks reachability and step count, and returns accuracy ratios. It also mentions optional dependencies. Could expand on error handling or performance implications.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured into three paragraphs: function, dependencies, usage. It is front-loaded with the core action. Slightly verbose but each sentence adds value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 9 parameters all documented in schema, the description covers the output (accuracy ratio per target, mean) and real-AT fidelity caveat. It also mentions dependencies. Lacks output format details but is adequate for the complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. The description adds value by explaining the 'strategy' parameter (linear vs. semantic) and noting 'maxTargets' default and trade-off. This goes beyond schema definitions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool validates Tactual's predicted navigation paths using a virtual screen reader, detailing the internal process and output. It distinguishes itself from siblings like analyze_url and trace_path by focusing on validation against predictions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit guidance on when to use ('closing the modeled-vs-virtual loop') and advises using sparingly due to cost. It lacks direct mention of when not to use or alternatives, but the context is informative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
2 tool updates
- Changed
analyze_url7 fields changed- added
Input schema / properties / autoScrollAdded value: +{ + "description": "Scroll to the bottom of the page in steps before capture so IntersectionObserver-driven lazy content materializes. Capped at 20 scrolls / 30 s; emits an auto-scrolled info diagnostic.", + "type": "boolean" +} - added
Input schema / properties / descendFramesAdded value: +{ + "description": "Descend into child iframes during capture (capped at 20 frames). Appends frame targets with frame-URL attribution; Chromium can recover many inaccessible cross-origin OOPIF trees via CDP. Surfaces a frames-descended info diagnostic when any frame content is captured.", + "type": "boolean" +} - added
Input schema / properties / detectRoutesAdded value: +{ + "description": "Record SPA route changes (history.pushState/replaceState, popstate, hashchange) that fire during analysis. Surfaces an spa-route-changes info diagnostic when any are observed.", + "type": "boolean" +} - added
Input schema / properties / diffViewportsAdded value: +{ + "description": "Capture the URL at desktop (1280×800) and mobile (375×667) viewports and diff the resulting target / landmark / heading lists. Surfaces a viewport-divergence diagnostic when content is set to display:none on small screens. Adds ~3-5 s.", + "type": "boolean" +} - added
Input schema / properties / dismissBannersAdded value: +{ + "description": "Best-effort dismiss of cookie/consent/GDPR banners. Clicks safe-accept buttons (Accept / OK / Got it / Allow all); explicitly skips Decline/Manage/Customize. Emits a banners-dismissed info diagnostic.", + "type": "boolean" +} - added
Input schema / properties / probeHoverAdded value: +{ + "description": "Hover candidate triggers to surface hover-only popups/tooltips without attribute hints. Diffs ariaSnapshot before/after each hover; new content becomes _hoverContent enrichment. Default budget 10 candidates; adds ~7-8 s.", + "type": "boolean" +} - added
Input schema / properties / walkTabOrderAdded value: +{ + "description": "Press Tab up to 30 times and record the focused-element sequence so the analyzer can flag positive-tabindex anti-patterns and focus traps. Adds ~1-3 s.", + "type": "boolean" +}
- Added
calibration_report
5 tool updates
v0.3.0- Changed
analyze_pages2 fields changed- changed
Input schema / properties / storageState / descriptionPrevious value: -"Path to Playwright storageState JSON for authenticated pages. Use save_auth to create."New value: +"Path to Playwright storageState JSON for authenticated pages. Use save_auth to create. Must be within cwd." - changed
Input schema / properties / urls / descriptionPrevious value: -"URLs to analyze (2-20 pages)"New value: +"URLs to analyze (1-20 pages)"
- Changed
analyze_url28 fields changed- added
Input schema / properties / allowActionAdded value: +{ + "description": "Glob patterns for controls that should be explorable despite the safety policy.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / channelAdded value: +{ + "description": "Browser channel: chrome, chrome-beta, msedge. Bypasses shared pool.", + "type": "string" +} - added
Input schema / properties / checkVisibilityAdded value: +{ + "description": "Run the per-icon visibility probe across profile-declared (colorScheme × forcedColors) modes. Emits hcm-icon-invisible, low-contrast-icon, and hcm-substitution-risk findings. Undefined defers to the profile default (desktop AT profiles declare the full matrix; mobile/generic do not).", + "type": "boolean" +} - added
Input schema / properties / entrySelectorAdded value: +{ + "description": "Activate this trigger before capture/probe, then prioritize newly revealed targets.", + "type": "string" +} - changed
Input schema / properties / exclude / descriptionPrevious value: -"Glob patterns to exclude targets by name/role/kind (e.g., ['*cookie*', '*notification*', 'banner']). Case-insensitive, supports * and ? wildcards."New value: +"Glob patterns to exclude targets by name/role/kind." - changed
Input schema / properties / excludeSelector / descriptionPrevious value: -"CSS selectors to hide from analysis (e.g., ['#notifications', '.cookie-banner']). Elements are set aria-hidden before capture."New value: +"CSS selectors to hide from analysis (set aria-hidden before capture)." - added
Input schema / properties / exploreBudgetAdded value: +{ + "description": "Max total actions during exploration across all branches (default: 30). Prevents pathological pages from exploding probe time. CLI default is 50; MCP default is 30 for tighter agent-loop latency.", + "maximum": 200, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / exploreDepthAdded value: +{ + "default": 2, + "description": "Max exploration depth (default: 2). How many levels of branches to walk when explore=true. Higher = more thorough, but each level multiplies action count. CLI defaults to 3 for power-user runs; MCP defaults to 2 for tighter agent-loop latency.", + "maximum": 5, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / exploreMaxTargetsAdded value: +{ + "description": "Max accumulated targets before exploration stops early (default: 2000).", + "maximum": 5000, + "minimum": 100, + "type": "integer" +} - added
Input schema / properties / exploreTimeoutAdded value: +{ + "description": "Total exploration timeout in ms; includes initial/revealed probe time when probe is enabled (default: 60000).", + "maximum": 300000, + "minimum": 1000, + "type": "integer" +} - changed
Input schema / properties / focus / descriptionPrevious value: -"Only analyze targets within these landmarks (e.g., ['main', 'navigation']). Reduces noise in large pages."New value: +"Only analyze targets within these landmarks." - changed
Input schema / properties / format / descriptionPrevious value: -"Output format. 'sarif' (recommended) filters to actionable findings only. 'json' includes all targets."New value: +"Output format. 'sarif' (recommended) filters to actionable findings only." - added
Input schema / properties / goalPatternAdded value: +{ + "description": "Glob pattern matched against target id/name/role/kind/selector for goal-directed probing.", + "type": "string" +} - added
Input schema / properties / goalTargetAdded value: +{ + "description": "Exact-ish target id, name, role, kind, or selector hint for goal-directed probing.", + "type": "string" +} - changed
Input schema / properties / includeStates / descriptionPrevious value: -"Include captured states in JSON output for passing to trace_path's statesJson parameter. Uses compact format (~5KB): state IDs, target IDs+selectors+roles, and provenance. The 'states' key in the output is the value to pass as statesJson to trace_path."New value: +"Include captured states in JSON output for passing to trace_path's statesJson parameter. Uses compact format (~5KB)." - changed
Input schema / properties / maxFindings / descriptionPrevious value: -"Maximum detailed findings to return (default: 15 for JSON/markdown, 25 for SARIF). Use 3-5 for quick checks, higher for thorough audits."New value: +"Maximum detailed findings to return." - changed
Input schema / properties / probe / descriptionPrevious value: -"Run keyboard probes on interactive targets (focus, activation, Escape recovery, Tab trapping). Adds ~30-60s but detects real focus management issues. Off by default — use for deep investigation, not for triage or fix-verify loops. analyze_pages never probes."New value: +"Run keyboard probes on interactive targets. Adds ~30-60s." - added
Input schema / properties / probeBudgetAdded value: +{ + "description": "Maximum number of targets for the generic probe. Overrides probeMode's generic budget.", + "maximum": 200, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / probeModeAdded value: +{ + "default": "standard", + "description": "Probe depth preset: fast=5/5/3/5, standard=20/20/10/20 (default), deep=50/40/20/40. Budgets are generic/menu/modal/widget.", + "enum": [ + "fast", + "standard", + "deep" + ], + "type": "string" +} - added
Input schema / properties / probeSelectorAdded value: +{ + "description": "CSS selectors that narrow probes without changing capture/scoring.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / probeStrategyAdded value: +{ + "description": "Probe family intent preset. Default all; use overlay, form, composite-widget, navigation, modal-return-focus, or menu-pattern to spend budget on one class of behavior.", + "enum": [ + "all", + "overlay", + "composite-widget", + "form", + "navigation", + "modal-return-focus", + "menu-pattern" + ], + "type": "string" +} - added
Input schema / properties / scopeSelectorAdded value: +{ + "description": "CSS selectors that define the subtree(s) to capture, score, and probe.", + "items": { + "type": "string" + }, + "type": "array" +} - added
Input schema / properties / stealthAdded value: +{ + "description": "Apply anti-bot-detection defaults. Pair with channel for Cloudflare-protected sites.", + "type": "boolean" +} - changed
Input schema / properties / storageState / descriptionPrevious value: -"Path to a Playwright storageState JSON file containing cookies and localStorage. Use save_auth to create this file, then pass the path here to analyze authenticated pages. Example: 'tactual-auth.json'"New value: +"Path to Playwright storageState JSON (cookies + localStorage). Must be within cwd." - changed
Input schema / properties / summaryOnly / descriptionPrevious value: -"Return only summary stats (severity counts, top issue groups, average score) without individual findings. ~500 bytes. Use for quick page health checks before diving deeper."New value: +"Return compact summary stats. Use for quick page health checks." - changed
Input schema / properties / timeout / descriptionPrevious value: -"Page load timeout in milliseconds"New value: +"Page load timeout in ms" - changed
Input schema / properties / waitForSelector / descriptionPrevious value: -"CSS selector to wait for before capturing (essential for SPAs). E.g., 'main', '#app', '[data-hydrated]'"New value: +"CSS selector to wait for before capturing (essential for SPAs)." - changed
Input schema / properties / waitTime / descriptionPrevious value: -"Additional milliseconds to wait after page load (default: 0). Use for slow-rendering SPAs."New value: +"Additional ms to wait after page load."
- Changed
save_auth5 fields changed- changed
Input schema / properties / outputPath / descriptionPrevious value: -"File path to save the storageState JSON"New value: +"File path to save the storageState JSON (must be within cwd)" - removed
Input schema / properties / steps / items / additionalPropertiesRemoved value: -{} - added
Input schema / properties / steps / items / anyOfAdded value: +[ + { + "additionalProperties": false, + "properties": { + "click": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "click" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "fill": { + "items": [ + { + "minLength": 1, + "type": "string" + }, + { + "type": "string" + } + ], + "type": "array" + } + }, + "required": [ + "fill" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "wait": { + "minimum": 0, + "type": "number" + } + }, + "required": [ + "wait" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "waitForUrl": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "waitForUrl" + ], + "type": "object" + } +] - removed
Input schema / properties / steps / items / propertyNamesRemoved value: -{ - "type": "string" -} - removed
Input schema / properties / steps / items / typeRemoved value: -"object"
- Changed
trace_path3 fields changed- changed
Input schema / properties / statesJson / descriptionPrevious value: -"Pre-captured states from a prior analyze_url run. Pass the 'states' array from the JSON output (use includeStates=true on analyze_url to include it). When provided, trace_path skips browser launch and traces against the captured state. Workflow: analyze_url(includeStates=true) → extract result.states → trace_path(statesJson=...)."New value: +"Pre-captured states from a prior analyze_url (use includeStates=true). When provided, trace_path skips browser launch." - changed
Input schema / properties / storageState / descriptionPrevious value: -"Path to Playwright storageState JSON for authenticated pages. Use save_auth to create."New value: +"Path to Playwright storageState JSON for authenticated pages. Must be within cwd." - changed
Input schema / properties / target / descriptionPrevious value: -"Target to trace to. Can be an exact target ID from an analysis result, or a glob pattern to match target names (e.g., '*search*', 'Submit*'). Case-insensitive."New value: +"Target to trace to. Exact target ID or glob pattern (e.g., '*search*', 'Submit*'). Case-insensitive."
- Added
validate_url
6 tool updates
v1.1.1- Changed
analyze_pages1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
- Changed
analyze_url1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
- Changed
diff_results1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
- Changed
save_auth2 fields changed- removed
Input schema / additionalPropertiesRemoved value: -false - added
Input schema / properties / steps / items / propertyNamesAdded value: +{ + "type": "string" +}
- Changed
suggest_remediations1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
- Changed
trace_path1 field changed- removed
Input schema / additionalPropertiesRemoved value: -false
1 tool update
v1.1.0- Added
list_profiles
6 tool updates
v1.0.3- Added
analyze_pages - Added
analyze_url - Added
diff_results - Added
save_auth - Added
suggest_remediations - Added
trace_path
7 tool updates
v1.0.2- Removed
analyze_pages - Removed
analyze_url - Removed
diff_results - Removed
list_profiles - Removed
save_auth - Removed
suggest_remediations - Removed
trace_path
7 tool updates
v1.0.2- Added
analyze_pages - Added
analyze_url - Added
diff_results - Added
list_profiles - Added
save_auth - Added
suggest_remediations - Added
trace_path
TDQS
Each tool has a clearly distinct purpose: page-level analysis, site-level aggregate, comparison, profile listing, authentication, remediation suggestions, navigation tracing, and prediction validation. No overlap in functionality.
All tool names follow a consistent verb_noun pattern with underscores (e.g., analyze_url, list_profiles, save_auth), making the set predictable and easy to navigate.
With 8 tools, the server covers the core accessibility analysis workflow without excess or deficiency. Each tool serves a distinct role within the domain.
The tool set covers the full lifecycle: authentication, single-page and site-level analysis, comparison, remediation suggestions, path tracing, and validation. No obvious gaps for the stated purpose.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Accessibility and WCAG data for your own websites: fix lists, live checks, and fix validation.
Scan a web page for accessibility, security, privacy, quality and SEO issues, with fixes.
Deterministic axe-core accessibility scans (WCAG 2.1 AA, EN 301 549, PDF/UA) via your account.
Real-browser WCAG audit that also finds keyboard-inoperable controls axe-core misses, with fixes.
Related MCP Servers
- AlicenseBqualityDmaintenanceProvides web accessibility analysis and color blindness simulation using axe-core and Puppeteer, enabling detailed accessibility checks and visual simulations based on WCAG guidelines.24MIT

Playwright MCP Serverofficial
AlicenseBqualityAmaintenanceA Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.2245,881,52736,824Apache 2.0- FlicenseNot gradedqualityDmaintenanceEnables automated WCAG 2.2 AA accessibility audits of Figma designs and webpages. Generates detailed markdown reports with severity-grouped violations, specific criterion references, and concrete fix recommendations.-
- AlicenseNot gradedqualityBmaintenanceAutonomous WCAG 2.1 accessibility auditor that scans, fixes, re-verifies, and generates VPAT 2.5 EN 301 549 reports using AI vision analysis + DOM scanning.11,5021MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/tactual-dev/tactual'
If you have feedback or need assistance with the MCP directory API, please join our Discord server