Zephyr Scale
БесплатноНе проверенMCP server for managing test cases, cycles, and results in Zephyr Scale on self-hosted Jira. Enables AI agents to create, search, update, and delete test artifa
Описание
MCP server for managing test cases, cycles, and results in Zephyr Scale on self-hosted Jira. Enables AI agents to create, search, update, and delete test artifacts through natural language.
README
MCP server for Zephyr Scale on self-hosted Jira Server / Data Center (formerly TM4J). It gives an AI agent — Claude Code, Claude Desktop, Cursor, any MCP client — 54 tools over your test management: test cases, folders, test cycles, executions, test plans, attachments and automation imports, through the Zephyr Scale REST API v1 at {JIRA_BASE_URL}/rest/atm/1.0.
npm: zephyr-scale-mcp · MCP Registry: io.github.vilaabo/zephyr-scale-mcp · Русская версия →
⚠️ Scope: Server / Data Center only. Zephyr Scale Cloud (API v2) and Zephyr Squad are different APIs and are out of scope — this server will not work against them.
Why this exists
Most Zephyr MCP servers target the Cloud API. If your Jira is on-premise you get the v1 API instead, and v1 has teeth: test runs are immutable after creation, folders cannot be listed, statuses are case-sensitive internal names, BDD scripts reject a Feature: header, and older plugin builds are missing whole endpoints. This server encodes that knowledge instead of leaving the agent to discover it one 400 at a time.
- 54 tools (42 public + 12 opt-in internal-API tools), each with a description that states the endpoint, the constraints a caller cannot guess, and the exact response shape.
- Composite tools for the API's blind spots —
add_test_stepsmerges steps by id (read → merge → write) so nothing is silently deleted;recreate_test_run_with_itemsworks around run immutability and can carry the last results over;get_issue_test_coverage,get_test_run_summary,clone_test_case,move_test_cases_to_folder. - Degradation on older builds — automatic fallback when the paginated results endpoint is absent, and a per-item fallback when the bulk create endpoint is broken. Both were found on a real legacy instance, not in a changelog.
- Plumbing that fails loudly — strict zod input validation,
Retry-After-aware retries with exponential backoff, error messages carrying actionable hints, read-only mode, secrets kept out of logs and tool output. - 1181 unit and contract tests (vitest + msw, no network) plus a gated end-to-end scenario against a real instance.
Quick start
Requirements: Node.js >= 20, Jira Server/DC with the Zephyr Scale plugin, and a Personal Access Token (Jira 8.14+) or a username/password pair.
Nothing to clone — the package is on npm. Claude Code:
claude mcp add zephyr-scale \
--env JIRA_BASE_URL=https://jira.example.com \
--env JIRA_PAT=<personal access token> \
--env ZEPHYR_DEFAULT_PROJECT_KEY=PROJ \
--env ZEPHYR_ALLOW_INTERNAL_API=true \
-- npx -y zephyr-scale-mcp
Claude Desktop / any MCP client (claude_desktop_config.json, .mcp.json):
{
"mcpServers": {
"zephyr-scale": {
"command": "npx",
"args": ["-y", "zephyr-scale-mcp"],
"env": {
"JIRA_BASE_URL": "https://jira.example.com",
"JIRA_PAT": "<personal access token>",
"ZEPHYR_DEFAULT_PROJECT_KEY": "PROJ",
"ZEPHYR_ALLOW_INTERNAL_API": "true"
}
}
}
}
Running from source instead
git clone https://github.com/vilaabo/zephyr-scale-mcp.git
cd zephyr-scale-mcp
npm install
npm run build # -> dist/index.js
Then point the client at the build: "command": "node", "args": ["/path/to/zephyr-scale-mcp/dist/index.js"].
ZEPHYR_ALLOW_INTERNAL_API=trueis optional and recommended. It registers 12 extra tools that reach the things the public API cannot do at all: editing a test run in place — renaming or moving it (update_test_run) and adding or removing cases without changing its key (add_test_cases_to_run,remove_test_cases_from_run) — plus listing the folder tree (get_folder_tree), deleting folders (delete_folder), editing older executions (update_test_result_by_id) and reading the exact status names the API silently expects (get_status_options). These call the same undocumented/rest/tests/1.0endpoints the Jira UI itself uses; the vendor does not support them and they may differ or be absent on another Zephyr Scale version. Leave the flag off if that trade-off is not acceptable — the other 42 tools are unaffected.
Then ask the agent to run health_check. It verifies connectivity and credentials via GET /rest/api/2/myself and, when ZEPHYR_DEFAULT_PROJECT_KEY is set, that the Zephyr plugin answers on /rest/atm/1.0.
What you can ask your agent to do
- "Create the folder
/Regression/Paymentsand add step-by-step test cases for the checkout flow described in this document." - "Find every Draft case in
/Regression, review them, and set the ready ones to Approved." - "Create a cycle for sprint 42 with all smoke cases, then record the results from this report — step by step where the script has steps."
- "Which test cases cover PROJ-123, and when did each of them last pass?" — traceability from the issue to its cases and their latest executions.
- "Take this ZIP of Cucumber JSON reports and publish it as a new cycle in PROJ." — automation import; scenarios are matched to BDD cases by their
@TestCaseKey=PROJ-T1tag. - "Add two steps to PROJ-T55 after step 3." — existing steps and their ids survive.
- "Recreate cycle PROJ-R7 with three more cases, keep the results, and delete the original."
Configuration
| Variable | Required | Default | Purpose |
|---|---|---|---|
JIRA_BASE_URL |
yes | — | Jira base URL without a trailing /, e.g. https://jira.example.com |
JIRA_AUTH |
no | pat |
pat | basic |
JIRA_PAT |
with pat |
— | Jira Server/DC Personal Access Token |
JIRA_USERNAME, JIRA_PASSWORD |
with basic |
— | Basic-auth credentials |
JIRA_TIMEOUT_MS |
no | 30000 |
Per-request timeout |
JIRA_MAX_RETRIES |
no | 2 |
Retries for GET and for any 429/503, honoring Retry-After, otherwise exponential backoff with jitter |
JIRA_TLS_REJECT_UNAUTHORIZED |
no | true |
false accepts self-signed certificates — this disables TLS verification process-wide and prints a warning to stderr |
ZEPHYR_DEFAULT_PROJECT_KEY |
no | — | Used whenever a tool is called without projectKey |
ZEPHYR_READONLY |
no | false |
true makes every write tool refuse with an error; read tools keep working |
ZEPHYR_ALLOW_INTERNAL_API |
no | false |
true registers the 12 UNOFFICIAL tools backed by the internal /rest/tests/1.0 API |
ZEPHYR_LOG_LEVEL |
no | info |
debug | info | warn | error |
Two guarantees the tests cover: JIRA_PAT and JIRA_PASSWORD never appear in logs, tool output or error messages — in any encoding they can take on the way out: raw, JSON-escaped, or the base64 basic-auth token (error text carries the method and path only — never the query string, which may contain data) — and stdout is reserved for the MCP protocol, every log line goes to stderr. One deliberate exception: a secret shorter than six characters is left alone, because redacting it would corrupt unrelated output while protecting nothing; the server warns about it at startup.
Configuration is validated at startup: all problems are reported at once and the process exits with a non-zero code rather than starting half-configured.
Tools
54 tools. The 42 public ones are always registered; the 12 in the last group only with ZEPHYR_ALLOW_INTERNAL_API=true.
Test cases — 13 tools
| Tool | What it does |
|---|---|
create_test_case |
Create a case with a STEP_BY_STEP / PLAIN_TEXT / BDD script, parameters, custom fields, Call-to-Test steps |
get_test_case |
Read a case, optionally narrowed by fields; step ids come back here |
search_test_cases |
TQL search with pagination; a query longer than 1500 characters is sent as POST /testcase/search (which supports only projectKey, key, name) |
update_test_case |
Partial update; testScript.steps is synchronized by id (see limitation 7) |
add_test_steps |
Insert steps at a position without losing the existing ones — read, merge by id, write back |
set_test_script |
Replace the whole script or change its format; destructive by design |
clone_test_case |
Copy a case inside its project with fresh step ids; links, attachments and history are not copied |
move_test_cases_to_folder |
Bulk-move by explicit keys or by source folder; a failing case does not abort the rest |
delete_test_case |
Permanent delete of the case, its script and its history |
create_test_cases_bulk |
Create many cases in one call, with a per-case fallback on broken bulk endpoints |
link_issues_to_test_cases |
Bulk-link cases to Jira issues (additive) |
get_test_cases_linked_to_issue |
Reverse lookup: issue → cases |
get_issue_test_coverage |
Traceability report: issue → linked cases → latest execution of each |
Test cycles (test runs) — 7 tools
| Tool | What it does |
|---|---|
create_test_run |
Create a cycle with its complete item list — optionally with each item's execution result in the same call |
get_test_run |
Read a cycle including its items |
search_test_runs |
TQL search — for runs only projectKey and folder are searchable |
delete_test_run |
Permanent delete of the cycle and all its results |
get_test_run_results |
Page through the executions of a cycle, with the legacy flat-endpoint fallback |
get_test_run_summary |
Last execution per item: byStatus counts verbatim, executionProgressPct, passRatePct when a literal Pass status exists |
recreate_test_run_with_items |
The public workaround for run immutability: rebuild under a new key with cases added or removed, optionally carrying the last results over and deleting the original |
Executions (test results) — 4 tools
| Tool | What it does |
|---|---|
create_test_result |
Append a new execution to a run item, including per-step scriptResults |
update_last_test_result |
Partial update of the most recent execution of an item |
create_test_results_bulk |
Many executions for one cycle in a single call |
get_latest_result_for_test_case |
The execution of a case with the greatest stored execution date, across all cycles — not necessarily the one recorded last |
Items that exist several times in a run (per environment or per assignee) are disambiguated with matchEnvironment / matchUserKey, sent as query parameters.
Test plans — 5 tools
| Tool | What it does |
|---|---|
create_test_plan |
Create a plan; returns { key }, e.g. PROJ-P123 |
get_test_plan |
Read a plan with its linked runs and issues |
update_test_plan |
Partial update |
delete_test_plan |
Permanent delete |
search_test_plans |
TQL search; the searchable field set varies by Zephyr Scale version |
Folders — 2 tools
| Tool | What it does |
|---|---|
create_folder |
Create a case / plan / cycle folder from a full path; with recursive (default true) a 400 on the full path triggers creating each parent prefix and one retry — 403, 409 and 5xx propagate untouched |
rename_folder |
Rename one folder segment by its numeric id (and optionally set its custom fields) |
Attachments — 4 tools
| Tool | What it does |
|---|---|
upload_attachment |
Attach a local file to a case, a case step, a cycle, a result or a result step (multipart) |
list_attachments |
List the attachments of any of those targets; each record carries the id and url the other tools need |
download_attachment |
Save an attachment by id or by the url list_attachments returned — that url must be on the configured Jira host. The only public tool that reads from the internal API: attachment content is served by /rest/tests/1.0/attachment/{id}, which exists regardless of ZEPHYR_ALLOW_INTERNAL_API |
delete_attachment |
Permanently delete one attachment by numeric id |
Automation — 3 tools
| Tool | What it does |
|---|---|
upload_automation_results |
Publish a ZIP of results in Zephyr's custom JSON format; always creates a new cycle |
upload_cucumber_results |
Publish a ZIP of Cucumber JSON reports; scenarios are matched by their @TestCaseKey=PROJ-T1 tag |
download_feature_files |
Export BDD cases as a ZIP of .feature files; tql is required and uses the testCase.-prefixed dialect. The archive is written only after its PK signature is verified, so an HTML login page served with HTTP 200 fails instead of leaving a corrupt file |
Service and diagnostics — 4 tools
| Tool | What it does |
|---|---|
health_check |
Jira reachability, credentials, and whether the Zephyr plugin answers |
list_environments |
The project's environments — the exact case-sensitive names results reference |
create_environment |
Create an environment in the project |
find_jira_user |
Resolve the Jira user key (JIRAUSER10000) that owner / executedBy / assignedTo require |
UNOFFICIAL — internal API, opt-in — 12 tools
Registered only with ZEPHYR_ALLOW_INTERNAL_API=true. These call /rest/tests/1.0, the undocumented API behind the Jira UI. The vendor does not support it: endpoints may differ or be absent on another Zephyr Scale version, and a 404/405 from one of these tools means exactly that. Every request shape below was either captured from the Jira UI's own traffic or verified live against a real instance — never guessed. Errors from this layer carry a hint saying so.
| Tool | What it does |
|---|---|
update_test_run |
Rename a cycle, move it to another folder (by numeric folder id) or change its planned dates in place, keeping its key, items and results. No PUT /testrun exists in the public API |
add_test_cases_to_run |
Append cases to an existing cycle in place; the key and the existing results survive |
remove_test_cases_from_run |
Remove items from an existing cycle in place — their whole execution history dies with them |
reorder_test_run_items |
Reorder the items of an existing cycle; a cycle already in the requested order makes no write at all |
link_issues_to_test_run |
Link Jira issues to an existing cycle — the public API rejects an issueLinks field on runs outright, so this is the only way |
link_test_run_to_plan |
Associate an existing cycle with a test plan after creation (the public API accepts testPlanKey only at creation) |
delete_test_results |
Delete individual executions by numeric id. The last execution of an item cannot be deleted |
update_test_result_by_id |
Edit any execution in an item's history, including older ones; status is resolved from its case-sensitive name to the internal id |
get_folder_tree |
The full folder tree of a project with the numeric ids rename_folder, delete_folder and update_test_run need. Each entity type has its own tree |
get_status_options |
The exact internal names of the project's execution statuses, case statuses or priorities — the values the public API silently ignores when they are wrong |
get_custom_field_definitions |
Custom field definitions per entity type: names, types, required flags, options |
delete_folder |
Delete a folder by numeric id. What happens to a non-empty folder is version-specific — empty it first |
Working around API v1
These are the constraints the server is built around. Every tool description repeats the ones relevant to it, so the agent sees them at call time.
- Test runs are immutable. There is no
PUT /testrun: a run cannot be renamed, moved, or have cases added or removed. Its items are fixed at creation and the run status is derived from item statuses. Escape hatches:recreate_test_run_with_items(public, produces a new key) or the internalupdate_test_run/add_test_cases_to_run/remove_test_cases_from_run(same key). - Folders are never created implicitly.
create_test_case,create_test_runandcreate_test_planfail with 400 on an unknown folder. Folders also cannot be listed through the public API, and renaming needs the numeric id returned bycreate_folder— orget_folder_treewith the internal API enabled. owner/executedBy/assignedTotake a Jira user key (JIRAUSER10000), never a username or an e-mail.find_jira_userresolves it.- TQL is strict: spaces around operators are mandatory, string values go in double quotes,
ANDis the only connector (noOR), and folder paths start with/. Test runs are searchable byprojectKeyandfolderonly. For single/multi-choice custom fields=does not work — useIN. - Statuses, priorities and environments are case-sensitive internal names. Built-in statuses are shown localized in the UI (the label you see for
Draftmay be a translation) while custom ones use their literal names —get_status_optionsshows what the API actually expects. A wrong execution status is silently ignored by the public API. - A BDD script is the scenario body only — bare
Given/When/Then/And/Butlines. Text wrapped inFeature:/Scenario:is rejected with400 Invalid BDD Script; the wrapper is generated on export. STEP_BY_STEPsteps are synchronized by id onPUT: a step without an id is created, a step with an id is updated, and every stored step missing from the list is deleted. Always send the complete final list with ids carried over fromget_test_case— or letadd_test_stepsdo the read-merge-write.- Deprecated fields are intentionally not accepted: use
issueLinksinstead ofissueKey,actualEndDateinstead ofexecutionDate,executedByinstead ofuserKey.
Quirks of older plugin builds
All of the following was observed live on a real legacy Zephyr Scale Server instance and is covered by tests.
- Cycle keys may use the
-Cprefix (PROJ-C34) instead of-R. Every run-key parameter takes the key as-is, so pass whatever your instance shows. GET /testrun/{key}/testresults/pagemay not exist.get_test_run_resultsandget_test_run_summaryfall back to the deprecated flat endpoint and paginate client-side, adding anoteto the response. A run that genuinely does not exist still surfaces as a 404.- An overall
statussent together withscriptResultsis ignored. Send the step results first, then set the overall status withupdate_last_test_result. POST /testcase/link-issuesmay answer 500. Link throughupdate_test_casewithissueLinksinstead.POST /testcase/bulkmay answer HTTP 500 with an empty body for any payload while single creation works.create_test_cases_bulkfalls back to creating the cases one by one — on any5xx, and on a JSON404(which means "no such endpoint on this build", unlike the HTML404Jira serves when the plugin is absent) — and reports which ones succeeded (#1). Every other4xxis a payload error and is not retried.- Posting a result for a case that is not among the run's items silently adds it to the run on this build; other builds reject the call with 400/404. The result tools document both.
- The custom automation-results format is validated strictly.
{"version": 1, "executions": [{"source", "result", "testCase": {"key"}}]}works; extra per-execution fields such asexecutionTimeare rejected withInvalid Custom Format JSON file. Cucumber JSON reports work as-is when the scenario carries@TestCaseKey=PROJ-T1. download_feature_filesrequires thetqlquery parameter — the API rejects the call without it.- The last execution of a run item cannot be deleted.
delete_test_resultsrejects it; remove the item instead. - Custom fields may be absent entirely — the definitions endpoint then returns
[].
Development
npm run typecheck # tsc --noEmit, strict
npm test # 1181 unit + contract tests (vitest + msw), no network
npm run build # tsup -> dist/index.js
npm run smoke # 13 end-to-end tests, ZEPHYR_E2E=1, real instance required
The smoke scenario is skipped unless ZEPHYR_E2E=1. It needs a real JIRA_BASE_URL, credentials and a dedicated ZEPHYR_DEFAULT_PROJECT_KEY — it creates and deletes real entities and leaves /mcp-smoke-* folders behind, since the public API cannot delete folders.
src/
├── index.ts # bootstrap: config, tool registration, stdio transport
├── config.ts # environment validation
├── http.ts # fetch wrapper: auth, timeouts, retries, error normalization,
│ # multipart and binary bodies
├── schemas.ts # shared parameter schemas, field shapes, recurring description constants
├── toolkit.ts # defineTool(): strict zod input, read-only guard,
│ # JSON / isError response shaping, shared helpers
├── internal.ts # the UNOFFICIAL /rest/tests/1.0 layer: key -> id resolution,
│ # run-item plumbing, status resolution, error hints
├── runResults.ts # paged run results with the legacy fallback
├── log.ts # leveled logger, stderr only
└── tools/ # one module per tool family: testCases, testRuns, testResults,
# testPlans, folders, attachments, automation, misc,
# runMaintenance, resultsMaintenance, internalRefs
test/ # one test module per source module + negative contract tests
# and the gated smoke scenario
Versioning and changelog
Version 1.0.0. Tool names, parameter names and response shapes are a public contract from this release on and change only in a major version. See CHANGELOG.md.
License
Установка Zephyr Scale
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/vilaabo/zephyr-scale-mcpFAQ
Zephyr Scale MCP бесплатный?
Да, Zephyr Scale MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Zephyr Scale?
Нет, Zephyr Scale работает без API-ключей и переменных окружения.
Zephyr Scale — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Zephyr Scale в Claude Desktop, Claude Code или Cursor?
Открой Zephyr Scale на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Compare Zephyr Scale with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории productivity




