@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.
Files changed (3) hide show
  1. package/README.md +166 -118
  2. package/dist/index.js +162 -282
  3. 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
- ## Authentication
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
- ### Project Upload Tokens (Legacy)
23
+ ### Claude Code (CLI)
31
24
 
32
- Project Upload Tokens (`gfr_` prefix) are designed for uploading test results and only provide access to a single project. While still supported for backward compatibility, user API Keys are preferred for the MCP server.
25
+ The easiest way to add the Gaffer MCP server is via the Claude Code CLI:
33
26
 
34
- ## Setup
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
- Add to your Claude Code settings (`~/.claude.json` or project `.claude/settings.json`):
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
- ### `list_projects`
69
+ ### Project & Test Run Tools
75
70
 
76
- List all projects you have access to. **Requires a user API Key (`gaf_`).**
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
- **Input:**
79
- - `organizationId` (optional): Filter by organization
80
- - `limit` (optional): Max results (default: 50)
80
+ ### Test Analysis Tools
81
81
 
82
- **Returns:**
83
- - List of projects with IDs, names, and organization info
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
- **Example prompt:** "What projects do I have in Gaffer?"
89
+ ### Coverage Tools
86
90
 
87
- ### `get_project_health`
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
- Get the health metrics for a project.
98
+ ## Tool Details
90
99
 
91
- **Input:**
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
- **Returns:**
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
- **Example prompt:** "What's the health of my test suite?"
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
- ### `get_test_history`
108
+ ### `get_project_health`
105
109
 
106
- Get the pass/fail history for a specific test.
110
+ Get the health metrics for a project.
107
111
 
108
- **Input:**
109
- - `projectId` (required with user API Keys): Project ID from `list_projects`
110
- - `testName`: Exact test name to search for (one of testName or filePath required)
111
- - `filePath`: File path containing the test (one of testName or filePath required)
112
- - `limit` (optional): Max results (default: 20)
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
- **Returns:**
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
- **Example prompts:**
122
- - "Is the login test flaky? Check its history"
123
- - "Show me the history for tests in auth.test.ts"
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
- - `projectId` (required with user API Keys): Project ID from `list_projects`
131
- - `threshold` (optional): Minimum flip rate to be considered flaky (0-1, default: 0.1)
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
- - `projectId` (required with user API Keys): Project ID from `list_projects`
151
- - `commitSha` (optional): Filter by commit SHA (supports prefix matching)
152
- - `branch` (optional): Filter by branch name
153
- - `status` (optional): Filter by "passed" (no failures) or "failed" (has failures)
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
- **Returns:**
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
- **Example prompts:**
162
- - "What tests failed in the last commit?"
163
- - "Show me test runs on the main branch"
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 the report files for a specific test run. **Requires a user API Key (`gaf_`).**
150
+ Get URLs for report files uploaded with a test run.
169
151
 
170
- **Input:**
171
- - `testRunId` (required): Test run ID from `list_test_runs`
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
- **Returns:**
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
- **Example prompts:**
183
- - "Get the Playwright report for the latest test run"
184
- - "What files were uploaded with this test run?"
185
- - "Show me the coverage report"
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. **Requires a user API Key (`gaf_`).**
190
-
191
- **Input:**
192
- - `projectId` (required): Project ID from `list_projects`
193
- - `days` (optional): Analysis period in days (default: 30, max: 365)
194
- - `limit` (optional): Max tests to return (default: 20, max: 100)
195
- - `framework` (optional): Filter by framework (e.g., "playwright", "vitest")
196
-
197
- **Returns:**
198
- - List of slowest tests with:
199
- - name: Short test name
200
- - fullName: Full test name including describe blocks
201
- - filePath: Test file path (if available)
202
- - framework: Test framework used
203
- - avgDurationMs: Average duration in milliseconds
204
- - p95DurationMs: 95th percentile duration
205
- - runCount: Number of runs in the period
206
- - Summary with project info
207
-
208
- **Example prompts:**
209
- - "Which tests are slowing down my CI pipeline?"
210
- - "Find the slowest Playwright tests"
211
- - "Show me e2e tests that need optimization"
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
- Use this to check coverage for specific files or find files by path pattern.`
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
- Use this to find files that need tests added.`
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
- title: getTestRunDetailsMetadata.title,
1597
- description: getTestRunDetailsMetadata.description,
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
- title: getCoverageSummaryMetadata.title,
1637
- description: getCoverageSummaryMetadata.description,
1638
- inputSchema: getCoverageSummaryInputSchema,
1639
- outputSchema: getCoverageSummaryOutputSchema
1640
- },
1641
- async (input) => {
1642
- try {
1643
- const output = await executeGetCoverageSummary(client, input);
1644
- return {
1645
- content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
1646
- structuredContent: output
1647
- };
1648
- } catch (error) {
1649
- return handleToolError(getCoverageSummaryMetadata.name, error);
1650
- }
1651
- }
1652
- );
1653
- server.registerTool(
1654
- getCoverageForFileMetadata.name,
1655
- {
1656
- title: getCoverageForFileMetadata.title,
1657
- description: getCoverageForFileMetadata.description,
1658
- inputSchema: getCoverageForFileInputSchema,
1659
- outputSchema: getCoverageForFileOutputSchema
1660
- },
1661
- async (input) => {
1662
- try {
1663
- const output = await executeGetCoverageForFile(client, input);
1664
- return {
1665
- content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
1666
- structuredContent: output
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gaffer-sh/mcp",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "description": "MCP server for Gaffer test history - give your AI assistant memory of your tests",
6
6
  "license": "MIT",
7
7
  "author": "Gaffer <hello@gaffer.sh>",