@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.
Files changed (3) hide show
  1. package/README.md +10 -2
  2. package/dist/index.js +119 -281
  3. 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
- Add to your Claude Code settings (`~/.claude.json` or project `.claude/settings.json`):
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
- server.registerTool(
1496
- getProjectHealthMetadata.name,
1497
- {
1498
- title: getProjectHealthMetadata.title,
1499
- description: getProjectHealthMetadata.description,
1500
- inputSchema: getProjectHealthInputSchema,
1501
- outputSchema: getProjectHealthOutputSchema
1502
- },
1503
- async (input) => {
1504
- try {
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gaffer-sh/mcp",
3
3
  "type": "module",
4
- "version": "0.4.1",
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>",