@testkase/test-management-mcp 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,60 +7,24 @@
7
7
  <h3 align="center">@testkase/test-management-mcp</h3>
8
8
 
9
9
  <p align="center">
10
- Official MCP server for <a href="https://testkase.com">TestKase</a> Test Management — connect your AI agent to a complete test management platform.<br/>Formerly <code>@testkase/mcp-server</code>. For real phones and browsers, see <code>@testkase/device-farm-mcp</code>.
10
+ Official MCP server for <a href="https://testkase.com">TestKase Test Management</a>: let your AI assistant manage test cases, cycles, plans and reports.
11
11
  </p>
12
12
 
13
13
  <p align="center">
14
14
  <a href="https://www.npmjs.com/package/@testkase/test-management-mcp"><img src="https://img.shields.io/npm/v/@testkase/test-management-mcp.svg?style=flat-square" alt="npm version" /></a>
15
- <a href="https://www.npmjs.com/package/@testkase/test-management-mcp"><img src="https://img.shields.io/npm/dm/@testkase/test-management-mcp.svg?style=flat-square" alt="npm downloads" /></a>
16
15
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="MIT License" /></a>
17
16
  </p>
18
17
 
19
18
  ---
20
19
 
21
- ## What is this?
20
+ Works with Claude, GitHub Copilot, Cursor and any [MCP](https://modelcontextprotocol.io) client. Pairs with [`@testkase/device-farm-mcp`](https://www.npmjs.com/package/@testkase/device-farm-mcp) for real phones and browsers.
22
21
 
23
- This package gives AI agents — Claude Desktop, GitHub Copilot, Cursor, or any [MCP](https://modelcontextprotocol.io)-compatible client — full access to your TestKase projects. Your agent can create test cases, run test cycles, record execution results, and pull reports, all through natural language.
22
+ > **Formerly `@testkase/mcp-server`.** Same tools and key: change the package name in your config to `@testkase/test-management-mcp`.
24
23
 
25
- **11 tools. Zero config beyond a token. Works out of the box.**
24
+ ## Quick start
26
25
 
27
- ## Quick Start
28
-
29
- ### 1. Get a PAT token
30
-
31
- Log in to [TestKase](https://www.testkase.com) > **API Keys** > **Generate New Token**
32
-
33
- ### 2. Add to your AI agent
34
-
35
- <details>
36
- <summary><strong>Claude Desktop</strong></summary>
37
-
38
- Edit your config file:
39
- - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
40
- - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
41
-
42
- ```json
43
- {
44
- "mcpServers": {
45
- "testkase": {
46
- "command": "npx",
47
- "args": ["-y", "@testkase/test-management-mcp"],
48
- "env": {
49
- "TESTKASE_API_BASE_URL": "https://api.testkase.com",
50
- "TESTKASE_PAT_TOKEN": "xyz_your_token_here"
51
- }
52
- }
53
- }
54
- }
55
- ```
56
- </details>
57
-
58
- <details>
59
- <summary><strong>GitHub Copilot (VS Code)</strong></summary>
60
-
61
- Edit your config file:
62
- - **Windows:** `%APPDATA%\Code\User\globalStorage\github.copilot\mcp.json`
63
- - **macOS:** `~/Library/Application Support/Code/User/globalStorage/github.copilot/mcp.json`
26
+ 1. **Get an access key:** TestKase > **API Keys** > **Create API Key** (starts with `xyz_`).
27
+ 2. **Add it to your assistant** (Claude Desktop, Copilot, Cursor: same block in their MCP config file):
64
28
 
65
29
  ```json
66
30
  {
@@ -68,122 +32,70 @@ Edit your config file:
68
32
  "testkase": {
69
33
  "command": "npx",
70
34
  "args": ["-y", "@testkase/test-management-mcp"],
71
- "env": {
72
- "TESTKASE_API_BASE_URL": "https://api.testkase.com",
73
- "TESTKASE_PAT_TOKEN": "xyz_your_token_here"
74
- }
35
+ "env": { "TESTKASE_PAT_TOKEN": "xyz_your_token_here" }
75
36
  }
76
37
  }
77
38
  }
78
39
  ```
79
- </details>
80
40
 
81
- <details>
82
- <summary><strong>Claude Code (CLI)</strong></summary>
41
+ Claude Code:
83
42
 
84
43
  ```bash
85
- claude mcp add testkase -e TESTKASE_API_BASE_URL=https://api.testkase.com -e TESTKASE_PAT_TOKEN=xyz_your_token_here -- npx -y @testkase/test-management-mcp
86
- ```
87
- </details>
88
-
89
- <details>
90
- <summary><strong>Cursor</strong></summary>
91
-
92
- Add to `.cursor/mcp.json` in your project root:
93
-
94
- ```json
95
- {
96
- "mcpServers": {
97
- "testkase": {
98
- "command": "npx",
99
- "args": ["-y", "@testkase/test-management-mcp"],
100
- "env": {
101
- "TESTKASE_API_BASE_URL": "https://api.testkase.com",
102
- "TESTKASE_PAT_TOKEN": "xyz_your_token_here"
103
- }
104
- }
105
- }
106
- }
107
- ```
108
- </details>
109
-
110
- ### 3. Restart your agent and start talking
111
-
112
- ```
113
- "List my projects"
114
- "Create a login test case in PRJ-1001 with steps"
115
- "Run TEST-1 as pass in cycle TCYCLE-5"
116
- "Show me the execution summary for PRJ-1001"
44
+ claude mcp add testkase -e TESTKASE_PAT_TOKEN=xyz_your_token_here -- npx -y @testkase/test-management-mcp
117
45
  ```
118
46
 
119
- ---
47
+ 3. **Restart your assistant and ask:** "List my projects", "Create a login test case in PRJ-1001 with steps", "Mark TEST-1 passed in cycle TCYCLE-5", "Show the execution summary".
120
48
 
121
49
  ## Tools
122
50
 
123
- | Tool | Description |
124
- |------|-------------|
125
- | `list_projects` | List all accessible projects |
126
- | `get_project_structure` | Get folders, labels, members, and field options for a project |
127
- | `search_testcases` | Search test cases by text, folder, filters, sorting, and pagination |
128
- | `get_testcase` | Get full test case details including steps |
129
- | `manage_testcase` | Create, bulk create, update, or delete test cases; add, edit, delete or reorder test steps |
130
- | `manage_folder` | Create, rename, move, or delete folders (all sections) |
131
- | `search_test_cycles` | Search test cycles with execution progress |
132
- | `manage_test_cycle` | Full cycle lifecycle — CRUD, link/unlink/assign test cases |
133
- | `execute_tests` | Record execution results — manual (default) or as a CI/automation run |
134
- | `manage_test_plan` | Full plan lifecycle — CRUD, link/unlink cycles, view test cases |
135
- | `get_report` | Pull from 40+ report types (see below) |
136
-
137
- ### Reporting
138
-
139
- `get_report` covers execution, coverage, trends, team, defect, and insight report types (insights are computed from your data and use no AI credits):
140
-
141
- | Category | Report Types |
142
- |----------|-------------|
143
- | **Execution** | `execution_summary`, `execution_by_cycle`, `execution_by_tester`, `execution_by_priority`, `execution_by_environment`, `execution_by_folder`, `execution_by_automation` |
144
- | **Coverage** | `requirement_coverage`, `traceability_matrix`, `failed_requirements`, `uncovered_requirements`, `testcase_coverage`, `unlinked_testcases` |
145
- | **Trends** | `execution_trend`, `execution_burnup`, `execution_burndown`, `test_creation`, `requirement_coverage_trend`, `execution_velocity` |
146
- | **Comparison** | `cycle_comparison`, `created_vs_executed`, `scorecard_by_folder`, `scorecard_by_tester` |
147
- | **Team** | `tester_workload`, `testcase_distribution`, `tester_effectiveness` |
148
- | **Defects** | `defects_by_cycle`, `defects_by_tester`, `defects_by_folder`, `defect_hotspots` |
149
- | **Insights** | `predictive_failure`, `smart_prioritization`, `testcase_quality`, `stale_tests`, `flaky_tests`, `suite_optimization` |
150
- | **Risk & Release** | `release_readiness`, `risk_heatmap_folder`, `risk_heatmap_feature`, `requirement_risk_matrix`, `cycle_health`, `project_health` |
51
+ | Tool | What it does |
52
+ |------|--------------|
53
+ | `list_projects` / `get_project_structure` | Your projects, and their folders, labels, members and fields. |
54
+ | `search_testcases` / `get_testcase` | Find test cases; read one with its steps. |
55
+ | `manage_testcase` | Create, bulk-create, update or delete test cases and their steps. |
56
+ | `manage_folder` | Create, rename, move or delete folders. |
57
+ | `search_test_cycles` / `manage_test_cycle` | Find and manage test cycles; link and assign test cases. |
58
+ | `execute_tests` | Record results, manual or as a CI run. |
59
+ | `manage_test_plan` | Manage test plans and their cycles. |
60
+ | `get_report` | 40+ reports: execution, coverage, trends, team, defects, insights, risk and release. |
151
61
 
152
- ---
62
+ ## Your key and privacy
153
63
 
154
- ## Configuration
64
+ - The key stays on your machine and goes only to TestKase.
65
+ - It acts as you, with your project permissions.
66
+ - Test data you ask about is passed to your AI assistant.
155
67
 
156
- | Variable | Required | Default | Description |
157
- |----------|----------|---------|-------------|
158
- | `TESTKASE_PAT_TOKEN` | Yes | — | Personal Access Token (starts with `xyz_`) |
159
- | `TESTKASE_API_BASE_URL` | No | `https://api.testkase.com` | API endpoint |
160
- | `TESTKASE_ORGANIZATION_ID` | No | your first organization | Set this if you belong to more than one organization |
161
- | `TESTKASE_APP_URL` | No | `https://test-management.testkase.com` | Base for the links returned with results |
68
+ ## Settings
162
69
 
163
- ---
70
+ | Variable | Required | Default |
71
+ |----------|----------|---------|
72
+ | `TESTKASE_PAT_TOKEN` | Yes | |
73
+ | `TESTKASE_API_BASE_URL` | No | `https://api.testkase.com` |
74
+ | `TESTKASE_ORGANIZATION_ID` | No | your first organization (set it if you belong to several) |
75
+ | `TESTKASE_TIMEOUT_MS` | No | `60000` (raise it for very large reports) |
164
76
 
165
- ## Troubleshooting
77
+ Replies are compact by default to save tokens: lists return the key fields, plus one `url` pattern with `{id}`. Add `verbose: true` to any read call to get the full API reply.
166
78
 
167
- | Problem | Solution |
168
- |---------|----------|
169
- | "Authentication required" | Verify your token starts with `xyz_` and hasn't expired |
170
- | Tool not showing up | Restart your AI agent completely; check JSON syntax in config |
171
- | API connection errors | Verify `TESTKASE_API_BASE_URL` is reachable |
79
+ ## Troubleshooting
172
80
 
173
- ---
81
+ | Problem | Fix |
82
+ |---------|-----|
83
+ | "Authentication required" | Check the key starts with `xyz_` and hasn't expired. |
84
+ | Tools don't show up | Restart the assistant fully; check the JSON in your config. |
85
+ | Connection errors | Check `TESTKASE_API_BASE_URL` is reachable. |
174
86
 
175
- ## Links
87
+ ## Testing (contributors)
176
88
 
177
- - [TestKase](https://testkase.com) — Product website
178
- - [Documentation](https://docs.testkase.com) — Full platform docs
179
- - [MCP Protocol](https://modelcontextprotocol.io) — Model Context Protocol spec
89
+ ```bash
90
+ npm test # offline: fake API, every tool and action
91
+ TESTKASE_PAT_TOKEN=xyz_... npm run test:live # real API: create → execute → report → delete in one sandbox project
92
+ npm run tokens -- test/results/tokens-2.4.1.json test/results/tokens-2.5.0.json
93
+ ```
180
94
 
181
- ## License
95
+ The live suite only touches its sandbox project (see `test/live/guard.mjs`), names everything `MCPTEST-…` and deletes it afterwards.
182
96
 
183
- MIT
97
+ ## Links
184
98
 
185
- ---
99
+ [TestKase](https://testkase.com) · [Docs](https://docs.testkase.com) · [`@testkase/device-farm-mcp`](https://www.npmjs.com/package/@testkase/device-farm-mcp)
186
100
 
187
- <p align="center">
188
- Built by <a href="https://testkase.com">TestKase</a>
189
- </p>
101
+ MIT License
@@ -0,0 +1,5 @@
1
+ export declare function compact(value: any): any;
2
+ export declare function pick(obj: any, keys: string[]): any;
3
+ export declare function shapeList(data: any, keys: string[], urlIdKey?: string): any;
4
+ export declare function flattenFolders(nodes: any[], parentPath?: string): any[];
5
+ //# sourceMappingURL=compact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact.d.ts","sourceRoot":"","sources":["../src/compact.ts"],"names":[],"mappings":"AAcA,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAevC;AAcD,wBAAgB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CASlD;AAID,wBAAgB,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,OA0BrE;AAGD,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,UAAU,SAAK,GAAG,GAAG,EAAE,CAWnE"}
@@ -0,0 +1,106 @@
1
+ // Reply trimming: fewer tokens for the AI, same information it needs to act
2
+ const isEmpty = (v) => v === null ||
3
+ v === undefined ||
4
+ (typeof v === "string" && v.trim() === "") ||
5
+ (Array.isArray(v) && v.length === 0) ||
6
+ (typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0);
7
+ const BOILERPLATE = /success/i;
8
+ // Drops null/blank/empty values and, at the top level only, the backend's {status:"success", message, data}
9
+ // envelope. A message is kept when it says something ("Linked 2, 1 already linked"), dropped when it is
10
+ // boilerplate ("Folders fetched successfully"). A reply with nothing left becomes {ok:true}.
11
+ export function compact(value) {
12
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.status !== "success")
13
+ return clean(value);
14
+ const { status, message, data, ...meta } = value;
15
+ const out = {};
16
+ if (typeof message === "string" && message.trim() && !BOILERPLATE.test(message))
17
+ out.message = message;
18
+ Object.assign(out, clean(meta));
19
+ if (data !== undefined) {
20
+ const inner = clean(data);
21
+ if (inner && typeof inner === "object" && !Array.isArray(inner)) {
22
+ for (const [k, v] of Object.entries(inner))
23
+ if (!(k in out))
24
+ out[k] = v;
25
+ }
26
+ else if (!isEmpty(inner) || inner === false || inner === 0) {
27
+ out.data = inner;
28
+ }
29
+ }
30
+ return Object.keys(out).length ? out : { ok: true };
31
+ }
32
+ function clean(value) {
33
+ if (Array.isArray(value))
34
+ return value.map(clean).filter((v) => !isEmpty(v));
35
+ if (!value || typeof value !== "object")
36
+ return value;
37
+ const out = {};
38
+ for (const [k, v] of Object.entries(value)) {
39
+ const c = clean(v);
40
+ if (!isEmpty(c))
41
+ out[k] = c;
42
+ }
43
+ return out;
44
+ }
45
+ // keys: "name" or "out=path.to.value"; keeps only non-empty values
46
+ export function pick(obj, keys) {
47
+ const out = {};
48
+ for (const spec of keys) {
49
+ const [outKey, path] = spec.includes("=") ? spec.split("=") : [spec, spec];
50
+ const v = path.split(".").reduce((o, k) => (o == null ? undefined : o[k]), obj);
51
+ const c = clean(v);
52
+ if (!isEmpty(c))
53
+ out[outKey] = c;
54
+ }
55
+ return out;
56
+ }
57
+ // Finds the list in a backend reply, keeps the given fields per item, and replaces
58
+ // the per-item url with one template (e.g. ".../testcases/{id}") to save tokens
59
+ export function shapeList(data, keys, urlIdKey) {
60
+ const holders = [data, data?.data];
61
+ let arr;
62
+ let meta = {};
63
+ for (const h of holders) {
64
+ if (!h || typeof h !== "object")
65
+ continue;
66
+ const found = Array.isArray(h) ? h : [h.data, h.rows, h.items, h.projects, h.testcases].find(Array.isArray);
67
+ if (found) {
68
+ arr = found;
69
+ meta = Array.isArray(h) ? {} : h;
70
+ break;
71
+ }
72
+ }
73
+ // Unknown shape: better the whole (compacted) reply than an empty list that reads as "nothing found"
74
+ if (!arr)
75
+ return compact(data);
76
+ const out = {};
77
+ const total = meta.total ?? meta.totalCount ?? data?.total;
78
+ if (total !== undefined)
79
+ out.total = Number(total);
80
+ for (const k of ["page", "limit"]) {
81
+ const v = meta[k] ?? data?.[k];
82
+ if (v !== undefined)
83
+ out[k] = v;
84
+ }
85
+ const sample = arr.find((x) => x?.url && urlIdKey && x[urlIdKey]);
86
+ if (sample)
87
+ out.url = String(sample.url).replace(String(sample[urlIdKey]), `{${urlIdKey}}`);
88
+ out.items = arr.map((x) => pick(x, keys));
89
+ return out;
90
+ }
91
+ // Folder tree → flat list with a readable path
92
+ export function flattenFolders(nodes, parentPath = "") {
93
+ const out = [];
94
+ for (const n of nodes ?? []) {
95
+ const path = parentPath ? `${parentPath} / ${n.name}` : n.name;
96
+ const row = { id: Number(n.id), name: n.name };
97
+ if (n.parentId != null && n.parentId !== "")
98
+ row.parent_id = Number(n.parentId);
99
+ row.path = path;
100
+ out.push(row);
101
+ if (Array.isArray(n.children))
102
+ out.push(...flattenFolders(n.children, path));
103
+ }
104
+ return out;
105
+ }
106
+ //# sourceMappingURL=compact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact.js","sourceRoot":"","sources":["../src/compact.ts"],"names":[],"mappings":"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"}