Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Build a small MCP server that accepts an authorized page or test target, opens it in a controlled browser, runs an automated accessibility check, and returns structured findings—not a declaration that the page is accessible. The server should expose a narrow task such as scan_page, validate its inputs, limit which sites it can reach, and report the page state and test limitations alongside results. Automated checks are useful evidence, but accessibility evaluation also requires human review.

What the server should—and should not—do

An MCP server makes capabilities available to an MCP host, such as an AI assistant or developer tool. Depending on the integration, those capabilities can include tools, resources, and prompts. For an accessibility workflow, start with a tool that answers a recognizable question: “What automated accessibility findings did the test environment return for this page?”

A useful result identifies the tested URL or view, the state that was scanned, the engine and ruleset version, findings and affected elements, checks that did not complete, and suggested human follow-up. It does not turn “zero automated violations” into “WCAG conformant.” The World Wide Web Consortium (W3C) says WCAG testing involves automated testing and human evaluation, and its accessibility evaluation guidance says knowledgeable human evaluation is required to determine whether a site is accessible.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep the server’s authority deliberately small. Do not make arbitrary browser control, unrestricted network access, or arbitrary JavaScript execution the default. A server that can browse internal addresses or run caller-supplied code can become a security boundary failure, not just a testing convenience.

Choose an SDK and transport for the deployment

Choose the implementation language based on your team, host compatibility, and operational needs—not a claim that one SDK is universally superior. The official Python SDK documentation describes v2, Python 3.10 or newer, and stdio, Streamable HTTP, and SSE transports. The TypeScript v2 server package is documented as @modelcontextprotocol/server and implements the 2026-07-28 MCP specification. Check the current SDK and host documentation before implementation; versions and compatibility can change.

Deployment Transport to consider Practical implication
Local tool launched by an MCP host stdio The host starts the server process and communicates over its standard input and output. Keep diagnostic logs off standard output so they do not corrupt the protocol stream.
Remote service used across a network Streamable HTTP Plan for service operation and authentication appropriate to your environment, and verify support in both the SDK and host.
Existing integration that depends on the older pattern HTTP plus SSE The TypeScript v1 guide documents it as deprecated compatibility support. Do not select it for a new remote deployment without a compatibility reason.

The Python SDK documents SSE as an available transport; that does not make it the right default for every deployment. The TypeScript v1 transport guide recommends Streamable HTTP for remote use. Verify the exact behavior and migration guidance for the SDK version you select.

Design a narrow accessibility workflow

  1. Define the permitted target. Prefer a fixed test environment or an allowlist of development and staging hosts. Reject unexpected schemes, credentials in URLs, local addresses, and destinations that should not be reachable from the server.
  2. Choose one task and schema. A first tool might accept a URL and an optional wait-for selector. Set maximum navigation and scan durations. Avoid a general-purpose “browse anywhere” tool.
  3. Render the intended state. Use browser automation to load the page and, when necessary, interact with known controls such as a test fixture’s menu or dialog. A scan of the initial page is not a scan of every state.
  4. Run an automated engine. An integration can run axe-core against rendered content. Deque describes axe-core as a free, open-source accessibility engine for websites and HTML-based interfaces.
  5. Return evidence and limits. Include findings, affected nodes, incomplete checks, and the tested state. Make clear that automated results need interpretation and do not establish conformance.

Tools should expose only the data and actions needed for the user’s goal. Browser automation can inspect and interact with pages, and Playwright MCP provides structured accessibility snapshots. Its documentation warns that enabling arbitrary JavaScript execution in the server process is equivalent to remote code execution; enable it only for fully trusted clients and code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Color Test Book with Ishihara Color Chart Plates for Vision Screening and Deficiency Detection Portable Eye Testing Chart for Drivers and Home Use
  • Core Functionality: This color test book provides a comprehensive and user-friendly color chart designed specifically for early detection of color deficiency, facilitating timely intervention and safer driving assessments
  • Material and Design: Crafted from stable, lightweight, and durable materials, this test book offers convenience and longevity for repeated use in various settings
  • Language and Accessibility: Designed in english to ensure easy understanding and accurate self-administration of the color test book by english-speaking users, enhancing usability and testing accuracy
  • Portability and Storage: Compact dimensions of approximately 3.81 by 3.34 by 0.11 inches and lightweight construction make this test book highly portable and easy to store for use in clinics, schools, or at home
  • Practical Application: Ideal for use in various scenarios such as driver screening, vision examinations, and color deficiency assessments, this color test book integrates multiple test charts to support thorough visual evaluations

Build a local Python server with a constrained scan tool

The example below shows the shape of a local stdio server using the Python MCP SDK’s FastMCP interface, Playwright, and a local copy of axe-core. It is an implementation scaffold, not a tested package-version recipe: use the current SDK documentation to confirm imports and host compatibility for your chosen release. Keep a local, reviewed axe-core file rather than downloading executable code from the page being scanned.

