Autonomous QA Engineer MCP

Autonomous QA Engineer MCP

Provides MCP tools that give LLM agents a full QA engineer workflow: scanning projects, generating deterministic test suites, executing them across browser/API/mobile, diagnosing failures, and proposing fixes that require human approval.

Category
Visit Server

README

Autonomous QA Engineer MCP

Give an LLM agent a real QA engineer's workflow — scan a project, design test cases by the book, run them across browser/API/mobile, diagnose failures, propose a fix, get human sign-off, apply it, and verify — all as MCP tools any MCP-compatible agent (Claude Desktop, Claude Code, or your own agent loop) can call.

The core idea: the LLM should never be the one deciding what counts as a complete test suite. It reports facts about a feature (its fields, its business rules, its roles, its states); this project deterministically expands those facts into a suite that follows Boundary Value Analysis, Equivalence Partitioning, OWASP Top 10, and exhaustive state-machine coverage — every time, the same way, with a rationale attached to every case. LLMs forget edge cases under load; a rules engine doesn't.

Python License Tests Validated


Why this, not just "ask the LLM to write tests"

Ask an LLM to write tests This project
Edge case coverage Whatever it remembers to think of, varies run to run Deterministic: BVA, Equivalence Partitioning, OWASP Top 10, exhaustive state × action matrix
Security cases Only if prompted, inconsistent payloads 8 OWASP-mapped injection payloads on every free-text field, automatically
State machine testing Usually just the happy path Every declared transition and every invalid transition from every state, plus automatic unreachable-state detection
Executing the tests Another manual step Same agent session drives Playwright/Selenium/Robot/Cypress, API, and mobile directly
Fixing what it finds Freeform patch, no guardrail Diagnose → propose → human approval required → apply → verify, with the auto-apply path hard-blocked unless a human sets an env var outside the agent's reach
State across a session Lost between calls Persists to disk, survives a restart, safe for concurrent writers

Quick start

pip install -e .

# See every tool the agent can call
qa-mcp --list-tools

# Try one tool directly (useful for debugging a single tool in isolation,
# NOT for driving a real workflow - state doesn't persist between calls)
qa-mcp --call project.scan --args '{"project_path": "."}'

Connecting an LLM

The debug CLI above (qa-mcp --call) spawns a fresh, stateless process per call - fine for checking one tool works, useless for a real agent loop where browser.open needs to still be talking to the same page when browser.click runs next. For that, an LLM needs to connect to qa-mcp-serve, which stays running as one long-lived process and keeps state (open browser page, pending patch proposals, defect tracker) alive across every tool call in the session.

Claude Desktop

  1. Find (or create) the config file:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: %APPDATA%\Claude\claude_desktop_config.json
    • Linux: ~/.config/Claude/claude_desktop_config.json
  2. Add an entry under mcpServers:

    {
      "mcpServers": {
        "qa-mcp": { "command": "qa-mcp-serve" }
      }
    }
    

    If qa-mcp-serve isn't on PATH where Claude Desktop launches from, use the full path instead (find it with which qa-mcp-serve after pip install -e .), or run it via "command": "python3", "args": ["-m", "qa_mcp.server"] with "cwd" set to this repo.

  3. Restart Claude Desktop. Open a new chat and check the tool/plug icon in the composer (or ask "what MCP tools do you have?") - you should see project.scan, test.generate, browser.open, etc. listed.

  4. Try it: "Use qa-mcp to scan this project and generate tests for its login form."

Claude Code

claude mcp add qa-mcp -- qa-mcp-serve

Or point it at the repo directly without installing the console script:

claude mcp add qa-mcp -- python3 -m qa_mcp.server

Verify with claude mcp list, then just ask Claude Code to use it in a session - e.g. "scan this repo with qa-mcp and generate a test suite for the checkout flow."

Any other MCP-compatible client

Cursor, Windsurf, and other MCP clients all use the same config shape shown above (command/args under an mcpServers key) - check that client's docs for where the config file lives; the qa-mcp-serve entry itself doesn't change.

A custom agent loop (no MCP client, or a non-MCP LLM)

Drive it directly over stdio with the official MCP Python SDK - this is what any MCP client is doing under the hood, so it also works if you're wiring qa_mcp into your own orchestration code instead of Claude Desktop/Code:

import asyncio
from mcp import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client

