@testledger/mcp 0.0.1 → 0.0.3
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 +9 -11
- package/dist/index.js +54 -104
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Test Reporter MCP Server
|
|
2
2
|
|
|
3
|
-
MCP (Model Context Protocol) server for [Test Ledger](https://testledger.dev) that enables Claude Code to analyze flaky tests, find failure patterns,
|
|
3
|
+
MCP (Model Context Protocol) server for [Test Ledger](https://testledger.dev) that enables Claude Code to analyze flaky tests, find failure patterns, suggest fixes and more.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ Add this to your Claude Code MCP config:
|
|
|
25
25
|
"command": "npx",
|
|
26
26
|
"args": ["-y", "@testledger/mcp"],
|
|
27
27
|
"env": {
|
|
28
|
-
"
|
|
28
|
+
"TEST_LEDGER_API_KEY": "your-api-key-here"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -42,7 +42,7 @@ Once configured, you can ask Claude Code things like:
|
|
|
42
42
|
|
|
43
43
|
- "Why is `checkout.spec.js` flaky?"
|
|
44
44
|
- "What tests have been failing the most this week?"
|
|
45
|
-
- "
|
|
45
|
+
- "Show me recent test failures"
|
|
46
46
|
- "Are there any tests that always fail together?"
|
|
47
47
|
|
|
48
48
|
### With the /fix-flaky-test command
|
|
@@ -71,21 +71,19 @@ The MCP server provides these tools to Claude:
|
|
|
71
71
|
| Tool | Description |
|
|
72
72
|
|------|-------------|
|
|
73
73
|
| `get_test_history` | Pass/fail/flaky statistics for a test |
|
|
74
|
-
| `get_test_errors` | Error messages and stacktraces, grouped by frequency |
|
|
75
74
|
| `get_failure_patterns` | Time-of-day, browser, and version patterns |
|
|
76
75
|
| `get_correlated_failures` | Tests that fail together (shared setup issues) |
|
|
77
76
|
| `get_flaky_tests` | Project-wide flaky test leaderboard |
|
|
78
77
|
| `get_recent_failures` | Recent failures for quick triage |
|
|
79
78
|
| `get_test_trend` | Failure rate over time |
|
|
80
|
-
| `search_errors` | Full-text search across all errors |
|
|
81
79
|
|
|
82
80
|
## Configuration Options
|
|
83
81
|
|
|
84
82
|
| Environment Variable | Required | Description |
|
|
85
|
-
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
83
|
+
|--------------------------|-----|------------------------------------------------------------|
|
|
84
|
+
| `TEST_LEDGER_API_KEY` | Yes | Your API key from the dashboard |
|
|
85
|
+
| `TEST_LEDGER_API_URL` | No | Custom API URL (default: `https://app-api.testledger.dev`) |
|
|
86
|
+
| `TEST_LEDGER_PROJECT_ID` | No | Default project ID to use for queries |
|
|
89
87
|
|
|
90
88
|
### Example with all options
|
|
91
89
|
|
|
@@ -96,8 +94,8 @@ The MCP server provides these tools to Claude:
|
|
|
96
94
|
"command": "npx",
|
|
97
95
|
"args": ["-y", "@testledger/mcp"],
|
|
98
96
|
"env": {
|
|
99
|
-
"
|
|
100
|
-
"
|
|
97
|
+
"TEST_LEDGER_API_KEY": "tr_live_abc123",
|
|
98
|
+
"TEST_LEDGER_PROJECT_ID": "42"
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,33 +3,49 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
// Configuration from environment
|
|
6
|
-
const API_BASE_URL = process.env.
|
|
7
|
-
const API_KEY = process.env.
|
|
8
|
-
const DEFAULT_PROJECT_ID = process.env.
|
|
9
|
-
//
|
|
6
|
+
const API_BASE_URL = process.env.TEST_LEDGER_API_URL;
|
|
7
|
+
const API_KEY = process.env.TEST_LEDGER_API_KEY || "";
|
|
8
|
+
const DEFAULT_PROJECT_ID = process.env.TEST_LEDGER_PROJECT_ID;
|
|
9
|
+
// API request timeout (25s to stay under 30s gateway limit)
|
|
10
|
+
const API_TIMEOUT_MS = 25000;
|
|
11
|
+
// Helper to make API calls with timeout
|
|
10
12
|
async function apiCall(endpoint, params = {}) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timeout = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(endpoint, API_BASE_URL);
|
|
17
|
+
// Inject default project ID if not provided
|
|
18
|
+
if (DEFAULT_PROJECT_ID && !params.project_id) {
|
|
19
|
+
params.project_id = DEFAULT_PROJECT_ID;
|
|
20
|
+
}
|
|
21
|
+
// Add query params
|
|
22
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
23
|
+
if (value !== undefined && value !== null) {
|
|
24
|
+
url.searchParams.append(key, String(value));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const response = await fetch(url.toString(), {
|
|
28
|
+
headers: {
|
|
29
|
+
"Authorization": `Bearer ${API_KEY}`,
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
},
|
|
32
|
+
signal: controller.signal,
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const error = await response.text();
|
|
36
|
+
throw new Error(`API error ${response.status}: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
return response.json();
|
|
15
39
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
url.searchParams.append(key, String(value));
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
42
|
+
throw new Error(`Request timeout after ${API_TIMEOUT_MS / 1000}s. Try reducing 'days' or 'limit' parameters.`);
|
|
20
43
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"Content-Type": "application/json",
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
if (!response.ok) {
|
|
29
|
-
const error = await response.text();
|
|
30
|
-
throw new Error(`API error ${response.status}: ${error}`);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
clearTimeout(timeout);
|
|
31
48
|
}
|
|
32
|
-
return response.json();
|
|
33
49
|
}
|
|
34
50
|
// Define the tools
|
|
35
51
|
const tools = [
|
|
@@ -60,38 +76,6 @@ const tools = [
|
|
|
60
76
|
required: ["spec_file"],
|
|
61
77
|
},
|
|
62
78
|
},
|
|
63
|
-
{
|
|
64
|
-
name: "get_test_errors",
|
|
65
|
-
description: "Get error messages and stacktraces for a test's failures, grouped by unique error. Use this to see what errors are occurring and how often.",
|
|
66
|
-
inputSchema: {
|
|
67
|
-
type: "object",
|
|
68
|
-
properties: {
|
|
69
|
-
spec_file: {
|
|
70
|
-
type: "string",
|
|
71
|
-
description: "The spec file path",
|
|
72
|
-
},
|
|
73
|
-
test_title: {
|
|
74
|
-
type: "string",
|
|
75
|
-
description: "Specific test title (optional)",
|
|
76
|
-
},
|
|
77
|
-
project_id: {
|
|
78
|
-
type: "number",
|
|
79
|
-
description: "Project ID to filter by (optional)",
|
|
80
|
-
},
|
|
81
|
-
days: {
|
|
82
|
-
type: "number",
|
|
83
|
-
description: "Days to look back (default: 30)",
|
|
84
|
-
default: 30,
|
|
85
|
-
},
|
|
86
|
-
limit: {
|
|
87
|
-
type: "number",
|
|
88
|
-
description: "Maximum number of unique errors to return (default: 20)",
|
|
89
|
-
default: 20,
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
required: ["spec_file"],
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
79
|
{
|
|
96
80
|
name: "get_failure_patterns",
|
|
97
81
|
description: "Analyze when and how tests fail to identify patterns. Returns failure rates by hour, day of week, version, browser/site, and duration analysis.",
|
|
@@ -153,7 +137,7 @@ const tools = [
|
|
|
153
137
|
},
|
|
154
138
|
{
|
|
155
139
|
name: "get_flaky_tests",
|
|
156
|
-
description: "Get a list of flaky tests (tests that fail then pass on retry) across the project, sorted by flakiness rate.",
|
|
140
|
+
description: "Get a list of flaky tests (tests that fail then pass on retry) across the project, sorted by flakiness rate. Note: This scans all tests - use smaller 'days' values for faster results.",
|
|
157
141
|
inputSchema: {
|
|
158
142
|
type: "object",
|
|
159
143
|
properties: {
|
|
@@ -163,8 +147,8 @@ const tools = [
|
|
|
163
147
|
},
|
|
164
148
|
days: {
|
|
165
149
|
type: "number",
|
|
166
|
-
description: "Days to look back (default:
|
|
167
|
-
default:
|
|
150
|
+
description: "Days to look back (default: 3). Use smaller values for faster results.",
|
|
151
|
+
default: 3,
|
|
168
152
|
},
|
|
169
153
|
min_flaky_rate: {
|
|
170
154
|
type: "number",
|
|
@@ -173,15 +157,15 @@ const tools = [
|
|
|
173
157
|
},
|
|
174
158
|
limit: {
|
|
175
159
|
type: "number",
|
|
176
|
-
description: "Maximum results to return (default:
|
|
177
|
-
default:
|
|
160
|
+
description: "Maximum results to return (default: 20)",
|
|
161
|
+
default: 20,
|
|
178
162
|
},
|
|
179
163
|
},
|
|
180
164
|
},
|
|
181
165
|
},
|
|
182
166
|
{
|
|
183
167
|
name: "get_recent_failures",
|
|
184
|
-
description: "Get the most recent test failures for quick triage. Useful for seeing what's currently broken.",
|
|
168
|
+
description: "Get the most recent test failures for quick triage. Useful for seeing what's currently broken. For faster results, provide a spec_file filter.",
|
|
185
169
|
inputSchema: {
|
|
186
170
|
type: "object",
|
|
187
171
|
properties: {
|
|
@@ -191,7 +175,7 @@ const tools = [
|
|
|
191
175
|
},
|
|
192
176
|
spec_file: {
|
|
193
177
|
type: "string",
|
|
194
|
-
description: "Filter by spec file (
|
|
178
|
+
description: "Filter by spec file (recommended for faster results)",
|
|
195
179
|
},
|
|
196
180
|
hours: {
|
|
197
181
|
type: "number",
|
|
@@ -200,8 +184,8 @@ const tools = [
|
|
|
200
184
|
},
|
|
201
185
|
limit: {
|
|
202
186
|
type: "number",
|
|
203
|
-
description: "Maximum results (default:
|
|
204
|
-
default:
|
|
187
|
+
description: "Maximum results (default: 20)",
|
|
188
|
+
default: 20,
|
|
205
189
|
},
|
|
206
190
|
},
|
|
207
191
|
},
|
|
@@ -239,34 +223,6 @@ const tools = [
|
|
|
239
223
|
required: ["spec_file"],
|
|
240
224
|
},
|
|
241
225
|
},
|
|
242
|
-
{
|
|
243
|
-
name: "search_errors",
|
|
244
|
-
description: "Full-text search across error messages and stacktraces. Use this to find all tests affected by a specific type of error.",
|
|
245
|
-
inputSchema: {
|
|
246
|
-
type: "object",
|
|
247
|
-
properties: {
|
|
248
|
-
query: {
|
|
249
|
-
type: "string",
|
|
250
|
-
description: "Search term (e.g., 'timeout', 'element not found', 'ECONNREFUSED')",
|
|
251
|
-
},
|
|
252
|
-
project_id: {
|
|
253
|
-
type: "number",
|
|
254
|
-
description: "Project ID to filter by (optional)",
|
|
255
|
-
},
|
|
256
|
-
days: {
|
|
257
|
-
type: "number",
|
|
258
|
-
description: "Days to look back (default: 30)",
|
|
259
|
-
default: 30,
|
|
260
|
-
},
|
|
261
|
-
limit: {
|
|
262
|
-
type: "number",
|
|
263
|
-
description: "Maximum results (default: 50)",
|
|
264
|
-
default: 50,
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
required: ["query"],
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
226
|
];
|
|
271
227
|
// Create the server
|
|
272
228
|
const server = new Server({
|
|
@@ -288,28 +244,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
288
244
|
let result;
|
|
289
245
|
switch (name) {
|
|
290
246
|
case "get_test_history":
|
|
291
|
-
result = await apiCall("/
|
|
292
|
-
break;
|
|
293
|
-
case "get_test_errors":
|
|
294
|
-
result = await apiCall("/api/tests/errors", args);
|
|
247
|
+
result = await apiCall("/tests/history", args);
|
|
295
248
|
break;
|
|
296
249
|
case "get_failure_patterns":
|
|
297
|
-
result = await apiCall("/
|
|
250
|
+
result = await apiCall("/tests/patterns", args);
|
|
298
251
|
break;
|
|
299
252
|
case "get_correlated_failures":
|
|
300
|
-
result = await apiCall("/
|
|
253
|
+
result = await apiCall("/tests/correlations", args);
|
|
301
254
|
break;
|
|
302
255
|
case "get_flaky_tests":
|
|
303
|
-
result = await apiCall("/
|
|
256
|
+
result = await apiCall("/tests/flaky", args);
|
|
304
257
|
break;
|
|
305
258
|
case "get_recent_failures":
|
|
306
|
-
result = await apiCall("/
|
|
259
|
+
result = await apiCall("/tests/recent-failures", args);
|
|
307
260
|
break;
|
|
308
261
|
case "get_test_trend":
|
|
309
|
-
result = await apiCall("/
|
|
310
|
-
break;
|
|
311
|
-
case "search_errors":
|
|
312
|
-
result = await apiCall("/api/tests/search-errors", args);
|
|
262
|
+
result = await apiCall("/tests/trend", args);
|
|
313
263
|
break;
|
|
314
264
|
default:
|
|
315
265
|
throw new Error(`Unknown tool: ${name}`);
|