Install the Python MCP SDK and Playwright in an isolated environment, install the Playwright browser required by your deployment, and place a reviewed axe-core JavaScript file at a path held in AXE_CORE_JS. The code assumes the test server has an explicit ACCESSIBILITY_ALLOWED_HOSTS environment variable containing comma-separated hostnames.

import asyncio
import json
import os
import socket
from ipaddress import ip_address
from urllib.parse import urlparse

from mcp.server.fastmcp import FastMCP
from playwright.async_api import async_playwright

mcp = FastMCP("web-accessibility")
ALLOWED_HOSTS = {
    host.strip().lower()
    for host in os.environ.get("ACCESSIBILITY_ALLOWED_HOSTS", "").split(",")
    if host.strip()
}
AXE_CORE_JS = os.environ.get("AXE_CORE_JS", "")
MAX_URL_LENGTH = 2048
NAVIGATION_TIMEOUT_MS = 20000


def validate_target(url: str) -> str:
    if len(url) > MAX_URL_LENGTH:
        raise ValueError("URL is too long")
    parsed = urlparse(url)
    if parsed.scheme != "https" or not parsed.hostname:
        raise ValueError("Use an HTTPS URL with a hostname")
    if parsed.username or parsed.password:
        raise ValueError("Credentials in URLs are not allowed")
    host = parsed.hostname.lower().rstrip(".")
    if host not in ALLOWED_HOSTS:
        raise ValueError("Host is not on the accessibility test allowlist")
    # Reject hosts resolving to non-public IPs as an additional SSRF safeguard.
    for result in socket.getaddrinfo(host, 443, type=socket.SOCK_STREAM):
        address = ip_address(result[4][0])
        if not address.is_global:
            raise ValueError("Target resolves to a non-public address")
    return url


@mcp.tool()
async def scan_page(url: str, wait_for_selector: str = "") -> str:
    """Run an automated axe-core scan on an authorized HTTPS test page."""
    target = validate_target(url)
    if not AXE_CORE_JS or not os.path.isfile(AXE_CORE_JS):
        raise ValueError("Set AXE_CORE_JS to a reviewed local axe-core JavaScript file")
    if len(wait_for_selector) > 300:
        raise ValueError("Selector is too long")

    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        try:
            response = await page.goto(
                target, wait_until="domcontentloaded", timeout=NAVIGATION_TIMEOUT_MS
            )
            if wait_for_selector:
                await page.locator(wait_for_selector).wait_for(
                    state="visible", timeout=5000
                )
            await page.add_script_tag(path=AXE_CORE_JS)
            result = await page.evaluate("""async () => {
                const report = await axe.run(document);
                return {
                    violations: report.violations.map(v => ({
                        id: v.id, impact: v.impact, help: v.help,
                        helpUrl: v.helpUrl,
                        nodes: v.nodes.map(n => ({
                            target: n.target,
                            html: n.html,
                            summary: n.failureSummary
                        }))
                    })),
                    incomplete: report.incomplete.map(v => ({
                        id: v.id, impact: v.impact, help: v.help
                    })),
                    passes: report.passes.length,
                    engine: report.testEngine,
                    ruleset: report.testRunner
                };
            }""")
            return json.dumps({
                "url": page.url,
                "http_status": response.status if response else None,
                "state": "domcontentloaded" + (
                    "; selector visible: " + wait_for_selector
                    if wait_for_selector else ""
                ),
                "automated_result": result,
                "limitations": [
                    "Automated results are not a WCAG conformance determination.",
                    "Review interactive states and perform appropriate human evaluation.",
                    "Incomplete checks require interpretation."
                ]
            })
        finally:
            await browser.close()


if __name__ == "__main__":
    mcp.run(transport="stdio")

The example returns a JSON string for portability. If your SDK and host support structured tool output, use the SDK’s supported typed result format and preserve the same fields. Keep logs on standard error for a stdio server. The DNS check in this example is only an additional guard: robust deployments should also control outbound network access at the infrastructure layer and defend against DNS rebinding and redirects to disallowed destinations.

Cover interactive states, not just the first render

A page’s accessibility problems can depend on its state. Deque documents that axe does not test hidden regions, such as inactive menus or modal windows, until tests activate or render them. A scan after initial navigation therefore cannot stand in for a scan of a closed-menu, open-menu, dialog, validation-error, or other relevant state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a dependable workflow:

  • List the states that matter for the feature being tested, such as the menu open, a dialog open, or a form showing validation errors.
  • Use deterministic test data and explicit selectors to reach those states; do not guess at controls based only on their visual appearance.
  • Run a scan after each state transition and label the state in the returned evidence.
  • Use a representative sample rather than claiming that a few tested views cover an entire site. W3C’s WCAG-EM evaluation methodology calls for defining scope, exploring the product, selecting a representative sample, and evaluating it.