async def main():
    params = StdioServerParameters(command="qa-mcp-serve")
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            await session.call_tool("browser.open", {"url": "https://example.com"})
            await session.call_tool("browser.click", {"selector": "a"})   # same page, still open
            await session.call_tool("browser.screenshot", {"name": "proof"})
            await session.call_tool("browser.close")

asyncio.run(main())

For an LLM that doesn't speak MCP natively (e.g. calling the OpenAI API directly), call session.list_tools() once, convert each tool's JSON schema into that provider's function/tool-calling format, and route the model's function-call requests through session.call_tool(name, arguments) - the MCP session above is the only qa_mcp-specific part; everything else is standard function calling.


See it work

Want to see it work against something real before wiring it into your own project?

python3 sample-apps/checkout-demo/run_demo.py    # fields + business_rules
python3 sample-apps/task-manager/run_demo.py     # fields + roles + states (login, RBAC, a state machine)

Each one scans a small sample app with intentional bugs, generates a full deterministic test suite, runs a handful of real tests against it (real headless-Chromium browser test, real HTTP API calls, real SQLite check), and produces HTML and PDF reports with an executive risk summary. Takes under a minute each, no extra setup. See sample-apps/ for exactly what each one demonstrates.


The pipeline

Phase What it does
Understand project.scan detects language, framework, database, auth, existing tests, CI/CD
Design test.generate builds a full suite from field/business-rule/role/UX-state/state-machine facts the agent reports
Execute Drives Playwright, Selenium, Robot Framework, or Cypress for browser; REST + k6 for API; Appium or Maestro for mobile
Observe Screenshots, console logs, network logs, stdout/stderr captured per test
Diagnose failure.inspect classifies failures into 15 categories via weighted pattern matching, with a category-specific root cause and fix suggestion
Fix fix_loop proposes a patch — blocked from auto-applying without human approval
Verify Re-runs the target test and compares against the baseline run to confirm the fix and catch new regressions
Report Self-contained HTML/JSON report with an executive risk summary and a real root cause + suggested fix on every failed test — not a bare pass/fail table; defect tracking; CI/CD trigger (GitHub Actions, GitLab CI)

Test case generation: what the LLM does vs. what the engine does

test.generate never lets the LLM invent edge/security/business-logic/state cases — it reports facts, the engine expands them:

The agent reports The engine generates Method
fields Positive case, per-field required-negative, boundary (at-limit and one-past-limit), type-mismatch negative, security injection probes Boundary Value Analysis, Equivalence Partitioning, OWASP Top 10
business_rules A case proving the invariant holds, and a case that actively tries to break it Business Logic invariant testing
roles Access-granted / access-denied cases per role OWASP A01:2021 Broken Access Control
ux_states Loading / empty / error / offline / disabled / success / concurrent-submit cases with standard expected behavior Nielsen UX heuristics
states Every (state × action) combination — every declared transition, every invalid transition from every other state, and automatic unreachable-state detection Exhaustive state-machine coverage (graph reachability)
await session.call_tool("test.generate", {
    "feature": "checkout",
    "fields": [
        {"name": "coupon_code", "type": "text", "required": False, "max_length": 20},
        {"name": "amount", "type": "number", "required": True},
    ],
    "business_rules": [
        {"name": "no-negative-total", "rule": "order total must never go below 0",
         "violation": "Apply a coupon larger than the order subtotal"},
    ],
    "roles": [
        {"role": "customer", "should_access": True},
        {"role": "guest", "should_access": False},
    ],
    "ux_states": ["loading", "empty", "error", "concurrent"],
})

→ 25 test cases across 7 categories, each with a rationale, e.g.:

Business Logic: "an invariant that is only checked on the happy path isn't actually enforced — it must be proven to hold under an explicit attempt to break it" Access Control: "OWASP A01:2021: an endpoint that merely checks authentication (not authorization) silently permits privilege escalation"

If the agent calls test.generate with no dimensions and the feature isn't "login" or an API path, it fails fast with an example payload rather than silently guessing — a generated suite you didn't ask for is worse than an error telling you what to send.

State machine example — 5 declared transitions across 5 states × 4 actions (20-cell matrix) generates 20 cases: 5 positive (every declared transition) and 15 negative (every undeclared combination, proven blocked) — the full matrix, not a sample. An intentionally unreachable state gets flagged as a Regression case automatically.


