@gaffer-sh/mcp 0.4.1 → 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 +10 -2
- package/dist/index.js +119 -281
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,9 +20,17 @@ This MCP server connects AI coding assistants like Claude Code and Cursor to you
|
|
|
20
20
|
|
|
21
21
|
## Setup
|
|
22
22
|
|
|
23
|
-
### Claude Code
|
|
23
|
+
### Claude Code (CLI)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The easiest way to add the Gaffer MCP server is via the Claude Code CLI:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add gaffer -e GAFFER_API_KEY=gaf_your_api_key_here -- npx -y @gaffer-sh/mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Claude Code (Manual)
|
|
32
|
+
|
|
33
|
+
Alternatively, add to your Claude Code settings (`~/.claude.json` or project `.claude/settings.json`):
|
|
26
34
|
|
|
27
35
|
```json
|
|
28
36
|
{
|
package/dist/index.js
CHANGED
|
@@ -1443,6 +1443,28 @@ function handleToolError(toolName, error) {
|
|
|
1443
1443
|
isError: true
|
|
1444
1444
|
};
|
|
1445
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
|
+
}
|
|
1446
1468
|
async function main() {
|
|
1447
1469
|
const apiKey = process.env.GAFFER_API_KEY;
|
|
1448
1470
|
if (!apiKey) {
|
|
@@ -1489,289 +1511,105 @@ When helping users improve test coverage, combine coverage data with codebase ex
|
|
|
1489
1511
|
|
|
1490
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.
|
|
1491
1513
|
|
|
1492
|
-
4. **Iterate**: Get baseline \u2192 identify targets \u2192 write tests \u2192 re-check coverage after CI uploads new results
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
const output = await executeGetProjectHealth(client, input);
|
|
1506
|
-
return {
|
|
1507
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1508
|
-
structuredContent: output
|
|
1509
|
-
};
|
|
1510
|
-
} catch (error) {
|
|
1511
|
-
return handleToolError(getProjectHealthMetadata.name, error);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
);
|
|
1515
|
-
server.registerTool(
|
|
1516
|
-
getTestHistoryMetadata.name,
|
|
1517
|
-
{
|
|
1518
|
-
title: getTestHistoryMetadata.title,
|
|
1519
|
-
description: getTestHistoryMetadata.description,
|
|
1520
|
-
inputSchema: getTestHistoryInputSchema,
|
|
1521
|
-
outputSchema: getTestHistoryOutputSchema
|
|
1522
|
-
},
|
|
1523
|
-
async (input) => {
|
|
1524
|
-
try {
|
|
1525
|
-
const output = await executeGetTestHistory(client, input);
|
|
1526
|
-
return {
|
|
1527
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1528
|
-
structuredContent: output
|
|
1529
|
-
};
|
|
1530
|
-
} catch (error) {
|
|
1531
|
-
return handleToolError(getTestHistoryMetadata.name, error);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
);
|
|
1535
|
-
server.registerTool(
|
|
1536
|
-
getFlakyTestsMetadata.name,
|
|
1537
|
-
{
|
|
1538
|
-
title: getFlakyTestsMetadata.title,
|
|
1539
|
-
description: getFlakyTestsMetadata.description,
|
|
1540
|
-
inputSchema: getFlakyTestsInputSchema,
|
|
1541
|
-
outputSchema: getFlakyTestsOutputSchema
|
|
1542
|
-
},
|
|
1543
|
-
async (input) => {
|
|
1544
|
-
try {
|
|
1545
|
-
const output = await executeGetFlakyTests(client, input);
|
|
1546
|
-
return {
|
|
1547
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1548
|
-
structuredContent: output
|
|
1549
|
-
};
|
|
1550
|
-
} catch (error) {
|
|
1551
|
-
return handleToolError(getFlakyTestsMetadata.name, error);
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
);
|
|
1555
|
-
server.registerTool(
|
|
1556
|
-
listTestRunsMetadata.name,
|
|
1557
|
-
{
|
|
1558
|
-
title: listTestRunsMetadata.title,
|
|
1559
|
-
description: listTestRunsMetadata.description,
|
|
1560
|
-
inputSchema: listTestRunsInputSchema,
|
|
1561
|
-
outputSchema: listTestRunsOutputSchema
|
|
1562
|
-
},
|
|
1563
|
-
async (input) => {
|
|
1564
|
-
try {
|
|
1565
|
-
const output = await executeListTestRuns(client, input);
|
|
1566
|
-
return {
|
|
1567
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1568
|
-
structuredContent: output
|
|
1569
|
-
};
|
|
1570
|
-
} catch (error) {
|
|
1571
|
-
return handleToolError(listTestRunsMetadata.name, error);
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
);
|
|
1575
|
-
server.registerTool(
|
|
1576
|
-
listProjectsMetadata.name,
|
|
1577
|
-
{
|
|
1578
|
-
title: listProjectsMetadata.title,
|
|
1579
|
-
description: listProjectsMetadata.description,
|
|
1580
|
-
inputSchema: listProjectsInputSchema,
|
|
1581
|
-
outputSchema: listProjectsOutputSchema
|
|
1582
|
-
},
|
|
1583
|
-
async (input) => {
|
|
1584
|
-
try {
|
|
1585
|
-
const output = await executeListProjects(client, input);
|
|
1586
|
-
return {
|
|
1587
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1588
|
-
structuredContent: output
|
|
1589
|
-
};
|
|
1590
|
-
} catch (error) {
|
|
1591
|
-
return handleToolError(listProjectsMetadata.name, error);
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
);
|
|
1595
|
-
server.registerTool(
|
|
1596
|
-
getReportMetadata.name,
|
|
1597
|
-
{
|
|
1598
|
-
title: getReportMetadata.title,
|
|
1599
|
-
description: getReportMetadata.description,
|
|
1600
|
-
inputSchema: getReportInputSchema,
|
|
1601
|
-
outputSchema: getReportOutputSchema
|
|
1602
|
-
},
|
|
1603
|
-
async (input) => {
|
|
1604
|
-
try {
|
|
1605
|
-
const output = await executeGetReport(client, input);
|
|
1606
|
-
return {
|
|
1607
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1608
|
-
structuredContent: output
|
|
1609
|
-
};
|
|
1610
|
-
} catch (error) {
|
|
1611
|
-
return handleToolError(getReportMetadata.name, error);
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
);
|
|
1615
|
-
server.registerTool(
|
|
1616
|
-
getSlowestTestsMetadata.name,
|
|
1617
|
-
{
|
|
1618
|
-
title: getSlowestTestsMetadata.title,
|
|
1619
|
-
description: getSlowestTestsMetadata.description,
|
|
1620
|
-
inputSchema: getSlowestTestsInputSchema,
|
|
1621
|
-
outputSchema: getSlowestTestsOutputSchema
|
|
1622
|
-
},
|
|
1623
|
-
async (input) => {
|
|
1624
|
-
try {
|
|
1625
|
-
const output = await executeGetSlowestTests(client, input);
|
|
1626
|
-
return {
|
|
1627
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1628
|
-
structuredContent: output
|
|
1629
|
-
};
|
|
1630
|
-
} catch (error) {
|
|
1631
|
-
return handleToolError(getSlowestTestsMetadata.name, error);
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
);
|
|
1635
|
-
server.registerTool(
|
|
1636
|
-
getTestRunDetailsMetadata.name,
|
|
1637
|
-
{
|
|
1638
|
-
title: getTestRunDetailsMetadata.title,
|
|
1639
|
-
description: getTestRunDetailsMetadata.description,
|
|
1640
|
-
inputSchema: getTestRunDetailsInputSchema,
|
|
1641
|
-
outputSchema: getTestRunDetailsOutputSchema
|
|
1642
|
-
},
|
|
1643
|
-
async (input) => {
|
|
1644
|
-
try {
|
|
1645
|
-
const output = await executeGetTestRunDetails(client, input);
|
|
1646
|
-
return {
|
|
1647
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1648
|
-
structuredContent: output
|
|
1649
|
-
};
|
|
1650
|
-
} catch (error) {
|
|
1651
|
-
return handleToolError(getTestRunDetailsMetadata.name, error);
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
);
|
|
1655
|
-
server.registerTool(
|
|
1656
|
-
compareTestMetricsMetadata.name,
|
|
1657
|
-
{
|
|
1658
|
-
title: compareTestMetricsMetadata.title,
|
|
1659
|
-
description: compareTestMetricsMetadata.description,
|
|
1660
|
-
inputSchema: compareTestMetricsInputSchema,
|
|
1661
|
-
outputSchema: compareTestMetricsOutputSchema
|
|
1662
|
-
},
|
|
1663
|
-
async (input) => {
|
|
1664
|
-
try {
|
|
1665
|
-
const output = await executeCompareTestMetrics(client, input);
|
|
1666
|
-
return {
|
|
1667
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1668
|
-
structuredContent: output
|
|
1669
|
-
};
|
|
1670
|
-
} catch (error) {
|
|
1671
|
-
return handleToolError(compareTestMetricsMetadata.name, error);
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
);
|
|
1675
|
-
server.registerTool(
|
|
1676
|
-
getCoverageSummaryMetadata.name,
|
|
1677
|
-
{
|
|
1678
|
-
title: getCoverageSummaryMetadata.title,
|
|
1679
|
-
description: getCoverageSummaryMetadata.description,
|
|
1680
|
-
inputSchema: getCoverageSummaryInputSchema,
|
|
1681
|
-
outputSchema: getCoverageSummaryOutputSchema
|
|
1682
|
-
},
|
|
1683
|
-
async (input) => {
|
|
1684
|
-
try {
|
|
1685
|
-
const output = await executeGetCoverageSummary(client, input);
|
|
1686
|
-
return {
|
|
1687
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1688
|
-
structuredContent: output
|
|
1689
|
-
};
|
|
1690
|
-
} catch (error) {
|
|
1691
|
-
return handleToolError(getCoverageSummaryMetadata.name, error);
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
);
|
|
1695
|
-
server.registerTool(
|
|
1696
|
-
getCoverageForFileMetadata.name,
|
|
1697
|
-
{
|
|
1698
|
-
title: getCoverageForFileMetadata.title,
|
|
1699
|
-
description: getCoverageForFileMetadata.description,
|
|
1700
|
-
inputSchema: getCoverageForFileInputSchema,
|
|
1701
|
-
outputSchema: getCoverageForFileOutputSchema
|
|
1702
|
-
},
|
|
1703
|
-
async (input) => {
|
|
1704
|
-
try {
|
|
1705
|
-
const output = await executeGetCoverageForFile(client, input);
|
|
1706
|
-
return {
|
|
1707
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1708
|
-
structuredContent: output
|
|
1709
|
-
};
|
|
1710
|
-
} catch (error) {
|
|
1711
|
-
return handleToolError(getCoverageForFileMetadata.name, error);
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
);
|
|
1715
|
-
server.registerTool(
|
|
1716
|
-
findUncoveredFailureAreasMetadata.name,
|
|
1717
|
-
{
|
|
1718
|
-
title: findUncoveredFailureAreasMetadata.title,
|
|
1719
|
-
description: findUncoveredFailureAreasMetadata.description,
|
|
1720
|
-
inputSchema: findUncoveredFailureAreasInputSchema,
|
|
1721
|
-
outputSchema: findUncoveredFailureAreasOutputSchema
|
|
1722
|
-
},
|
|
1723
|
-
async (input) => {
|
|
1724
|
-
try {
|
|
1725
|
-
const output = await executeFindUncoveredFailureAreas(client, input);
|
|
1726
|
-
return {
|
|
1727
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1728
|
-
structuredContent: output
|
|
1729
|
-
};
|
|
1730
|
-
} catch (error) {
|
|
1731
|
-
return handleToolError(findUncoveredFailureAreasMetadata.name, error);
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
);
|
|
1735
|
-
server.registerTool(
|
|
1736
|
-
getUntestedFilesMetadata.name,
|
|
1737
|
-
{
|
|
1738
|
-
title: getUntestedFilesMetadata.title,
|
|
1739
|
-
description: getUntestedFilesMetadata.description,
|
|
1740
|
-
inputSchema: getUntestedFilesInputSchema,
|
|
1741
|
-
outputSchema: getUntestedFilesOutputSchema
|
|
1742
|
-
},
|
|
1743
|
-
async (input) => {
|
|
1744
|
-
try {
|
|
1745
|
-
const output = await executeGetUntestedFiles(client, input);
|
|
1746
|
-
return {
|
|
1747
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1748
|
-
structuredContent: output
|
|
1749
|
-
};
|
|
1750
|
-
} catch (error) {
|
|
1751
|
-
return handleToolError(getUntestedFilesMetadata.name, error);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
);
|
|
1755
|
-
server.registerTool(
|
|
1756
|
-
getReportBrowserUrlMetadata.name,
|
|
1757
|
-
{
|
|
1758
|
-
title: getReportBrowserUrlMetadata.title,
|
|
1759
|
-
description: getReportBrowserUrlMetadata.description,
|
|
1760
|
-
inputSchema: getReportBrowserUrlInputSchema,
|
|
1761
|
-
outputSchema: getReportBrowserUrlOutputSchema
|
|
1762
|
-
},
|
|
1763
|
-
async (input) => {
|
|
1764
|
-
try {
|
|
1765
|
-
const output = await executeGetReportBrowserUrl(client, input);
|
|
1766
|
-
return {
|
|
1767
|
-
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
1768
|
-
structuredContent: output
|
|
1769
|
-
};
|
|
1770
|
-
} catch (error) {
|
|
1771
|
-
return handleToolError(getReportBrowserUrlMetadata.name, error);
|
|
1772
|
-
}
|
|
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.`
|
|
1773
1527
|
}
|
|
1774
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
|
+
});
|
|
1775
1613
|
const transport = new StdioServerTransport();
|
|
1776
1614
|
await server.connect(transport);
|
|
1777
1615
|
}
|
package/package.json
CHANGED