Skip to content

Commit 5572e3e

Browse files
committed
feat(docs): automate README tools table synchronization and enhance validation tests
- Introduced a script to automatically sync the tools table in README.md with registered tools in server.ts. - Added a pre-commit hook to trigger the README update when server.ts changes. - Enhanced unit tests to ensure all registered tools are accurately reflected in README.md, checking for missing or stale entries. - Updated package.json to include new scripts for README synchronization.
1 parent cfb14e1 commit 5572e3e

5 files changed

Lines changed: 181 additions & 30 deletions

File tree

README.md

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,39 @@ This is a MCP server that allows you to provide test results context to your AI
88

99
## Tools
1010

11-
| Tool | Description |
12-
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
13-
| `currents-get-projects` | Retrieves projects available in the Currents platform. |
14-
| `currents-get-project` | Get a single project by ID. |
15-
| `currents-get-project-insights` | Get aggregated run and test metrics for a project within a date range. |
16-
| `currents-get-runs` | Retrieves a list of runs for a specific project with optional filtering. |
17-
| `currents-get-run-details` | Retrieves details of a specific test run. |
18-
| `currents-find-run` | Find a run by query parameters. |
19-
| `currents-cancel-run` | Cancel a run in progress. |
20-
| `currents-reset-run` | Reset failed spec files in a run to allow re-execution. |
21-
| `currents-delete-run` | Delete a run and all associated data. |
22-
| `currents-cancel-run-github-ci` | Cancel a run by GitHub Actions workflow run ID and attempt number. |
23-
| `currents-get-spec-instance` | Retrieves debugging data from a specific execution of a test spec file by instanceId. |
24-
| `currents-get-spec-files-performance` | Retrieves spec files performance metrics for a specific project within a date range. |
25-
| `currents-get-tests-performance` | Retrieves aggregated test metrics for a specific project within a date range. |
26-
| `currents-get-tests-signatures` | Generates a unique test signature based on project, spec file path, and test title. |
27-
| `currents-get-test-results` | Retrieves historical test execution results for a specific test signature. |
28-
| `currents-get-errors-explorer` | Get aggregated error metrics for a project within a date range with comprehensive filtering and grouping. |
29-
| `currents-list-actions` | List all actions for a project with optional filtering. |
30-
| `currents-create-action` | Create a new action for a project. |
31-
| `currents-get-action` | Get a single action by ID. |
32-
| `currents-update-action` | Update an existing action. |
33-
| `currents-delete-action` | Delete (archive) an action. |
34-
| `currents-enable-action` | Enable a disabled action. |
35-
| `currents-disable-action` | Disable an active action. |
36-
| `currents-list-webhooks` | List all webhooks for a project. |
37-
| `currents-create-webhook` | Create a new webhook for a project. |
38-
| `currents-get-webhook` | Get a single webhook by ID. |
39-
| `currents-update-webhook` | Update an existing webhook. |
40-
| `currents-delete-webhook` | Delete a webhook. |
11+
| Tool | Description |
12+
| --------------------------------------- | ----------------------------------------------------------------------------------------- |
13+
| `currents-list-actions` | List all actions for a project with optional filtering. |
14+
| `currents-create-action` | Create a new action for a project. |
15+
| `currents-get-action` | Get a single action by ID. |
16+
| `currents-update-action` | Update an existing action. |
17+
| `currents-delete-action` | Delete (archive) an action. |
18+
| `currents-enable-action` | Enable a disabled action. |
19+
| `currents-disable-action` | Disable an active action. |
20+
| `currents-list-affected-tests` | List tests affected by actions (quarantine, skip, tag) for a project within a date range. |
21+
| `currents-get-affected-test-executions` | Get execution details for a specific affected test (by signature) within a date range. |
22+
| `currents-get-affected-executions` | List test executions where a specific action/rule was applied, within a date range. |
23+
| `currents-get-projects` | Retrieves projects available in the Currents platform. |
24+
| `currents-get-project` | Get a single project by ID. |
25+
| `currents-get-project-insights` | Get aggregated run and test metrics for a project within a date range. |
26+
| `currents-get-runs` | Retrieves a list of runs for a specific project with optional filtering. |
27+
| `currents-get-run-details` | Retrieves details of a specific test run. |
28+
| `currents-find-run` | Find a run by query parameters. |
29+
| `currents-cancel-run` | Cancel a run in progress. |
30+
| `currents-reset-run` | Reset failed spec files in a run to allow re-execution. |
31+
| `currents-delete-run` | Delete a run and all associated data. |
32+
| `currents-cancel-run-github-ci` | Cancel a run by GitHub Actions workflow run ID and attempt number. |
33+
| `currents-get-spec-instance` | Retrieves debugging data from a specific execution of a test spec file by instanceId. |
34+
| `currents-get-spec-files-performance` | Retrieves spec files performance metrics for a specific project within a date range. |
35+
| `currents-get-tests-performance` | Retrieves aggregated test metrics for a specific project within a date range. |
36+
| `currents-get-tests-signatures` | Generates a unique test signature based on project, spec file path, and test title. |
37+
| `currents-get-test-results` | Retrieves historical test execution results for a specific test signature. |
38+
| `currents-get-errors-explorer` | Get aggregated error metrics for a project within a date range. |
39+
| `currents-list-webhooks` | List all webhooks for a project. |
40+
| `currents-create-webhook` | Create a new webhook for a project. |
41+
| `currents-get-webhook` | Get a single webhook by ID. |
42+
| `currents-update-webhook` | Update an existing webhook. |
43+
| `currents-delete-webhook` | Delete a webhook. |
4144