Framework support

Not just browser — four categories of tools, each with its own *.* prefix. Within a category, the underlying engine is just a framework parameter; swap it without changing how you call the tool.

Category Tools Engine framework= Requires
Browser browser.open / .click / .fill / .screenshot / .assert / .close Playwright "playwright" (default) playwright install
Selenium "selenium" Chrome + ChromeDriver
Robot Framework "robot" Chrome (via SeleniumLibrary directly — no .robot files needed)
Cypress "cypress" Node.js + npx
API api.request (REST) httpx nothing extra
api.load_test (load testing) k6 k6 binary (or set QA_MCP_K6_BIN)

api.load_test takes a test_type: "load" (default — flat VUs at the expected volume), "stress" (ramps well beyond the requested VUs in stages to find the breaking point), "stability"/"soak" (a long, flat, sustained run to catch leaks/degradation), or "spike" (a short, sudden burst to 5× VUs then back down) — each is a real k6 execution pattern (staged options, not just a different flag), matching the StrongQA performance-testing taxonomy. Call knowledge.get_testing_type("stress") for the full definition of any of these before choosing one. | Mobile | mobile.launch / .tap / .swipe / .type_text / .assert_element / .close | Appium | "appium" (default) | running Appium server + a device/emulator reachable via adb | | | | Maestro | "maestro" | Maestro CLI + a device/emulator | | Database | db.get_table_state / .check_fk_integrity / .query | SQLAlchemy | — | any DB SQLAlchemy supports (Postgres, MySQL, SQLite, …) |

Notes:

  • Cypress has no interactive session across commands, so each new action replays every prior action in the session as one spec and reruns it — correct results, but it gets slower as a session grows. For long workflows, prefer Playwright/Selenium/Robot.
  • Appium and Maestro were both validated against a real physical Android device; see CHANGELOG.md for what that surfaced and how it was fixed.
  • Database tools allowlist table/column identifiers against SQL injection and are used to verify row state and foreign-key integrity after an E2E test — not a general query tool.

Reporting: a real Test Summary Report, not a CI dashboard

report.generate/report.generate_html/report.generate_pdf produce an actual Test Summary Report — the document shape a QA team files or sends, not a bare pass/fail table:

  1. Document control — project/system under test, report version, prepared by, status, test environment
  2. Introduction & scope — what was tested, how many cases, which categories
  3. Test summary — total/passed/failed/errors/skipped, pass rate, duration
  4. Defect summary — counts by severity, with a one-line risk headline
  5. Detailed test results — every test executed, not just the failures
  6. Defect details — every failure with a real category, root cause, and suggested fix, auto-diagnosed through the same classification engine failure_analysis uses (no separate failure.inspect call needed)
  7. Recommendation — a deterministic go/no-go call: RECOMMENDED FOR RELEASE / CONDITIONAL / NOT RECOMMENDED FOR RELEASE, driven by the worst severity present (database/auth/security failures are Critical and always block; api/logic/concurrency are High and also block; everything else is Medium/Low and release-conditional)
  8. Sign-off — a blank Name/Date row per reviewer you specify, ready to print and sign
run = await session.call_tool("test.create_run", {"suite_name": "checkout-suite"})
await session.call_tool("test.run", {"test_id": "t1", "command": ["pytest", "test_login.py"]})
await session.call_tool("test.run", {"test_id": "t2", "command": ["pytest", "test_checkout.py"]})
report = await session.call_tool("report.generate_html", {
    "run_id": run_id,
    "project_name": "Acme Checkout",
    "version": "2.4.1",
    "prepared_by": "QA Team",
    "test_environment": "Staging - Chrome 120, Ubuntu 22.04",
    "reviewers": ["QA Lead", "Engineering Manager"],
})
# -> {"report_id": "...", "path": "./reports/report-....html", "summary": {...}, "executive_summary": {...}}

All five metadata fields are optional — omit them and the report still generates with sensible defaults (project name falls back to the suite name, etc.).

report.generate_html writes a self-contained HTML file to ./reports/ — open it straight in a browser. report.generate_pdf renders that same report to a real PDF (via headless Chromium print-to-PDF, not a screenshot — proper page breaks, A4, print-safe colors) so you have something you can actually email, attach to a ticket, or file as a dated record without converting anything yourself.


