Claude Code is where the TestRail MCP workflow feels most natural, mostly because it's already terminal-native and most QA automation lives in a terminal-native workflow anyway (pytest, Playwright, JUnit, CI logs). This topic walks through wiring TestRail MCP into Claude Code specifically — both the CLI and the VS Code extension, which share the same .mcp.json config but differ slightly in how you'll actually use the tool calls day to day.
The core idea: instead of alt-tabbing to TestRail's web UI to write a case, then alt-tabbing to your terminal to run tests, then alt-tabbing back to TestRail to log results, you keep the requirement doc, the test code, and the TestRail state all reachable from one Claude Code session.
Installing and Connecting TestRail MCP to Claude Code
Claude Code reads MCP server definitions from .mcp.json at the project root (shared with your team via git) or from user-level config for personal setups. For TestRail, project-level config is almost always the right call, since the whole point is a shared source of truth for case IDs across the team.
claude mcp add testrail --scope project \
-- npx -y @testrail/mcp-server
This scaffolds an entry in .mcp.json. Edit it to add the environment variables (never commit the API key itself — reference it from the shell):
{
"mcpServers": {
"testrail": {
"command": "npx",
"args": ["-y", "@testrail/mcp-server"],
"env": {
"TESTRAIL_URL": "https://yourcompany.testrail.io",
"TESTRAIL_USERNAME": "qa-bot@yourcompany.com",
"TESTRAIL_API_KEY": "${TESTRAIL_API_KEY}",
"TESTRAIL_PROJECT_ID": "14"
}
}
}
}
Export TESTRAIL_API_KEY in your shell profile or a local .env sourced before launching Claude Code — .mcp.json supports ${VAR} expansion from the calling shell's environment. Verify the connection with:
claude mcp list
If it shows "Failed to connect," the most common causes in order of frequency are: wrong TESTRAIL_URL (trailing slash, http vs https), an API key generated for a user who no longer has project access, or the org blocking outbound npx package fetches on a locked-down corporate network — in that last case, install the package globally first (npm install -g @testrail/mcp-server) and point command at the resolved binary path instead of npx.
For the VS Code extension (Claude Code's VS Code integration), the same .mcp.json is picked up automatically when you open the project folder — there's no separate config step, which is one less thing to keep in sync. The one VS Code-specific detail worth knowing: the extension surfaces MCP tool calls inline in the chat panel with collapsible JSON, which makes it much easier to eyeball a proposed add_case payload before approving it than scrolling raw terminal output.
Tips
- Runclaude mcp listafter any.mcp.jsonedit — a malformed JSON silently drops the server with no error banner in some Claude Code versions.
- If your org uses an npm private registry proxy,npx -ycan hang waiting on registry resolution; pin the exact version (@testrail/mcp-server@1.4.2) to skip a "check latest" round trip.
- Use/mcpinside a Claude Code session to see live tool availability and permission state for thetestrailserver without leaving the conversation.
Generating Test Cases from Requirements and Acceptance Criteria
The highest-value habit here is feeding Claude Code the actual requirement artifact — a Jira ticket export, a PR description, a markdown spec — rather than describing the feature from memory. Precision in equals precision out.
Read specs/checkout-retry-flow.md. Generate TestRail cases for suite 12,
section "Checkout - Payment Failures". Cover: declined card, expired card,
insufficient funds, and network timeout during payment authorization.
Each case needs steps_separated format, priority based on payment-blocking
severity, and a refs field pointing to JIRA-4521. Show me the payloads
before creating anything.
That last sentence — "show me the payloads before creating anything" — is worth making a standing habit, not a one-off ask. Claude Code will draft the add_case JSON, and you can catch template mismatches (wrong template_id for your project's configured case format) before they hit the API. A real draft from this prompt looked like this:
{
"title": "Checkout declines card, network timeout during authorization",
"template_id": 1,
"type_id": 6,
"priority_id": 4,
"custom_steps_separated": [
{
"content": "Submit valid card details, simulate network timeout mid-authorization (use test env toggle QA_SIMULATE_TIMEOUT=payment)",
"expected": "User sees 'We couldn't confirm your payment, please retry' — no duplicate charge is created"
},
{
"content": "Retry the payment after timeout message appears",
"expected": "Second attempt processes normally; order total is charged exactly once"
}
],
"refs": "JIRA-4521"
}
Notice the model correctly flagged priority 4 (highest in this instance's scale) because payment double-charging is a severe class of bug — but it also invented a specific env toggle name (QA_SIMULATE_TIMEOUT) that doesn't necessarily exist in your test environment. Always sanity-check any concrete implementation detail an agent asserts about your own systems; it will write plausible specifics with full confidence whether or not they're real.
For bulk generation across multiple acceptance criteria, batch by section rather than asking for everything in one shot — asking Claude Code to generate 40 cases across 6 feature areas in a single turn produces noticeably shallower step detail per case than four focused batches of 8–10 cases each.
Tips
- Always request the raw payload preview before callingadd_case— reviewing JSON is faster than reviewing rendered TestRail cases after the fact, and catches template/field mismatches earlier.
- Batch case generation by feature section (8-12 cases per batch) rather than one giant request — depth per case degrades noticeably past that range.
- Explicitly tell it which fields are mandatory in your instance (custom fields likecustom_automation_statusare common) — Claude Code won't know your custom schema unless you pasteget_case_fieldsoutput or describe it.
Creating Test Runs and Pushing Automation Results from the Terminal
This is where Claude Code's terminal-native nature pays off directly — it can run your test suite, parse the output, and post results in the same session, without a separate CI round trip for local verification runs.
Run `npx playwright test tests/checkout/ --reporter=json > /tmp/results.json`.
Then create a TestRail run in project 14, suite 12, named
"Local Verification - checkout retry - $(date +%Y-%m-%d)" covering the
case IDs referenced via "TestRail: C\d+" comments in tests/checkout/.
Parse /tmp/results.json and post a result for each case: passed tests
get status_id 1, failed get status_id 5 with the failure message as
the comment, elapsed time from the JSON report.
Claude Code will chain: run the shell command, grep the spec files for TestRail: C\d+ annotations, call add_run with the resolved case IDs, then loop add_result_for_case for each. A realistic result payload it produces:
POST /index.php?/api/v2/add_result_for_case/187/1050
{
"status_id": 5,
"comment": "FAILED: expect(page.locator('.retry-banner')).toBeVisible() — timed out after 5000ms. Element not found; payment error page rendered generic 500 instead of retry UI.",
"elapsed": "5s",
"version": "local-dev"
}
That level of comment detail — actual assertion text, not "test failed" — is what makes TestRail results useful to someone triaging later without re-running the test. Push the agent toward including the raw assertion failure, not a summary of it.
For CI-driven posting (the more common production setup), wire the same pattern into a GitHub Actions step using claude -p in non-interactive mode:
- name: Sync results to TestRail
run: |
claude -p "Parse ./playwright-report/results.json and post results \
to TestRail run ${{ steps.create_run.outputs.run_id }} using \
TestRail: C\d+ annotations in tests/. Non-interactive, no confirmations." \
--allowedTools "mcp__testrail__add_result_for_case,Read,Grep"
env:
TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }}
Scoping --allowedTools to exactly the tools this step needs matters in CI — you don't want a non-interactive pipeline run accidentally invoking add_case or close_run because a prompt was ambiguous.
Tips
- Always pass raw test-framework JSON/JUnit output to the agent rather than a human-summarized description of results — summaries lose the assertion detail that makes TestRail comments useful.
- In CI, scope--allowedToolsexplicitly to the read and result-posting tools you need; don't grantadd_caseorclose_runto an automated pipeline step.
- Name runs with a timestamp or build number ($(date +%Y-%m-%d)or${{ github.run_id }}) so repeated local verification runs don't collide or get confused with the canonical release run.
Prompting Patterns for Consistent Case Structure and Naming
Left unguided, Claude Code will happily vary case title format, step granularity, and priority judgment call by call — perfectly serviceable individually, inconsistent as a set. A CLAUDE.md snippet at the project root fixes this once instead of re-explaining it every session:
## TestRail Case Conventions
- Title format: "{Feature} {condition} {expected behavior}" — no question marks,
no "should" phrasing. Good: "Checkout rejects card with invalid CVV".
Bad: "Should checkout reject invalid CVV?"
- Always use custom_steps_separated (template_id: 1), never legacy single-field steps.
- Priority: 4=P1 payment/auth/data-loss, 3=P2 core flow breakage,
2=P3 UX/cosmetic with workaround, 1=P4 edge case, low traffic path.
- Every case must set refs to the originating Jira ticket key.
- Section placement: match the folder structure in tests/e2e/, not the
feature's marketing name.
With this in place, a terse prompt like "add 3 cases for the new address-autocomplete field, ref JIRA-4890" produces consistent output without re-specifying format every time. This is the single biggest lever for reducing review overhead — QA leads reviewing agent-drafted cases stop catching format nits and start catching actual coverage gaps.
One pattern worth adopting explicitly: ask the agent to flag its own uncertainty rather than silently guessing.
Draft cases for the new address-autocomplete field per JIRA-4890.
If you're unsure about priority or which section it belongs in,
mark it "NEEDS REVIEW: <reason>" in the case title prefix rather
than guessing.
This surfaces exactly the cases that need a human's actual judgment call, instead of burying an uncertain P2/P3 call inside a batch of confident-looking, indistinguishable JSON.
Tips
- Keep case-naming and priority conventions inCLAUDE.md, not in ad hoc prompt text — it's read automatically every session and survives across contributors.
- Have the agent explicitly flag low-confidence judgment calls (priority, section placement) instead of silently guessing — it costs one prefix string and saves a review pass.
- Periodically audit a sample of agent-authored case titles against the convention doc — conventions drift as the doc gets stale relative to how the project actually evolved.
Tips
Tips
- Treat.mcp.jsonas shared team infrastructure — commit it (minus secrets) so every contributor gets the same TestRail tool surface without individual setup.
- Use the VS Code extension's inline JSON preview to review payloads visually when working through a large batch of case generation; it's faster than scanning raw terminal output.
- Standardize case-naming and CI result-posting conventions inCLAUDE.mdearly — retrofitting consistency onto 500 already-created cases is far more painful than defining the convention before case one.