4245
## Setup
4346

mcp-server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
"publish:npm": "npm run rm && npm run build && ./publish.cjs",
3232
"start": "node build/index.js",
3333
"rm": "rimraf dist",
34+
"prepare": "git config core.hooksPath mcp-server/scripts/hooks",
35+
"sync-readme": "node scripts/sync-readme-tools.mjs",
3436
"test": "vitest",
3537
"test:ui": "vitest --ui",
3638
"test:run": "npm run build && vitest run",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
3+
# Auto-sync README tools table when server.ts changes
4+
if git diff --cached --name-only | grep -q 'mcp-server/src/server\.ts'; then
5+
node mcp-server/scripts/sync-readme-tools.mjs
6+
git add README.md
7+
fi
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Regenerates the tools table in README.md from the actual registered tools.
3+
*
4+
* Usage: node scripts/sync-readme-tools.mjs [--check]
5+
* --check exit with code 1 if the README is out of date (useful in CI)
6+
*/
7+
8+
import { readFileSync, writeFileSync } from "node:fs";
9+
import { join, dirname } from "node:path";
10+
import { fileURLToPath } from "node:url";
11+
import { register } from "node:module";
12+
13+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
14+
const readmePath = join(root, "..", "README.md");
15+
const checkOnly = process.argv.includes("--check");
16+
17+
// ── Capture tool registrations via a loader hook ────────────────
18+
const registeredTools = [];
19+
20+
// Mock the MCP SDK so importing server.ts just records registerTool calls
21+
// without starting any I/O.
22+
const origResolve = await (async () => {
23+
// We can't use loader hooks easily from a script, so we take the simpler
24+
// approach: build a small inline module that re-exports a mock McpServer,
25+
// then patch globalThis so the built server.js picks it up.
26+
27+
// Dynamically import the built server module after shimming the dep.
28+
// The built output lives at build/server.js and uses:
29+
// import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
30+
// import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
31+
//
32+
// We register a custom loader that intercepts those two specifiers.
33+
return null;
34+
})();
35+
36+
// Simpler approach: run vitest in JSON mode and parse the registered tools
37+
// from test output. But that couples us to tests. Instead, we'll parse
38+
// server.ts source directly — it's a flat list of registerTool() calls.
39+
40+
const serverSrc = readFileSync(join(root, "src", "server.ts"), "utf-8");
41+
42+
const toolRegex =
43+
/server\.registerTool\(\s*"([^"]+)",\s*\{\s*description:\s*\n?\s*"([^"]*(?:"[^"]*)*?)"/g;
44+
45+
// More robust: match multiline descriptions
46+
const toolRegex2 =
47+
/server\.registerTool\(\s*\n?\s*"([^"]+)",\s*\n?\s*\{\s*\n?\s*description:\s*\n?\s*"((?:[^"\\]|\\.)*)"/g;
48+
49+
let match;
50+
while ((match = toolRegex2.exec(serverSrc)) !== null) {
51+
const name = match[1];
52+
const description = match[2].replace(/\\"/g, '"');
53+
const firstSentence = description.split(/\.\s/)[0];
54+
const shortDesc = firstSentence.endsWith(".")
55+
? firstSentence
56+
: firstSentence + ".";
57+
registeredTools.push({ name, shortDesc });
58+
}
59+
60+
if (registeredTools.length === 0) {
61+
console.error("ERROR: No tools found in server.ts — regex may need updating");
62+
process.exit(1);
63+
}
64+
65+
// ── Build the markdown table ────────────────────────────────────
66+
const nameColWidth = Math.max(
67+
"Tool".length,
68+
...registeredTools.map((t) => `\`${t.name}\``.length)
69+
);
70+
const descColWidth = Math.max(
71+
"Description".length,
72+
...registeredTools.map((t) => t.shortDesc.length)
73+
);
74+
75+
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
76+
77+
const header = `| ${pad("Tool", nameColWidth)} | ${pad("Description", descColWidth)} |`;
78+
const separator = `| ${"-".repeat(nameColWidth)} | ${"-".repeat(descColWidth)} |`;
79+
const rows = registeredTools.map(
80+
(t) =>
81+
`| ${pad(`\`${t.name}\``, nameColWidth)} | ${pad(t.shortDesc, descColWidth)} |`
82+
);
83+
84+
const table = [header, separator, ...rows].join("\n");
85+
86+
// ── Splice the table into README.md ─────────────────────────────
87+
const readme = readFileSync(readmePath, "utf-8");
88+
const tableStart = readme.indexOf("| Tool");
89+
const tableEndMarker = readme.indexOf("\n\n", tableStart);
90+
const tableEnd = tableEndMarker === -1 ? readme.length : tableEndMarker;
91+
92+
const oldTable = readme.slice(tableStart, tableEnd);
93+
94+
if (oldTable === table) {
95+
console.log("README.md tools table is up to date.");
96+
process.exit(0);
97+
}
98+
99+
if (checkOnly) {
100+
console.error("README.md tools table is out of date. Run: npm run sync-readme");
101+
process.exit(1);
102+
}
103+
104+
const updated = readme.slice(0, tableStart) + table + readme.slice(tableEnd);
105+
writeFileSync(readmePath, updated);
106+
console.log(`README.md updated with ${registeredTools.length} tools.`);

mcp-server/src/server.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { readFileSync } from "node:fs";
12
import { describe, expect, it, vi } from "vitest";
23

34
const { registeredTools } = vi.hoisted(() => {
@@ -46,6 +47,38 @@ describe("MCP tool best practices", () => {
4647
expect(dupes, `duplicate tool names: ${dupes.join(", ")}`).toHaveLength(0);
4748
});
4849

50+
describe("README.md tools table", () => {
51+
const readme = readFileSync(
52+
new URL("../../README.md", import.meta.url),
53+
"utf-8"
54+
);
55+
const toolNamesInReadme = [
56+
...readme.matchAll(/\| `(currents-[\w-]+)` /g),
57+
].map((m) => m[1]);
58+
59+
it("every registered tool is listed in README", () => {
60+
const registered = registeredTools.map((t) => t.name);
61+
const missing = registered.filter(
62+
(name) => !toolNamesInReadme.includes(name)
63+
);
64+
expect(
65+
missing,
66+
`tools missing from README: ${missing.join(", ")}. Run: npm run sync-readme`
67+
).toHaveLength(0);
68+
});
69+
70+
it("README does not list removed tools", () => {
71+
const registered = registeredTools.map((t) => t.name);
72+
const stale = toolNamesInReadme.filter(
73+
(name) => !registered.includes(name)
74+
);
75+
expect(
76+
stale,
77+
`stale tools in README: ${stale.join(", ")}. Run: npm run sync-readme`
78+
).toHaveLength(0);
79+
});
80+
});
81+
4982
describe.each(registeredTools)("$name", ({ name, description }) => {
5083
// ── name constraints (SEP-986) ──────────────────────────────
5184
it(`name length ≤ ${TOOL_NAME_MAX_LENGTH}`, () => {

0 commit comments

Comments
 (0)