Built for a team, not a demo

State survives a restart. Test runs, defects, failure evidence, and patch proposals persist to a JSON store (qa_mcp/core/persistence.py, atomic writes). Kill the qa-mcp-serve process, start a new one, and test.get_run / failure.get_evidence / report.generate still return everything.

Safe for concurrent writers. If your whole team points QA_MCP_STATE_DB at the same file to share defects/runs, writes are protected by a cross-process file lock, merged at the entry level (not a namespace-level replace), and IDs include a random suffix to avoid same-second collisions. Verified with 8 processes writing concurrently to the same file: 8/8 survive.

CI/CD is a real trigger, not a stub. ci.run/ci.get_status call the real GitHub Actions (workflow_dispatch) and GitLab CI (trigger/pipeline) REST APIs — set GITHUB_TOKEN or GITLAB_TRIGGER_TOKEN/GITLAB_API_TOKEN. No token, no repo, or a bad token all produce a real, actionable error instead of a fake success.

A patch can't apply itself. fix_loop requires an explicit fix_loop.approve before fix_loop.apply_patch will touch the filesystem, and even then read_only=False is refused unless a human has set QA_MCP_ALLOW_AUTO_APPLY=1 in the environment ahead of time — something the agent cannot do from inside its own tool calls. Don't leave that variable set permanently; set it only for the moment a human is actually approving, or gate it behind a real approval workflow (e.g. a CI job that only sets it after a reviewer approves a PR).


Running the test suite

pip install -e .
python -m pytest tests/ -v

108 tests, and wherever the environment allowed it, they exercise real infrastructure instead of mocks:

Test file Runs against
test_fix_loop_engine.py Approval-gate logic, auto-apply block
test_persistence.py Real disk I/O, process-restart simulation
test_test_design_generator.py BVA/security/state-matrix generation logic
test_failure_analyzer.py Pattern-matching classification, evidence persistence
test_executor.py Real subprocesses (pass/fail/timeout), real artifact files
test_defect_manager.py Real git repo (git init/status/log/commit), fail-fast CI checks
test_database_analyzer.py Real SQLite database, real orphaned-FK detection
test_reporter.py Root-cause diagnosis attached to failed tests, executive-summary risk rollup
test_report_pdf.py Real PDF generation via headless Chromium — verifies actual %PDF- file output
test_mcp_server.py The real MCP server (qa_mcp.server) an LLM actually connects to — a full stdio session over a real subprocess (initializelist_toolscall_tool), a check that every tool visible to the debug CLI is also reachable through the real server, and that saved rules / an instructions file actually reach the LLM's connection-time instructions
test_knowledge_base.py Company/project conventions and failure patterns persist correctly and survive a restart
test_api_adapter.py Fake HTTP transport (schema/assertion logic)
test_api_integration.py Real HTTP server + real k6 binary — actual sockets, actual load test
test_browser_adapter.py Real headless Chromium via Playwright — actual DOM, actual screenshots
test_mobile_adapter.py Device-independent option-building + retry logic (Appium/Maestro were also validated against a real physical Android device — see CHANGELOG.md)

Every bug this hardening pass found — a silent data-loss bug in the persistence layer, an approval-gate bypass, a blank project name, a misdetected language, a crash on the most natural test-generation workflow, and two real-device mobile issues — is documented with root cause and fix in CHANGELOG.md.


Project layout

Phase Module Purpose
1 project_intelligence Scan and detect project stack
2 test_design Deterministic test-case generation and coverage analysis
3 adapters Browser / API / mobile automation engines
4 execution Test run lifecycle and evidence capture
5 failure_analysis Failure classification and root-cause analysis
6 fix_loop Diagnose → propose → approve → apply → verify
7 defect_cicd Defect tracking, git operations, CI/CD triggers
8 core.reporter JSON/HTML reporting
9 analyzers.database_analyzer Post-test database state verification
10 knowledge Company/project conventions and failure patterns that persist across sessions — see Customizing for your company
11 knowledge.concepts Grounded QA reference definitions (testing theory, testing types) an agent can look up instead of guessing — see below

Run qa-mcp --list-tools for the full list of registered tools with their implementing function paths.


Dependencies

