@slbdn/mcp-tester 1.0.2 → 1.2.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/README.md CHANGED
@@ -1,329 +1,133 @@
1
1
  # MCP Tester
2
2
 
3
+ > **This is not your grandpa's MCP Inspector.**
4
+ > This is a real testing framework. No GUI, no clicking, no manual verification.
5
+ > Write tests, run them in CI, ship with confidence. 🚀
6
+
3
7
  [![npm version](https://img.shields.io/npm/v/@slbdn/mcp-tester)](https://www.npmjs.com/package/@slbdn/mcp-tester)
4
8
  [![npm downloads](https://img.shields.io/npm/dm/@slbdn/mcp-tester)](https://www.npmjs.com/package/@slbdn/mcp-tester)
5
- [![Node.js Version](https://img.shields.io/node/v/@slbdn/mcp-tester)](https://nodejs.org)
9
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
6
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![Test Status](https://img.shields.io/badge/tests-passing-brightgreen)](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml)
8
-
9
- A minimal, production-ready MCP (Model Context Protocol) client implementation for CI/CD testing of MCP servers with Jest.
10
-
11
- ## 🎯 Purpose
12
-
13
- MCP Tester provides a complete testing framework for MCP server implementations, enabling automated testing of tools, resources, prompts, and advanced capabilities like sampling and elicitation. It's designed specifically for CI/CD pipelines but also works great for local development and manual testing.
14
-
15
- ## ✨ Features
16
-
17
- - **Full MCP Protocol Support** - Tools, Resources, Prompts, Sampling, Elicitation, Tasks, and Notifications
18
- - **Official SDK Integration** - Built on `@modelcontextprotocol/sdk` v1.29.0 for reliable, standards-compliant testing
19
- - **Jest Integration** - Complete test suite with 43 tests covering all major functionality
20
- - **Mock Server Included** - Built-in mock MCP server for unit testing without external dependencies
21
- - **TypeScript Support** - Full type safety and IntelliSense with comprehensive TypeScript definitions
22
- - **CI/CD Ready** - GitHub Actions workflow included for automated testing
23
- - **Simple API** - Clean, intuitive client interface for easy test writing
24
- - **stdio Transport** - Optimized for local server testing via stdin/stdout
25
- - **Error Handling** - Robust timeout handling and error reporting
26
- - **Concurrent Requests** - Support for parallel tool calls and multiple simultaneous operations
27
-
28
- ## 📋 Table of Contents
29
-
30
- - [For AI Agents](#for-ai-agents)
31
- - [Installation](#installation)
32
- - [Node.js Compatibility](#nodejs-compatibility)
33
- - [Quick Start](#quick-start)
34
- - [API Reference](#api-reference)
35
- - [Testing](#testing)
36
- - [Examples](#examples)
37
- - [CI/CD Integration](#cicd-integration)
38
- - [Releases](#releases)
39
- - [Advanced Usage](#advanced-usage)
40
- - [Troubleshooting](#troubleshooting)
41
- - [Project Structure](#project-structure)
42
- - [Development](#development)
43
- - [Test Coverage](#test-coverage)
44
- - [Security](#security)
45
- - [License](#license)
46
- - [Contributing](#contributing)
47
- - [Additional Resources](#additional-resources)
48
- - [Support](#support)
49
-
50
-
51
- ## 🤖 For AI Agents
52
-
53
- If you're an AI agent or automated system working with this codebase, see [AGENTS.md](./AGENTS.md) for comprehensive documentation including:
54
-
55
- - **Essential commands** for development, testing, and releases
56
- - **Code organization** and file structure
57
- - **Naming conventions** and style guidelines
58
- - **Testing patterns** and common gotchas
59
- - **CI/CD integration** details
60
- - **Error handling** patterns
61
- - **Development workflow** and best practices
62
- - **Key files reference** for quick lookup
63
-
64
- The AGENTS.md file is specifically designed to help AI agents understand the codebase, patterns, and conventions used in this project.
65
-
66
- ## 🚀 Installation
67
-
68
- ### Prerequisites
69
-
70
- - **Node.js** >= 18
71
- - **npm** >= 9
72
-
73
- ### Install from npm
11
+ [![Test Status](https://img.shields.io/badge/tests-306%20passing-brightgreen)](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml)
12
+ [![Coverage](https://img.shields.io/badge/coverage-68%2F61%2F60%2F68-brightgreen)](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml) <!-- statements/branches/functions/lines -->
13
+
14
+ A production-ready MCP (Model Context Protocol) client for **automated testing** of MCP servers with Jest.
15
+ No more manual clicking through inspectors — write real tests, run them in CI, break builds on regressions.
16
+
17
+ ## Capabilities
18
+
19
+ | Category | What You Can Test |
20
+ |----------|-------------------|
21
+ | **Tools** | List, call with arguments, validate results, test error handling |
22
+ | **Resources** | List available resources, read by URI, validate content |
23
+ | **Prompts** | List templates, get with arguments, validate generated messages |
24
+ | **Sampling** | Request LLM sampling, validate model responses |
25
+ | **Elicitation** | Handle server-initiated user input requests |
26
+ | **Notifications** | React to resource changes, logging messages, tool list updates |
27
+ | **Error Handling** | Custom error classes for timeouts, connection failures, server errors |
28
+ | **Retries** | Exponential backoff with configurable attempts and delay |
29
+ | **Benchmarks** | Measure latency, throughput, and payload performance with `npm run benchmark` |
30
+ | **CLI** | Smoke-test any server from the terminal in seconds |
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
+
67
+ ## Why MCP Tester?
68
+
69
+ - **Full protocol support** — tools, resources, prompts, sampling, elicitation, notifications
70
+ - **Built on the official SDK** — `@modelcontextprotocol/sdk` v1.29.0
71
+ - **Works out of the box** — mock server included, 257 tests passing
72
+ - **CLI included** — test any server from the command line
73
+ - **TypeScript first** — strict types, ESM, full IntelliSense
74
+ - **Parallel execution** — fire multiple tool calls concurrently with `Promise.all` for blazing-fast test suites
75
+ - **Retry with backoff** — flaky servers? Configure retries so your CI stays green
76
+ - **In-memory mock server** — unit test your test logic without spawning real processes
77
+ - **Custom Jest matchers** — `toHaveTool()`, `toHaveResource()`, `toHavePrompt()` — and Vitest support
78
+
79
+ ## Test Coverage
80
+
81
+ | Metric | Coverage |
82
+ |--------|----------|
83
+ | Statements | 68% |
84
+ | Branches | 61% |
85
+ | Functions | 60% |
86
+ | Lines | 68% |
87
+ | Tests | 306 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
+ ## Install
74
92
 
75
93
  ```bash
76
94
  npm install @slbdn/mcp-tester --save-dev
77
95
  ```
78
96
 
79
- ### Install from git
80
-
81
- ```bash
82
- git clone https://github.com/islobodan/mcp-tester.git
83
- cd mcp-tester
84
- npm install
85
- ```
86
-
87
- ### Development Installation
88
-
89
- ```bash
90
- # Clone the repository
91
- git clone https://github.com/islobodan/mcp-tester.git
92
- cd mcp-tester
93
-
94
- # Install dependencies
95
- npm install
96
-
97
- # Build the project
98
- npm run build
99
- ```
100
-
101
- ## 🌐 Node.js Compatibility
102
-
103
- MCP Tester is designed to work with a wide range of Node.js versions. Here's the compatibility matrix:
104
-
105
- ### Supported Versions
106
-
107
- | Node.js Version | Status | Notes |
108
- |---------------|--------|-------|
109
- | 18.x | ✅ Fully Supported | Minimum required version |
110
- | 20.x (LTS) | ✅ Fully Supported | Recommended for production |
111
- | 21.x | ✅ Fully Supported | Latest stable release |
112
- | 22.x | ✅ Supported | Tested in CI/CD |
113
-
114
- ### Testing Matrix
115
-
116
- MCP Tester is automatically tested against multiple Node.js versions in CI/CD:
117
-
118
- ```yaml
119
- strategy:
120
- matrix:
121
- node-version: [18, 20, 21]
122
- ```
123
-
124
- All tests pass successfully on:
125
- - Node.js 18.19.0+
126
- - Node.js 20.11.0+
127
- - Node.js 21.7.0+
128
-
129
- ### Runtime Requirements
130
-
131
- The following Node.js features are required:
132
- - ES2022 Modules (ESM)
133
- - Async/await support
134
- - Promise-based APIs
135
- - Child process `spawn` with stdio
136
- - Fetch API (for potential HTTP transport support)
137
-
138
- ### Version-Specific Notes
139
-
140
- #### Node.js 18.x
141
- - Minimum required version
142
- - All features fully supported
143
- - No known issues
144
-
145
- #### Node.js 20.x (LTS)
146
- - **Recommended for production use**
147
- - Includes performance improvements
148
- - All features fully supported
149
-
150
- #### Node.js 21.x
151
- - Latest stable features
152
- - Improved garbage collection
153
- - Full feature compatibility
154
-
155
- #### Node.js 22.x
156
- - Testing indicates compatibility
157
- - May include newer V8 improvements
158
-
159
- ### Package Engine Configuration
160
-
161
- The `package.json` specifies the Node.js engine requirement:
162
-
163
- ```json
164
- {
165
- "engines": {
166
- "node": ">=18"
167
- }
168
- }
169
- ```
170
-
171
- This ensures that npm will prevent installation on unsupported Node.js versions.
172
-
173
- ### TypeScript Compatibility
174
-
175
- MCP Tester targets TypeScript 5.3.0 with ES2022 output:
176
-
177
- ```json
178
- {
179
- "compilerOptions": {
180
- "target": "ES2022",
181
- "module": "ES2022",
182
- "lib": ["ES2022"]
183
- }
184
- }
185
- ```
186
-
187
- ### Testing Your Node.js Version
188
-
189
- To check your Node.js version:
190
-
191
- ```bash
192
- # Check Node.js version
193
- node --version
194
-
195
- # Check npm version
196
- npm --version
197
-
198
- # Verify compatibility
199
- node --version | awk '{print $1}' | xargs -I {} echo "Node.js {} is supported"
200
- ```
201
-
202
- ### Upgrading Node.js
203
-
204
- If you're using an older version of Node.js (< 18):
205
-
206
- **Using nvm (Node Version Manager):**
207
- ```bash
208
- # Install latest Node.js
209
- nvm install 20
210
-
211
- # Use specific version
212
- nvm use 20
213
-
214
- # Set as default
215
- nvm alias default 20
216
- ```
217
-
218
- **Using n:**
219
- ```bash
220
- # Install latest LTS
221
- n 20
222
-
223
- # Switch versions
224
- n 18
225
- ```
226
-
227
- **Using Node.js installer:**
228
- ```bash
229
- # Download from https://nodejs.org/
230
- # Install the latest LTS version (20.x recommended)
231
- ```
232
-
233
- ### CI/CD Version Specification
234
-
235
- When using MCP Tester in CI/CD, specify the Node.js version:
236
-
237
- **GitHub Actions:**
238
- ```yaml
239
- - uses: actions/setup-node@v3
240
- with:
241
- node-version: '20' # Specify desired version
242
- ```
243
-
244
- **CircleCI:**
245
- ```yaml
246
- docker:
247
- - image: cimg/node:20.11.0
248
- ```
249
-
250
- **Jenkins:**
251
- ```groovy
252
- tools {
253
- nodejs 'Node.js 20.11.0'
254
- }
255
- ```
256
-
257
- ### Known Issues by Version
258
-
259
- | Node.js Version | Known Issue | Workaround |
260
- |---------------|--------------|-------------|
261
- | < 18 | Not supported | Upgrade to 18+ |
262
- | 18.0-18.16 | Potential issues with ESM | Use 18.17+ |
263
- | Any | Memory limits for large operations | Increase `--max-old-space-size` |
264
-
265
- ### Performance by Version
266
-
267
- Relative performance benchmarks (lower is better):
268
-
269
- | Operation | Node 18 | Node 20 | Node 21 |
270
- |-----------|-----------|-----------|-----------|
271
- | Tool Call | 1.0x | 0.95x | 0.92x |
272
- | Resource Read | 1.0x | 0.96x | 0.93x |
273
- | Concurrent Ops | 1.0x | 1.02x | 1.05x |
274
-
275
- *Based on 1000 operations per test
276
-
277
- ### Platform Compatibility
278
-
279
- MCP Tester works on:
280
- - ✅ macOS (Intel & Apple Silicon)
281
- - ✅ Linux (Ubuntu, Debian, CentOS, Alpine)
282
- - ✅ Windows 10/11 (via WSL or native Node.js)
97
+ Requires **Node.js >= 18**. See [Node.js Compatibility](./docs/nodejs-compatibility.md) for details.
283
98
 
284
- Windows Notes:
285
- - Native support with Node.js
286
- - WSL2 recommended for Unix-like development
287
- - Ensure proper line endings (CRLF vs LF)
99
+ ## Quick Start
288
100
 
289
- ## ⚡ Quick Start
290
-
291
- ### Basic Usage
101
+ ### Connect and Use Tools
292
102
 
293
103
  ```typescript
294
- import { MCPClient } from 'mcp-tester';
104
+ import { MCPClient } from '@slbdn/mcp-tester';
295
105
 
296
106
  const client = new MCPClient({
297
107
  name: 'my-test-client',
298
108
  version: '1.0.0',
299
- timeout: 30000,
300
109
  });
301
110
 
302
- // Start the client with your MCP server
111
+ // Connect to your MCP server
303
112
  await client.start({
304
113
  command: 'node',
305
114
  args: ['./your-mcp-server.js'],
306
115
  });
307
116
 
308
- // List available tools
117
+ // List and call tools
309
118
  const tools = await client.listTools();
310
- console.log('Available tools:', tools.map(t => t.name));
311
-
312
- // Call a tool
313
119
  const result = await client.callTool({
314
- name: 'your-tool-name',
315
- arguments: { param: 'value' },
120
+ name: 'your-tool',
121
+ arguments: { input: 'test' },
316
122
  });
317
- console.log('Tool result:', result);
318
123
 
319
- // Clean up
320
124
  await client.stop();
321
125
  ```
322
126
 
323
- ### Testing an MCP Server
127
+ ### Test with Jest
324
128
 
325
129
  ```typescript
326
- import { MCPClient } from 'mcp-tester';
130
+ import { MCPClient } from '@slbdn/mcp-tester';
327
131
 
328
132
  describe('My MCP Server', () => {
329
133
  let client: MCPClient;
@@ -357,1206 +161,329 @@ describe('My MCP Server', () => {
357
161
  });
358
162
  ```
359
163
 
360
- ### Run Included Examples
164
+ ### Test from the Command Line
361
165
 
362
166
  ```bash
363
- # Basic functionality test
364
- npx tsx examples/basic-test.ts
365
-
366
- # Full capabilities test
367
- npx tsx examples/full-test.ts
368
-
369
- # Run mock server standalone (for manual testing)
370
- node examples/mock-server.js
371
- ```
372
-
373
- ## 📚 API Reference
374
-
375
- ### MCPClient
376
-
377
- The main class for interacting with MCP servers.
167
+ # Quick smoke test
168
+ npx @slbdn/mcp-tester test node ./server.js
378
169
 
379
- #### Constructor
170
+ # List tools (JSON output)
171
+ npx @slbdn/mcp-tester list-tools node ./server.js --json
380
172
 
381
- ```typescript
382
- new MCPClient(options?: MCPClientOptions)
383
- ```
384
-
385
- **Parameters:**
386
-
387
- - `options` (optional) - Client configuration options
388
- - `name` (string): Client identifier (default: `'mcp-test-client'`)
389
- - `version` (string): Client version (default: `'1.0.0'`)
390
- - `timeout` (number): Default request timeout in milliseconds (default: `30000`)
173
+ # Call a tool
174
+ npx @slbdn/mcp-tester call-tool echo node ./server.js --params '{"message":"Hello"}'
391
175
 
392
- **Example:**
393
-
394
- ```typescript
395
- const client = new MCPClient({
396
- name: 'production-test-client',
397
- version: '2.1.0',
398
- timeout: 60000,
399
- });
176
+ # Short aliases: lt, ct, rr, gp
177
+ npx @slbdn/mcp-tester lt node ./server.js
400
178
  ```
401
179
 
402
- #### Methods
180
+ See [CLI Reference](./docs/cli.md) for all commands and options.
403
181
 
404
- ##### `start(config: MCPServerConfig): Promise<void>`
182
+ ## Speed Up Your Tests with Parallel Execution
405
183
 
406
- Start the client and connect to an MCP server.
407
-
408
- **Parameters:**
409
-
410
- - `config.command` (string): Command to execute the MCP server (e.g., `'node'`, `'python3'`)
411
- - `config.args` (string[], optional): Arguments to pass to the server command
412
- - `config.env` (Record<string, string>, optional): Environment variables for the server process
413
-
414
- **Returns:** `Promise<void>`
415
-
416
- **Throws:** `Error` if client is already started or server fails to start
417
-
418
- **Example:**
184
+ MCP Tester is built for concurrency. Fire off multiple tool calls at once — no waiting in line:
419
185
 
420
186
  ```typescript
421
- await client.start({
422
- command: 'node',
423
- args: ['./server.js', '--production'],
424
- env: {
425
- NODE_ENV: 'production',
426
- API_KEY: process.env.API_KEY,
427
- },
428
- });
429
- ```
430
-
431
- ##### `stop(): Promise<void>`
187
+ import { MCPClient } from '@slbdn/mcp-tester';
432
188
 
433
- Stop the client and disconnect from the MCP server.
189
+ const client = new MCPClient();
190
+ await client.start({ command: 'node', args: ['./server.js'] });
434
191
 
435
- **Returns:** `Promise<void>`
192
+ // 🔥 Run all calls in parallel — 5x faster than sequential
193
+ const [tools, resources, prompts] = await Promise.all([
194
+ client.listTools(),
195
+ client.listResources(),
196
+ client.listPrompts(),
197
+ ]);
436
198
 
437
- **Example:**
199
+ // Parallel tool calls? No problem.
200
+ const [echo, sum, env] = await Promise.all([
201
+ client.callTool({ name: 'echo', arguments: { message: 'hello' } }),
202
+ client.callTool({ name: 'get-sum', arguments: { a: 1, b: 2 } }),
203
+ client.callTool({ name: 'get-env', arguments: {} }),
204
+ ]);
438
205
 
439
- ```typescript
440
206
  await client.stop();
441
207
  ```
442
208
 
443
- ##### `isConnected(): boolean`
444
-
445
- Check if the client is currently connected to an MCP server.
446
-
447
- **Returns:** `boolean` - `true` if connected, `false` otherwise
448
-
449
- **Example:**
450
-
451
- ```typescript
452
- if (client.isConnected()) {
453
- console.log('Client is active');
454
- }
455
- ```
456
-
457
- ##### `listTools(): Promise<Tool[]>`
209
+ Works because each MCP call is independent — the client multiplexes requests over a single stdio connection.
210
+ No extra config needed. Just `Promise.all` and go.
458
211
 
459
- List all available tools from the connected MCP server.
212
+ ## Core API
460
213
 
461
- **Returns:** `Promise<Tool[]>` - Array of tool objects
214
+ | Method | Returns | Description |
215
+ |--------|---------|-------------|
216
+ | `start(config)` | `Promise<void>` | Connect to an MCP server |
217
+ | `stop()` | `Promise<void>` | Disconnect and clean up |
218
+ | `isConnected()` | `boolean` | Check connection state |
219
+ | `listTools()` | `Promise<Tool[]>` | List available tools |
220
+ | `callTool(options)` | `Promise<CallToolResult>` | Call a tool with arguments |
221
+ | `listResources()` | `Promise<Resource[]>` | List available resources |
222
+ | `readResource(uri)` | `Promise<ReadResourceResult>` | Read a resource by URI |
223
+ | `listPrompts()` | `Promise<Prompt[]>` | List available prompts |
224
+ | `getPrompt(name, args?)` | `Promise<GetPromptResult>` | Get a prompt template |
225
+ | `requestSampling(request)` | `Promise<CreateMessageResult>` | Request LLM sampling |
226
+ | `setElicitationHandler(fn)` | `Promise<void>` | Handle server user-input requests |
227
+ | `setNotificationHandlers(handlers)` | `void` | Handle server notifications |
462
228
 
463
- **Throws:** `Error` if client is not started
229
+ See [API Reference](./docs/api-reference.md) for full details including types, options, and error classes.
464
230
 
465
- **Tool Object Properties:**
466
- - `name` (string): Unique tool identifier
467
- - `description` (string): Human-readable tool description
468
- - `inputSchema` (object): JSON Schema for tool parameters
231
+ ## Custom Jest Matchers
469
232
 
470
- **Example:**
233
+ Built-in matchers so your assertions read like English:
471
234
 
472
235
  ```typescript
473
- const tools = await client.listTools();
474
- tools.forEach(tool => {
475
- console.log(`- ${tool.name}: ${tool.description}`);
476
- });
477
- ```
236
+ import {
237
+ toHaveTool, toHaveResource, toHavePrompt,
238
+ toReturnText, toReturnError, toReturnJson,
239
+ setupJestMatchers,
240
+ } from '@slbdn/mcp-tester';
478
241
 
479
- ##### `callTool(options: ToolCallOptions): Promise<ToolResult>`
242
+ beforeAll(() => setupJestMatchers());
480
243
 
481
- Call a specific tool with optional arguments.
244
+ // Collection matchers
245
+ expect(tools).toHaveTool('echo');
246
+ expect(resources).toHaveResource('config://settings');
247
+ expect(prompts).toHavePrompt('greet');
248
+ expect(tools).toHaveToolCount(5);
482
249
 
483
- **Parameters:**
484
-
485
- - `options.name` (string): Name of the tool to call
486
- - `options.arguments` (Record<string, unknown>, optional): Arguments to pass to the tool
487
- - `options.timeout` (number, optional): Override default timeout for this call
488
-
489
- **Returns:** `Promise<ToolResult>` - Result containing content array
490
-
491
- **Throws:** `Error` if client is not started, tool not found, or call fails
250
+ // Tool result matchers
251
+ expect(result).toReturnText('Hello World');
252
+ expect(result).toReturnTextContaining('hello');
253
+ expect(result).toReturnError();
254
+ expect(result).toReturnJson({ status: 'ok' });
255
+ expect(result).toReturnContentCount(2);
256
+ ```
492
257
 
493
- **Example:**
258
+ ### Vitest Setup
494
259
 
495
260
  ```typescript
496
- const result = await client.callTool({
497
- name: 'calculator-add',
498
- arguments: { a: 5, b: 3 },
499
- timeout: 10000,
500
- });
261
+ import { setupVitestMatchers } from '@slbdn/mcp-tester';
262
+ import { beforeAll } from 'vitest';
263
+ // Add vitest.d.ts to your tsconfig for type safety
264
+ // /// <reference types="@slbdn/mcp-tester/vitest" />
501
265
 
502
- console.log('Result:', result.content[0].text);
266
+ beforeAll(() => setupVitestMatchers());
503
267
  ```
504
268
 
505
- ##### `listResources(): Promise<Resource[]>`
506
-
507
- List all available resources from the connected MCP server.
508
-
509
- **Returns:** `Promise<Resource[]>` - Array of resource objects
269
+ Works identically — same matchers, same `{ pass, message }` format, just `setupVitestMatchers()` instead.
510
270
 
511
- **Throws:** `Error` if client is not started
271
+ | Matcher | Description |
272
+ |---------|-------------|
273
+ | `toHaveTool(name)` | Assert a tool exists by name |
274
+ | `toHaveToolWithSchema(name)` | Assert a tool exists and has an input schema |
275
+ | `toHaveToolCount(n)` | Assert exact number of tools |
276
+ | `toHaveResource(uri)` | Assert a resource exists by URI |
277
+ | `toHaveResourceByName(name)` | Assert a resource exists by display name |
278
+ | `toHaveResourceCount(n)` | Assert exact number of resources |
279
+ | `toHavePrompt(name)` | Assert a prompt exists by name |
280
+ | `toHavePromptWithArgs(name)` | Assert a prompt exists and has arguments |
281
+ | `toHavePromptCount(n)` | Assert exact number of prompts |
282
+ | `toReturnText(expected?)` | Tool text exact match (or just has text) |
283
+ | `toReturnTextContaining(sub)` | Tool text contains substring |
284
+ | `toReturnError()` | Tool result is an error |
285
+ | `toReturnOk()` | Tool result is successful |
286
+ | `toReturnJson(obj)` | Parse tool text as JSON, deep compare |
287
+ | `toReturnContentCount(n)` | Tool has N content items |
288
+ | `toReturnImage()` | Tool result contains an image |
289
+ | `toReturnResourceText(expected?)` | Resource text exact match (or just has text) |
290
+ | `toReturnResourceTextContaining(sub)` | Resource text contains substring |
291
+ | `toReturnPromptTextContaining(sub)` | Prompt message text contains substring |
292
+ | `toReturnPromptMessageCount(n)` | Prompt has N messages |
293
+
294
+ See [Testing Guide](./docs/testing.md) for full testing patterns.
295
+
296
+ ## Assertion Utilities
297
+
298
+ Not using Jest? Running tests with a custom `runTest()` harness? The built-in `assert` module throws descriptive errors on failure — works with any runner:
299
+
300
+ ```typescript
301
+ import { MCPClient, assert } from '@slbdn/mcp-tester';
512
302
 
513
- **Resource Object Properties:**
514
- - `uri` (string): Unique resource identifier
515
- - `name` (string): Human-readable resource name
516
- - `description` (string): Resource description
517
- - `mimeType` (string): Resource MIME type
303
+ const client = new MCPClient();
304
+ await client.start({ command: 'node', args: ['./server.js'] });
518
305
 
519
- **Example:**
306
+ await runTest("Get constant: c (speed of light)", async () => {
307
+ const result = await client.callTool({
308
+ name: "get_constant",
309
+ arguments: { name: "c" },
310
+ });
311
+ assert.toolNumEquals(result, 299792458);
312
+ });
520
313
 
521
- ```typescript
522
- const resources = await client.listResources();
523
- resources.forEach(resource => {
524
- console.log(`- ${resource.uri}: ${resource.mimeType}`);
314
+ await runTest("Echo returns input", async () => {
315
+ const result = await client.callTool({
316
+ name: "echo",
317
+ arguments: { message: "hello" },
318
+ });
319
+ assert.toolTextContains(result, "hello");
525
320
  });
526
321
  ```
527
322
 
528
- ##### `readResource(uri: string): Promise<ResourceContents>`
529
-
530
- Read a specific resource by its URI.
531
-
532
- **Parameters:**
323
+ | Assertion | Description |
324
+ |-----------|-------------|
325
+ | `equal(a, b)` / `notEqual(a, b)` | Strict equality checks |
326
+ | `deepEqual(a, b)` | JSON deep equality |
327
+ | `ok(val)` / `notOk(val)` | Truthy / falsy |
328
+ | `throws(fn)` / `doesNotThrow(fn)` | Async error checks |
329
+ | `greaterThan(a, b)` / `lessThan(a, b)` / `closeTo(a, b, eps?)` | Numeric comparisons |
330
+ | `contains(str, sub)` / `matches(str, regex)` | String checks |
331
+ | `toolTextEquals(r, str)` | Tool text exact match |
332
+ | `toolTextContains(r, str)` | Tool text contains |
333
+ | `toolNumEquals(r, num)` | Parse tool text as number, compare |
334
+ | `toolNumCloseTo(r, num, eps?)` | Numeric approximate match |
335
+ | `toolJsonEquals(r, obj)` | Parse tool text as JSON, deep compare |
336
+ | `toolIsError(r)` / `toolIsOk(r)` | Error/success check |
337
+ | `toolHasImage(r)` | Check for image content |
338
+ | `resourceTextContains(r, str)` | Resource text contains |
339
+ | `promptTextContains(r, str)` | Prompt text contains |
340
+
341
+ ## Documentation
342
+
343
+ | Doc | Description |
344
+ |-----|-------------|
345
+ | [API Reference](./docs/api-reference.md) | Full method docs, types, and error classes |
346
+ | [Testing Guide](./docs/testing.md) | Writing tests, matchers, mock server |
347
+ | [Examples](./docs/examples.md) | Practical code examples |
348
+ | [Advanced Usage](./docs/advanced.md) | Timeouts, retries, concurrency, notifications, logging |
349
+ | [CLI Reference](./docs/cli.md) | CLI commands, options, and output formats |
350
+ | [CI/CD Integration](./docs/cicd.md) | GitHub Actions, CircleCI, Jenkins |
351
+ | [Troubleshooting](./docs/troubleshooting.md) | Common issues and solutions |
352
+ | [Releases](./docs/releases.md) | Release process and commands |
353
+ | [Node.js Compatibility](./docs/nodejs-compatibility.md) | Version matrix and upgrade guide |
354
+ | [AGENTS.md](./AGENTS.md) | Guide for AI agents working with this codebase |
355
+
356
+ ## Project Structure
533
357
 
534
- - `uri` (string): URI of the resource to read
535
-
536
- **Returns:** `Promise<ResourceContents>` - Object containing resource contents
537
-
538
- **Throws:** `Error` if client is not started or resource not found
539
-
540
- **Example:**
541
-
542
- ```typescript
543
- const result = await client.readResource('config://settings');
544
- console.log('Settings:', result.contents[0].text);
545
358
  ```
546
-
547
- ##### `listPrompts(): Promise<Prompt[]>`
548
-
549
- List all available prompts from the connected MCP server.
550
-
551
- **Returns:** `Promise<Prompt[]>` - Array of prompt objects
552
-
553
- **Throws:** `Error` if client is not started
554
-
555
- **Prompt Object Properties:**
556
- - `name` (string): Unique prompt identifier
557
- - `description` (string): Human-readable prompt description
558
- - `arguments` (array): Required/optional prompt arguments
559
-
560
- **Example:**
561
-
562
- ```typescript
563
- const prompts = await client.listPrompts();
564
- prompts.forEach(prompt => {
565
- console.log(`- ${prompt.name}: ${prompt.description}`);
566
- });
359
+ mcp-tester/
360
+ ├── src/
361
+ │ ├── client/MCPClient.ts # Main client class
362
+ │ ├── cli/index.ts # CLI tool
363
+ │ ├── assert.ts # Assertion utilities
364
+ │ ├── matchers.ts # Custom Jest matchers
365
+ │ ├── utils/
366
+ │ │ ├── errors.ts # Error classes
367
+ │ │ ├── logger.ts # Logging
368
+ │ │ └── env.ts # Environment utilities
369
+ │ └── __tests__/ # 306 tests
370
+ │ ├── everything-server.test.ts # Integration tests (server-everything)
371
+ │ ├── real-server.test.ts # Integration tests (stdio transport)
372
+ │ ├── cli.test.ts # CLI tool tests
373
+ │ └── fixtures/mock-server.ts # In-memory mock server
374
+ ├── examples/
375
+ │ ├── basic-test.ts # Basic usage example
376
+ │ ├── full-test.ts # Full capabilities example
377
+ │ ├── jest-matchers-example.ts # Jest matchers usage
378
+ │ ├── assert-example.ts # Assert utilities usage
379
+ │ ├── everything-server-test.ts # Test against real MCP server
380
+ │ ├── stateful-server-test.ts # Stateful server testing pattern
381
+ │ ├── mock-server.js # Basic standalone MCP server
382
+ │ ├── simple-server.ts # Minimal TypeScript MCP server
383
+ │ └── stateful-server.ts # Stateful MCP server (counter + todos)
384
+ └── docs/ # Documentation
567
385
  ```
568
386
 
569
- ##### `getPrompt(name: string, args?: Record<string, string>): Promise<PromptMessage[]>`
570
-
571
- Get a specific prompt template, optionally with argument values.
572
-
573
- **Parameters:**
574
-
575
- - `name` (string): Name of the prompt to retrieve
576
- - `args` (Record<string, string>, optional): Values for prompt arguments
387
+ ## Development
577
388
 
578
- **Returns:** `Promise<PromptMessage[]>` - Array of message objects
579
-
580
- **Throws:** `Error` if client is not started or prompt not found
581
-
582
- **Example:**
583
-
584
- ```typescript
585
- const messages = await client.getPrompt('greet', { name: 'Alice' });
586
- console.log('Prompt:', messages[0].content.text);
389
+ ```bash
390
+ npm install # Install dependencies
391
+ npm run build # Compile TypeScript
392
+ npm run dev # Watch mode — auto-rebuild on source changes
393
+ npm test # Run tests (306 tests)
394
+ npm run lint # Lint code
395
+ npm run format # Format with Prettier
587
396
  ```
588
397
 
589
- ##### `requestSampling(request: CreateMessageRequestParams): Promise<SamplingResult>`
590
-
591
- Request LLM sampling from the MCP server.
592
-
593
- **Parameters:**
594
-
595
- - `request` (CreateMessageRequestParams): Sampling request parameters
596
- - `messages` (array): Array of conversation messages
597
- - `maxTokens` (number): Maximum tokens to generate
598
- - `modelPreferences` (object, optional): Model selection preferences
398
+ ## Troubleshooting
599
399
 
600
- **Returns:** `Promise<SamplingResult>` - Sampling result with generated content
400
+ ### "Client not started" Error
601
401
 
602
- **Throws:** `Error` if client is not started or sampling not supported
603
-
604
- **Example:**
402
+ Calling methods before `start()`:
605
403
 
606
404
  ```typescript
607
- const result = await client.requestSampling({
608
- messages: [
609
- {
610
- role: 'user',
611
- content: {
612
- type: 'text',
613
- text: 'Explain quantum computing',
614
- },
615
- },
616
- ],
617
- maxTokens: 500,
618
- });
619
- ```
620
-
621
- ##### `setElicitationHandler(handler: Function): Promise<void>`
622
-
623
- Configure an elicitation handler to respond to server requests for user input.
624
-
625
- **Parameters:**
626
-
627
- - `handler` (Function): Async function that receives elicitation requests
628
- - Returns object with `action` field: `'accept' | 'decline' | 'cancel'`
629
- - Optionally returns `content` with collected input
630
-
631
- **Returns:** `Promise<void>`
632
-
633
- **Example:**
405
+ // ✗ Wrong
406
+ const client = new MCPClient();
407
+ const tools = await client.listTools(); // throws MCPNotStartedError
634
408
 
635
- ```typescript
636
- await client.setElicitationHandler(async (request) => {
637
- if (request.params.mode === 'form') {
638
- return {
639
- action: 'accept',
640
- content: { userInput: 'User provided data' },
641
- };
642
- }
643
- return { action: 'decline' };
644
- });
409
+ // ✓ Correct
410
+ const client = new MCPClient();
411
+ await client.start({ command: 'node', args: ['./server.js'] });
412
+ const tools = await client.listTools();
645
413
  ```
646
414
 
647
- ##### `setNotificationHandlers(handlers: NotificationHandler): void`
648
-
649
- Configure handlers for server-initiated notifications.
650
-
651
- **Parameters:**
652
-
653
- - `handlers.onLoggingMessage` (Function): Handle logging notifications
654
- - `level` (string): Log level (`'debug' | 'info' | 'warning' | 'error'`)
655
- - `data` (string): Log message content
656
-
657
- - `handlers.onResourceListChanged` (Function): Handle resource list change notifications
658
-
659
- **Example:**
415
+ ### Timeout Errors
660
416
 
661
417
  ```typescript
662
- client.setNotificationHandlers({
663
- onLoggingMessage: (level, data) => {
664
- console.log(`[${level}] ${data}`);
665
- },
666
- onResourceListChanged: () => {
667
- console.log('Resources updated, refreshing...');
668
- },
669
- });
670
- ```
671
-
672
- ## 🧪 Testing
673
-
674
- ### Running Tests
675
-
676
- ```bash
677
- # Run all tests
678
- npm test
679
-
680
- # Run in watch mode (auto-rerun on changes)
681
- npm run test:watch
682
-
683
- # Run with coverage report
684
- npm run test:coverage
418
+ // Global timeout
419
+ const client = new MCPClient({ timeout: 60000 });
685
420
 
686
- # Lint code
687
- npm run lint
421
+ // Per-call override
422
+ await client.callTool({ name: 'slow-tool', arguments: {}, timeout: 30000 });
688
423
  ```
689
424
 
690
- ### Test Structure
425
+ ### Server Won't Start
691
426
 
692
- ```
693
- src/__tests__/
694
- ├── client.test.ts # Basic client operations
695
- ├── resources-prompts.test.ts # Resources and prompts functionality
696
- ├── advanced.test.ts # Advanced features (sampling, elicitation)
697
- ├── real-server.test.ts # Integration tests (real server process via stdio)
698
- ├── helpers-example.test.ts # Test helpers usage examples
699
- ├── helpers.ts # Test utility functions
700
- ├── matchers.ts # Custom Jest matchers
701
- └── fixtures/
702
- └── mock-server.ts # In-memory mock MCP server for unit testing
427
+ ```bash
428
+ # Verify the server runs standalone first
429
+ node ./my-server.js
703
430
  ```
704
431
 
705
- ### Writing Custom Tests
432
+ Check that the command and args are correct — `MCPConnectionError` includes the command and suggestions.
706
433
 
707
- #### Test Template
434
+ ### ESM Import Errors
708
435
 
709
436
  ```typescript
710
- import { MCPClient } from '../client/MCPClient.js';
711
- import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
712
-
713
- describe('My Feature', () => {
714
- let client: MCPClient;
715
-
716
- beforeEach(() => {
717
- client = new MCPClient({
718
- name: 'test-client',
719
- version: '1.0.0',
720
- });
721
- });
722
-
723
- afterEach(async () => {
724
- if (client.isConnected()) {
725
- await client.stop();
726
- }
727
- });
728
-
729
- it('should test feature', async () => {
730
- const result = await client.callTool({
731
- name: 'my-feature',
732
- arguments: {},
733
- });
734
- expect(result).toBeDefined();
735
- });
736
- });
737
- ```
738
-
739
- #### Testing Patterns
437
+ // ✗ Wrong — missing .js extension
438
+ import { MCPClient } from './client/MCPClient';
740
439
 
741
- **Async/Await Pattern**
742
-
743
- Always use `async/await` for MCP client operations:
744
-
745
- ```typescript
746
- it('should handle async operation', async () => {
747
- const result = await client.listTools(); // ✓ Correct
748
- // const result = client.listTools(); // ✗ Returns Promise, not result
749
- });
440
+ // ✓ Correct
441
+ import { MCPClient } from './client/MCPClient.js';
750
442
  ```
751
443
 
752
- **Cleanup Pattern**
753
-
754
- Always clean up connections in `afterEach`:
755
-
756
- ```typescript
757
- afterEach(async () => {
758
- if (client.isConnected()) {
759
- await client.stop();
760
- }
761
- });
762
- ```
444
+ ### Tests Fail in CI
763
445
 
764
- **Error Testing**
446
+ - Set `maxWorkers: 1` in Jest config (parallel workers can crash)
447
+ - Verify Node.js >= 18
448
+ - Check that server paths are correct
765
449
 
766
- Use `rejects.toThrow()` for expected errors:
450
+ ### Debug Mode
767
451
 
768
452
  ```typescript
769
- it('should handle invalid tool', async () => {
770
- await expect(
771
- client.callTool({ name: 'non-existent', arguments: {} })
772
- ).rejects.toThrow();
773
- });
453
+ const client = new MCPClient({ logLevel: 'debug', enableProtocolLogging: true });
774
454
  ```
775
455
 
776
- ### Test Helpers and Matchers
456
+ All logs go to stderr — they won't interfere with test output.
777
457
 
778
- MCP Tester includes custom test helpers and Jest matchers to reduce boilerplate and improve test readability.
779
-
780
- #### Using Test Helpers
781
-
782
- Import helper functions to simplify test setup:
458
+ New logging features:
459
+ - **Timing**: operations log elapsed time (e.g., `Listed 4 tools in 12ms`)
460
+ - **Secret masking**: API keys, tokens, and passwords are auto-masked (e.g., `sk-ab...789`)
461
+ - **Pretty-printed JSON**: protocol messages are formatted and masked
462
+ - **Timestamps**: enable with `{ timestamps: true }`
463
+ - **Colors**: auto-enabled in terminals (cyan=debug, green=info, yellow=warn, red=error)
783
464
 
784
465
  ```typescript
785
- import { createTestClient, createTestSuite, createMockServerConfig } from './helpers.js';
786
- import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
787
-
788
- describe('My Feature', () => {
789
- // Use createTestSuite for automatic setup/teardown
790
- const testSuite = createTestSuite(createMockServerConfig());
791
-
792
- beforeEach(async () => {
793
- await testSuite.setup(); // Starts server automatically
794
- });
466
+ import { startTimer, prettyPrint } from '@slbdn/mcp-tester';
795
467
 
796
- afterEach(async () => {
797
- await testSuite.teardown(); // Stops server automatically
798
- });
468
+ // Measure operation timing
469
+ const elapsed = startTimer();
470
+ await someOperation();
471
+ console.log(`Took ${elapsed()}ms`);
799
472
 
800
- it('should test feature', async () => {
801
- const tools = await testSuite.client.listTools();
802
- expect(tools).toBeDefined();
803
- });
804
- });
473
+ // Pretty-print objects with secret masking
474
+ prettyPrint({ apiKey: 'sk-proj-abc123...', count: 42 });
805
475
  ```
806
476
 
807
- **Available Helper Functions:**
808
-
809
- | Function | Description |
810
- |----------|-------------|
811
- | `createTestClient(options?)` | Creates client with default test options |
812
- | `createTestSuite(serverConfig, options?)` | Creates test suite with auto setup/teardown |
813
- | `setupTestServer(config, options?)` | Manual server setup |
814
- | `teardownTestServer(client)` | Manual server cleanup |
815
- | `createMockServerConfig(args?)` | Creates mock server configuration |
816
- | `waitForClientState(client, isConnected, timeout?)` | Waits for client state |
817
- | `runWithTimeout(fn, timeout?)` | Runs function with timeout |
818
- | `retryUntil(fn, attempts?, delay?)` | Retries function until success |
819
- | `callTool(client, toolName, args?)` | Helper for tool calls |
820
- | `validateToolResult(result)` | Validates tool call result |
821
-
822
- #### Using Custom Matchers
477
+ See [Troubleshooting Guide](./docs/troubleshooting.md) for more details.
823
478
 
824
- Import custom matchers for improved assertions:
825
-
826
- ```typescript
827
- import { toHaveTool, toHaveResource, toHavePrompt } from './matchers.js';
828
-
829
- it('should verify tool exists', async () => {
830
- const tools = await client.listTools();
831
- expect(tools).toHaveTool('echo'); // Custom matcher
832
- });
833
-
834
- it('should verify resource exists', async () => {
835
- const resources = await client.listResources();
836
- expect(resources).toHaveResource('text://example'); // Custom matcher
837
- });
838
-
839
- it('should verify prompt exists', async () => {
840
- const prompts = await client.listPrompts();
841
- expect(prompts).toHavePrompt('greeting'); // Custom matcher
842
- });
843
- ```
844
-
845
- **Available Custom Matchers:**
846
-
847
- | Matcher | Description |
848
- |----------|-------------|
849
- | `toHaveTool(toolName)` | Checks if tool exists in tools array |
850
- | `toHaveResource(uri)` | Checks if resource exists in resources array |
851
- | `toHavePrompt(promptName)` | Checks if prompt exists in prompts array |
852
- | `toHaveToolWithSchema(toolName)` | Checks if tool has input schema |
853
-
854
- #### Example with Helpers and Matchers
855
-
856
- ```typescript
857
- import { createTestSuite, createMockServerConfig, callTool } from './helpers.js';
858
- import { toHaveTool, toHaveResource } from './matchers.js';
859
-
860
- describe('Complete Example', () => {
861
- const testSuite = createTestSuite(createMockServerConfig());
862
-
863
- beforeEach(async () => await testSuite.setup());
864
- afterEach(async () => await testSuite.teardown());
865
-
866
- it('should verify tools', async () => {
867
- const tools = await testSuite.client.listTools();
868
- expect(tools).toHaveTool('echo');
869
- });
870
-
871
- it('should verify resources', async () => {
872
- const resources = await testSuite.client.listResources();
873
- expect(resources).toHaveResource('text://example');
874
- });
875
-
876
- it('should call tool', async () => {
877
- const result = await callTool(testSuite.client, 'echo', {
878
- message: 'hello',
879
- });
880
- expect(result).toBeDefined();
881
- });
882
- });
883
- ```
884
-
885
- For more examples, see `src/__tests__/helpers-example.test.ts`.
886
-
887
- ## 💡 Examples
888
-
889
- ### Example 1: Basic Tool Testing
890
-
891
- ```typescript
892
- import { MCPClient } from 'mcp-tester';
893
-
894
- async function testTool() {
895
- const client = new MCPClient();
896
-
897
- try {
898
- await client.start({
899
- command: 'node',
900
- args: ['./my-server.js'],
901
- });
902
-
903
- const tools = await client.listTools();
904
- console.log(`Found ${tools.length} tools`);
905
-
906
- for (const tool of tools) {
907
- console.log(`\nTesting: ${tool.name}`);
908
- const result = await client.callTool({
909
- name: tool.name,
910
- arguments: {},
911
- });
912
- console.log(`Result: ${result.content[0]?.text}`);
913
- }
914
-
915
- } finally {
916
- await client.stop();
917
- }
918
- }
919
-
920
- testTool();
921
- ```
922
-
923
- ### Example 2: Resource Reading
924
-
925
- ```typescript
926
- import { MCPClient } from 'mcp-tester';
927
-
928
- async function readConfiguration() {
929
- const client = new MCPClient();
930
-
931
- try {
932
- await client.start({
933
- command: 'node',
934
- args: ['./config-server.js'],
935
- });
936
-
937
- const resources = await client.listResources();
938
- console.log('Available resources:', resources.map(r => r.uri));
939
-
940
- const settings = await client.readResource('config://settings');
941
- console.log('Settings:', JSON.parse(settings.contents[0].text as string));
942
-
943
- } finally {
944
- await client.stop();
945
- }
946
- }
947
-
948
- readConfiguration();
949
- ```
950
-
951
- ### Example 3: Using Prompts
952
-
953
- ```typescript
954
- import { MCPClient } from 'mcp-tester';
955
-
956
- async function usePrompt() {
957
- const client = new MCPClient();
958
-
959
- try {
960
- await client.start({
961
- command: 'node',
962
- args: ['./prompt-server.js'],
963
- });
964
-
965
- const prompt = await client.getPrompt('greet', { name: 'World' });
966
- console.log('Generated message:', prompt.messages[0].content.text);
967
-
968
- } finally {
969
- await client.stop();
970
- }
971
- }
972
-
973
- usePrompt();
974
- ```
975
-
976
- ### Example 4: Complete Test Suite
977
-
978
- ```typescript
979
- import { MCPClient } from 'mcp-tester';
980
-
981
- describe('My MCP Server', () => {
982
- let client: MCPClient;
983
-
984
- beforeEach(async () => {
985
- client = new MCPClient();
986
- await client.start({
987
- command: 'node',
988
- args: ['./server.js'],
989
- });
990
- });
991
-
992
- afterEach(async () => {
993
- if (client.isConnected()) {
994
- await client.stop();
995
- }
996
- });
997
-
998
- it('should list tools correctly', async () => {
999
- const tools = await client.listTools();
1000
- expect(tools).toHaveLength(5);
1001
- });
1002
-
1003
- it('should execute tool with correct arguments', async () => {
1004
- const result = await client.callTool({
1005
- name: 'calculate',
1006
- arguments: { operation: 'add', values: [10, 20] },
1007
- });
1008
- expect(result.content[0].text).toBe('30');
1009
- });
1010
-
1011
- it('should handle tool errors gracefully', async () => {
1012
- await expect(
1013
- client.callTool({ name: 'invalid-tool', arguments: {} })
1014
- ).rejects.toThrow();
1015
- });
1016
-
1017
- it('should read resources correctly', async () => {
1018
- const resources = await client.listResources();
1019
- expect(resources.length).toBeGreaterThan(0);
1020
- });
1021
- });
1022
- ```
1023
-
1024
- ## 🔄 CI/CD Integration
1025
-
1026
- ### GitHub Actions
1027
-
1028
- Create `.github/workflows/test.yml`:
1029
-
1030
- ```yaml
1031
- name: Test MCP Server
1032
-
1033
- on:
1034
- push:
1035
- branches: [main, develop]
1036
- pull_request:
1037
- branches: [main, develop]
1038
-
1039
- jobs:
1040
- test:
1041
- runs-on: ubuntu-latest
1042
-
1043
- strategy:
1044
- matrix:
1045
- node-version: [18, 20, 21]
1046
-
1047
- steps:
1048
- - name: Checkout code
1049
- uses: actions/checkout@v3
1050
-
1051
- - name: Setup Node.js
1052
- uses: actions/setup-node@v3
1053
- with:
1054
- node-version: ${{ matrix.node-version }}
1055
-
1056
- - name: Install dependencies
1057
- run: npm ci
1058
-
1059
- - name: Build TypeScript
1060
- run: npm run build
1061
-
1062
- - name: Run tests
1063
- run: npm test
1064
-
1065
- - name: Run tests with coverage
1066
- run: npm run test:coverage
1067
-
1068
- - name: Upload coverage to Codecov
1069
- uses: codecov/codecov-action@v3
1070
- with:
1071
- files: ./coverage/lcov.info
1072
- flags: unittests
1073
- name: codecov-umbrella
1074
- ```
1075
-
1076
- ### CircleCI
1077
-
1078
- Create `.circleci/config.yml`:
1079
-
1080
- ```yaml
1081
- version: 2.1
1082
- jobs:
1083
- test:
1084
- docker:
1085
- - image: cimg/node:18
1086
- steps:
1087
- - checkout
1088
- - run: npm ci
1089
- - run: npm run build
1090
- - run: npm test
1091
- ```
1092
-
1093
- ### Jenkins
1094
-
1095
- Create `Jenkinsfile`:
1096
-
1097
- ```groovy
1098
- pipeline {
1099
- agent any
1100
- stages {
1101
- stage('Test') {
1102
- steps {
1103
- sh 'npm ci'
1104
- sh 'npm run build'
1105
- sh 'npm test'
1106
- }
1107
- }
1108
- }
1109
- }
1110
- ```
1111
-
1112
- ## 🚀 Releases
1113
-
1114
- ### Automated Release Process
1115
-
1116
- MCP Tester uses automated release workflow triggered by version tags:
1117
-
1118
- ```bash
1119
- # 1. Update version
1120
- npm run release:patch # or release:minor, release:major
1121
-
1122
- # 2. Update CHANGELOG.md
1123
- # Move items from [Unreleased] to new version section
1124
-
1125
- # 3. Commit and tag
1126
- git add package.json CHANGELOG.md
1127
- git commit -m "chore: release v1.0.1"
1128
- git tag v1.0.1
1129
- git push origin main --tags
1130
- ```
1131
-
1132
- The release workflow automatically:
1133
- - ✅ Runs all tests across Node.js 18, 20, 21
1134
- - ✅ Builds the project
1135
- - ✅ Publishes to npm
1136
- - ✅ Creates GitHub release with CHANGELOG notes
1137
-
1138
- ### Quick Release Commands
1139
-
1140
- ```bash
1141
- # Patch release (1.0.0 → 1.0.1)
1142
- npm run release:patch
1143
-
1144
- # Minor release (1.0.0 → 1.1.0)
1145
- npm run release:minor
1146
-
1147
- # Major release (1.0.0 → 2.0.0)
1148
- npm run release:major
1149
-
1150
- # Prerelease (1.0.0 → 1.1.0-alpha.1)
1151
- npm run release:prerelease
1152
- ```
1153
-
1154
- ### Prerequisites
1155
-
1156
- Before releasing, ensure:
1157
-
1158
- 1. **NPM Token** is set in GitHub repository secrets as `NPM_TOKEN`
1159
- 2. **CHANGELOG.md** is updated with all changes
1160
- 3. **Tests pass** locally (`npm test`)
1161
- 4. **Build succeeds** (`npm run build`)
1162
-
1163
- For detailed instructions, see [RELEASE.md](https://github.com/your-org/mcp-tester/blob/main/RELEASE.md).
1164
-
1165
- ## 🚀 Advanced Usage
1166
-
1167
- ### Concurrent Operations
1168
-
1169
- Run multiple operations in parallel:
1170
-
1171
- ```typescript
1172
- const results = await Promise.all([
1173
- client.callTool({ name: 'tool1', arguments: {} }),
1174
- client.callTool({ name: 'tool2', arguments: {} }),
1175
- client.callTool({ name: 'tool3', arguments: {} }),
1176
- ]);
1177
-
1178
- results.forEach((result, index) => {
1179
- console.log(`Tool ${index + 1}:`, result.content[0]?.text);
1180
- });
1181
- ```
1182
-
1183
- ### Timeout Handling
1184
-
1185
- Customize timeouts for specific operations:
1186
-
1187
- ```typescript
1188
- const result = await client.callTool({
1189
- name: 'slow-operation',
1190
- arguments: { duration: 5000 },
1191
- timeout: 10000, // 10 second timeout for this call only
1192
- });
1193
- ```
1194
-
1195
- ### Environment Variables
1196
-
1197
- Pass environment variables to the MCP server:
1198
-
1199
- ```typescript
1200
- await client.start({
1201
- command: 'node',
1202
- args: ['./server.js'],
1203
- env: {
1204
- NODE_ENV: 'test',
1205
- API_ENDPOINT: process.env.API_ENDPOINT || 'http://localhost:3000',
1206
- LOG_LEVEL: 'debug',
1207
- },
1208
- });
1209
- ```
1210
-
1211
- ### Notification Handling
1212
-
1213
- Monitor server-initiated notifications:
1214
-
1215
- ```typescript
1216
- client.setNotificationHandlers({
1217
- onLoggingMessage: (level, data) => {
1218
- console.log(`[${level}] ${data}`);
1219
-
1220
- // Store for testing
1221
- if (level === 'error') {
1222
- // Handle errors
1223
- }
1224
- },
1225
- onResourceListChanged: async () => {
1226
- console.log('Resources changed, refreshing...');
1227
- const resources = await client.listResources();
1228
- console.log(`Now have ${resources.length} resources`);
1229
- },
1230
- });
1231
- ```
1232
-
1233
- ### Elicitation Handling
1234
-
1235
- Respond to server requests for user input:
1236
-
1237
- ```typescript
1238
- await client.setElicitationHandler(async (request) => {
1239
- console.log('Elicitation request:', request.params.mode);
1240
-
1241
- if (request.params.mode === 'form') {
1242
- // Validate and collect user input
1243
- const userInput = {
1244
- username: 'test-user',
1245
- email: 'test@example.com',
1246
- };
1247
-
1248
- return {
1249
- action: 'accept',
1250
- content: userInput,
1251
- };
1252
- }
1253
-
1254
- return { action: 'decline' };
1255
- });
1256
- ```
1257
-
1258
- ### Mock Server for Unit Testing
1259
-
1260
- Use the included mock server for tests that don't require a real MCP server:
1261
-
1262
- ```typescript
1263
- import { MockMCPServer } from './fixtures/mock-server.js';
1264
-
1265
- describe('My Feature', () => {
1266
- let mockServer: MockMCPServer;
1267
-
1268
- beforeEach(() => {
1269
- mockServer = new MockMCPServer();
1270
- });
1271
-
1272
- it('should handle mock tool call', async () => {
1273
- const result = await mockServer.handleToolCall('echo', {
1274
- message: 'test',
1275
- });
1276
- expect(result.content[0].text).toBe('Echo: test');
1277
- });
1278
-
1279
- it('should handle mock resource read', async () => {
1280
- const result = await mockServer.handleResourceRead('config://settings');
1281
- expect(result.contents[0].text).toContain('setting1');
1282
- });
1283
-
1284
- it('should handle mock prompt', async () => {
1285
- const result = await mockServer.handlePromptGet('greet', {
1286
- name: 'Alice',
1287
- });
1288
- expect(result.messages[0].content.text).toContain('Alice');
1289
- });
1290
- });
1291
- ```
1292
-
1293
- ## 🛠️ Troubleshooting
1294
-
1295
- ### Common Issues
1296
-
1297
- #### "Client not started" Error
1298
-
1299
- **Problem:** Calling client methods before `start()`
1300
-
1301
- **Solution:** Always call `await client.start()` before other methods
1302
-
1303
- ```typescript
1304
- // ✗ Wrong
1305
- const client = new MCPClient();
1306
- const tools = await client.listTools(); // Error: Client not started
1307
-
1308
- // ✓ Correct
1309
- const client = new MCPClient();
1310
- await client.start();
1311
- const tools = await client.listTools(); // Works
1312
- ```
1313
-
1314
- #### Timeout Errors
1315
-
1316
- **Problem:** Requests timing out
1317
-
1318
- **Solution:** Increase timeout or check server performance
1319
-
1320
- ```typescript
1321
- // Increase global timeout
1322
- const client = new MCPClient({ timeout: 60000 });
1323
-
1324
- // Or per-call timeout
1325
- await client.callTool({ name: 'slow-tool', arguments: {}, timeout: 30000 });
1326
- ```
1327
-
1328
- #### Server Process Fails to Start
1329
-
1330
- **Problem:** Server command not found or fails immediately
1331
-
1332
- **Solution:** Check command path and ensure server executable exists
1333
-
1334
- ```bash
1335
- # Verify server exists
1336
- ls -la ./my-server.js
1337
-
1338
- # Test server standalone
1339
- node ./my-server.js
1340
- ```
1341
-
1342
- #### TypeScript Build Errors
1343
-
1344
- **Problem:** Type errors after building
1345
-
1346
- **Solution:** Ensure all dependencies installed and TypeScript version compatible
1347
-
1348
- ```bash
1349
- # Clean and rebuild
1350
- rm -rf dist/
1351
- npm run build
1352
- ```
1353
-
1354
- #### Test Failures
1355
-
1356
- **Problem:** Tests failing in CI but passing locally
1357
-
1358
- **Solution:** Check environment variables and server path
1359
-
1360
- ```yaml
1361
- # GitHub Actions - add environment variables
1362
- - name: Run tests
1363
- env:
1364
- MOCK_SERVER_PATH: ${{ github.workspace }}/my-server.js
1365
- run: npm test
1366
- ```
1367
-
1368
- ### Debug Mode
1369
-
1370
- Enable verbose logging for debugging:
1371
-
1372
- ```typescript
1373
- import { MCPClient } from 'mcp-tester';
1374
-
1375
- const client = new MCPClient({
1376
- name: 'debug-client',
1377
- });
1378
-
1379
- await client.start({
1380
- command: 'node',
1381
- args: ['./server.js', '--verbose'],
1382
- });
1383
-
1384
- // Server logs to stderr, check for detailed output
1385
- ```
1386
-
1387
- ### Test Coverage
1388
-
1389
- Generate coverage reports:
1390
-
1391
- ```bash
1392
- # Run tests with coverage
1393
- npm run test:coverage
1394
-
1395
- # View coverage report
1396
- open coverage/lcov-report/index.html
1397
- ```
1398
-
1399
- ## 📊 Project Structure
1400
-
1401
- ```
1402
- mcp-tester/
1403
- ├── src/
1404
- │ ├── client/
1405
- │ │ ├── MCPClient.ts # Main client wrapper class
1406
- │ │ └── index.ts # Client module exports
1407
- │ ├── __tests__/
1408
- │ │ ├── client.test.ts # Basic client operations tests
1409
- │ │ ├── resources-prompts.test.ts # Resources & prompts tests
1410
- │ │ ├── advanced.test.ts # Advanced features tests
1411
- │ │ ├── real-server.test.ts # Integration tests (stdio transport)
1412
- │ │ ├── helpers-example.test.ts # Test helpers usage examples
1413
- │ │ ├── helpers.ts # Test utility functions
1414
- │ │ ├── matchers.ts # Custom Jest matchers
1415
- │ │ └── fixtures/
1416
- │ │ └── mock-server.ts # In-memory mock MCP server
1417
- │ └── index.ts # Library exports
1418
- ├── examples/
1419
- │ ├── basic-test.ts # Basic usage example
1420
- │ ├── full-test.ts # Full capabilities example
1421
- │ └── mock-server.js # Standalone MCP server for testing
1422
- ├── dist/ # Compiled JavaScript output
1423
- ├── package.json
1424
- ├── tsconfig.json
1425
- ├── jest.config.js
1426
- ├── .eslintrc.json
1427
- ├── .github/workflows/
1428
- │ └── test.yml # CI/CD workflow
1429
- ├── .gitignore
1430
- ├── LICENSE
1431
- └── README.md
1432
- ```
1433
-
1434
- ## 🔧 Development
1435
-
1436
- ### Build
1437
-
1438
- ```bash
1439
- # Build TypeScript to JavaScript
1440
- npm run build
1441
- ```
1442
-
1443
- ### Lint
1444
-
1445
- ```bash
1446
- # Run ESLint
1447
- npm run lint
1448
-
1449
- # Auto-fix issues
1450
- npm run lint --fix
1451
- ```
1452
-
1453
- ### Watch Mode
1454
-
1455
- For development with hot reloading:
1456
-
1457
- ```bash
1458
- # Install tsx for TypeScript execution
1459
- npm install -D tsx
1460
-
1461
- # Watch and run tests
1462
- npx tsx --watch --clear-screen=false
1463
- ```
1464
-
1465
- ## 📈 Test Coverage
1466
-
1467
- Current test suite: **43 tests**
1468
-
1469
- ```
1470
- Test Categories:
1471
- - Basic Operations 3 tests
1472
- - Tools (in-memory) 11 tests
1473
- - Resources (in-memory) 5 tests
1474
- - Prompts (in-memory) 4 tests
1475
- - Advanced Features 3 tests
1476
- - Helpers Examples 4 tests
1477
- - Real Server (stdio) 13 tests
1478
- ```
1479
-
1480
- Coverage targets:
1481
- - **Branches:** 70%
1482
- - **Functions:** 70%
1483
- - **Lines:** 70%
1484
- - **Statements:** 70%
1485
-
1486
- ## 🔐 Security
1487
-
1488
- ### Best Practices
1489
-
1490
- 1. **Never Commit Secrets**
1491
- - Don't commit API keys, passwords, or tokens
1492
- - Use environment variables in CI/CD
1493
- - Add sensitive patterns to `.gitignore`
1494
-
1495
- 2. **Validate Input**
1496
- - Always validate server responses
1497
- - Use TypeScript for type safety
1498
- - Sanitize user inputs
1499
-
1500
- 3. **Secure Connections**
1501
- - Use stdio for local servers
1502
- - Verify server identity in production
1503
- - Use HTTPS for remote connections
1504
-
1505
- 4. **Error Handling**
1506
- - Never expose internal errors to users
1507
- - Log errors securely
1508
- - Implement proper error recovery
1509
-
1510
- ### Known Limitations
1511
-
1512
- - Only supports stdio transport (HTTP transport not implemented)
1513
- - Designed for Node.js servers (other runtimes may need adjustments)
1514
- - Mock server is minimal (real servers may have different behavior)
1515
-
1516
- ## 🤝 Contributing
1517
-
1518
- Contributions are welcome! Please:
479
+ ## Contributing
1519
480
 
1520
481
  1. Fork the repository
1521
482
  2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1522
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
1523
- 4. Push to the branch (`git push origin feature/amazing-feature`)
483
+ 3. Make changes and test (`npm run build && npm test`)
484
+ 4. Format and lint (`npm run format && npm run lint`)
1524
485
  5. Open a Pull Request
1525
486
 
1526
- ### Development Setup
1527
-
1528
- ```bash
1529
- # Clone and setup
1530
- git clone <your-fork>
1531
- cd mcp-tester
1532
- npm install
1533
-
1534
- # Create feature branch
1535
- git checkout -b feature/my-feature
1536
-
1537
- # Make changes and test
1538
- npm run build
1539
- npm test
1540
-
1541
- # Format and lint
1542
- npm run lint --fix
1543
- ```
1544
-
1545
- ## 📚 Additional Resources
1546
- - [AGENTS.md](./AGENTS.md) - Documentation for AI agents working with this codebase
1547
-
1548
- - [MCP Specification](https://spec.modelcontextprotocol.io) - Official MCP protocol specification
1549
- - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) - Official SDK documentation
1550
- - [MCP Examples](https://github.com/modelcontextprotocol/servers) - Example MCP servers
1551
- - [Jest Documentation](https://jestjs.io/) - Testing framework documentation
1552
- - [TypeScript Handbook](https://www.typescriptlang.org/docs/) - TypeScript language reference
1553
-
1554
- ## 📞 Support
1555
-
1556
- - **Issues:** [GitHub Issues](https://github.com/your-org/mcp-tester/issues)
1557
- - **Discussions:** [GitHub Discussions](https://github.com/your-org/mcp-tester/discussions)
1558
- - **Documentation:** [Project Wiki](https://github.com/your-org/mcp-tester/wiki)
1559
-
1560
- ## 📝 License
487
+ ## License
1561
488
 
1562
- MIT License - see [LICENSE](LICENSE) for details.
489
+ MIT — see [LICENSE](LICENSE) for details.