Accessibility testing can combine automated, semi-automated, and manual evaluation. W3C’s ACT framework includes rules across those approaches. Automated findings are best treated as a useful signal and a repeatable part of development—not as a substitute for evaluating keyboard operation, content meaning, interaction behavior, and experiences with assistive technologies.

Return a report people can act on

Do not flatten a scan into a single pass/fail field. A client needs enough context to decide what to fix, what to investigate, and what remains unknown. Include:

  • Target and state: final URL, timestamp, relevant route or fixture, and how the tested state was reached.
  • Tool identity: browser and accessibility-engine versions, rule-set details if available, and server version.
  • Findings: rule identifier, impact as reported by the engine, explanation, affected selectors or nodes, and remediation reference when available.
  • Coverage limits: incomplete checks, navigation failures, timeouts, and states not exercised.
  • Next steps: a concise request for human verification rather than a blanket compliance label.

W3C notes that tools vary and that no tool alone determines accessibility. WCAG conformance also does not necessarily establish usability for people with a wide variety of disabilities. Use a report label such as “automated findings for the tested page state,” not “accessible,” “WCAG compliant,” or “passed WCAG” based solely on a scan.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Security, reliability, and operating cost

Limit what the browser can reach

Only scan targets you are authorized to test. Enforce an allowlist, block private and link-local address ranges, revalidate redirects, set navigation and total-run timeouts, and apply outbound network controls. Do not pass credentials or unrestricted headers from an MCP caller into browser requests. Treat page content as untrusted input, and avoid exposing arbitrary JavaScript execution to clients.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Bound resource use

Headless browsers consume more resources than a simple HTTP request. Limit concurrent browser contexts, page size where practical, navigation time, and the number of states per request. Close browser resources in cleanup paths, as the example does. For a remote deployment, add authentication, request quotas, monitoring, and isolated workers appropriate to its exposure; stdio is not a substitute for a remote service’s access controls.

Make failures visible

A timeout is not a clean scan. Return an explicit failure or partial-result status when navigation fails, the selector never appears, the engine cannot run, or the scan is incomplete. Preserve enough diagnostic detail for developers while avoiding disclosure of secrets, cookies, or sensitive page content to an untrusted MCP host. Cache only when you can identify the page state and test configuration well enough to avoid presenting stale results as current.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server, not an accessibility auditing engine. It can capture the page state for a visual record or an agent workflow; it does not replace axe-core, interactive-state coverage, or human accessibility evaluation. One GET request returns an image or PDF. For example, save a WebP screenshot of an authorized test page:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

See the ScreenshotNeo API documentation for request options. Cookie banners are accepted and removed before capture, along with supported newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers indicate the page verdict and billing status. An MCP server lets AI agents use screenshot tools. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo’s free plan.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot common failures

Symptom Likely cause What to do
The MCP host cannot start or connect to the server The host’s configured command, environment, transport, or SDK version does not match the server. Check the host’s current MCP compatibility guidance, verify the Python environment and launch command, and confirm that both sides support the chosen protocol version and transport.
Protocol messages appear malformed in a local integration Diagnostic output was written to stdout instead of stderr. Reserve stdout for stdio protocol traffic and send logs to stderr.
The tool rejects a URL The scheme, hostname, DNS result, or allowlist does not satisfy policy. Use an authorized HTTPS staging target, add only the exact intended hostname to the allowlist, and investigate DNS and redirect behavior rather than weakening the guard wholesale.
Navigation succeeds but the scan is empty or incomplete The relevant content may be hidden, rendered later, or dependent on an interaction; the engine may also have returned incomplete checks. Wait for a deterministic state, activate the relevant control in a controlled test, scan again, and retain incomplete findings for review.
The selector wait times out The selector is wrong, the page is in a different state, or the control is not visible. Check the test fixture and selector, confirm the expected interaction occurred, and report the state failure rather than treating it as a successful scan.
A clean automated result is mistaken for conformance The report overstates what the engine established. Describe the result as automated findings for a specific state, then complete appropriate manual and representative evaluation.

FAQ

Can an MCP server itself decide whether a website is accessible?

No. It can coordinate checks and return evidence, but accessibility requires interpretation and human evaluation as well as automation.

Should I use Playwright MCP as my whole accessibility server?

It can provide browser interaction and structured accessibility snapshots. A focused server may still be preferable when you need a constrained scan workflow, a specific engine’s findings, or narrowly controlled target access.

Does a screenshot prove a page is accessible?

No. A screenshot records visual output, not the full semantic, keyboard, assistive-technology, or interaction experience.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.