Python 3.10+. Core libraries: mcp, pydantic, httpx, playwright, selenium, robotframework, robotframework-seleniumlibrary, Appium-Python-Client, pytest, sqlalchemy — full list in requirements.txt.

Non-Python, install only what you'll use:

Tool Needed for
Node.js + npx Cypress
k6 api.load_test
Appium server + Android/iOS SDK mobile.* with framework="appium"
Maestro CLI mobile.* with framework="maestro"

Environment variables

See .env.example for the full list with explanations — copy it to .env and fill in what you need (qa-mcp-serve doesn't load .env automatically; export the values some other way, e.g. through your MCP client's server config).

Variable Purpose
QA_MCP_STATE_DB Persistence file path (default ./qa-mcp-state.json) — point your whole team at the same path to share state
QA_MCP_ALLOW_AUTO_APPLY Must be 1 for fix_loop.apply_patch to write to disk — see Built for a team, not a demo
QA_MCP_INSTRUCTIONS_FILE Path to a text/markdown file of your team's conventions, appended to what the LLM is told on connect — see Customizing for your company
QA_MCP_K6_BIN Path to the k6 binary if it's not on PATH
GITHUB_TOKEN For ci.run/ci.get_status with GitHub Actions (needs actions:write)
GITLAB_TRIGGER_TOKEN / GITLAB_API_TOKEN For ci.run (trigger) / ci.get_status (API) with GitLab CI

The persistence layer's cross-process file lock uses fcntl (POSIX only). On Windows, locking is silently skipped — writes still work, but aren't safe under concurrent writers from multiple processes.


Customizing for your company

An LLM connecting fresh to qa-mcp-serve doesn't know your staging URL, which browser framework your CI actually has installed, or that your team already decided not to do visual regression testing and why. Two ways to teach it that once instead of every session:

1. A conventions file (QA_MCP_INSTRUCTIONS_FILE) — for things you know up front and that don't change often. Write plain text/markdown, point the env var at it:

<!-- company-qa-conventions.md -->
- Always use Playwright, never Selenium - our CI images don't have ChromeDriver.
- The staging environment is https://staging.acme.internal.
- Any auth or payment failure is P0, regardless of what fix_loop suggests.
QA_MCP_INSTRUCTIONS_FILE=./company-qa-conventions.md qa-mcp-serve

This gets appended to what the LLM is told the moment it connects — no tool call needed.

2. The knowledge.* tools — for things that accumulate as you actually use the tool: a failure pattern specific to your codebase you keep manually re-diagnosing, or a QA decision worth remembering so the agent doesn't propose it again next quarter.

await session.call_tool("knowledge.add_rule", {
    "rule_name": "default_framework",
    "rule": "Always use Playwright, never Selenium - our CI images don't have ChromeDriver",
})
await session.call_tool("knowledge.add_failure_pattern", {
    "pattern": "connection pool exhausted",
    "fix": "This is almost always the payment webhook retry storm, not a real DB issue - check webhook retry config first",
    "confidence": 0.9,
})
await session.call_tool("knowledge.add_decision", {
    "context": "visual regression testing",
    "decision": "not doing it",
    "rationale": "design changes too frequently for it to be worth the maintenance cost",
})

Every knowledge.add_rule call persists through the same QA_MCP_STATE_DB store as everything else, and — unlike the failure patterns and decisions, which an agent looks up on demand via knowledge.get_similar_failures/knowledge.get_decisions — saved rules are folded automatically into the instructions every future qa-mcp-serve session sees, the same way the conventions file is. Add a rule once; every agent that connects after that already knows it.


QA reference concepts

Separate from your own conventions above, knowledge.get_concept/knowledge.list_concepts and knowledge.get_testing_type/knowledge.list_testing_types give an agent grounded definitions of general QA theory instead of letting it guess — content adapted from StrongQA's QA knowledge base:

  • Key concepts: Software Testing, Testing vs QA vs QC, Testing vs Debugging, Verification vs Validation, Testing Documentation (test plan/test case/test scenario/traceability matrix), Myths about QA
  • Testing types: Functional, Performance, Load, Stress, Stability — the same taxonomy api.load_test's test_type parameter implements as real k6 execution patterns (see Framework support)
await session.call_tool("knowledge.get_concept", {"topic": "verification_vs_validation"})
await session.call_tool("knowledge.get_testing_type", {"type_name": "stress"})

License

MIT

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured