OpenCode's appeal for TestRail work is its model flexibility — you can run case-authoring sessions against Claude, GPT, or a local model through the same MCP config, which matters if your org has data-residency constraints around sending requirement docs (some of which contain customer PII in examples) to a specific vendor's API. This topic covers wiring TestRail MCP into OpenCode, the suite/case/run workflow as it actually plays out in OpenCode's TUI, a worked example converting a user story into a full suite, and the rough edges specific to OpenCode's MCP implementation as of the current release cycle.
Installing and Connecting TestRail MCP to OpenCode
OpenCode reads MCP server config from opencode.json (project-level, preferred for team sharing) or ~/.config/opencode/opencode.json for personal global config. The structure differs slightly from Claude Code's .mcp.json — OpenCode nests MCP definitions under an mcp key with an explicit type field:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"testrail": {
"type": "local",
"command": ["npx", "-y", "@testrail/mcp-server"],
"environment": {
"TESTRAIL_URL": "https://yourcompany.testrail.io",
"TESTRAIL_USERNAME": "qa-bot@yourcompany.com",
"TESTRAIL_API_KEY": "{env:TESTRAIL_API_KEY}",
"TESTRAIL_PROJECT_ID": "14"
},
"enabled": true
}
}
}
Note {env:TESTRAIL_API_KEY} — OpenCode's variable substitution syntax is {env:VAR_NAME}, not the ${VAR} shell-style syntax Claude Code uses. Mixing these up (pasting a Claude Code config verbatim) is the single most common setup error when teams run both tools side by side; the server will start but every TestRail call will 401 because it's literally sending the string ${TESTRAIL_API_KEY} as the key.
Start OpenCode and check connection status:
opencode
/mcp
The /mcp command lists configured servers and their connection state. A healthy connection shows testrail — connected, 14 tools available. If it shows 0 tools available while still "connected," that's usually a project ID scoping issue — the service account's API key resolves, but the configured TESTRAIL_PROJECT_ID returns a 403 on tool discovery, and OpenCode's MCP client in some versions swallows that as an empty tool list rather than a visible error.
Tips
- Double-check{env:VAR}vs${VAR}syntax when copying MCP config between OpenCode and Claude Code — they look similar enough to miss on a skim.
- Run/mcpafter every config change; OpenCode caches server state per session and a stale TUI won't reflect a fixed config until restarted.
- If tool discovery returns zero tools despite a "connected" status, verify the service account's project-level permissions directly in TestRail's Administration panel before assuming the MCP server is broken.
Managing Suites, Cases, and Runs from OpenCode
OpenCode's TUI handles multi-step tool chains reasonably well, but it surfaces tool-call approvals differently than Claude Code — by default it batches consecutive calls to the same tool into one approval prompt, which is convenient for bulk case creation but means you should review the full batch before approving, not just the first item.
A typical suite-setup session:
List suites in project 14. If no suite exists for "Notifications Service",
create one. Then add sections: "Push Notifications", "Email Digests",
"In-App Banners" under it.
POST /index.php?/api/v2/add_suite/14
{
"name": "Notifications Service",
"description": "Test suite for the notifications microservice, covers push, email digest, and in-app banner delivery paths."
}
Followed by three add_section calls, each nested correctly under the new suite_id. OpenCode handles this dependency chain fine — it waits for the suite_id in the response before issuing section calls, rather than guessing an ID.
For run management, OpenCode is equally capable but slightly more verbose in its intermediate reasoning output, which is worth knowing before you decide this is "slower" than another tool — it's often doing the same number of API calls, just narrating more.
Create a run in suite 12 covering every case tagged priority P1 or P2
that hasn't been executed in the last 14 days. Name it
"Weekly Regression - $(date +%W)".
This requires the agent to combine get_cases (filtered) with get_results_for_run history checks across recent runs to determine "not executed in 14 days" — a genuinely multi-step query. In practice, OpenCode does this correctly but slowly on suites with 500+ cases, because it's making several sequential API calls rather than one filtered query (TestRail's API doesn't support a native "last executed" filter on cases directly, so any MCP client has to reconstruct this from run history).
Tips
- Review batched tool-call approvals fully before accepting — OpenCode groups consecutive same-tool calls into one prompt, and it's easy to rubber-stamp a batch without checking every payload.
- For "not executed recently" style queries, expect several seconds of latency on large suites — this is a TestRail API limitation (no native last-executed filter), not an OpenCode bug.
- Use explicit suite/section names in prompts rather than IDs when working interactively — OpenCode resolves names to IDs viaget_suites/get_sectionsreliably, and it keeps your prompt readable in session history.
Practical Example: Converting a User Story into a Full Test Suite
Here's a complete worked example — a real user story turned into a suite structure, cases, and an initial run, run inside a single OpenCode session.
Input (pasted from Jira, JIRA-5102):
As a subscriber, I want to pause my subscription for up to 3 months
so that I don't lose my account history during a temporary break.
Acceptance criteria:
- Pause option available from account settings, only for active subscriptions
- Max pause duration is 3 months; UI blocks longer selections
- Billing stops during pause; resumes automatically at pause end date
- User can manually resume early
- Paused accounts retain full history/data
- Push notification sent 3 days before auto-resume
Prompt:
Create a new suite "Subscription Pause" in project 14. Generate cases
covering every acceptance criterion above, plus these edge cases I want
you to add: pausing an already-paused subscription, pausing with a
past-due invoice, and canceling during an active pause. Use
custom_steps_separated. Tag refs with JIRA-5102. Priority: billing/data-loss
paths are P1, UI validation is P2, notification timing is P3.
Show me the full case list before creating.
Output (abbreviated — OpenCode drafted 9 cases; three shown):
[
{
"title": "Subscription pause blocks selection beyond 3 months",
"priority_id": 2,
"custom_steps_separated": [
{"content": "Navigate to Account Settings > Pause Subscription, attempt to select a 4-month pause duration", "expected": "UI disables selections beyond 3 months; max selectable option is capped at 3"}
],
"refs": "JIRA-5102"
},
{
"title": "Billing stops immediately when pause begins, resumes automatically at pause end date",
"priority_id": 4,
"custom_steps_separated": [
{"content": "Pause an active subscription with a billing date in 5 days, advance test clock past that date", "expected": "No charge occurs during pause window"},
{"content": "Advance test clock to the pause end date", "expected": "Billing resumes automatically; charge occurs on the correct prorated date"}
],
"refs": "JIRA-5102"
},
{
"title": "Attempting to pause an already-paused subscription is rejected",
"priority_id": 4,
"custom_steps_separated": [
{"content": "With subscription already in paused state, attempt to trigger pause flow again via API and via UI", "expected": "Request is rejected with clear messaging; no duplicate pause record is created, existing pause end date is unchanged"}
],
"refs": "JIRA-5102"
}
]
After review and one correction (I bumped the billing-resume case to include an explicit assertion on proration, which the first draft's expected result had left vague), the batch was created via add_case calls and a run was scoped:
Create a run "Subscription Pause - Initial QA Pass" in suite [new suite id],
include_all: true.
Total session time from pasting the story to a ready-to-execute run: about six minutes, most of it spent on the one review-and-correct step. That correction is the point — the agent got 8 of 9 cases right on the first pass, and the ninth needed a human's judgment about what "resumes automatically" actually needs to assert.
Tips
- Explicitly list edge cases you want covered in the prompt rather than trusting the agent to infer all of them from acceptance criteria alone — it catches the obvious ones (validation, happy path) more reliably than adversarial ones (double-pause, past-due interactions).
- Review generated cases for vague expected-result language ("works correctly," "resumes as expected") — these read fine but don't give a tester or an automated check anything concrete to assert against.
- Useinclude_all: trueonadd_runonly for genuinely new suites; for established suites, scope explicitcase_idsso unrelated legacy cases don't inflate the run.
Known Limitations for TestRail MCP in OpenCode
Three limitations are worth knowing before you rely on this stack for anything time-sensitive.
No native streaming progress on long tool chains. When OpenCode runs a chain of 10+ sequential add_case calls, the TUI shows the tool name but not per-call progress within a batch — you wait for the full batch to complete rather than seeing case 4 of 10 land. On a slow TestRail instance (self-hosted, under load) this can look like a hang when it's actually working through the queue.
Rate-limit handling is not automatic. TestRail Cloud enforces API rate limits (180 requests/minute on standard plans as of TestRail's current published limits), and OpenCode's MCP client doesn't currently implement backoff-and-retry on 429 responses — it surfaces the error and stops the chain. For bulk operations (50+ cases in one session), expect to occasionally re-run the tail end of a batch manually.
Multi-suite project resolution is more manual than in Claude Code. OpenCode doesn't proactively call get_suites before an add_case request unless the prompt is unambiguous about which suite — it's more likely to ask you to disambiguate than to guess, which is safer but slower for rapid-fire case creation across multiple suites in one session.
None of these are dealbreakers, but they shape how you'd structure a session: smaller batches, explicit suite references, and patience on self-hosted instances under load.
Tips
- Cap bulkadd_casebatches at roughly 20 per session on TestRail Cloud to stay comfortably under the per-minute rate limit, especially if other CI jobs are also hitting the API concurrently.
- If a batch chain stops on a429, ask the agent to resume from the last confirmed case ID rather than restarting the whole batch — it canget_casesto check what already landed.
- In multi-suite projects, always name the suite explicitly in your prompt — letting OpenCode ask for clarification is fine, but it costs a round trip you can skip by being specific upfront.
Tips
Tips
- Watch for{env:VAR}vs${VAR}syntax mismatches when reusing MCP config across OpenCode and other agents — it's the most common silent auth failure in mixed-tool teams.
- Keep bulk case-creation batches modest (15-20 cases) to stay under TestRail Cloud's rate limits and to keep each batch reviewable in one pass.
- Use explicit suite and section names in prompts rather than relying on inference — OpenCode resolves names reliably but won't proactively guess ambiguous suite targeting.