qa-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@qa-mcp run the full QA suite and diff against last run"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
qa-mcp
Automated QA for a .NET microservice backend (several independent services, each with its own Swagger/OpenAPI document) and a React frontend, running against stage, exposed to an MCP client over stdio.
The one architectural rule
MCP is a thin layer on top of the test engine. The engine does not depend on the model.
Tests are never generated by an LLM at call time. They are either derived deterministically from
OpenAPI, or written by hand as YAML flows / Playwright specs. That is what makes two consecutive
runs comparable — and comparability is the entire value of the tool. If every run produced a
different set of assertions, qa_diff could not tell a regression from noise.
Consequence: everything under src/api, src/flows, src/ui and src/report runs perfectly well
from CI without an MCP client. src/index.ts only registers tools and shrinks the output.
Related MCP server: Argus
Install
npm install
npm run buildRequires Node ≥ 20. npm install also pulls Playwright; download the browser once with:
npx playwright install chromiumConfigure
qa.config.json is the single source of truth. Its path comes from QA_MCP_CONFIG, defaulting to
./qa.config.json. Start from the example:
cp qa.config.example.json qa.config.jsonSection | What it does |
| Free-form label stored in every run report. |
|
|
|
|
|
|
|
|
| Real values used to fill required parameters, keyed by parameter name. |
| Where runs, HTML reports and screenshots are written. Default |
Secrets never live in the config
Any string starting with env: is read from the environment; if the variable is unset, startup
fails with an explicit message naming the field and the variable.
"password": "env:QA_ADMIN_PASSWORD"export QA_ADMIN_PASSWORD=...
export QA_CUSTOMER_PASSWORD=...
export QA_CLIENT_SECRET=...Register the server in an MCP client
command + args, transport stdio:
{
"mcpServers": {
"qa-mcp": {
"command": "node",
"args": ["G:/mcp/qatester/dist/index.js"],
"env": {
"QA_MCP_CONFIG": "G:/mcp/qatester/qa.config.json",
"QA_ADMIN_PASSWORD": "...",
"QA_CUSTOMER_PASSWORD": "...",
"QA_CLIENT_SECRET": "..."
}
}
}
}Claude Code CLI equivalent:
claude mcp add qa-mcp -- node G:/mcp/qatester/dist/index.jsVerify the handshake at any time without touching stage:
npm run smokeTools
Tool | Input | What it does |
|
| Fetches every Swagger document, reports endpoints per service, planned assertions, operations skipped by policy with the reason, and services that could not be reached. Sends no request to the endpoints. |
|
| Generates and runs the API matrix. Mutating endpoints are skipped while |
|
| Runs |
|
| Playwright specs and/or the automatic crawl. |
|
| Full pass, then an automatic diff against the previous stored run. Release gate. |
|
| Reads a stored run with filters. Use it when a digest says results were truncated. |
|
| Compares two runs by stable test id. |
Every run tool returns a digest, not the raw results: the summary, failures grouped by service,
at most 40 failures sorted critical-first, how many were truncated, and the runId to drill into
with qa_report. A full pass over several services produces thousands of assertions; returning all
of them would bury the signal.
What gets tested
Generated API tests
Per operation, from the OpenAPI document:
Check | Assertion | Severity |
| Valid request → status must be declared in the spec and the body must validate against that status' schema | major |
| Secured operation called with no token → anything other than 401/403 is a leak | critical |
| Malformed value in a required typed parameter → expect 400/404/422 | major |
| Response time over | minor |
Any 5xx is an unconditional critical failure, including when the input was deliberately broken. A well-behaved service answers 400, not 500. On a .NET backend this single rule is the highest-yield automated check there is — it drags unhandled exceptions straight out of the framework.
Parameter values come from config.samples (by parameter name) first, then enum[0], default,
example, then a format/type heuristic (uuid → zero-uuid, date → today, integer → 1, …).
Malformed values match the type: uuid → not-a-uuid, numeric → not-a-number, date →
31-31-9999.
Each test carries a stable id — service:METHOD:/path#check — with no timestamp and no random
value in it. qa_diff depends entirely on that.
One valid request produces both the contract and perf results, so an operation costs at most
three HTTP calls (valid, unauthenticated, malformed).
Safety model — read this before pointing it at anything
Running generated tests against stage can destroy data or fire notifications at real users.
With
policy.readOnly: true(the default) onlyGET/HEAD/OPTIONSare executed. Every mutating verb is refused unless its path starts with one ofpolicy.allowedMutationPaths.policy.excludePathsis honoured always, including whenreadOnlyis off.Every refused operation is reported with its reason by
qa_discover, so you always know what is not covered.Destructive operations are only allowed inside YAML flows, where a human wrote the steps.
Cross-service flows
tests/flows/*.yaml. Real microservice bugs live here, not in single-service tests: each service
passes its own tests while the order id never reaches the notification consumer.
name: order-lifecycle
description: why this scenario matters
role: customer
severity: critical
steps:
- name: create order
service: orders # must match a service name in qa.config.json
method: POST
path: /api/orders
role: customer # optional, overrides the flow role
body: { productId: "{{productId}}" }
expectStatus: [200, 201]
expectBody: { status: Pending } # dot-path -> expected value
capture: { orderId: id } # variable name -> dot-path in the response
waitMs: 0 # pause before the step, for eventual consistency{{var}} interpolation works in path, body, headers and expectBody. Dot-paths understand
array indexes (items[0].status). Flows run sequentially — they mutate shared state. One flow
produces exactly one result: the business scenario either completes or it does not. On failure the
evidence holds the failing step name, the trace of the steps that did succeed, and every captured
variable. waitMs exists for the case where the target service only learns about the change through
a message broker.
Frontend — two layers
Fixed specs (tests/ui/, standalone playwright.config.ts) are run as a child process with
--reporter=json and normalised into results. QA_BASE_URL, QA_LOGIN_PATH and QA_LOGIN
(credentials for the requested role) are passed in as environment variables, so the project also
runs directly in CI. If playwright.config.ts is missing you get a skip with a clear message, not
a crash. Put @critical or @minor in a test title to set its severity.
The three shipped specs assert business outcomes rather than "did the page load": a successful
login, a list that must contain rows (an empty table where data is expected is the classic silent
data-layer failure), and one that fakes a 500 with page.route to prove the UI shows the user an
error instead of hanging on a spinner.
Automatic crawl covers the pages nobody wrote a spec for. It optionally logs in with a role; if
that login fails it returns one critical result and stops immediately, because every later result
would be meaningless without a session. It then BFS-crawls same-origin links up to maxPages,
skipping crawl.ignore prefixes. A route fails on navigation error, HTTP ≥ 400, console errors, an
XHR ≥ 400, or an effectively empty render. Console noise (React DevTools, HMR, React Router future
flags, unused-preload warnings, favicon 404s) is filtered out — without that the output is unusable.
Every failure gets a full-page screenshot whose path lands in the evidence.
Runs, reports and diff
Each run is written to artifactsDir as run-<timestamp>.json plus a readable .html next to it,
with a severity-coloured failure table. Colons and dots in the timestamp are replaced with -
(illegal in Windows filenames); the ids stay lexicographically sortable.
qa_diff compares two runs by stable id and splits the delta:
Bucket | Meaning |
| Passed before, fails now. The only bucket that should block a release. |
| Failed before, passes now. |
| Test did not exist in the baseline and fails now (new endpoint, new page). |
| Already broken before. |
| Was in the baseline, gone now (endpoint or page deleted). |
When no baselineRunId is given, the baseline is the most recent earlier run covering the same
suites — comparing an API-only run against a UI-only run would mark every test as removed and mean
nothing.
Expect the first run to be noisy
This is normal and it is not a bug. The first pass against a real stage environment will report plenty of failures that are configuration, not defects: endpoints needing an id you did not provide, admin-only paths, export jobs, health probes.
Work through it in this order:
Run
qa_discoverand read what was skipped by policy — that is your coverage gap.Run
qa_run_api, then push obvious non-defects intopolicy.excludePaths.Fill
sampleswith real stage ids (orderId,customerId,productId, …) so path and required-query parameters resolve to rows that actually exist. Most 404 noise disappears here.Raise
maxResponseMsif stage is simply slower than production.Re-run until what remains is genuinely interesting.
Keep that run as the baseline. From then on
qa_diff/qa_run_allanswer the only question that matters before a release: did anything that used to work stop working?
Layout
src/
config.ts zod config schema + env: resolution
types.ts TestResult / RunReport / summarize()
discovery/openapi.ts fetch + normalise specs, resolve $ref
auth/session.ts per-role token with cache
api/generator.ts test matrix per operation + safety gate
api/runner.ts bounded-concurrency execution + schema validation
flows/runner.ts cross-service YAML scenarios
ui/crawler.ts automatic Playwright crawl
ui/specs.ts runs Playwright specs, normalises the JSON report
report/store.ts store, diff, render HTML
index.ts MCP tool registration
tests/
flows/*.yaml
ui/ playwright.config.ts + *.spec.tsNotes for whoever extends this
stdio transport: never write to stdout.
console.logcorrupts JSON-RPC and the client disconnects with an opaque error. Every log goes toprocess.stderr(seelog()insrc/index.ts).npm run smokefails if anything non-protocol reaches stdout.ajv/ajv-formatsare CommonJS. UnderNodeNextthe constructable sits on.default, andimport type Ajv from "ajv"is unusable as a type —src/api/runner.tsshows the working pattern.Relative ESM imports need the
.jsextension even from.tssources.Every tool handler is wrapped in
try/catchand returnsisError: truerather than throwing.Every
fetchcarriesAbortSignal.timeout(policy.requestTimeoutMs).Uncompilable response schemas are treated as "no schema", not as an endpoint failure — the defect is in the spec, not in the service.
$refresolution has both a depth cap and a per-branch seen-set; self-referencing DTOs are common in .NET and would otherwise hang the process.
Available Tools
7 toolsqa_diffCompare two runsA
Compares two stored runs by stable test id and splits the delta into regressions (passed before, fails now — the release blocker), fixed, newFailures (test did not exist in the baseline), stillFailing and removed (endpoint or page gone). Defaults to comparing the latest run against the most recent earlier run that covered the same suites.
Inputs: currentRunId, baselineRunId.
qa_discoverDiscover API surfaceA
Fetches the OpenAPI/Swagger document of every configured service and reports the testable surface: endpoint count per service, how many assertions would be executed, which operations are skipped by the safety policy (and why), and which services could not be reached. Runs no test and sends no request to the endpoints themselves. Use this first to check coverage before running anything.
Inputs: services.
qa_reportRead a stored runA
Reads the full detail of a stored run with filters, for drilling into what a run digest truncated. Filter by status, severity, suite or service. Omit runId to read the most recent run.
Inputs: limit, runId, suite, status, +2 more.
qa_run_allFull QA pass with regression diffA
One complete pass: discovery, generated API tests, YAML flows, Playwright specs and the frontend crawl — then an automatic comparison against the previous stored run. Returns the digest plus the diff buckets, of which regressions (passed before, fails now) is the one that should block a release. Mutating endpoints are still governed by policy.readOnly. Use this as the release gate.
Inputs: role, skipUi, maxPages, services.
qa_run_apiRun generated API testsA
Generates deterministic tests from OpenAPI and executes them against stage: contract (status declared in the spec + response body matches its schema), authz (secured endpoint called without a token must answer 401/403), robustness (malformed required parameter must answer 400/404/422), and perf (policy.maxResponseMs). Any 5xx is an unconditional critical failure. IMPORTANT: while policy.readOnly is true, every mutating endpoint (POST/PUT/PATCH/DELETE) is skipped unless its path is listed in policy.allowedMutationPaths — call qa_discover to see exactly what was skipped. Returns a digest; the full run is stored under artifactsDir.
Inputs: role, services, pathContains.
qa_run_flowsRun cross-service YAML scenariosA
Executes the hand-written business scenarios in tests/flows/*.yaml sequentially (they share state, so never in parallel). Each flow chains calls across services with {{variable}} interpolation, capture, expectStatus/expectBody and waitMs for eventual consistency. This is the only place where destructive operations are allowed, because a human wrote the steps. One flow = one result: on failure you get the failing step name, the trace of successful steps and every captured variable.
Inputs: names, flowsDir.
qa_run_uiRun frontend testsA
Two layers against the React frontend. mode=specs runs the fixed Playwright specs in tests/ui (skipped with a clear message if playwright.config.ts is missing). mode=crawl logs in with a role and BFS-crawls same-origin links, failing a route on navigation error, HTTP >= 400, console errors, failing XHRs or an effectively empty render, with a full-page screenshot for every failure. If the login itself fails the crawl stops immediately with one critical result. mode=both runs the two in order.
Inputs: mode (required), grep, role, maxPages, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 7 tools
Each tool targets a distinct task: discovery, API tests, UI tests, flow tests, combined run, report reading, and diffing. There is no overlap in their purposes, and descriptions clearly delineate their roles.
All tools use a consistent qa_ prefix followed by a clear verb (discover, run_flows, run_api, run_ui, run_all, report, diff). The pattern is uniform and predictable, making it easy to infer tool functions from names.
Seven tools is well within the ideal range for a QA-focused server. Each tool covers a necessary step in the workflow without redundancy or bloat, making the set tightly scoped.
The tool surface covers the full QA lifecycle: discovering testable surface (qa_discover), executing different test types (flows, API, UI), running all together (qa_run_all), and then analyzing results via qa_report and qa_diff. There are no obvious dead ends or missing operations for the server's purpose.
Maintenance
Related MCP Connectors
QA after every deploy: walks your live web app as a real user and tells your agent what broke.
Browser-based QA for AI-built software. Test pages with real browsers via agents.
AI QA that runs your app in a browser on every pull request: projects, test targets, test cases.
AI QA tester — real browsers scan sites for bugs, SEO, perf, and accessibility issues via chat.
Related MCP Servers
AlicenseAqualityBmaintenanceZero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.8882 npm69Apache 2.0- AlicenseBqualityBmaintenanceTesting MCP for coding agents: exercise real user journeys, independently recheck suspected bugs, and export verified findings as Playwright regression tests. Includes browser interaction, goal coverage, evidence-linked reports, and optional local monitoring. The host agent supplies the model and judgment; Argus needs no separate provider key.29267 PyPI5MIT
- AlicenseNot gradedqualityDmaintenanceEnables automated QA testing by running a pipeline of AI agents that generate test scenarios, architect test layers, write Playwright tests, and review code, all grounded in feature requirements and API contracts.ISC
- AlicenseCqualityCmaintenanceAutomates generation of QA artifacts such as API tests, E2E tests, and documentation exports. It supports REST Assured, Cypress, and Excel/Word document generation.56 npmMIT