Skip to main content
Glama
tactual-dev

tactual-mcp

Official
by tactual-dev

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-reader for 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 tactual

Tactual 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 10

Console 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 navigation

Where 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 finding

  • grouped issueGroups and remediation candidates in summarized output

  • analyze_pages.site.repeatedNavigation for repeated navigation cost across routes

  • diff-results / diff_results for 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.json

Use 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.json

The 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_url

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.

trace_path

Step-by-step navigation path to a target with modeled SR announcements.

validate_url

Validate predicted paths against @guidepup/virtual-screen-reader. Returns reachable + mean accuracy per strategy (linear/semantic). Closes the predicted-vs-validated loop.

calibration_report

Run observed calibration datasets against saved full analysis JSON and return structured scoring signals for tuning/review workflows.

list_profiles

List available AT profiles.

diff_results

Compare two analysis results — improvements, regressions, severity changes.

suggest_remediations

Ranked fix suggestions by impact.

save_auth

Authenticate and save session state for analyzing protected content.

analyze_pages

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 tactual

Claude 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 stdio

GitHub 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.sarif

Regression 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 regressed

Or 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

--probe-strategy modal-return-focus

MCP and library options

camelCase fields

probeStrategy: "modal-return-focus"

GitHub Action

kebab-case inputs

probe-strategy: modal-return-focus

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 diagnostics

tactual.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

generic-mobile-web-sr-v0

Mobile

Normalized mobile SR primitives (default)

voiceover-ios-v0

Mobile

VoiceOver on iOS Safari — rotor-based navigation

talkback-android-v0

Mobile

TalkBack on Android Chrome — reading controls

nvda-desktop-v0

Desktop

NVDA on Windows — browse mode quick keys

jaws-desktop-v0

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

Icon invisible in <mode>

Contrast < 1.5:1, no adjacent text label

Operability capped at 60

Decorative icon invisible in <mode>

Contrast < 1.5:1, control has visible text label

Operability −5

Low icon contrast in <mode>

Contrast 1.5–3.0:1, no adjacent text label

Operability −5

Author-set SVG fill in <mode>

Contrast OK in Playwright (≥3:1) but mode is forced-colors: active and the fill is an author CSS literal (non-system, non-currentColor)

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

ecommerce-checkout

Shopping flows

main

checkout, cart, payment, buy

docs-site

Documentation

main, navigation

search, nav

dashboard

Web apps

main, navigation

save, submit, create, delete, search

form-heavy

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 details

Presets 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

blocked-by-bot-protection

error

Bot/challenge page detected; captured content is not the intended page.

empty-page

error

No targets found at all.

ok

info

Capture produced a target set without reliability warnings.

possibly-degraded-content

warning

Suspiciously few targets for an HTTP page.

sparse-content

warning

Only 1-4 targets found.

possible-login-wall

warning

Auth-gated content or login redirect suspected.

possible-cookie-wall

info

Cookie consent may obscure content.

redirect-detected

info/warning

Capture landed on a different URL or domain.

timeout-during-render

warning

A requested render wait did not complete before capture.

framework-detected

info

Frontend framework signals were detected during capture.

spa-route-changes

info

SPA route changes happened during analysis.

exploration-no-new-states

warning

--explore ran but did not reveal additional states.

frames-descended

info

Iframe descent captured or skipped child frames.

auto-scrolled

info

Auto-scroll ran before capture and reports what it surfaced.

banners-dismissed

info

Cookie/consent banner dismissal was attempted.

tab-order-walked

info/warning

Tab-order walk recorded focus stops; warns on positive tabindex.

viewport-divergence

warning

Desktop/mobile viewport diff found missing targets, landmarks, or headings.

no-headings

warning

No heading elements found.

heading-skip

warning

Heading hierarchy skips a level, such as h1 -> h3.

empty-heading

warning

Heading exists but has no text.

numeric-heading

warning

Heading text is only digits, punctuation, or trivial single-character content.

h1-count

info/warning

Page has no useful single H1, or has multiple H1s worth reviewing.

no-landmarks

warning

No landmark regions found.

no-main-landmark

warning

Missing <main> landmark.

no-banner-landmark

info

Missing <header> / banner landmark.

no-contentinfo-landmark

info

Missing <footer> / contentinfo landmark.

no-nav-landmark

info

Missing <nav> / navigation landmark.

landmark-demoted

warning

HTML landmark exists but is demoted by nesting context.

structural-summary

info

One-line structural overview.

no-skip-link

warning

No skip-to-content link on pages with 5+ targets.

broken-skip-link

warning

Skip-style link points to a missing fragment target.

skip-link-not-first

warning

A skip link exists but is not reachable in the first two Tab stops.

visual-order-divergence

warning

Visual order appears to diverge from DOM/SR navigation order.

shared-structural-issue

warning

A penalty affecting >50% of targets is promoted to page level.

redundant-tab-stops

warning

Multiple link targets create repeated Tab stops to the same destination.

data-flow-dependencies

info

Explored states reveal controls that become enabled only after prior action.

form-summary

info/warning

Summarizes forms and warns when a form appears to lack a submit control.

missing-autocomplete

warning

Standard form fields lack useful autocomplete tokens or disable them.

empty-interactive

warning

Interactive target has no accessible name.

fake-interactive-elements

warning

Clickable non-semantic elements are not keyboard/SR reachable.

cdp-click-listeners

warning

CDP found click-like listeners on non-interactive elements.

ambiguous-link-names

warning

Links with the same accessible name point to different destinations.

media-without-controls

warning

Audio/video lacks controls and is not hidden.

duplicate-id

warning

Duplicate id values can break labels and ARIA references.

nested-interactive

warning

Interactive controls are nested inside other interactive controls.

meta-refresh

warning

Page auto-refreshes or redirects via meta refresh.

missing-image-alt

warning

Images lack alt attributes.

suspicious-image-alt

warning

Image alt text looks like filler or a filename-like placeholder.

missing-iframe-title

warning

Iframes lack a title or accessible label.

missing-html-lang

warning

<html lang> is missing or does not look like a BCP 47 language tag.

poor-document-title

warning

Document title is missing, empty, too short, or generic.

viewport-blocks-zoom

warning

Viewport meta settings restrict user zoom.

low-contrast-text

warning

Interactive text or headings fail WCAG-style text contrast thresholds.

color-only-conveyance

warning

Text appears to rely on color alone to convey meaning.

color-blindness-contrast-fail

warning

Text loses contrast under simulated color-vision deficiency.

lang-switch-without-marker

warning

Text language appears to change without a lang marker.

invalid-aria-role

warning

Non-standard ARIA role is present.

unknown-aria-attr

warning

Unknown aria-* attribute is present.

invalid-aria-attr-value

warning

ARIA attribute value is outside the allowed value set.

missing-required-aria-attr

warning

ARIA role is missing a required state or property.

aria-naming-prohibited

warning

Name is applied to a role that prohibits naming.

unsupported-aria-attr-for-role

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 requiresBranchOpen

  • Respects 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

--scope-selector "#drawer"

scopeSelector / scope-selector

Captures, scores, and probes only the selected subtree(s).

Probe one subtree

--probe-selector "#drawer"

probeSelector / probe-selector

Keeps page-wide scoring but spends probe budget only inside the selected subtree(s).

Open one branch first

--entry-selector "[aria-controls='menu']"

entrySelector / entry-selector

Activates the trigger before capture/probe and prioritizes newly revealed targets.

Aim at a known target

--goal-target "checkout"

goalTarget / goal-target

Narrows probing to matching target ids, names, roles, kinds, or selectors.

Aim by glob

--goal-pattern "*dialog*"

goalPattern / goal-pattern

Same as goal target, with glob matching.

Spend budget by intent

--probe-strategy modal-return-focus

probeStrategy / probe-strategy

Runs the probe families relevant to all, overlay, composite-widget, form, navigation, modal-return-focus, or menu-pattern.

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 markdown

Exploration budgets

Budget

CLI flag

Default

Purpose

Depth

--explore-depth

3

Max recursion depth

Actions

--explore-budget

50

Total click budget across all branches

Targets

--explore-max-targets

2000

Stop if accumulated targets exceed this

Time

--explore-timeout

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

--explore-depth 3 --explore-budget 50 (defaults)

Captures one level of menu opens

Complex app (Figma, Notion, etc.)

--explore-depth 4 --explore-budget 100 --explore-max-targets 5000

Deeper menus, more state

Pages with very large hidden UI (emoji pickers, color grids)

--explore-max-targets 10000 plus --exclude "emoji-*"

Cap or filter out the firehose

Quick triage of unknown page

--explore-depth 1 --explore-budget 10

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-routes records pushState, replaceState, popstate, and hashchange events that happen during analysis.

  • --auto-scroll surfaces IntersectionObserver-driven lazy content before capture.

  • --descend-frames appends 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-viewports catches target, landmark, or heading content that disappears between desktop and mobile viewports.

  • --dismiss-banners, --probe-hover, and --walk-tab-order add 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-pages

The 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.json

The 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

button, link, heading

0

Well-supported

dialog

5

Focus management varies

combobox

8

Most interop-problematic pattern

tree

10

Poorly supported outside JAWS

application

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

console

~8KB

Human review in terminal

markdown

~11KB

PRs and issue comments

json

~18KB

Programmatic consumption

sarif

~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 gate

Security

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 tools
analyze_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlsYesURLs to analyze (1-20 pages)
profileNoAT profile IDgeneric-mobile-web-sr-v0
timeoutNoPage load timeout per URL
waitTimeNoAdditional wait per page in ms
storageStateNoPath to Playwright storageState JSON for authenticated pages. Use save_auth to create. Must be within cwd.
waitForSelectorNoCSS selector to wait for on each page (for SPAs)

TDQS

A4.6/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesURL to analyze
focusNoOnly analyze targets within these landmarks.
probeNoRun keyboard probes on interactive targets. Adds ~30-60s.
deviceNoPlaywright device name for emulation (e.g., 'iPhone 14')
formatNoOutput format. 'sarif' (recommended) filters to actionable findings only.sarif
channelNoBrowser channel: chrome, chrome-beta, msedge. Bypasses shared pool.
excludeNoGlob patterns to exclude targets by name/role/kind.
exploreNoExplore hidden branches (menus, tabs, dialogs). Use with format='sarif' to avoid output overflow.
profileNoAT profile ID (generic-mobile-web-sr-v0, nvda-desktop-v0, jaws-desktop-v0, voiceover-ios-v0, talkback-android-v0)generic-mobile-web-sr-v0
stealthNoApply anti-bot-detection defaults. Pair with channel for Cloudflare-protected sites.
timeoutNoPage load timeout in ms
waitTimeNoAdditional ms to wait after page load.
probeModeNoProbe 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
autoScrollNoScroll 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.
goalTargetNoExact-ish target id, name, role, kind, or selector hint for goal-directed probing.
probeHoverNoHover 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.
allowActionNoGlob patterns for controls that should be explorable despite the safety policy.
goalPatternNoGlob pattern matched against target id/name/role/kind/selector for goal-directed probing.
maxFindingsNoMaximum detailed findings to return.
minSeverityNoOnly include findings at this severity or worse. Reduces output size.
probeBudgetNoMaximum number of targets for the generic probe. Overrides probeMode's generic budget.
summaryOnlyNoReturn compact summary stats. Use for quick page health checks.
detectRoutesNoRecord SPA route changes (history.pushState/replaceState, popstate, hashchange) that fire during analysis. Surfaces an spa-route-changes info diagnostic when any are observed.
exploreDepthNoMax 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.
storageStateNoPath to Playwright storageState JSON (cookies + localStorage). Must be within cwd.
walkTabOrderNoPress 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.
descendFramesNoDescend 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.
diffViewportsNoCapture 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.
entrySelectorNoActivate this trigger before capture/probe, then prioritize newly revealed targets.
exploreBudgetNoMax 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.
includeStatesNoInclude captured states in JSON output for passing to trace_path's statesJson parameter. Uses compact format (~5KB).
probeSelectorNoCSS selectors that narrow probes without changing capture/scoring.
probeStrategyNoProbe 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.
scopeSelectorNoCSS selectors that define the subtree(s) to capture, score, and probe.
dismissBannersNoBest-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.
exploreTimeoutNoTotal exploration timeout in ms; includes initial/revealed probe time when probe is enabled (default: 60000).
checkVisibilityNoRun 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).
excludeSelectorNoCSS selectors to hide from analysis (set aria-hidden before capture).
waitForSelectorNoCSS selector to wait for before capturing (essential for SPAs).
exploreMaxTargetsNoMax accumulated targets before exploration stops early (default: 2000).

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
formatNoOutput format. JSON is best for agent workflows; markdown is for human review.json
analysisDirNoDirectory of full analysis JSON files, within the current working directory
datasetPathYesPath to calibration dataset JSON, within the current working directory
allowMissingNoAllow observations whose URLs have no matching analysis JSON
analysisPathsNoFull analysis JSON files produced by analyze_url/analyze-url

TDQS

