QTM4J MCP Server
# QTM4J MCP Server
[](https://www.npmjs.com/package/qtm4j-mcp-server)
[](https://www.npmjs.com/package/qtm4j-mcp-server)
[](https://www.npmjs.com/package/qtm4j-mcp-server)
[](LICENSE)
An [MCP](https://modelcontextprotocol.io) server with **87 tools** for [QMetry Test Management for Jira (QTM4J)](https://www.qmetry.com/qmetry-test-management-jira). Search and manage test cases, cycles, executions, plans, folders, comments, defects, automation rules, and project metadata from Claude Desktop, Claude Code, VS Code Copilot, Cursor, or any MCP-compatible client.
**Distribution**:
- npm: [`qtm4j-mcp-server`](https://www.npmjs.com/package/qtm4j-mcp-server)
- MCP Registry: `io.github.salehrifai42/qtm4j-mcp-server`
- GitHub: [`salehrifai42/qmetrymcp`](https://github.com/salehrifai42/qmetrymcp)
## Quick start (no clone required)
You need a QMetry API key (QMetry → *API Keys*) and Node.js 18+.
### Claude Desktop
Edit your config file and restart Claude:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
- Linux: `~/.config/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"qtm4j": {
"command": "npx",
"args": ["-y", "qtm4j-mcp-server@^0.1"],
"env": {
"QTM4J_API_KEY": "your-api-key-here",
"QTM4J_REGION": "US"
}
}
}
}
```
### Claude Code (CLI)
```bash
claude mcp add qtm4j -e QTM4J_API_KEY=your-api-key-here -e QTM4J_REGION=US -- npx -y qtm4j-mcp-server@^0.1
```
### VS Code (GitHub Copilot Agent mode)
Create `.vscode/mcp.json` in your workspace:
```json
{
"servers": {
"qtm4j": {
"type": "stdio",
"command": "npx",
"args": ["-y", "qtm4j-mcp-server@^0.1"],
"env": {
"QTM4J_API_KEY": "${env:QTM4J_API_KEY}",
"QTM4J_REGION": "US"
}
}
}
}
```
Switch Copilot Chat to **Agent** mode and the `qtm4j_*` tools appear automatically.
### Cursor
Add to `~/.cursor/mcp.json` (or `<project>/.cursor/mcp.json` for project-level):
```json
{
"mcpServers": {
"qtm4j": {
"command": "npx",
"args": ["-y", "qtm4j-mcp-server@^0.1"],
"env": { "QTM4J_API_KEY": "your-api-key-here" }
}
}
}
```
> The example configs above pin the package to `^0.1` so a future breaking release won't auto-upgrade you. Drop the `@^0.1` suffix if you'd rather always run the latest.
### Global install (faster startup)
`npx` re-resolves the package on every launch, which adds a few seconds of startup latency. If you use the server frequently, install it globally and point your client at the binary directly:
```bash
npm install -g qtm4j-mcp-server
```
Then in your client config, replace the `npx` command:
```json
{
"mcpServers": {
"qtm4j": {
"command": "qtm4j-mcp-server",
"env": {
"QTM4J_API_KEY": "your-api-key-here",
"QTM4J_REGION": "US"
}
}
}
}
```
Tradeoff: instant startup and works offline, but you'll need to run `npm update -g qtm4j-mcp-server` to get new versions.
## Configuration
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `QTM4J_API_KEY` | yes | — | QMetry API key, sent on every request as the `apiKey` header |
| `QTM4J_REGION` | no | `US` | `US` → `https://qtmcloud.qmetry.com`, `AU` → `https://syd-qtmcloud.qmetry.com` |
> 💡 Set `QTM4J_REGION=AU` if your QMetry instance is on the Sydney cluster.
## Tools
All tools are prefixed with `qtm4j_` to avoid collisions with other MCP servers.
| Area | Tools |
| --- | --- |
| **Test Cases** | `qtm4j_create_test_case`, `qtm4j_get_test_case`, `qtm4j_search_test_cases`, `qtm4j_update_test_case`, `qtm4j_delete_test_case`, `qtm4j_archive_test_case`, `qtm4j_unarchive_test_case`, `qtm4j_clone_test_cases`, `qtm4j_create_test_steps`, `qtm4j_update_test_steps` |
| **Test Cycles** | `qtm4j_create_test_cycle`, `qtm4j_get_test_cycle`, `qtm4j_search_test_cycles`, `qtm4j_update_test_cycle`, `qtm4j_delete_test_cycle`, `qtm4j_archive_test_cycle`, `qtm4j_unarchive_test_cycle` |
| **Test Executions** | `qtm4j_get_test_cycle_executions`, `qtm4j_get_test_execution`, `qtm4j_update_test_execution`, `qtm4j_update_test_step_execution`, `qtm4j_bulk_update_test_executions`, `qtm4j_get_execution_teststeps`, `qtm4j_update_execution_custom_fields`, `qtm4j_upload_execution_attachment`, `qtm4j_list_execution_attachments`, `qtm4j_delete_execution_attachment` |
| **Test Plans** | `qtm4j_create_test_plan`, `qtm4j_get_test_plan`, `qtm4j_search_test_plans`, `qtm4j_update_test_plan`, `qtm4j_delete_test_plan`, `qtm4j_archive_test_plan`, `qtm4j_unarchive_test_plan`, `qtm4j_link_test_cycles_to_plan`, `qtm4j_get_linked_test_cycles`, `qtm4j_unlink_test_cycles_from_plan` |
| **Comments** | `qtm4j_get_test_case_comments`, `qtm4j_add_test_case_comment`, `qtm4j_update_test_case_comment`, `qtm4j_delete_test_case_comment`, `qtm4j_get_test_cycle_comments`, `qtm4j_add_test_cycle_comment`, `qtm4j_update_test_cycle_comment`, `qtm4j_delete_test_cycle_comment`, `qtm4j_get_test_plan_comments`, `qtm4j_add_test_plan_comment`, `qtm4j_update_test_plan_comment`, `qtm4j_delete_test_plan_comment` |
| **Defects** | `qtm4j_get_execution_defects`, `qtm4j_link_execution_defects`, `qtm4j_unlink_execution_defects`, `qtm4j_get_step_execution_defects`, `qtm4j_link_step_execution_defects`, `qtm4j_unlink_step_execution_defects`, `qtm4j_search_cycle_defects`, `qtm4j_get_cycle_defect_summary` |
| **Folders** | `qtm4j_list_folders`, `qtm4j_create_folder` |
| **Automation** | `qtm4j_link_automation_rule`, `qtm4j_unlink_automation_rule`, `qtm4j_run_automation_rules` |
| **Metadata** (read-only) | `qtm4j_get_projects`, `qtm4j_get_priorities`, `qtm4j_get_priority_icons`, `qtm4j_get_statuses`, `qtm4j_get_environments`, `qtm4j_get_builds`, `qtm4j_get_labels`, `qtm4j_get_components`, `qtm4j_get_execution_results`, `qtm4j_get_custom_fields`, `qtm4j_get_parameters`, `qtm4j_get_user_permissions` |
See [`docs/TOOLS.md`](docs/TOOLS.md) for the full reference and [`docs/COOKBOOK.md`](docs/COOKBOOK.md) for example prompts colleagues can paste into any MCP client.
Every tool ships with annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) so clients can decide whether to ask for confirmation. Read tools accept a `response_format` parameter (`json` default, or `markdown` for human-readable output). Large responses are automatically truncated at 25k characters with a hint to narrow the query.
All tools validate inputs with Zod, paginate via `startAt`/`maxResults`, and automatically retry rate-limited (HTTP 429) responses with exponential back-off (up to 3 attempts).
## Trying it out
Once connected, ask your assistant something like:
> *Search QMetry project `<your project ID>` for test cases with status "To Do" and show me the first 5.*
The client will call `qtm4j_search_test_cases` and render the response.
> *Get all executions in test cycle `<KEY>-TR-747` and mark any unexecuted ones as Pass.*
## Example tool calls
> Replace `<projectId>` and `<KEY>` with your tenant's numeric Jira project ID and project key. Find them in the project URL: `…?projectId=<projectId>&projectKey=<KEY>`. Use `qtm4j_get_execution_results` to discover your tenant's `executionResultId` values.
```jsonc
// Search test cases
{
"name": "qtm4j_search_test_cases",
"arguments": {
"projectId": "<projectId>",
"status": ["Approved"],
"maxResults": 20,
"response_format": "markdown"
}
}
// Update an execution result
{
"name": "qtm4j_update_test_execution",
"arguments": {
"cycleId": "<internal-cycle-id>",
"testCaseExecutionId": "<internal-tc-execution-id>",
"executionResultId": "<pass-id>",
"comment": "Verified on staging"
}
}
```
## Troubleshooting
- **Tools don't appear in my client.** Restart the client after editing config. Check `claude mcp list` (Claude Code) or VS Code's MCP panel for connection status. On first run, `npx -y qtm4j-mcp-server` may take a few seconds to download the package.
- **401 Unauthorized.** Your `QTM4J_API_KEY` is invalid or expired. Generate a new one in QMetry → *API Keys*.
- **404 on execution or search endpoints.** Many endpoints want the **internal numeric `id`**, not the human key like `<KEY>-TR-747`. Call `qtm4j_get_test_cycle` first to translate the key into the internal id.
- **Empty or oversized folder response.** Pass `folderId` to `qtm4j_list_folders` to scope to a subtree — full project trees on large projects can exceed the response size limit.
- **`projectId` rejected.** Use the **numeric** Jira project ID, not the project key string. You can find it in the Jira project URL: `…?projectId=<numeric-id>&projectKey=<KEY>`.
## Notes
- Search endpoints use `POST /…/search` — filters go in the body under `filter`, pagination/sort on the query string. Tool handlers wrap this for you.
- `204 No Content` responses resolve as `{ message: "…" }`.
- The Swagger spec does not currently document a framework-style automation import-result endpoint (e.g. JUnit/TestNG/Cucumber ingestion); the automation tools cover the rules-run and rule-link flows exposed in the spec.
## Development
Local setup if you want to modify the server:
```bash
git clone https://github.com/salehrifai42/qmetrymcp.git
cd qmetrymcp
npm install
npm run build
QTM4J_API_KEY=your-key npm start
```
Test changes with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
```bash
QTM4J_API_KEY=your-key npx @modelcontextprotocol/inspector node dist/index.js
```
Run the unit tests (no API key or network needed — the HTTP client's transport is faked in-memory):
```bash
npm test
```
## Replicating the bulk xlsx import workflow
The repo ships a Claude Code skill (`.claude/skills/xlsx-to-qmetry/`) and a Python importer (`scripts/import-xlsx-to-qmetry.py`) for pushing folders of Excel test cases into QMetry. Tenant-specific IDs (project, parent folder, status, custom-field IDs, components) are kept out of git — you supply them in your own `config.json`.
First-run after cloning:
```bash
cp config.template.json config.json
# Edit config.json — fill in:
# connection.apiKey (QMetry → avatar → API Keys → Generate)
# connection.projectId (numeric Jira project ID)
# xlsxImport.parentFolderId (target folder for new test cases)
# xlsxImport.statusId (e.g. Draft / Approved)
# xlsxImport.apiTestFieldId (custom field ID for "API Test" toggle, if used)
# xlsxImport.options (Yes/No option IDs for that custom field)
# xlsxImport.componentIds (default components to attach; can be [])
cp .claude/commands/qtm4j.template.md .claude/commands/qtm4j.md
# Optional: populate qtm4j.md with your tenant's IDs using the GET endpoints listed inside.
```
Discover the IDs you need with the running MCP server (`qtm4j_get_projects`, `qtm4j_get_statuses`, `qtm4j_get_components`, `qtm4j_get_custom_fields`, `qtm4j_list_folders`) or with `node scripts/refresh-field-reference.mjs` to dump everything to `field_reference.json`.
Then launch Claude Code from the repo root and the `xlsx-to-qmetry` skill auto-loads. Drop your workbooks under `Input/<batch-name>/` and ask Claude to import them — see [`docs/EXCEL-IMPORT-GUIDE.md`](docs/EXCEL-IMPORT-GUIDE.md) for the full workflow.
## Bugs and contributions
Found a bug or want to suggest a feature? Open an issue at <https://github.com/salehrifai42/qmetrymcp/issues>. PRs welcome.
## License
[MIT](LICENSE)
TDQS
Scored across 30 tools
Each tool targets a distinct entity or operation (folders, test cases, cycles, plans, executions, automation rules, linking). Despite high tool count, descriptions make purposes clear with no overlapping functionality.
All tools use a consistent verb_noun pattern in snake_case (e.g., create_folder, update_test_execution, search_test_plans). Variations between get/list/search are semantically justified by the operation type.
30 tools is on the high side, but the domain (test management) requires many entities and operations. While borderline heavy, most tools serve essential functions without redundancy.
Covers CRUD for core entities (test cases, cycles, plans, folders), steps, executions (single/bulk), linking, and automation. Missing delete for folders and some retrieval details, but core workflows are complete.