test-inventory-mcp
# test-inventory-mcp
An MCP server for [test-inventory](https://github.com/Huldoser/test-inventory): AI agents ask which tests a project
has, which are skipped and why, and which tests a change can affect, without running them.
It reads Playwright, Vitest, Jest, Mocha, Jasmine, Cypress, WebdriverIO, node:test and Bun test files with
test-inventory and answers from their source code. The running server executes no project code, writes no files and
opens no network connections. Installation through `npx` may download packages. Answers are passed to the MCP
client; its model provider and privacy settings determine what happens to that text next.
## Setup
Node.js 22.12 or newer. The client starts the server with `npx` and talks to it over standard input and output. The
server scans the test files your test runner's config runs, with the runner `--framework` names or, without
it, the one the project uses. Globs pick the files instead, as on test-inventory's command line.
**Claude Code**, in the project folder. `--scope project` writes `.mcp.json`, for everyone who works on the project:
```sh
claude mcp add --scope project test-inventory -- npx -y test-inventory-mcp@0.9 --framework playwright
```
**Cursor**, in `.cursor/mcp.json`:
```json
{
"mcpServers": {
"test-inventory": {
"command": "npx",
"args": ["-y", "test-inventory-mcp@0.9", "--framework", "playwright"]
}
}
}
```
**VS Code**, in `.vscode/mcp.json`:
```json
{
"servers": {
"test-inventory": {
"type": "stdio",
"command": "npx",
"args": ["-y", "test-inventory-mcp@0.9", "--framework", "playwright"]
}
}
}
```
Without `--framework`, as in a configuration shared by every project, each call scans with the runner the project
uses. In a project with several runners the agent passes `framework`, as the tools tell it, and `patterns` when it
wants other files than the configs run.
The project is the folder given with `--root`, otherwise the one Claude Code names in `CLAUDE_PROJECT_DIR`, otherwise
the folder the client starts the server in.
### Verify the setup
1. Open your client's MCP server list and confirm that `test-inventory` is connected. In Claude Code use `/mcp`;
in VS Code run **MCP: List Servers** from the Command Palette; in Cursor open its MCP settings.
2. Confirm the five tools below are available, then ask: "Use test-inventory to summarize this project's tests."
3. Check that `test_summary` reports the expected framework and file counts. If it reports `scanErrors`, ask the
agent to call `scan_diagnostics` before trusting the counts.
Try "List tests marked fixme, with their conditions and reasons" or "Which tests may be affected by a change to
pages/orders-blotter.ts?" A conditional skip is not proof a test was skipped in a run. Expected-to-fail tests run;
their non-active state does not mean they are disabled. See [key concepts](https://huldoser.github.io/test-inventory/guide/concepts).
## Command line
```text
test-inventory-mcp [--framework <name>] [<glob...>] [--root <dir>]
```
| Option | Meaning |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--framework <name>` | `playwright`, `vitest`, `jest`, `mocha`, `jasmine`, `cypress`, `webdriverio`, `node` or `bun`. Without it, the runner the project uses. A tool call can pass another. |
| `<glob...>` | The test files, relative to the project, in place of those the configs run. A tool call can pass others. |
| `--root <dir>` | The project. Tool calls can scan folders inside it, never outside. |
| `-h`, `--help` | Print the help. |
| `-v`, `--version` | Print the version. |
## Tools
The generated [tool reference](https://github.com/Huldoser/test-inventory-mcp/blob/main/docs/tools.md) lists each argument's type, default and bounds, response fields,
complete request/response examples, pagination and errors. It is checked against the running server in tests.
| Tool | Answers |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `test_summary` | Counts by state, tag and annotation, and the configs with their projects; with `groupBy`, the counts of each file, tag or project. |
| `find_tests` | The tests that match `states`, `tags`, `grep`, `related`, `projects`, `only` or `withoutReason`, with the `fields` you pick. |
| `get_tests` | Every key of up to 20 tests by `ids`, with the scan's diagnostics about them; ids it can't find come back in `notFound`. |
| `scan_diagnostics` | The problems the scan found, such as a file that failed to parse, by `levels`, `codes` or `path`. |
| `diff_inventories` | Changes between saved scans named by `before` and `after`, including renames, moves and newly declared skip states. |
A typical call sequence is `test_summary`, `find_tests`, then `get_tests` for a few returned ids. For example, the
arguments to `find_tests` can be:
```json
{
"states": ["skip", "fixme"],
"fields": ["fullName", "state", "condition", "reason"],
"limit": 10
}
```
The answer contains matching `tests`, their `total`, `scanErrors`, `nextCursor` and `hint`, plus the framework and its
version. The records have selected keys and their `id`. Pass a non-null `nextCursor` as `cursor` with the same other
arguments to read another page; a narrow filter returning zero tests is a valid result.
`related` takes the files a change touches, from the project root as `git diff --name-only` writes them, and keeps
the tests whose `dependencies` include one of them, and those that reach code the scan can't follow, as
test-inventory's [`--related`](https://huldoser.github.io/test-inventory/guide/cli#tests-a-change-can-affect) does.
The filter uses the actual scanned folder when `directory` names an internal link or an absolute path inside the project.
It also keeps tests that import a changed helper elsewhere in the project, outside that scanned folder.
`dependencies` can list hundreds of files, so it isn't among the default `fields`, and `get_tests` returns as many
tests as fit in one answer, with the other ids in `remaining`. An id stays the same when a test moves to another
line, and changes when its title, describes or file change.
Every scan tool also takes `directory`, a folder inside the project to scan, and `framework` and `patterns`, relative to
`directory`, to override the command line, and `expandCases`, which lists each case of a `.each` or `.for` table and
each pass of a loop as its own test, with the name the runner gives it, as test-inventory's
[`--expand-cases`](https://huldoser.github.io/test-inventory/guide/cli) does. Ids differ with it, so `get_tests` takes
the same `expandCases` as the `find_tests` call the ids came from. Records have the keys of test-inventory's
[output format](https://huldoser.github.io/test-inventory/reference/output).
Unknown tool arguments are refused with an error naming the unrecognized key, so misspelled filters are not silently ignored.
Answers are JSON, sent both as structured content and as text. Lists come 50 items at a time (`limit` takes up to
200), and an answer stays under about 30,000 characters; passing `nextCursor` back as `cursor` gets the next page.
A single oversized record is returned alone. A final page can fit more records because it needs no next-page cursor or hint.
`scanErrors` counts the problems that may leave tests out, and `hint` says what to do next when there is something to
say.
### Compare saved inventories
`diff_inventories` takes `before` and `after`, paths to complete saved JSON inventories inside the project, plus
`limit` and `cursor`. It returns paged `changes` with original records and matching evidence, the full `summary`,
`total`, `nextCursor`, `hashesComparable`, `warnings` and `hint`. It does not scan or write files. Use the same
framework, root, patterns, filters and `expandCases` setting for both scans. Hash comparisons and rename matching
need the exact same core version. Incomplete scans produce warnings. Zero changes returns an empty page and a hint.
Each saved file must be a regular file of at most 128 MiB. Save scans with the core CLI, without `--fields` or
`--format jsonl`; see [inventory diffs](https://huldoser.github.io/test-inventory/guide/diff). If a saved file disappears or becomes unreadable while opening it, the error names the supplied file and suggests checking its existence and readability, without exposing the resolved host path. Cancellation during these operations retains the request's cancellation reason.
## Troubleshooting
| Symptom | Next step |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The server is disconnected or tools are missing | Open the client's server logs. Check that the client can find Node.js 22.12 or newer and `npx`, then restart the server through the client. A client launched by a desktop app may use a different PATH from your terminal. |
| No files or tests were found | Check `--root`, `directory`, the framework and the config's selected files. Try explicit `patterns`, such as `["tests/**/*.spec.ts"]`. Call `scan_diagnostics` for details. |
| Several runners were detected | Pass `framework` on the call, or set `--framework` in the server configuration. Scan each runner separately. |
| The answer has `scanErrors` | Call `scan_diagnostics` and resolve the errors before treating the inventory as complete. |
| A path is refused | Keep the scanned files and dependencies inside the configured project. For an intended shared dependency, start the server at the shared parent and use `directory` to select a package. |
| A cursor is refused | Start the query again without `cursor`; keep filters, directory and case expansion unchanged when requesting later pages. Files may have changed between calls. |
| `get_tests` cannot find an id | Obtain current ids from `find_tests`. Keep its `directory`, `framework`, `patterns` and `expandCases` settings. |
| A saved scan is refused | Use complete JSON inventories, not JSON Lines or `--fields` output. Keep both files inside the project and below the size limit. |
For incorrect source analysis, use the core's [troubleshooting guide](https://huldoser.github.io/test-inventory/guide/troubleshooting).
For a server bug, include the server version, client, tool arguments, expected answer and a minimal reproduction.
Remove private project data before sharing it.
## Security
The server reads the test files its globs match inside the project, or without globs those the configs run, and,
for Vitest, the files that symbolic links among them point to, as Vitest does. It reads the configs as code, without
running them. It refuses a `directory` or pattern that reaches outside the project, including parent-directory components in brace or extglob alternatives, and refuses scan answers whose test files, configs or dependencies resolve outside it. Saved inventories must resolve inside the project too. The scanner may read a linked file before the answer is refused; this boundary is not a filesystem sandbox. It runs no code from the project, writes no files, and opens no network connections.
Answers hold text from the project's files, such as test titles, skip reasons and comments. A client should treat it
as content of that project, not as instructions. The client's handling of that text is separate from the server's
local operation. To report a vulnerability, see [SECURITY.md](SECURITY.md).
## Versioning
Tool names, their arguments and their answers are the server's API. Before 1.0, a change that breaks them raises the
minor version, so `test-inventory-mcp@0.9` in the setup above gets fixes only.
## License
[MIT](LICENSE)
TDQS
Scored across 5 tools
The summary/find/get trio covers counts vs. filtered lists vs. full records, and the descriptions explicitly route the agent between them ("use test_summary for counts and get_tests for every key of a few ids"). scan_diagnostics and diff_inventories are clearly separate operations with no overlap.
All names are snake_case two-token identifiers, which is predictable and readable. The only deviation is test_summary, which leads with a noun while the other four use a verb (find_, get_, scan_, diff_), a minor inconsistency in an otherwise uniform set.
Five tools is well within a healthy range and each one covers a distinct, substantive capability (aggregate counts, filtered listing, full record retrieval, diagnostics, historical diff). No thin wrapper tools and nothing redundant.
The surface covers the read-only inventory lifecycle well: counting, listing, drilling into records, surfacing scan errors, and comparing snapshots. One workaround gap: diff_inventories requires "complete saved JSON scans" but no tool produces or exports such a snapshot, so the diff workflow depends on an out-of-band step.