A4.4/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
baselineYesBaseline analysis result as JSON string
candidateYesCandidate analysis result as JSON string

TDQS

A5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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' }]

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesLogin page URL
stepsYesLogin steps to execute (see description for format)
timeoutNoTimeout per step in ms
outputPathNoFile path to save the storageState JSON (must be within cwd)tactual-auth.json

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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').

ParametersJSON Schema
NameRequiredDescriptionDefault
analysisYesAnalysis result as JSON string
maxSuggestionsNoMaximum number of suggestions to return

TDQS

A4.7/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesURL of the page to trace
deviceNoPlaywright device name for emulation (e.g., 'iPhone 14')
targetYesTarget to trace to. Exact target ID or glob pattern (e.g., '*search*', 'Submit*'). Case-insensitive.
exploreNoExplore hidden branches (menus, tabs, dialogs) before tracing
profileNoAT profile IDgeneric-mobile-web-sr-v0
timeoutNoPage load timeout in milliseconds
statesJsonNoPre-captured states from a prior analyze_url (use includeStates=true). When provided, trace_path skips browser launch.
storageStateNoPath to Playwright storageState JSON for authenticated pages. Must be within cwd.
waitForSelectorNoCSS selector to wait for before capturing (essential for SPAs)

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesURL to analyze and validate
channelNoBrowser channel: chrome, chrome-beta, msedge
profileNoAT profile ID (default: nvda-desktop-v0). Use list_profiles to see options.
stealthNoApply anti-bot-detection defaults
timeoutNoPage load timeout in ms
strategyNoNavigation 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
waitTimeNoAdditional wait after load (ms)
maxTargetsNoMaximum findings to validate (worst-first). Higher = slower but more signal.
storageStateNoPath to a Playwright storageState JSON (for authenticated pages). Must be within the current working directory.

TDQS

A4.3/5.0
Behavior4/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 2 tool updates
    • Changedanalyze_url7 fields changed
      • addedInput schema / properties / autoScroll
        Added 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"
        +}
      • addedInput schema / properties / descendFrames
        Added 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"
        +}
      • addedInput schema / properties / detectRoutes
        Added 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"
        +}
      • addedInput schema / properties / diffViewports
        Added 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"
        +}
      • addedInput schema / properties / dismissBanners
        Added 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"
        +}
      • addedInput schema / properties / probeHover
        Added 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"
        +}
      • addedInput schema / properties / walkTabOrder
        Added 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"
        +}
    • Addedcalibration_report
  2. 5 tool updatesv0.3.0
    • Changedanalyze_pages2 fields changed
      • changedInput schema / properties / storageState / description
        Previous 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."
      • changedInput schema / properties / urls / description
        Previous value: -"URLs to analyze (2-20 pages)"New value: +"URLs to analyze (1-20 pages)"
    • Changedanalyze_url28 fields changed
      • addedInput schema / properties / allowAction
        Added value: +{
        +  "description": "Glob patterns for controls that should be explorable despite the safety policy.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedInput schema / properties / channel
        Added value: +{
        +  "description": "Browser channel: chrome, chrome-beta, msedge. Bypasses shared pool.",
        +  "type": "string"
        +}
      • addedInput schema / properties / checkVisibility
        Added 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"
        +}
      • addedInput schema / properties / entrySelector
        Added value: +{
        +  "description": "Activate this trigger before capture/probe, then prioritize newly revealed targets.",
        +  "type": "string"
        +}
      • changedInput schema / properties / exclude / description
        Previous 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."
      • changedInput schema / properties / excludeSelector / description
        Previous 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)."
      • addedInput schema / properties / exploreBudget
        Added 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"
        +}
      • addedInput schema / properties / exploreDepth
        Added 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"
        +}
      • addedInput schema / properties / exploreMaxTargets
        Added value: +{
        +  "description": "Max accumulated targets before exploration stops early (default: 2000).",
        +  "maximum": 5000,
        +  "minimum": 100,
        +  "type": "integer"
        +}
      • addedInput schema / properties / exploreTimeout
        Added value: +{
        +  "description": "Total exploration timeout in ms; includes initial/revealed probe time when probe is enabled (default: 60000).",
        +  "maximum": 300000,
        +  "minimum": 1000,
        +  "type": "integer"
        +}
      • changedInput schema / properties / focus / description
        Previous value: -"Only analyze targets within these landmarks (e.g., ['main', 'navigation']). Reduces noise in large pages."New value: +"Only analyze targets within these landmarks."
      • changedInput schema / properties / format / description
        Previous 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."
      • addedInput schema / properties / goalPattern
        Added value: +{
        +  "description": "Glob pattern matched against target id/name/role/kind/selector for goal-directed probing.",
        +  "type": "string"
        +}
      • addedInput schema / properties / goalTarget
        Added value: +{
        +  "description": "Exact-ish target id, name, role, kind, or selector hint for goal-directed probing.",
        +  "type": "string"
        +}
      • changedInput schema / properties / includeStates / description
        Previous 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)."
      • changedInput schema / properties / maxFindings / description
        Previous 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."
      • changedInput schema / properties / probe / description
        Previous 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."
      • addedInput schema / properties / probeBudget
        Added value: +{
        +  "description": "Maximum number of targets for the generic probe. Overrides probeMode's generic budget.",
        +  "maximum": 200,
        +  "minimum": 1,
        +  "type": "integer"
        +}
      • addedInput schema / properties / probeMode
        Added 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"
        +}
      • addedInput schema / properties / probeSelector
        Added value: +{
        +  "description": "CSS selectors that narrow probes without changing capture/scoring.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedInput schema / properties / probeStrategy
        Added 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"
        +}
      • addedInput schema / properties / scopeSelector
        Added value: +{
        +  "description": "CSS selectors that define the subtree(s) to capture, score, and probe.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedInput schema / properties / stealth
        Added value: +{
        +  "description": "Apply anti-bot-detection defaults. Pair with channel for Cloudflare-protected sites.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / storageState / description
        Previous 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."
      • changedInput schema / properties / summaryOnly / description
        Previous 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."
      • changedInput schema / properties / timeout / description
        Previous value: -"Page load timeout in milliseconds"New value: +"Page load timeout in ms"
      • changedInput schema / properties / waitForSelector / description
        Previous 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)."
      • changedInput schema / properties / waitTime / description
        Previous value: -"Additional milliseconds to wait after page load (default: 0). Use for slow-rendering SPAs."New value: +"Additional ms to wait after page load."
    • Changedsave_auth5 fields changed
      • changedInput schema / properties / outputPath / description
        Previous value: -"File path to save the storageState JSON"New value: +"File path to save the storageState JSON (must be within cwd)"
      • removedInput schema / properties / steps / items / additionalProperties
        Removed value: -{}
      • addedInput schema / properties / steps / items / anyOf
        Added 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"
        +  }
        +]
      • removedInput schema / properties / steps / items / propertyNames
        Removed value: -{
        -  "type": "string"
        -}
      • removedInput schema / properties / steps / items / type
        Removed value: -"object"
    • Changedtrace_path3 fields changed
      • changedInput schema / properties / statesJson / description
        Previous 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."
      • changedInput schema / properties / storageState / description
        Previous 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."
      • changedInput schema / properties / target / description
        Previous 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."
    • Addedvalidate_url
  3. 6 tool updatesv1.1.1
    • Changedanalyze_pages1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedanalyze_url1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changeddiff_results1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedsave_auth2 fields changed
      • removedInput schema / additionalProperties
        Removed value: -false
      • addedInput schema / properties / steps / items / propertyNames
        Added value: +{
        +  "type": "string"
        +}
    • Changedsuggest_remediations1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
    • Changedtrace_path1 field changed
      • removedInput schema / additionalProperties
        Removed value: -false
  4. 1 tool updatev1.1.0
    • Addedlist_profiles
  5. 6 tool updatesv1.0.3
    • Addedanalyze_pages
    • Addedanalyze_url
    • Addeddiff_results
    • Addedsave_auth
    • Addedsuggest_remediations
    • Addedtrace_path
  6. 7 tool updatesv1.0.2
    • Removedanalyze_pages
    • Removedanalyze_url
    • Removeddiff_results
    • Removedlist_profiles
    • Removedsave_auth
    • Removedsuggest_remediations
    • Removedtrace_path
  7. 7 tool updatesv1.0.2
    • Addedanalyze_pages
    • Addedanalyze_url
    • Addeddiff_results
    • Addedlist_profiles
    • Addedsave_auth
    • Addedsuggest_remediations
    • Addedtrace_path

TDQS

A4.5/5.0
Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

With 8 tools, the server covers the core accessibility analysis workflow without excess or deficiency. Each tool serves a distinct role within the domain.

Completeness5/5

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

ActivityStale
ResponsivenessSyncing

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

Related MCP Servers

Latest Blog Posts

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