@slbdn/mcp-tester 1.1.0 → 1.3.0
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/CHANGELOG.md +103 -0
- package/README.md +278 -7
- package/dist/cli/index.js +114 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/client/MCPClient.d.ts +86 -0
- package/dist/client/MCPClient.d.ts.map +1 -1
- package/dist/client/MCPClient.js +198 -53
- package/dist/client/MCPClient.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/errors.d.ts +43 -5
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +86 -7
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/logger.d.ts +51 -24
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +103 -9
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/masking.d.ts +66 -0
- package/dist/utils/masking.d.ts.map +1 -0
- package/dist/utils/masking.js +171 -0
- package/dist/utils/masking.js.map +1 -0
- package/dist/utils/validation.d.ts +48 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +249 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +10 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,109 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-07-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **TypeScript type generator** (`src/generate-types.ts`) — generate typed `.d.ts` from MCP tool schemas:
|
|
12
|
+
- `generateTypes()` API: connect to server, inspect schemas, emit TypeScript declarations
|
|
13
|
+
- CLI: `npx mcp-tester generate-types node ./server.js -o server.d.ts`
|
|
14
|
+
- Handles: primitives, enums, arrays, nested objects, oneOf/anyOf/allOf, $ref, const
|
|
15
|
+
- Generated types: `{Tool}Args`, `ToolName`, `ToolArgsMap`, `ToolCall`, `ResourceUri`, `PromptCall`
|
|
16
|
+
- 59 tests (12 e2e + 4 options + 43 unit)
|
|
17
|
+
- **Server health checks** — detect zombie processes and monitor server health:
|
|
18
|
+
- `isHealthy()` — sends a lightweight `tools/list` ping, returns `HealthStatus` with latency, PID, message
|
|
19
|
+
- `getServerPid()` — get the server process PID
|
|
20
|
+
- `getLastHealthStatus()` — cached result without re-checking
|
|
21
|
+
- `startHealthMonitor(options)` — periodic health monitoring with `onUnhealthy`/`onRecovery`/`onCheck` callbacks
|
|
22
|
+
- `stopHealthMonitor()` — stop periodic monitoring (also auto-stopped by `client.stop()`)
|
|
23
|
+
- Zombie process detection via `process.kill(pid, 0)`
|
|
24
|
+
- 17 tests (including SIGKILL zombie detection test)
|
|
25
|
+
- **Enhanced mock server** (`src/__tests__/fixtures/mock-server.ts`) — 68 tests:
|
|
26
|
+
- Configurable delays (`defaultDelay`) and random failures (`failureRate`)
|
|
27
|
+
- Input schema validation (`validateSchemas` option)
|
|
28
|
+
- Stateful tools: `counter` (increment/get/reset), `items` (add/list/remove/clear)
|
|
29
|
+
- Transform tool: `upper`/`lower`/`reverse`/`length` operations
|
|
30
|
+
- Custom handlers: `registerToolHandler`, `registerResourceHandler`, `registerPromptHandler`
|
|
31
|
+
- Call history: `getCallHistory`, `getCallCount` for test assertions
|
|
32
|
+
- Streaming support: `setupStream`, `nextStreamChunk`
|
|
33
|
+
- Dynamic registration/removal of tools, resources, prompts
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- **CLI version** is now read dynamically from `package.json` (was hardcoded `1.0.0`)
|
|
37
|
+
- Applied safe dependency patches: `@typescript-eslint/*` 8.60.0, `@types/node` 20.19.41, `prettier` 3.8.3, `ts-jest` 29.4.11
|
|
38
|
+
- `npm audit fix` — 0 vulnerabilities (was 11)
|
|
39
|
+
|
|
40
|
+
### Test Suite
|
|
41
|
+
- **635 tests** (17 suites), up from 491 (14 suites)
|
|
42
|
+
- New: mock server (68), generate-types (59), health checks (17)
|
|
43
|
+
|
|
44
|
+
## [1.2.0] - 2026-04-30
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- **Visual test reports** — HTML test report auto-generated on every `npm test` run:
|
|
48
|
+
- Uses `jest-html-reporters` with collapsible test trees, timing, failure details
|
|
49
|
+
- Report saved to `reports/test-report.html`
|
|
50
|
+
- CI uploads report as artifact (14-day retention)
|
|
51
|
+
- Open locally: `open reports/test-report.html`
|
|
52
|
+
- **Property-based tests** (`src/__tests__/property-based.test.ts`) — 73 tests using fast-check:
|
|
53
|
+
- 44 validation tests: reject/accept all input types, error code invariants
|
|
54
|
+
- 17 masking tests: idempotency, completeness, secret detection, length bounds
|
|
55
|
+
- 12 generate-tests tests: no-throw, serializability, schema priority, nested arrays
|
|
56
|
+
- Fixed flaky `fc.double()` producing NaN/Infinity → use `fc.integer()` for numeric fields
|
|
57
|
+
- **Enhanced mock server** (`src/__tests__/fixtures/mock-server.ts`) — 68 tests:
|
|
58
|
+
- Configurable delays (`defaultDelay`) and random failures (`failureRate`)
|
|
59
|
+
- Input schema validation (`validateSchemas` option)
|
|
60
|
+
- Stateful tools: `counter` (increment/get/reset), `items` (add/list/remove/clear)
|
|
61
|
+
- Transform tool: `upper`/`lower`/`reverse`/`length` operations
|
|
62
|
+
- Custom handlers: `registerToolHandler`, `registerResourceHandler`, `registerPromptHandler`
|
|
63
|
+
- Call history: `getCallHistory`, `getCallCount` for test assertions
|
|
64
|
+
- Streaming support: `setupStream`, `nextStreamChunk`
|
|
65
|
+
- Dynamic registration/removal of tools, resources, prompts
|
|
66
|
+
- **TypeScript type generator** (`src/generate-types.ts`) — generate typed `.d.ts` from MCP tool schemas:
|
|
67
|
+
- `generateTypes()` API: connect to server, inspect schemas, emit TypeScript declarations
|
|
68
|
+
- CLI: `npx mcp-tester generate-types node ./server.js -o server.d.ts`
|
|
69
|
+
- Handles: primitives, enums, arrays, nested objects, oneOf/anyOf/allOf, $ref, const
|
|
70
|
+
- Generated types: `{Tool}Args`, `ToolName`, `ToolArgsMap`, `ToolCall`, `ResourceUri`, `PromptCall`
|
|
71
|
+
- 59 tests (12 e2e + 4 options + 43 unit)
|
|
72
|
+
- **Server health checks** — detect zombie processes and monitor server health:
|
|
73
|
+
- `isHealthy()` — sends a lightweight `tools/list` ping, returns `HealthStatus` with latency, PID, message
|
|
74
|
+
- `getServerPid()` — get the server process PID
|
|
75
|
+
- `getLastHealthStatus()` — cached result without re-checking
|
|
76
|
+
- `startHealthMonitor(options)` — periodic health monitoring with `onUnhealthy`/`onRecovery`/`onCheck` callbacks
|
|
77
|
+
- `stopHealthMonitor()` — stop periodic monitoring (also auto-stopped by `client.stop()`)
|
|
78
|
+
- Zombie process detection via `process.kill(pid, 0)`
|
|
79
|
+
- 17 tests (including SIGKILL zombie detection test)
|
|
80
|
+
- **Test code generator** (`src/generate-tests.ts`) — generate a complete test file from MCP server inspection:
|
|
81
|
+
- CLI: `mcp-tester generate node ./server.js -o server.test.ts` (alias: `gen`)
|
|
82
|
+
- API: `generateTests({ command, args, framework, ... })`
|
|
83
|
+
- Generates lifecycle tests, tool call tests with sample args from JSON Schema, resource read tests, prompt tests
|
|
84
|
+
- Options: `--framework jest|vitest`, `--output <file>`, `--description <name>`, `--no-tools`, `--no-resources`, `--no-prompts`, `--no-matchers`
|
|
85
|
+
- Sample arguments derived from JSON Schema: types, enums, defaults, examples
|
|
86
|
+
- 21 tests in `generate-tests.test.ts`
|
|
87
|
+
- **Input validation** (`src/utils/validation.ts`) — all MCPClient methods validate inputs before execution:
|
|
88
|
+
- `start()`: validates command (required string), args (string array), env (string values), startupDelay (non-negative number)
|
|
89
|
+
- `callTool()`: validates name (required string), arguments (object), timeout (positive number), retries (non-negative number)
|
|
90
|
+
- `readResource()`: validates URI (required non-empty string)
|
|
91
|
+
- `getPrompt()`: validates name (required string), args (string values)
|
|
92
|
+
- `requestSampling()`: validates messages (non-empty array)
|
|
93
|
+
- Constructor: validates timeout (positive), retries/retryDelay/startupDelay (non-negative), name/version (string)
|
|
94
|
+
- Validation runs before connection checks — invalid args throw `MCPClientError`, not `MCPNotStartedError`
|
|
95
|
+
- 91 tests in `validation.test.ts`
|
|
96
|
+
- **Code coverage comments on PRs** — GitHub Actions workflow now posts a coverage table as a sticky comment on every pull request (statements/branches/functions/lines with thresholds)
|
|
97
|
+
- **Dedicated `coverage` CI job** — runs on single Node.js version (faster than matrix), generates `coverage-summary.json`, uploads to Codecov v4
|
|
98
|
+
- **`coverageReporters`** in `jest.config.js` — added `json-summary` reporter for CI coverage data extraction
|
|
99
|
+
- **VS Code snippets** (`.vscode/mcp-tester.code-snippets`) — 15 snippets for MCP test patterns (type `mcp` prefix)
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
- **Per-file coverage thresholds** updated for new files (`masking.ts`, `logger.ts`) and actual coverage numbers
|
|
103
|
+
- Global thresholds: 67% statements, 61% branches, 60% functions, 67% lines (was 76/57/64/76)
|
|
104
|
+
- **README** coverage badge and table updated with real numbers (68/61/60/68)
|
|
105
|
+
- **docs/cicd.md** — rewritten with full workflow including coverage job, PR comments, and Codecov
|
|
106
|
+
- **docs/testing.md** — updated coverage thresholds (was "80%", now per-file table)
|
|
107
|
+
- **AGENTS.md** — updated test count, coverage stats, CI job descriptions, file listing
|
|
108
|
+
- Fixed flaky `delay` test (100ms → 90ms tolerance for timing variance)
|
|
109
|
+
- **Updated test count**: 491 (was 306)
|
|
110
|
+
|
|
8
111
|
## [1.1.0] - 2026-04-23
|
|
9
112
|
|
|
10
113
|
### Added
|
package/README.md
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@slbdn/mcp-tester)
|
|
8
8
|
[](https://www.npmjs.com/package/@slbdn/mcp-tester)
|
|
9
|
-
[](https://nodejs.org)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
|
-
[](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml)
|
|
12
|
+
[](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml) <!-- statements/branches/functions/lines -->
|
|
12
13
|
|
|
13
14
|
A production-ready MCP (Model Context Protocol) client for **automated testing** of MCP servers with Jest.
|
|
14
15
|
No more manual clicking through inspectors — write real tests, run them in CI, break builds on regressions.
|
|
@@ -25,13 +26,49 @@ No more manual clicking through inspectors — write real tests, run them in CI,
|
|
|
25
26
|
| **Notifications** | React to resource changes, logging messages, tool list updates |
|
|
26
27
|
| **Error Handling** | Custom error classes for timeouts, connection failures, server errors |
|
|
27
28
|
| **Retries** | Exponential backoff with configurable attempts and delay |
|
|
29
|
+
| **Benchmarks** | Measure latency, throughput, and payload performance with `npm run benchmark` |
|
|
28
30
|
| **CLI** | Smoke-test any server from the terminal in seconds |
|
|
29
31
|
|
|
32
|
+
## Architecture
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ Your Test Suite │
|
|
37
|
+
│ ┌──────────┐ ┌───────────┐ ┌────────┐ ┌───────────────────┐ │
|
|
38
|
+
│ │ Jest │ │ Vitest │ │ assert │ │ Custom Runner │ │
|
|
39
|
+
│ │ matchers │ │ matchers │ │ module │ │ (any framework) │ │
|
|
40
|
+
│ └────┬─────┘ └─────┬─────┘ └───┬────┘ └────────┬──────────┘ │
|
|
41
|
+
│ │ │ │ │ │
|
|
42
|
+
│ ┌────▼──────────────▼─────────────▼─────────────────▼──────────┐ │
|
|
43
|
+
│ │ MCPClient │ │
|
|
44
|
+
│ │ ┌──────────────────────────────────────────────────────┐ │ │
|
|
45
|
+
│ │ │ start() │ stop() │ listTools() │ callTool() │ ... │ │ │
|
|
46
|
+
│ │ └──────────────────────┬───────────────────────────────────┘ │ │
|
|
47
|
+
│ │ │ │ │
|
|
48
|
+
│ │ ┌──────────────────────▼───────────────────────────────┐ │ │
|
|
49
|
+
│ │ │ Retry │ Timeout │ Error Handling │ │ │
|
|
50
|
+
│ │ │ MCPTimeoutError │ MCPConnectionError │ ... │ │ │
|
|
51
|
+
│ │ └──────────────────────┬───────────────────────────────┘ │ │
|
|
52
|
+
│ └─────────────────────────┼─────────────────────────────────────┘ │
|
|
53
|
+
│ │ stdio (JSON-RPC) │
|
|
54
|
+
│ ┌─────────────────────────▼─────────────────────────────────────┐ │
|
|
55
|
+
│ │ MCP Server (child process) │ │
|
|
56
|
+
│ │ ┌────────┐ ┌───────────┐ ┌──────────┐ ┌────────────┐ │ │
|
|
57
|
+
│ │ │ Tools │ │ Resources │ │ Prompts │ │ Sampling │ │ │
|
|
58
|
+
│ │ └────────┘ └───────────┘ └──────────┘ └────────────┘ │ │
|
|
59
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
60
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Data flow**: Your test calls `MCPClient` → which sends JSON-RPC over stdio → to the MCP server process → server responds → `MCPClient` returns typed results → your assertion validates them.
|
|
64
|
+
|
|
65
|
+
**Key design**: `MCPClient` manages the server lifecycle (spawn/kill) and protocol handling. Your tests never deal with the transport layer directly.
|
|
66
|
+
|
|
30
67
|
## Why MCP Tester?
|
|
31
68
|
|
|
32
69
|
- **Full protocol support** — tools, resources, prompts, sampling, elicitation, notifications
|
|
33
70
|
- **Built on the official SDK** — `@modelcontextprotocol/sdk` v1.29.0
|
|
34
|
-
- **Works out of the box** — mock server included,
|
|
71
|
+
- **Works out of the box** — mock server included, 257 tests passing
|
|
35
72
|
- **CLI included** — test any server from the command line
|
|
36
73
|
- **TypeScript first** — strict types, ESM, full IntelliSense
|
|
37
74
|
- **Parallel execution** — fire multiple tool calls concurrently with `Promise.all` for blazing-fast test suites
|
|
@@ -39,6 +76,30 @@ No more manual clicking through inspectors — write real tests, run them in CI,
|
|
|
39
76
|
- **In-memory mock server** — unit test your test logic without spawning real processes
|
|
40
77
|
- **Custom Jest matchers** — `toHaveTool()`, `toHaveResource()`, `toHavePrompt()` — and Vitest support
|
|
41
78
|
|
|
79
|
+
## Test Coverage
|
|
80
|
+
|
|
81
|
+
| Metric | Coverage |
|
|
82
|
+
|--------|----------|
|
|
83
|
+
| Statements | 68% |
|
|
84
|
+
| Branches | 61% |
|
|
85
|
+
| Functions | 60% |
|
|
86
|
+
| Lines | 68% |
|
|
87
|
+
| Tests | 635 passing |
|
|
88
|
+
|
|
89
|
+
Per-file coverage thresholds are set in `jest.config.js` to catch regressions while avoiding CI noise. Run `npm run test:coverage` to see the full breakdown by file. PRs automatically receive a coverage comment via GitHub Actions.
|
|
90
|
+
|
|
91
|
+
## Test Reports
|
|
92
|
+
|
|
93
|
+
Every `npm test` run auto-generates an HTML test report:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm test # generates reports/test-report.html
|
|
97
|
+
open reports/test-report.html # macOS
|
|
98
|
+
xdg-open reports/test-report.html # Linux
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The report includes collapsible test trees, timing per test, and failure details with stack traces. CI uploads reports as artifacts (14-day retention).
|
|
102
|
+
|
|
42
103
|
## Install
|
|
43
104
|
|
|
44
105
|
```bash
|
|
@@ -124,12 +185,90 @@ npx @slbdn/mcp-tester list-tools node ./server.js --json
|
|
|
124
185
|
# Call a tool
|
|
125
186
|
npx @slbdn/mcp-tester call-tool echo node ./server.js --params '{"message":"Hello"}'
|
|
126
187
|
|
|
127
|
-
#
|
|
188
|
+
# Generate test file from server inspection
|
|
189
|
+
npx @slbdn/mcp-tester generate node ./server.js -o server.test.ts
|
|
190
|
+
npx @slbdn/mcp-tester gen node ./server.js --framework vitest --no-resources
|
|
191
|
+
|
|
192
|
+
# Short aliases: lt, ct, rr, gp, gen
|
|
128
193
|
npx @slbdn/mcp-tester lt node ./server.js
|
|
129
194
|
```
|
|
130
195
|
|
|
131
196
|
See [CLI Reference](./docs/cli.md) for all commands and options.
|
|
132
197
|
|
|
198
|
+
### Generate Test Files
|
|
199
|
+
|
|
200
|
+
Connect to any MCP server and generate a ready-to-run test file:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Generate Jest test file
|
|
204
|
+
npx @slbdn/mcp-tester generate node ./server.js -o server.test.ts
|
|
205
|
+
|
|
206
|
+
# Generate Vitest test file
|
|
207
|
+
npx @slbdn/mcp-tester gen node ./server.js --framework vitest
|
|
208
|
+
|
|
209
|
+
# Skip specific sections
|
|
210
|
+
npx @slbdn/mcp-tester generate node ./server.js --no-resources --no-prompts -o tools-only.test.ts
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Or use the API:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { generateTests } from '@slbdn/mcp-tester';
|
|
217
|
+
|
|
218
|
+
const code = await generateTests({
|
|
219
|
+
command: 'node',
|
|
220
|
+
args: ['./server.js'],
|
|
221
|
+
framework: 'jest',
|
|
222
|
+
description: 'My Server Tests',
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
fs.writeFileSync('server.test.ts', code);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The generator connects to the server, inspects all tools/resources/prompts, and generates:
|
|
229
|
+
- Connection and lifecycle tests
|
|
230
|
+
- Tool call tests with **sample arguments derived from JSON Schema**
|
|
231
|
+
- Resource read tests
|
|
232
|
+
- Prompt tests with sample arguments
|
|
233
|
+
- Proper `beforeEach`/`afterEach` cleanup
|
|
234
|
+
|
|
235
|
+
### Generate TypeScript Types
|
|
236
|
+
|
|
237
|
+
Connect to any MCP server and generate typed `.d.ts` declarations from tool schemas:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Generate TypeScript types
|
|
241
|
+
npx @slbdn/mcp-tester generate-types node ./server.js -o server.d.ts
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Or use the API:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { generateTypes } from '@slbdn/mcp-tester';
|
|
248
|
+
import { writeFileSync } from 'fs';
|
|
249
|
+
|
|
250
|
+
const types = await generateTypes({
|
|
251
|
+
command: 'node',
|
|
252
|
+
args: ['./server.js'],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
writeFileSync('server.d.ts', types);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This produces typed interfaces you can use for type-safe tool calls:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import type { ToolArgsMap } from './server.d.ts';
|
|
262
|
+
|
|
263
|
+
// TypeScript knows: { a: number; b: number }
|
|
264
|
+
const result = await client.callTool({
|
|
265
|
+
name: 'add',
|
|
266
|
+
arguments: { a: 1, b: 2 } as ToolArgsMap['add'],
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Supports: primitives, enums, nested objects, arrays, `oneOf`/`anyOf`/`allOf`, `$ref`, `const`.
|
|
271
|
+
|
|
133
272
|
## Speed Up Your Tests with Parallel Execution
|
|
134
273
|
|
|
135
274
|
MCP Tester is built for concurrency. Fire off multiple tool calls at once — no waiting in line:
|
|
@@ -167,6 +306,10 @@ No extra config needed. Just `Promise.all` and go.
|
|
|
167
306
|
| `start(config)` | `Promise<void>` | Connect to an MCP server |
|
|
168
307
|
| `stop()` | `Promise<void>` | Disconnect and clean up |
|
|
169
308
|
| `isConnected()` | `boolean` | Check connection state |
|
|
309
|
+
| `isHealthy()` | `Promise<HealthStatus>` | Check server health and responsiveness |
|
|
310
|
+
| `getServerPid()` | `number | null` | Get server process PID |
|
|
311
|
+
| `startHealthMonitor(opts)` | `void` | Start periodic health monitoring |
|
|
312
|
+
| `stopHealthMonitor()` | `void` | Stop health monitoring |
|
|
170
313
|
| `listTools()` | `Promise<Tool[]>` | List available tools |
|
|
171
314
|
| `callTool(options)` | `Promise<CallToolResult>` | Call a tool with arguments |
|
|
172
315
|
| `listResources()` | `Promise<Resource[]>` | List available resources |
|
|
@@ -179,6 +322,25 @@ No extra config needed. Just `Promise.all` and go.
|
|
|
179
322
|
|
|
180
323
|
See [API Reference](./docs/api-reference.md) for full details including types, options, and error classes.
|
|
181
324
|
|
|
325
|
+
## Server Health Checks
|
|
326
|
+
|
|
327
|
+
Detect crashed servers and zombie processes during long test runs:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// One-time check
|
|
331
|
+
const health = await client.isHealthy();
|
|
332
|
+
console.log(`Healthy: ${health.healthy}, PID: ${health.pid}, latency: ${health.latencyMs}ms`);
|
|
333
|
+
|
|
334
|
+
// Periodic monitoring
|
|
335
|
+
client.startHealthMonitor({
|
|
336
|
+
interval: 3000,
|
|
337
|
+
onUnhealthy: (status) => console.error('Server down:', status.message),
|
|
338
|
+
onRecovery: (status) => console.log('Server recovered!'),
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Health checks detect zombie processes (killed/crashed but `isConnected()` still returns `true`) using `process.kill(pid, 0)`.
|
|
343
|
+
|
|
182
344
|
## Custom Jest Matchers
|
|
183
345
|
|
|
184
346
|
Built-in matchers so your assertions read like English:
|
|
@@ -317,7 +479,7 @@ mcp-tester/
|
|
|
317
479
|
│ │ ├── errors.ts # Error classes
|
|
318
480
|
│ │ ├── logger.ts # Logging
|
|
319
481
|
│ │ └── env.ts # Environment utilities
|
|
320
|
-
│ └── __tests__/ #
|
|
482
|
+
│ └── __tests__/ # 306 tests
|
|
321
483
|
│ ├── everything-server.test.ts # Integration tests (server-everything)
|
|
322
484
|
│ ├── real-server.test.ts # Integration tests (stdio transport)
|
|
323
485
|
│ ├── cli.test.ts # CLI tool tests
|
|
@@ -328,7 +490,10 @@ mcp-tester/
|
|
|
328
490
|
│ ├── jest-matchers-example.ts # Jest matchers usage
|
|
329
491
|
│ ├── assert-example.ts # Assert utilities usage
|
|
330
492
|
│ ├── everything-server-test.ts # Test against real MCP server
|
|
331
|
-
│
|
|
493
|
+
│ ├── stateful-server-test.ts # Stateful server testing pattern
|
|
494
|
+
│ ├── mock-server.js # Basic standalone MCP server
|
|
495
|
+
│ ├── simple-server.ts # Minimal TypeScript MCP server
|
|
496
|
+
│ └── stateful-server.ts # Stateful MCP server (counter + todos)
|
|
332
497
|
└── docs/ # Documentation
|
|
333
498
|
```
|
|
334
499
|
|
|
@@ -337,11 +502,117 @@ mcp-tester/
|
|
|
337
502
|
```bash
|
|
338
503
|
npm install # Install dependencies
|
|
339
504
|
npm run build # Compile TypeScript
|
|
340
|
-
npm
|
|
505
|
+
npm run dev # Watch mode — auto-rebuild on source changes
|
|
506
|
+
npm test # Run tests (auto-generates reports/test-report.html)
|
|
341
507
|
npm run lint # Lint code
|
|
342
508
|
npm run format # Format with Prettier
|
|
343
509
|
```
|
|
344
510
|
|
|
511
|
+
## Troubleshooting
|
|
512
|
+
|
|
513
|
+
### "Client not started" Error
|
|
514
|
+
|
|
515
|
+
Calling methods before `start()`:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// ✗ Wrong
|
|
519
|
+
const client = new MCPClient();
|
|
520
|
+
const tools = await client.listTools(); // throws MCPNotStartedError
|
|
521
|
+
|
|
522
|
+
// ✓ Correct
|
|
523
|
+
const client = new MCPClient();
|
|
524
|
+
await client.start({ command: 'node', args: ['./server.js'] });
|
|
525
|
+
const tools = await client.listTools();
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Timeout Errors
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// Global timeout
|
|
532
|
+
const client = new MCPClient({ timeout: 60000 });
|
|
533
|
+
|
|
534
|
+
// Per-call override
|
|
535
|
+
await client.callTool({ name: 'slow-tool', arguments: {}, timeout: 30000 });
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Server Won't Start
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
# Verify the server runs standalone first
|
|
542
|
+
node ./my-server.js
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Check that the command and args are correct — `MCPConnectionError` includes the command and suggestions.
|
|
546
|
+
|
|
547
|
+
### ESM Import Errors
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
// ✗ Wrong — missing .js extension
|
|
551
|
+
import { MCPClient } from './client/MCPClient';
|
|
552
|
+
|
|
553
|
+
// ✓ Correct
|
|
554
|
+
import { MCPClient } from './client/MCPClient.js';
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Tests Fail in CI
|
|
558
|
+
|
|
559
|
+
- Set `maxWorkers: 1` in Jest config (parallel workers can crash)
|
|
560
|
+
- Verify Node.js >= 18
|
|
561
|
+
- Check that server paths are correct
|
|
562
|
+
|
|
563
|
+
### Debug Mode
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
const client = new MCPClient({ logLevel: 'debug', enableProtocolLogging: true });
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
All logs go to stderr — they won't interfere with test output.
|
|
570
|
+
|
|
571
|
+
New logging features:
|
|
572
|
+
- **Timing**: operations log elapsed time (e.g., `Listed 4 tools in 12ms`)
|
|
573
|
+
- **Secret masking**: API keys, tokens, and passwords are auto-masked (e.g., `sk-ab...789`)
|
|
574
|
+
- **Pretty-printed JSON**: protocol messages are formatted and masked
|
|
575
|
+
- **Timestamps**: enable with `{ timestamps: true }`
|
|
576
|
+
- **Colors**: auto-enabled in terminals (cyan=debug, green=info, yellow=warn, red=error)
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { startTimer, prettyPrint } from '@slbdn/mcp-tester';
|
|
580
|
+
|
|
581
|
+
// Measure operation timing
|
|
582
|
+
const elapsed = startTimer();
|
|
583
|
+
await someOperation();
|
|
584
|
+
console.log(`Took ${elapsed()}ms`);
|
|
585
|
+
|
|
586
|
+
// Pretty-print objects with secret masking
|
|
587
|
+
prettyPrint({ apiKey: 'sk-proj-abc123...', count: 42 });
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
See [Troubleshooting Guide](./docs/troubleshooting.md) for more details.
|
|
591
|
+
|
|
592
|
+
## VS Code Snippets
|
|
593
|
+
|
|
594
|
+
Type `mcp` in any TypeScript file to get started — 15 built-in snippets:
|
|
595
|
+
|
|
596
|
+
| Prefix | Inserts |
|
|
597
|
+
|--------|--------|
|
|
598
|
+
| `mcp-test` | Full test suite with lifecycle setup |
|
|
599
|
+
| `mcp-tool` | Tool call test + assertion |
|
|
600
|
+
| `mcp-resource` | Resource read test |
|
|
601
|
+
| `mcp-prompt` | Prompt get test |
|
|
602
|
+
| `mcp-client` | `new MCPClient()` + `start()` |
|
|
603
|
+
| `mcp-client-opts` | Client with timeout, retries, log level |
|
|
604
|
+
| `mcp-error` | Error handling test (`rejects.toThrow`) |
|
|
605
|
+
| `mcp-parallel` | `Promise.all` parallel tool calls |
|
|
606
|
+
| `mcp-list-tools` | List tools + `toHaveTool` assertion |
|
|
607
|
+
| `mcp-list-resources` | List resources + `toHaveResource` assertion |
|
|
608
|
+
| `mcp-list-prompts` | List prompts + `toHavePrompt` assertion |
|
|
609
|
+
| `mcp-assert-text` | `assert.toolTextEquals()` |
|
|
610
|
+
| `mcp-assert-json` | `assert.toolJsonEquals()` |
|
|
611
|
+
| `mcp-notifications` | Notification handler setup |
|
|
612
|
+
| `mcp-import` / `mcp-import-full` | Import statements |
|
|
613
|
+
|
|
614
|
+
Snippets are in `.vscode/mcp-tester.code-snippets` — works out of the box when you open the project in VS Code.
|
|
615
|
+
|
|
345
616
|
## Contributing
|
|
346
617
|
|
|
347
618
|
1. Fork the repository
|
package/dist/cli/index.js
CHANGED
|
@@ -3,11 +3,27 @@
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { MCPClient } from '../client/MCPClient.js';
|
|
5
5
|
import { MCPConnectionError, MCPTimeoutError, MCPServerError } from '../utils/errors.js';
|
|
6
|
+
import { generateTests } from '../generate-tests.js';
|
|
7
|
+
import { generateTypes } from '../generate-types.js';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
// Read version from package.json (works in both src and dist)
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
function getPackageVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
|
|
16
|
+
return pkg.version || '0.0.0';
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return '0.0.0';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
6
22
|
const program = new Command();
|
|
7
23
|
program
|
|
8
24
|
.name('mcp-tester')
|
|
9
25
|
.description('CLI tool for testing MCP servers')
|
|
10
|
-
.version(
|
|
26
|
+
.version(getPackageVersion(), '-V, --version');
|
|
11
27
|
// Global options - use string type since commander passes strings
|
|
12
28
|
program
|
|
13
29
|
.option('-t, --timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
@@ -290,6 +306,101 @@ program
|
|
|
290
306
|
await client.stop();
|
|
291
307
|
}
|
|
292
308
|
});
|
|
309
|
+
// Generate test file command
|
|
310
|
+
program
|
|
311
|
+
.command('generate')
|
|
312
|
+
.alias('gen')
|
|
313
|
+
.description('Generate test file from MCP server inspection')
|
|
314
|
+
.argument('<command>', 'Command to run the MCP server')
|
|
315
|
+
.argument('[args...]', 'Arguments for the server command')
|
|
316
|
+
.option('-o, --output <file>', 'Output file path (prints to stdout if omitted)')
|
|
317
|
+
.option('--framework <fw>', 'Test framework: jest or vitest', 'jest')
|
|
318
|
+
.option('--description <desc>', 'Description for the test suite')
|
|
319
|
+
.option('--no-resources', 'Skip resource tests')
|
|
320
|
+
.option('--no-prompts', 'Skip prompt tests')
|
|
321
|
+
.option('--no-tools', 'Skip tool tests')
|
|
322
|
+
.option('--no-matchers', 'Skip custom matchers import')
|
|
323
|
+
.action(async (command, serverArgs, opts) => {
|
|
324
|
+
const framework = String(opts['framework'] || 'jest');
|
|
325
|
+
if (framework !== 'jest' && framework !== 'vitest') {
|
|
326
|
+
console.error('❌ Framework must be "jest" or "vitest"');
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
console.error('🔍 Inspecting MCP server...');
|
|
331
|
+
const code = await generateTests({
|
|
332
|
+
command,
|
|
333
|
+
args: serverArgs,
|
|
334
|
+
framework: framework,
|
|
335
|
+
description: opts['description'],
|
|
336
|
+
includeResources: opts['resources'] !== false,
|
|
337
|
+
includePrompts: opts['prompts'] !== false,
|
|
338
|
+
includeTools: opts['tools'] !== false,
|
|
339
|
+
includeMatchers: opts['matchers'] !== false,
|
|
340
|
+
timeout: parseInt(String(opts['timeout'] || '30000')),
|
|
341
|
+
});
|
|
342
|
+
if (opts['output']) {
|
|
343
|
+
const fs = await import('fs');
|
|
344
|
+
const path = await import('path');
|
|
345
|
+
const outputPath = String(opts['output']);
|
|
346
|
+
const dir = path.dirname(outputPath);
|
|
347
|
+
if (dir && dir !== '.') {
|
|
348
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
349
|
+
}
|
|
350
|
+
fs.writeFileSync(outputPath, code, 'utf-8');
|
|
351
|
+
console.error(`✅ Generated test file: ${outputPath}`);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
process.stdout.write(code);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
console.error(`❌ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
// Generate TypeScript types command
|
|
363
|
+
program
|
|
364
|
+
.command('generate-types')
|
|
365
|
+
.alias('gen-types')
|
|
366
|
+
.description('Generate TypeScript type declarations from MCP server tool schemas')
|
|
367
|
+
.argument('<command>', 'Command to run the MCP server')
|
|
368
|
+
.argument('[args...]', 'Arguments for the server command')
|
|
369
|
+
.option('-o, --output <file>', 'Output file path (prints to stdout if omitted)')
|
|
370
|
+
.option('--module-name <name>', 'Module name for import hints', '@slbdn/mcp-tester')
|
|
371
|
+
.option('--no-resources', 'Skip resource URI types')
|
|
372
|
+
.option('--no-prompts', 'Skip prompt argument types')
|
|
373
|
+
.action(async (command, serverArgs, opts) => {
|
|
374
|
+
try {
|
|
375
|
+
console.error('🔍 Inspecting MCP server for type generation...');
|
|
376
|
+
const types = await generateTypes({
|
|
377
|
+
command,
|
|
378
|
+
args: serverArgs,
|
|
379
|
+
moduleName: opts['moduleName'],
|
|
380
|
+
includeResources: opts['resources'] !== false,
|
|
381
|
+
includePrompts: opts['prompts'] !== false,
|
|
382
|
+
timeout: parseInt(String(opts['timeout'] || '30000')),
|
|
383
|
+
});
|
|
384
|
+
if (opts['output']) {
|
|
385
|
+
const fs = await import('fs');
|
|
386
|
+
const path = await import('path');
|
|
387
|
+
const outputPath = String(opts['output']);
|
|
388
|
+
const dir = path.dirname(outputPath);
|
|
389
|
+
if (dir && dir !== '.') {
|
|
390
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
391
|
+
}
|
|
392
|
+
fs.writeFileSync(outputPath, types, 'utf-8');
|
|
393
|
+
console.error(`✅ Generated type declarations: ${outputPath}`);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
process.stdout.write(types);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
console.error(`❌ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
293
404
|
// Add custom help with examples using Commander's built-in help system
|
|
294
405
|
program.addHelpText('after', `
|
|
295
406
|
Examples:
|
|
@@ -300,6 +411,8 @@ Examples:
|
|
|
300
411
|
mcp-tester get-prompt greet node ./server.js --args '{"name":"World"}'
|
|
301
412
|
mcp-tester lt node ./server.js # short alias for list-tools
|
|
302
413
|
mcp-tester ct echo node ./server.js --params '{}' # short alias for call-tool
|
|
414
|
+
mcp-tester generate node ./server.js -o server.test.ts # generate test file
|
|
415
|
+
mcp-tester generate-types node ./server.js -o server.d.ts # generate TypeScript types
|
|
303
416
|
`);
|
|
304
417
|
// Show help when invoked with no arguments
|
|
305
418
|
const args = process.argv.slice(2);
|