@gaffer-sh/mcp 0.4.0 → 0.4.2
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 +166 -118
- package/dist/index.js +162 -282
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,12 @@ MCP (Model Context Protocol) server for [Gaffer](https://gaffer.sh) - give your
|
|
|
4
4
|
|
|
5
5
|
## What is this?
|
|
6
6
|
|
|
7
|
-
This MCP server connects AI coding assistants like Claude Code and Cursor to your Gaffer test history. It allows AI to:
|
|
7
|
+
This MCP server connects AI coding assistants like Claude Code and Cursor to your Gaffer test history and coverage data. It allows AI to:
|
|
8
8
|
|
|
9
9
|
- Check your project's test health (pass rate, flaky tests, trends)
|
|
10
10
|
- Look up the history of specific tests to understand stability
|
|
11
11
|
- Get context about test failures when debugging
|
|
12
|
+
- Analyze code coverage and identify untested areas
|
|
12
13
|
- Browse all your projects (with user API Keys)
|
|
13
14
|
- Access test report files (HTML reports, coverage, etc.)
|
|
14
15
|
|
|
@@ -17,25 +18,19 @@ This MCP server connects AI coding assistants like Claude Code and Cursor to you
|
|
|
17
18
|
1. A [Gaffer](https://gaffer.sh) account with test results uploaded
|
|
18
19
|
2. An API Key from Account Settings > API Keys
|
|
19
20
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
The MCP server supports two types of authentication:
|
|
23
|
-
|
|
24
|
-
### User API Keys (Recommended)
|
|
25
|
-
|
|
26
|
-
User API Keys (`gaf_` prefix) provide read-only access to all projects across your organizations. This is the recommended approach as it allows your AI assistant to work across multiple projects.
|
|
27
|
-
|
|
28
|
-
Get your API Key from: **Account Settings > API Keys**
|
|
21
|
+
## Setup
|
|
29
22
|
|
|
30
|
-
###
|
|
23
|
+
### Claude Code (CLI)
|
|
31
24
|
|
|
32
|
-
|
|
25
|
+
The easiest way to add the Gaffer MCP server is via the Claude Code CLI:
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add gaffer -e GAFFER_API_KEY=gaf_your_api_key_here -- npx -y @gaffer-sh/mcp
|
|
29
|
+
```
|
|
35
30
|
|
|
36
|
-
### Claude Code
|
|
31
|
+
### Claude Code (Manual)
|
|
37
32
|
|
|
38
|
-
|
|
33
|
+
Alternatively, add to your Claude Code settings (`~/.claude.json` or project `.claude/settings.json`):
|
|
39
34
|
|
|
40
35
|
```json
|
|
41
36
|
{
|
|
@@ -71,144 +66,197 @@ Add to `.cursor/mcp.json` in your project:
|
|
|
71
66
|
|
|
72
67
|
## Available Tools
|
|
73
68
|
|
|
74
|
-
###
|
|
69
|
+
### Project & Test Run Tools
|
|
75
70
|
|
|
76
|
-
|
|
71
|
+
| Tool | Description |
|
|
72
|
+
|------|-------------|
|
|
73
|
+
| `list_projects` | List all projects you have access to |
|
|
74
|
+
| `get_project_health` | Get health metrics (pass rate, flaky count, trends) |
|
|
75
|
+
| `list_test_runs` | List recent test runs with optional filtering |
|
|
76
|
+
| `get_test_run_details` | Get parsed test results for a specific test run |
|
|
77
|
+
| `get_report` | Get report file URLs for a test run |
|
|
78
|
+
| `get_report_browser_url` | Get a browser-navigable URL for viewing reports |
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
- `organizationId` (optional): Filter by organization
|
|
80
|
-
- `limit` (optional): Max results (default: 50)
|
|
80
|
+
### Test Analysis Tools
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
| Tool | Description |
|
|
83
|
+
|------|-------------|
|
|
84
|
+
| `get_test_history` | Get pass/fail history for a specific test |
|
|
85
|
+
| `get_flaky_tests` | Get tests with high flip rates (pass↔fail) |
|
|
86
|
+
| `get_slowest_tests` | Get slowest tests by P95 duration |
|
|
87
|
+
| `compare_test_metrics` | Compare test performance between commits |
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
### Coverage Tools
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
| Tool | Description |
|
|
92
|
+
|------|-------------|
|
|
93
|
+
| `get_coverage_summary` | Get overall coverage metrics and trends |
|
|
94
|
+
| `get_coverage_for_file` | Get coverage for specific files or paths |
|
|
95
|
+
| `get_untested_files` | Get files below a coverage threshold |
|
|
96
|
+
| `find_uncovered_failure_areas` | Find files with low coverage AND test failures |
|
|
88
97
|
|
|
89
|
-
|
|
98
|
+
## Tool Details
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
- `projectId` (required with user API Keys): Project ID from `list_projects`
|
|
93
|
-
- `days` (optional): Number of days to analyze (default: 30)
|
|
100
|
+
### `list_projects`
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
- Health score (0-100)
|
|
97
|
-
- Pass rate percentage
|
|
98
|
-
- Number of test runs
|
|
99
|
-
- Flaky test count
|
|
100
|
-
- Trend (up/down/stable)
|
|
102
|
+
List all projects you have access to.
|
|
101
103
|
|
|
102
|
-
**
|
|
104
|
+
- **Input:** `organizationId` (optional), `limit` (optional, default: 50)
|
|
105
|
+
- **Returns:** List of projects with IDs, names, and organization info
|
|
106
|
+
- **Example:** "What projects do I have in Gaffer?"
|
|
103
107
|
|
|
104
|
-
### `
|
|
108
|
+
### `get_project_health`
|
|
105
109
|
|
|
106
|
-
Get the
|
|
110
|
+
Get the health metrics for a project.
|
|
107
111
|
|
|
108
|
-
**Input:**
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
- **Input:** `projectId` (required), `days` (optional, default: 30)
|
|
113
|
+
- **Returns:** Health score (0-100), pass rate, test run count, flaky test count, trend
|
|
114
|
+
- **Example:** "What's the health of my test suite?"
|
|
115
|
+
|
|
116
|
+
### `get_test_history`
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
- History of test runs with pass/fail status
|
|
116
|
-
- Duration of each run
|
|
117
|
-
- Branch and commit info
|
|
118
|
-
- Error messages for failures
|
|
119
|
-
- Summary statistics
|
|
118
|
+
Get the pass/fail history for a specific test.
|
|
120
119
|
|
|
121
|
-
**
|
|
122
|
-
-
|
|
123
|
-
- "
|
|
120
|
+
- **Input:** `projectId` (required), `testName` or `filePath` (one required), `limit` (optional)
|
|
121
|
+
- **Returns:** History of runs with status, duration, branch, commit, errors
|
|
122
|
+
- **Example:** "Is the login test flaky? Check its history"
|
|
124
123
|
|
|
125
124
|
### `get_flaky_tests`
|
|
126
125
|
|
|
127
126
|
Get the list of flaky tests in a project.
|
|
128
127
|
|
|
129
|
-
**Input:**
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
- `limit` (optional): Max results (default: 50)
|
|
133
|
-
- `days` (optional): Analysis period in days (default: 30)
|
|
134
|
-
|
|
135
|
-
**Returns:**
|
|
136
|
-
- List of flaky tests with flip rates
|
|
137
|
-
- Number of status transitions
|
|
138
|
-
- Total runs analyzed
|
|
139
|
-
- When each test last ran
|
|
140
|
-
|
|
141
|
-
**Example prompts:**
|
|
142
|
-
- "Which tests are flaky in my project?"
|
|
143
|
-
- "Show me the most unstable tests"
|
|
128
|
+
- **Input:** `projectId` (required), `threshold` (optional, default: 0.1), `days` (optional), `limit` (optional)
|
|
129
|
+
- **Returns:** List of flaky tests with flip rates, transition counts, run counts
|
|
130
|
+
- **Example:** "Which tests are flaky in my project?"
|
|
144
131
|
|
|
145
132
|
### `list_test_runs`
|
|
146
133
|
|
|
147
134
|
List recent test runs with optional filtering.
|
|
148
135
|
|
|
149
|
-
**Input:**
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
- `limit` (optional): Max results (default: 20)
|
|
136
|
+
- **Input:** `projectId` (required), `commitSha` (optional), `branch` (optional), `status` (optional), `limit` (optional)
|
|
137
|
+
- **Returns:** List of test runs with pass/fail/skip counts, commit and branch info
|
|
138
|
+
- **Example:** "What tests failed in the last commit?"
|
|
139
|
+
|
|
140
|
+
### `get_test_run_details`
|
|
155
141
|
|
|
156
|
-
|
|
157
|
-
- List of test runs with pass/fail/skip counts
|
|
158
|
-
- Commit SHA and branch info
|
|
159
|
-
- Pagination info
|
|
142
|
+
Get parsed test results for a specific test run.
|
|
160
143
|
|
|
161
|
-
**
|
|
162
|
-
-
|
|
163
|
-
- "Show me
|
|
164
|
-
- "Did any tests fail on my feature branch?"
|
|
144
|
+
- **Input:** `testRunId` (required), `projectId` (required), `status` (optional filter), `limit` (optional)
|
|
145
|
+
- **Returns:** Individual test results with name, status, duration, file path, errors
|
|
146
|
+
- **Example:** "Show me all failed tests from this test run"
|
|
165
147
|
|
|
166
148
|
### `get_report`
|
|
167
149
|
|
|
168
|
-
Get
|
|
150
|
+
Get URLs for report files uploaded with a test run.
|
|
169
151
|
|
|
170
|
-
**Input:**
|
|
171
|
-
-
|
|
152
|
+
- **Input:** `testRunId` (required)
|
|
153
|
+
- **Returns:** List of files with filename, size, content type, download URL
|
|
154
|
+
- **Example:** "Get the Playwright report for the latest test run"
|
|
172
155
|
|
|
173
|
-
|
|
174
|
-
- Test run ID and project info
|
|
175
|
-
- Framework used (e.g., playwright, vitest)
|
|
176
|
-
- List of files with:
|
|
177
|
-
- Filename (e.g., "report.html", "coverage/index.html")
|
|
178
|
-
- File size in bytes
|
|
179
|
-
- Content type (e.g., "text/html")
|
|
180
|
-
- Download URL
|
|
156
|
+
### `get_report_browser_url`
|
|
181
157
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
-
|
|
185
|
-
-
|
|
158
|
+
Get a browser-navigable URL for viewing a test report.
|
|
159
|
+
|
|
160
|
+
- **Input:** `projectId` (required), `testRunId` (required), `filename` (optional)
|
|
161
|
+
- **Returns:** Signed URL valid for 30 minutes
|
|
162
|
+
- **Example:** "Give me a link to view the test report"
|
|
186
163
|
|
|
187
164
|
### `get_slowest_tests`
|
|
188
165
|
|
|
189
|
-
Get the slowest tests in a project, sorted by P95 duration.
|
|
190
|
-
|
|
191
|
-
**Input:**
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
**
|
|
209
|
-
- "
|
|
210
|
-
|
|
211
|
-
|
|
166
|
+
Get the slowest tests in a project, sorted by P95 duration.
|
|
167
|
+
|
|
168
|
+
- **Input:** `projectId` (required), `days` (optional), `limit` (optional), `framework` (optional), `branch` (optional)
|
|
169
|
+
- **Returns:** List of tests with average and P95 duration, run count
|
|
170
|
+
- **Example:** "Which tests are slowing down my CI pipeline?"
|
|
171
|
+
|
|
172
|
+
### `compare_test_metrics`
|
|
173
|
+
|
|
174
|
+
Compare test metrics between two commits or test runs.
|
|
175
|
+
|
|
176
|
+
- **Input:** `projectId` (required), `testName` (required), `beforeCommit`/`afterCommit` OR `beforeRunId`/`afterRunId`
|
|
177
|
+
- **Returns:** Before/after metrics with duration change and percentage
|
|
178
|
+
- **Example:** "Did my fix make this test faster?"
|
|
179
|
+
|
|
180
|
+
### `get_coverage_summary`
|
|
181
|
+
|
|
182
|
+
Get the coverage metrics summary for a project.
|
|
183
|
+
|
|
184
|
+
- **Input:** `projectId` (required), `days` (optional, default: 30)
|
|
185
|
+
- **Returns:** Line/branch/function coverage percentages, trend, report count, lowest coverage files
|
|
186
|
+
- **Example:** "What's our test coverage?"
|
|
187
|
+
|
|
188
|
+
### `get_coverage_for_file`
|
|
189
|
+
|
|
190
|
+
Get coverage metrics for specific files or paths.
|
|
191
|
+
|
|
192
|
+
- **Input:** `projectId` (required), `filePath` (required - exact or partial match)
|
|
193
|
+
- **Returns:** List of matching files with line/branch/function coverage
|
|
194
|
+
- **Example:** "What's the coverage for our API routes?"
|
|
195
|
+
|
|
196
|
+
### `get_untested_files`
|
|
197
|
+
|
|
198
|
+
Get files with little or no test coverage.
|
|
199
|
+
|
|
200
|
+
- **Input:** `projectId` (required), `maxCoverage` (optional, default: 10%), `limit` (optional)
|
|
201
|
+
- **Returns:** List of files below threshold sorted by coverage (lowest first)
|
|
202
|
+
- **Example:** "Which files have no tests?"
|
|
203
|
+
|
|
204
|
+
### `find_uncovered_failure_areas`
|
|
205
|
+
|
|
206
|
+
Find code areas with both low coverage AND test failures (high risk).
|
|
207
|
+
|
|
208
|
+
- **Input:** `projectId` (required), `days` (optional), `coverageThreshold` (optional, default: 80%)
|
|
209
|
+
- **Returns:** Risk areas ranked by score, with file path, coverage %, failure count
|
|
210
|
+
- **Example:** "Where should we focus our testing efforts?"
|
|
211
|
+
|
|
212
|
+
## Prioritizing Coverage Improvements
|
|
213
|
+
|
|
214
|
+
When using coverage tools to improve your test suite, combine coverage data with codebase exploration for best results:
|
|
215
|
+
|
|
216
|
+
### 1. Understand Code Utilization
|
|
217
|
+
|
|
218
|
+
Before targeting files purely by coverage percentage, explore which code is actually critical:
|
|
219
|
+
|
|
220
|
+
- **Find entry points:** Look for route definitions, event handlers, exported functions - these reveal what code actually executes in production
|
|
221
|
+
- **Find heavily-imported files:** Files imported by many others are high-value targets
|
|
222
|
+
- **Identify critical business logic:** Look for files handling auth, payments, data mutations, or core domain logic
|
|
223
|
+
|
|
224
|
+
### 2. Prioritize by Impact
|
|
225
|
+
|
|
226
|
+
Low coverage alone doesn't indicate priority. Consider:
|
|
227
|
+
|
|
228
|
+
- **High utilization + low coverage = highest priority** - Code that runs frequently but lacks tests
|
|
229
|
+
- **Large files with 0% coverage** - More uncovered lines means bigger impact on overall coverage
|
|
230
|
+
- **Files with both failures and low coverage** - Use `find_uncovered_failure_areas` for this
|
|
231
|
+
|
|
232
|
+
### 3. Use Path-Based Queries
|
|
233
|
+
|
|
234
|
+
The `get_untested_files` tool may return many frontend components. For backend or specific areas:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
# Query specific paths with get_coverage_for_file
|
|
238
|
+
get_coverage_for_file(filePath="server/services")
|
|
239
|
+
get_coverage_for_file(filePath="src/api")
|
|
240
|
+
get_coverage_for_file(filePath="lib/core")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 4. Iterative Improvement
|
|
244
|
+
|
|
245
|
+
1. Get baseline with `get_coverage_summary`
|
|
246
|
+
2. Identify targets with `get_coverage_for_file` on critical paths
|
|
247
|
+
3. Write tests for highest-impact files
|
|
248
|
+
4. Re-check coverage after CI uploads new results
|
|
249
|
+
5. Repeat
|
|
250
|
+
|
|
251
|
+
## Authentication
|
|
252
|
+
|
|
253
|
+
### User API Keys (Recommended)
|
|
254
|
+
|
|
255
|
+
User API Keys (`gaf_` prefix) provide read-only access to all projects across your organizations. Get your API Key from: **Account Settings > API Keys**
|
|
256
|
+
|
|
257
|
+
### Project Upload Tokens (Legacy)
|
|
258
|
+
|
|
259
|
+
Project Upload Tokens (`gfr_` prefix) are designed for uploading test results and only provide access to a single project. User API Keys are preferred for the MCP server.
|
|
212
260
|
|
|
213
261
|
## Environment Variables
|
|
214
262
|
|
package/dist/index.js
CHANGED
|
@@ -668,7 +668,16 @@ Returns:
|
|
|
668
668
|
- Branch coverage (covered/total/percentage)
|
|
669
669
|
- Function coverage (covered/total/percentage)
|
|
670
670
|
|
|
671
|
-
|
|
671
|
+
This is the preferred tool for targeted coverage analysis. Use path prefixes to focus on
|
|
672
|
+
specific areas of the codebase:
|
|
673
|
+
- "server/services" - Backend service layer
|
|
674
|
+
- "server/utils" - Backend utilities
|
|
675
|
+
- "src/api" - API routes
|
|
676
|
+
- "lib/core" - Core business logic
|
|
677
|
+
|
|
678
|
+
Before querying, explore the codebase to identify critical paths - entry points,
|
|
679
|
+
heavily-imported files, and code handling auth/payments/data mutations.
|
|
680
|
+
Prioritize: high utilization + low coverage = highest impact.`
|
|
672
681
|
};
|
|
673
682
|
|
|
674
683
|
// src/tools/get-coverage-summary.ts
|
|
@@ -726,7 +735,11 @@ Returns:
|
|
|
726
735
|
- Latest report date
|
|
727
736
|
- Top 5 files with lowest coverage
|
|
728
737
|
|
|
729
|
-
Use this to understand your project's overall test coverage health
|
|
738
|
+
Use this to understand your project's overall test coverage health.
|
|
739
|
+
|
|
740
|
+
After getting the summary, use get_coverage_for_file with path prefixes to drill into
|
|
741
|
+
specific areas (e.g., "server/services", "src/api", "lib/core"). This helps identify
|
|
742
|
+
high-value targets in critical code paths rather than just the files with lowest coverage.`
|
|
730
743
|
};
|
|
731
744
|
|
|
732
745
|
// src/tools/get-flaky-tests.ts
|
|
@@ -1280,7 +1293,13 @@ Returns:
|
|
|
1280
1293
|
- Each file includes line/branch/function coverage metrics
|
|
1281
1294
|
- Total count of files matching the criteria
|
|
1282
1295
|
|
|
1283
|
-
|
|
1296
|
+
IMPORTANT: Results may be dominated by certain file types (e.g., UI components) that are
|
|
1297
|
+
numerous but not necessarily the highest priority. For targeted analysis of specific code
|
|
1298
|
+
areas (backend, services, utilities), use get_coverage_for_file with path prefixes instead.
|
|
1299
|
+
|
|
1300
|
+
To prioritize effectively, explore the codebase to understand which code is heavily utilized
|
|
1301
|
+
(entry points, frequently-imported files, critical business logic) and then query coverage
|
|
1302
|
+
for those specific paths.`
|
|
1284
1303
|
};
|
|
1285
1304
|
|
|
1286
1305
|
// src/tools/list-projects.ts
|
|
@@ -1424,6 +1443,28 @@ function handleToolError(toolName, error) {
|
|
|
1424
1443
|
isError: true
|
|
1425
1444
|
};
|
|
1426
1445
|
}
|
|
1446
|
+
function registerTool(server, client, tool) {
|
|
1447
|
+
server.registerTool(
|
|
1448
|
+
tool.metadata.name,
|
|
1449
|
+
{
|
|
1450
|
+
title: tool.metadata.title,
|
|
1451
|
+
description: tool.metadata.description,
|
|
1452
|
+
inputSchema: tool.inputSchema,
|
|
1453
|
+
outputSchema: tool.outputSchema
|
|
1454
|
+
},
|
|
1455
|
+
async (input) => {
|
|
1456
|
+
try {
|
|
1457
|
+
const output = await tool.execute(client, input);
|
|
1458
|
+
return {
|
|
1459
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1460
|
+
structuredContent: output
|
|
1461
|
+
};
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
return handleToolError(tool.metadata.name, error);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1427
1468
|
async function main() {
|
|
1428
1469
|
const apiKey = process.env.GAFFER_API_KEY;
|
|
1429
1470
|
if (!apiKey) {
|
|
@@ -1446,290 +1487,129 @@ async function main() {
|
|
|
1446
1487
|
process.exit(1);
|
|
1447
1488
|
}
|
|
1448
1489
|
const client = GafferApiClient.fromEnv();
|
|
1449
|
-
const server = new McpServer(
|
|
1450
|
-
name: "gaffer",
|
|
1451
|
-
version: "0.1.0"
|
|
1452
|
-
});
|
|
1453
|
-
server.registerTool(
|
|
1454
|
-
getProjectHealthMetadata.name,
|
|
1455
|
-
{
|
|
1456
|
-
title: getProjectHealthMetadata.title,
|
|
1457
|
-
description: getProjectHealthMetadata.description,
|
|
1458
|
-
inputSchema: getProjectHealthInputSchema,
|
|
1459
|
-
outputSchema: getProjectHealthOutputSchema
|
|
1460
|
-
},
|
|
1461
|
-
async (input) => {
|
|
1462
|
-
try {
|
|
1463
|
-
const output = await executeGetProjectHealth(client, input);
|
|
1464
|
-
return {
|
|
1465
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1466
|
-
structuredContent: output
|
|
1467
|
-
};
|
|
1468
|
-
} catch (error) {
|
|
1469
|
-
return handleToolError(getProjectHealthMetadata.name, error);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
);
|
|
1473
|
-
server.registerTool(
|
|
1474
|
-
getTestHistoryMetadata.name,
|
|
1475
|
-
{
|
|
1476
|
-
title: getTestHistoryMetadata.title,
|
|
1477
|
-
description: getTestHistoryMetadata.description,
|
|
1478
|
-
inputSchema: getTestHistoryInputSchema,
|
|
1479
|
-
outputSchema: getTestHistoryOutputSchema
|
|
1480
|
-
},
|
|
1481
|
-
async (input) => {
|
|
1482
|
-
try {
|
|
1483
|
-
const output = await executeGetTestHistory(client, input);
|
|
1484
|
-
return {
|
|
1485
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1486
|
-
structuredContent: output
|
|
1487
|
-
};
|
|
1488
|
-
} catch (error) {
|
|
1489
|
-
return handleToolError(getTestHistoryMetadata.name, error);
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
);
|
|
1493
|
-
server.registerTool(
|
|
1494
|
-
getFlakyTestsMetadata.name,
|
|
1495
|
-
{
|
|
1496
|
-
title: getFlakyTestsMetadata.title,
|
|
1497
|
-
description: getFlakyTestsMetadata.description,
|
|
1498
|
-
inputSchema: getFlakyTestsInputSchema,
|
|
1499
|
-
outputSchema: getFlakyTestsOutputSchema
|
|
1500
|
-
},
|
|
1501
|
-
async (input) => {
|
|
1502
|
-
try {
|
|
1503
|
-
const output = await executeGetFlakyTests(client, input);
|
|
1504
|
-
return {
|
|
1505
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1506
|
-
structuredContent: output
|
|
1507
|
-
};
|
|
1508
|
-
} catch (error) {
|
|
1509
|
-
return handleToolError(getFlakyTestsMetadata.name, error);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
);
|
|
1513
|
-
server.registerTool(
|
|
1514
|
-
listTestRunsMetadata.name,
|
|
1515
|
-
{
|
|
1516
|
-
title: listTestRunsMetadata.title,
|
|
1517
|
-
description: listTestRunsMetadata.description,
|
|
1518
|
-
inputSchema: listTestRunsInputSchema,
|
|
1519
|
-
outputSchema: listTestRunsOutputSchema
|
|
1520
|
-
},
|
|
1521
|
-
async (input) => {
|
|
1522
|
-
try {
|
|
1523
|
-
const output = await executeListTestRuns(client, input);
|
|
1524
|
-
return {
|
|
1525
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1526
|
-
structuredContent: output
|
|
1527
|
-
};
|
|
1528
|
-
} catch (error) {
|
|
1529
|
-
return handleToolError(listTestRunsMetadata.name, error);
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
);
|
|
1533
|
-
server.registerTool(
|
|
1534
|
-
listProjectsMetadata.name,
|
|
1535
|
-
{
|
|
1536
|
-
title: listProjectsMetadata.title,
|
|
1537
|
-
description: listProjectsMetadata.description,
|
|
1538
|
-
inputSchema: listProjectsInputSchema,
|
|
1539
|
-
outputSchema: listProjectsOutputSchema
|
|
1540
|
-
},
|
|
1541
|
-
async (input) => {
|
|
1542
|
-
try {
|
|
1543
|
-
const output = await executeListProjects(client, input);
|
|
1544
|
-
return {
|
|
1545
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1546
|
-
structuredContent: output
|
|
1547
|
-
};
|
|
1548
|
-
} catch (error) {
|
|
1549
|
-
return handleToolError(listProjectsMetadata.name, error);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
);
|
|
1553
|
-
server.registerTool(
|
|
1554
|
-
getReportMetadata.name,
|
|
1555
|
-
{
|
|
1556
|
-
title: getReportMetadata.title,
|
|
1557
|
-
description: getReportMetadata.description,
|
|
1558
|
-
inputSchema: getReportInputSchema,
|
|
1559
|
-
outputSchema: getReportOutputSchema
|
|
1560
|
-
},
|
|
1561
|
-
async (input) => {
|
|
1562
|
-
try {
|
|
1563
|
-
const output = await executeGetReport(client, input);
|
|
1564
|
-
return {
|
|
1565
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1566
|
-
structuredContent: output
|
|
1567
|
-
};
|
|
1568
|
-
} catch (error) {
|
|
1569
|
-
return handleToolError(getReportMetadata.name, error);
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
);
|
|
1573
|
-
server.registerTool(
|
|
1574
|
-
getSlowestTestsMetadata.name,
|
|
1575
|
-
{
|
|
1576
|
-
title: getSlowestTestsMetadata.title,
|
|
1577
|
-
description: getSlowestTestsMetadata.description,
|
|
1578
|
-
inputSchema: getSlowestTestsInputSchema,
|
|
1579
|
-
outputSchema: getSlowestTestsOutputSchema
|
|
1580
|
-
},
|
|
1581
|
-
async (input) => {
|
|
1582
|
-
try {
|
|
1583
|
-
const output = await executeGetSlowestTests(client, input);
|
|
1584
|
-
return {
|
|
1585
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1586
|
-
structuredContent: output
|
|
1587
|
-
};
|
|
1588
|
-
} catch (error) {
|
|
1589
|
-
return handleToolError(getSlowestTestsMetadata.name, error);
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
);
|
|
1593
|
-
server.registerTool(
|
|
1594
|
-
getTestRunDetailsMetadata.name,
|
|
1490
|
+
const server = new McpServer(
|
|
1595
1491
|
{
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
inputSchema: getTestRunDetailsInputSchema,
|
|
1599
|
-
outputSchema: getTestRunDetailsOutputSchema
|
|
1492
|
+
name: "gaffer",
|
|
1493
|
+
version: "0.1.0"
|
|
1600
1494
|
},
|
|
1601
|
-
async (input) => {
|
|
1602
|
-
try {
|
|
1603
|
-
const output = await executeGetTestRunDetails(client, input);
|
|
1604
|
-
return {
|
|
1605
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1606
|
-
structuredContent: output
|
|
1607
|
-
};
|
|
1608
|
-
} catch (error) {
|
|
1609
|
-
return handleToolError(getTestRunDetailsMetadata.name, error);
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
);
|
|
1613
|
-
server.registerTool(
|
|
1614
|
-
compareTestMetricsMetadata.name,
|
|
1615
|
-
{
|
|
1616
|
-
title: compareTestMetricsMetadata.title,
|
|
1617
|
-
description: compareTestMetricsMetadata.description,
|
|
1618
|
-
inputSchema: compareTestMetricsInputSchema,
|
|
1619
|
-
outputSchema: compareTestMetricsOutputSchema
|
|
1620
|
-
},
|
|
1621
|
-
async (input) => {
|
|
1622
|
-
try {
|
|
1623
|
-
const output = await executeCompareTestMetrics(client, input);
|
|
1624
|
-
return {
|
|
1625
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1626
|
-
structuredContent: output
|
|
1627
|
-
};
|
|
1628
|
-
} catch (error) {
|
|
1629
|
-
return handleToolError(compareTestMetricsMetadata.name, error);
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
);
|
|
1633
|
-
server.registerTool(
|
|
1634
|
-
getCoverageSummaryMetadata.name,
|
|
1635
1495
|
{
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
};
|
|
1668
|
-
} catch (error) {
|
|
1669
|
-
return handleToolError(getCoverageForFileMetadata.name, error);
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
);
|
|
1673
|
-
server.registerTool(
|
|
1674
|
-
findUncoveredFailureAreasMetadata.name,
|
|
1675
|
-
{
|
|
1676
|
-
title: findUncoveredFailureAreasMetadata.title,
|
|
1677
|
-
description: findUncoveredFailureAreasMetadata.description,
|
|
1678
|
-
inputSchema: findUncoveredFailureAreasInputSchema,
|
|
1679
|
-
outputSchema: findUncoveredFailureAreasOutputSchema
|
|
1680
|
-
},
|
|
1681
|
-
async (input) => {
|
|
1682
|
-
try {
|
|
1683
|
-
const output = await executeFindUncoveredFailureAreas(client, input);
|
|
1684
|
-
return {
|
|
1685
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1686
|
-
structuredContent: output
|
|
1687
|
-
};
|
|
1688
|
-
} catch (error) {
|
|
1689
|
-
return handleToolError(findUncoveredFailureAreasMetadata.name, error);
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
);
|
|
1693
|
-
server.registerTool(
|
|
1694
|
-
getUntestedFilesMetadata.name,
|
|
1695
|
-
{
|
|
1696
|
-
title: getUntestedFilesMetadata.title,
|
|
1697
|
-
description: getUntestedFilesMetadata.description,
|
|
1698
|
-
inputSchema: getUntestedFilesInputSchema,
|
|
1699
|
-
outputSchema: getUntestedFilesOutputSchema
|
|
1700
|
-
},
|
|
1701
|
-
async (input) => {
|
|
1702
|
-
try {
|
|
1703
|
-
const output = await executeGetUntestedFiles(client, input);
|
|
1704
|
-
return {
|
|
1705
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1706
|
-
structuredContent: output
|
|
1707
|
-
};
|
|
1708
|
-
} catch (error) {
|
|
1709
|
-
return handleToolError(getUntestedFilesMetadata.name, error);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
);
|
|
1713
|
-
server.registerTool(
|
|
1714
|
-
getReportBrowserUrlMetadata.name,
|
|
1715
|
-
{
|
|
1716
|
-
title: getReportBrowserUrlMetadata.title,
|
|
1717
|
-
description: getReportBrowserUrlMetadata.description,
|
|
1718
|
-
inputSchema: getReportBrowserUrlInputSchema,
|
|
1719
|
-
outputSchema: getReportBrowserUrlOutputSchema
|
|
1720
|
-
},
|
|
1721
|
-
async (input) => {
|
|
1722
|
-
try {
|
|
1723
|
-
const output = await executeGetReportBrowserUrl(client, input);
|
|
1724
|
-
return {
|
|
1725
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1726
|
-
structuredContent: output
|
|
1727
|
-
};
|
|
1728
|
-
} catch (error) {
|
|
1729
|
-
return handleToolError(getReportBrowserUrlMetadata.name, error);
|
|
1730
|
-
}
|
|
1496
|
+
instructions: `Gaffer provides test analytics and coverage data for your projects.
|
|
1497
|
+
|
|
1498
|
+
## Coverage Analysis Best Practices
|
|
1499
|
+
|
|
1500
|
+
When helping users improve test coverage, combine coverage data with codebase exploration:
|
|
1501
|
+
|
|
1502
|
+
1. **Understand code utilization first**: Before targeting files by coverage percentage, explore which code is critical:
|
|
1503
|
+
- Find entry points (route definitions, event handlers, exported functions)
|
|
1504
|
+
- Find heavily-imported files (files imported by many others are high-value targets)
|
|
1505
|
+
- Identify critical business logic (auth, payments, data mutations)
|
|
1506
|
+
|
|
1507
|
+
2. **Prioritize by impact**: Low coverage alone doesn't indicate priority. Consider:
|
|
1508
|
+
- High utilization + low coverage = highest priority
|
|
1509
|
+
- Large files with 0% coverage have bigger impact than small files
|
|
1510
|
+
- Use find_uncovered_failure_areas for files with both low coverage AND test failures
|
|
1511
|
+
|
|
1512
|
+
3. **Use path-based queries**: The get_untested_files tool may return many files of a certain type (e.g., UI components). For targeted analysis, use get_coverage_for_file with path prefixes to focus on specific areas of the codebase.
|
|
1513
|
+
|
|
1514
|
+
4. **Iterate**: Get baseline \u2192 identify targets \u2192 write tests \u2192 re-check coverage after CI uploads new results.
|
|
1515
|
+
|
|
1516
|
+
## Finding Invisible Files
|
|
1517
|
+
|
|
1518
|
+
Coverage tools can only report on files that were loaded during test execution. Some files have 0% coverage but don't appear in reports at all - these are "invisible" files that were never imported.
|
|
1519
|
+
|
|
1520
|
+
To find invisible files:
|
|
1521
|
+
1. Use get_coverage_for_file with a path prefix (e.g., "server/") to see what Gaffer tracks
|
|
1522
|
+
2. Use the local Glob tool to list all source files in that path
|
|
1523
|
+
3. Compare the lists - files in local but NOT in Gaffer are invisible
|
|
1524
|
+
4. These files need tests that actually import them
|
|
1525
|
+
|
|
1526
|
+
Example: If get_coverage_for_file("server/api") returns user.ts, auth.ts, but Glob finds user.ts, auth.ts, billing.ts - then billing.ts is invisible and needs tests that import it.`
|
|
1731
1527
|
}
|
|
1732
1528
|
);
|
|
1529
|
+
registerTool(server, client, {
|
|
1530
|
+
metadata: getProjectHealthMetadata,
|
|
1531
|
+
inputSchema: getProjectHealthInputSchema,
|
|
1532
|
+
outputSchema: getProjectHealthOutputSchema,
|
|
1533
|
+
execute: executeGetProjectHealth
|
|
1534
|
+
});
|
|
1535
|
+
registerTool(server, client, {
|
|
1536
|
+
metadata: getTestHistoryMetadata,
|
|
1537
|
+
inputSchema: getTestHistoryInputSchema,
|
|
1538
|
+
outputSchema: getTestHistoryOutputSchema,
|
|
1539
|
+
execute: executeGetTestHistory
|
|
1540
|
+
});
|
|
1541
|
+
registerTool(server, client, {
|
|
1542
|
+
metadata: getFlakyTestsMetadata,
|
|
1543
|
+
inputSchema: getFlakyTestsInputSchema,
|
|
1544
|
+
outputSchema: getFlakyTestsOutputSchema,
|
|
1545
|
+
execute: executeGetFlakyTests
|
|
1546
|
+
});
|
|
1547
|
+
registerTool(server, client, {
|
|
1548
|
+
metadata: listTestRunsMetadata,
|
|
1549
|
+
inputSchema: listTestRunsInputSchema,
|
|
1550
|
+
outputSchema: listTestRunsOutputSchema,
|
|
1551
|
+
execute: executeListTestRuns
|
|
1552
|
+
});
|
|
1553
|
+
registerTool(server, client, {
|
|
1554
|
+
metadata: listProjectsMetadata,
|
|
1555
|
+
inputSchema: listProjectsInputSchema,
|
|
1556
|
+
outputSchema: listProjectsOutputSchema,
|
|
1557
|
+
execute: executeListProjects
|
|
1558
|
+
});
|
|
1559
|
+
registerTool(server, client, {
|
|
1560
|
+
metadata: getReportMetadata,
|
|
1561
|
+
inputSchema: getReportInputSchema,
|
|
1562
|
+
outputSchema: getReportOutputSchema,
|
|
1563
|
+
execute: executeGetReport
|
|
1564
|
+
});
|
|
1565
|
+
registerTool(server, client, {
|
|
1566
|
+
metadata: getSlowestTestsMetadata,
|
|
1567
|
+
inputSchema: getSlowestTestsInputSchema,
|
|
1568
|
+
outputSchema: getSlowestTestsOutputSchema,
|
|
1569
|
+
execute: executeGetSlowestTests
|
|
1570
|
+
});
|
|
1571
|
+
registerTool(server, client, {
|
|
1572
|
+
metadata: getTestRunDetailsMetadata,
|
|
1573
|
+
inputSchema: getTestRunDetailsInputSchema,
|
|
1574
|
+
outputSchema: getTestRunDetailsOutputSchema,
|
|
1575
|
+
execute: executeGetTestRunDetails
|
|
1576
|
+
});
|
|
1577
|
+
registerTool(server, client, {
|
|
1578
|
+
metadata: compareTestMetricsMetadata,
|
|
1579
|
+
inputSchema: compareTestMetricsInputSchema,
|
|
1580
|
+
outputSchema: compareTestMetricsOutputSchema,
|
|
1581
|
+
execute: executeCompareTestMetrics
|
|
1582
|
+
});
|
|
1583
|
+
registerTool(server, client, {
|
|
1584
|
+
metadata: getCoverageSummaryMetadata,
|
|
1585
|
+
inputSchema: getCoverageSummaryInputSchema,
|
|
1586
|
+
outputSchema: getCoverageSummaryOutputSchema,
|
|
1587
|
+
execute: executeGetCoverageSummary
|
|
1588
|
+
});
|
|
1589
|
+
registerTool(server, client, {
|
|
1590
|
+
metadata: getCoverageForFileMetadata,
|
|
1591
|
+
inputSchema: getCoverageForFileInputSchema,
|
|
1592
|
+
outputSchema: getCoverageForFileOutputSchema,
|
|
1593
|
+
execute: executeGetCoverageForFile
|
|
1594
|
+
});
|
|
1595
|
+
registerTool(server, client, {
|
|
1596
|
+
metadata: findUncoveredFailureAreasMetadata,
|
|
1597
|
+
inputSchema: findUncoveredFailureAreasInputSchema,
|
|
1598
|
+
outputSchema: findUncoveredFailureAreasOutputSchema,
|
|
1599
|
+
execute: executeFindUncoveredFailureAreas
|
|
1600
|
+
});
|
|
1601
|
+
registerTool(server, client, {
|
|
1602
|
+
metadata: getUntestedFilesMetadata,
|
|
1603
|
+
inputSchema: getUntestedFilesInputSchema,
|
|
1604
|
+
outputSchema: getUntestedFilesOutputSchema,
|
|
1605
|
+
execute: executeGetUntestedFiles
|
|
1606
|
+
});
|
|
1607
|
+
registerTool(server, client, {
|
|
1608
|
+
metadata: getReportBrowserUrlMetadata,
|
|
1609
|
+
inputSchema: getReportBrowserUrlInputSchema,
|
|
1610
|
+
outputSchema: getReportBrowserUrlOutputSchema,
|
|
1611
|
+
execute: executeGetReportBrowserUrl
|
|
1612
|
+
});
|
|
1733
1613
|
const transport = new StdioServerTransport();
|
|
1734
1614
|
await server.connect(transport);
|
|
1735
1615
|
}
|
package/package.json
CHANGED