Skill: dylanb/axe-devtools (version 4.0.0)

--- name: axe-accessibility description: Accessibility testing and remediation using the axe MCP Server. Use when creating or modifying UI code (HTML, JSX, TSX, Vue, Svelte, CSS) to ensure accessibility compliance. Triggers on tasks involving web pages, components, forms, navigation, modals, tables, images, or any user-facing markup. Also use when explicitly asked to check accessibility or run an axe scan. --- # axe Accessibility Skill Test web pages for accessibility violations and get AI-powered remediation guidance using the axe DevTools MCP Server. ## Prerequisites - Docker running locally - `AXE_API_KEY` environment variable set - Docker image pulled: `dequesystems/axe-mcp-server:latest` ## Tools The wrapper script at `scripts/axe-mcp.js` (Node.js — no extra dependencies) provides two tools: ### analyze Scan a live web page for accessibility violations. Requires a URL (works with localhost). ```bash node scripts/axe-mcp.js analyze <url> ``` Returns JSON-RPC response. The violations are in `result.content[0].text` (JSON string) under the `data` array. Each violation has: `rule`, `impact`, `description`, `selector`, `source`, `helpUrl`. ### remediate Get AI-powered fix guidance for a specific violation. Handles HTML with quotes/brackets safely. ```bash node scripts/axe-mcp.js remediate <ruleId> <elementHtml> <issueRemediation> [pageUrl] ``` Returns `general_description`, `remediation`, and `code_fix` in `result.content[0].text`. ### tools-list List available MCP tools. ```bash node scripts/axe-mcp.js tools-list ``` ## Workflow When modifying UI code and a live page is available: 1. **Analyze** — `node scripts/axe-mcp.js analyze <url>` 2. **Parse** — extract violations from the JSON response 3. **Remediate** — for each unique rule violation, call remediate with ruleId, element HTML, and issue description 4. **Apply** — implement the recommended code fixes in source 5. **Verify** — re-run analyze to confirm zero violations When no live page is available (static code review), apply accessibility best practices directly: - Images: `alt` text (or `alt=""` for decorative) - Forms: inputs need associated `<label>` elements - Interactive elements: keyboard accessible, visible focus - Color contrast: WCAG AA (4.5:1 normal text, 3:1 large text) - ARIA: valid, complete, not redundant with native semantics - Headings: proper hierarchy (h1 → h2 → h3) - Dynamic content: focus management for modals, SPAs, live regions ## Notes - Each `remediate` call uses AI credits from your organization's allocation - The analyze tool spins up a real browser in Docker — allow ~30s for results - Works with localhost URLs for local development testing > **Note**: Requires a paid Axe DevTools for Web subscription. ## Support For technical support, bug reports, and feature requests: - **Email**: [[REDACTED_SECRET]](mailto:[REDACTED_SECRET]) - **Support Portal**: [support.deque.com](https://support.deque.com) - **[Support Guide](.github/SUPPORT.md)** ## Pricing & Sales - **Product Page**: [deque.com/axe/mcp-server](https://www.deque.com/axe/mcp-server/) - **Contact Sales**: [deque.com/contact](https://www.deque.com/contact) ## About Deque [Deque Systems](https://www.deque.com) is the trusted leader in digital accessibility. - **LinkedIn**: [Deque Systems](https://www.linkedin.com/company/deque-systems/)

path: scripts/axe-mcp.js
content: #!/usr/bin/env node
// axe MCP Server wrapper — sends JSON-RPC requests over Docker stdio
// Usage: node axe-mcp.js analyze <url>
//        node axe-mcp.js remediate <ruleId> <elementHtml> <issueRemediation> [pageUrl]
//        node axe-mcp.js tools-list

const { spawn } = require("child_process");

const AXE_API_KEY = process.env.AXE_API_KEY;
if (!AXE_API_KEY) {
  console.error("Error: Set AXE_API_KEY environment variable");
  process.exit(1);
}

const DOCKER_IMAGE = "dequesystems/axe-mcp-server:latest";

const INIT = JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "initialize",
  params: {
    protocolVersion: "2024-11-05",
    capabilities: {},
    clientInfo: { name: "openclaw", version: "1.0" },
  },
});

const INITIALIZED = JSON.stringify({
  jsonrpc: "2.0",
  method: "notifications/initialized",
});

function sendMcp(call) {
  return new Promise((resolve, reject) => {
    const dockerArgs = [
      "run", "-i", "--rm",
      "-e", `AXE_API_KEY=${AXE_API_KEY}`,
    ];
    if (process.env.AXE_SERVER_URL) {
      dockerArgs.push("-e", `AXE_SERVER_URL=${process.env.AXE_SERVER_URL}`);
    }
    dockerArgs.push(DOCKER_IMAGE);

    const proc = spawn("docker", dockerArgs, { stdio: ["pipe", "pipe", "pipe"] });

    let stdout = "";
    let stderr = "";

    proc.stdout.on("data", (d) => (stdout += d.toString()));
    proc.stderr.on("data", (d) => (stderr += d.toString()));

    proc.on("close", (code) => {
      if (code !== 0) {
        return reject(new Error(`Docker exited with code ${code}: ${stderr}`));
      }
      // Find the response line with id:2
      const lines = stdout.trim().split("\n");
      for (const line of lines) {
        try {
          const parsed = JSON.parse(line);
          if (parsed.id === 2) return resolve(parsed);
        } catch {}
      }
      reject(new Error(`No response found. stdout: ${stdout}`));
    });

    proc.on("error", reject);

    const messages = `${INIT}\n${INITIALIZED}\n${JSON.stringify(call)}\n`;
    proc.stdin.write(messages);
    proc.stdin.end();
  });
}

async function main() {
  const [, , command, ...args] = process.argv;

  let call;

  switch (command) {
    case "analyze": {
      const url = args[0];
      if (!url) {
        console.error("Usage: axe-mcp.js analyze <url>");
        process.exit(1);
      }
      call = {
        jsonrpc: "2.0",
        id: 2,
        method: "tools/call",
        params: { name: "analyze", arguments: { url } },
      };
      break;
    }

    case "remediate": {
      const [ruleId, elementHtml, issueRemediation, pageUrl] = args;
      if (!ruleId || !elementHtml || !issueRemediation) {
        console.error(
          "Usage: axe-mcp.js remediate <ruleId> <elementHtml> <issueRemediation> [pageUrl]"
        );
        process.exit(1);
      }
      const remArgs = { ruleId, elementHtml, issueRemediation };
      if (pageUrl) remArgs.pageUrl = pageUrl;
      call = {
        jsonrpc: "2.0",
        id: 2,
        method: "tools/call",
        params: { name: "remediate", arguments: remArgs },
      };
      break;
    }

    case "tools-list": {
      call = {
        jsonrpc: "2.0",
        id: 2,
        method: "tools/list",
        params: {},
      };
      break;
    }

    default:
      console.error("Usage: axe-mcp.js {analyze|remediate|tools-list} [args...]");
      process.exit(1);
  }

  try {
    const response = await sendMcp(call);
    console.log(JSON.stringify(response, null, 2));
  } catch (err) {
    console.error(`Error: ${err.message}`);
    process.exit(1);
  }
}

main();


Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: clean (LOW)