@slbdn/mcp-tester 1.0.2 → 1.1.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,84 @@
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
9
  [![Node.js Version](https://img.shields.io/node/v/@slbdn/mcp-tester)](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-227_passing-brightgreen)](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml)
12
+
13
+ A production-ready MCP (Model Context Protocol) client for **automated testing** of MCP servers with Jest.
14
+ No more manual clicking through inspectors — write real tests, run them in CI, break builds on regressions.
15
+
16
+ ## Capabilities
17
+
18
+ | Category | What You Can Test |
19
+ |----------|-------------------|
20
+ | **Tools** | List, call with arguments, validate results, test error handling |
21
+ | **Resources** | List available resources, read by URI, validate content |
22
+ | **Prompts** | List templates, get with arguments, validate generated messages |
23
+ | **Sampling** | Request LLM sampling, validate model responses |
24
+ | **Elicitation** | Handle server-initiated user input requests |
25
+ | **Notifications** | React to resource changes, logging messages, tool list updates |
26
+ | **Error Handling** | Custom error classes for timeouts, connection failures, server errors |
27
+ | **Retries** | Exponential backoff with configurable attempts and delay |
28
+ | **CLI** | Smoke-test any server from the terminal in seconds |
29
+
30
+ ## Why MCP Tester?
31
+
32
+ - **Full protocol support** — tools, resources, prompts, sampling, elicitation, notifications
33
+ - **Built on the official SDK** — `@modelcontextprotocol/sdk` v1.29.0
34
+ - **Works out of the box** — mock server included, 227 tests passing
35
+ - **CLI included** — test any server from the command line
36
+ - **TypeScript first** — strict types, ESM, full IntelliSense
37
+ - **Parallel execution** — fire multiple tool calls concurrently with `Promise.all` for blazing-fast test suites
38
+ - **Retry with backoff** — flaky servers? Configure retries so your CI stays green
39
+ - **In-memory mock server** — unit test your test logic without spawning real processes
40
+ - **Custom Jest matchers** — `toHaveTool()`, `toHaveResource()`, `toHavePrompt()` — and Vitest support
41
+
42
+ ## Install
74
43
 
75
44
  ```bash
76
45
  npm install @slbdn/mcp-tester --save-dev
77
46
  ```
78
47
 
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)
48
+ Requires **Node.js >= 18**. See [Node.js Compatibility](./docs/nodejs-compatibility.md) for details.
283
49
 
284
- Windows Notes:
285
- - Native support with Node.js
286
- - WSL2 recommended for Unix-like development
287
- - Ensure proper line endings (CRLF vs LF)
50
+ ## Quick Start
288
51
 
289
- ## ⚡ Quick Start
290
-
291
- ### Basic Usage
52
+ ### Connect and Use Tools
292
53
 
293
54
  ```typescript
294
- import { MCPClient } from 'mcp-tester';
55
+ import { MCPClient } from '@slbdn/mcp-tester';
295
56
 
296
57
  const client = new MCPClient({
297
58
  name: 'my-test-client',
298
59
  version: '1.0.0',
299
- timeout: 30000,
300
60
  });
301
61
 
302
- // Start the client with your MCP server
62
+ // Connect to your MCP server
303
63
  await client.start({
304
64
  command: 'node',
305
65
  args: ['./your-mcp-server.js'],
306
66
  });
307
67
 
308
- // List available tools
68
+ // List and call tools
309
69
  const tools = await client.listTools();
310
- console.log('Available tools:', tools.map(t => t.name));
311
-
312
- // Call a tool
313
70
  const result = await client.callTool({
314
- name: 'your-tool-name',
315
- arguments: { param: 'value' },
71
+ name: 'your-tool',
72
+ arguments: { input: 'test' },
316
73
  });
317
- console.log('Tool result:', result);
318
74
 
319
- // Clean up
320
75
  await client.stop();
321
76
  ```
322
77
 
323
- ### Testing an MCP Server
78
+ ### Test with Jest
324
79
 
325
80
  ```typescript
326
- import { MCPClient } from 'mcp-tester';
81
+ import { MCPClient } from '@slbdn/mcp-tester';
327
82
 
328
83
  describe('My MCP Server', () => {
329
84
  let client: MCPClient;
@@ -357,1206 +112,244 @@ describe('My MCP Server', () => {
357
112
  });
358
113
  ```
359
114
 
360
- ### Run Included Examples
115
+ ### Test from the Command Line
361
116
 
362
117
  ```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.
118
+ # Quick smoke test
119
+ npx @slbdn/mcp-tester test node ./server.js
378
120
 
379
- #### Constructor
121
+ # List tools (JSON output)
122
+ npx @slbdn/mcp-tester list-tools node ./server.js --json
380
123
 
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`)
124
+ # Call a tool
125
+ npx @slbdn/mcp-tester call-tool echo node ./server.js --params '{"message":"Hello"}'
391
126
 
392
- **Example:**
393
-
394
- ```typescript
395
- const client = new MCPClient({
396
- name: 'production-test-client',
397
- version: '2.1.0',
398
- timeout: 60000,
399
- });
127
+ # Short aliases: lt, ct, rr, gp
128
+ npx @slbdn/mcp-tester lt node ./server.js
400
129
  ```
401
130
 
402
- #### Methods
131
+ See [CLI Reference](./docs/cli.md) for all commands and options.
403
132
 
404
- ##### `start(config: MCPServerConfig): Promise<void>`
133
+ ## Speed Up Your Tests with Parallel Execution
405
134
 
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:**
135
+ MCP Tester is built for concurrency. Fire off multiple tool calls at once — no waiting in line:
419
136
 
420
137
  ```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>`
138
+ import { MCPClient } from '@slbdn/mcp-tester';
432
139
 
433
- Stop the client and disconnect from the MCP server.
140
+ const client = new MCPClient();
141
+ await client.start({ command: 'node', args: ['./server.js'] });
434
142
 
435
- **Returns:** `Promise<void>`
143
+ // 🔥 Run all calls in parallel — 5x faster than sequential
144
+ const [tools, resources, prompts] = await Promise.all([
145
+ client.listTools(),
146
+ client.listResources(),
147
+ client.listPrompts(),
148
+ ]);
436
149
 
437
- **Example:**
150
+ // Parallel tool calls? No problem.
151
+ const [echo, sum, env] = await Promise.all([
152
+ client.callTool({ name: 'echo', arguments: { message: 'hello' } }),
153
+ client.callTool({ name: 'get-sum', arguments: { a: 1, b: 2 } }),
154
+ client.callTool({ name: 'get-env', arguments: {} }),
155
+ ]);
438
156
 
439
- ```typescript
440
157
  await client.stop();
441
158
  ```
442
159
 
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[]>`
458
-
459
- List all available tools from the connected MCP server.
460
-
461
- **Returns:** `Promise<Tool[]>` - Array of tool objects
462
-
463
- **Throws:** `Error` if client is not started
464
-
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
469
-
470
- **Example:**
471
-
472
- ```typescript
473
- const tools = await client.listTools();
474
- tools.forEach(tool => {
475
- console.log(`- ${tool.name}: ${tool.description}`);
476
- });
477
- ```
478
-
479
- ##### `callTool(options: ToolCallOptions): Promise<ToolResult>`
480
-
481
- Call a specific tool with optional arguments.
482
-
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
492
-
493
- **Example:**
494
-
495
- ```typescript
496
- const result = await client.callTool({
497
- name: 'calculator-add',
498
- arguments: { a: 5, b: 3 },
499
- timeout: 10000,
500
- });
501
-
502
- console.log('Result:', result.content[0].text);
503
- ```
504
-
505
- ##### `listResources(): Promise<Resource[]>`
506
-
507
- List all available resources from the connected MCP server.
508
-
509
- **Returns:** `Promise<Resource[]>` - Array of resource objects
510
-
511
- **Throws:** `Error` if client is not started
512
-
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
518
-
519
- **Example:**
520
-
521
- ```typescript
522
- const resources = await client.listResources();
523
- resources.forEach(resource => {
524
- console.log(`- ${resource.uri}: ${resource.mimeType}`);
525
- });
526
- ```
527
-
528
- ##### `readResource(uri: string): Promise<ResourceContents>`
529
-
530
- Read a specific resource by its URI.
531
-
532
- **Parameters:**
533
-
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
- ```
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
- });
567
- ```
568
-
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
577
-
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);
587
- ```
588
-
589
- ##### `requestSampling(request: CreateMessageRequestParams): Promise<SamplingResult>`
590
-
591
- Request LLM sampling from the MCP server.
160
+ Works because each MCP call is independent — the client multiplexes requests over a single stdio connection.
161
+ No extra config needed. Just `Promise.all` and go.
592
162
 
593
- **Parameters:**
163
+ ## Core API
594
164
 
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
165
+ | Method | Returns | Description |
166
+ |--------|---------|-------------|
167
+ | `start(config)` | `Promise<void>` | Connect to an MCP server |
168
+ | `stop()` | `Promise<void>` | Disconnect and clean up |
169
+ | `isConnected()` | `boolean` | Check connection state |
170
+ | `listTools()` | `Promise<Tool[]>` | List available tools |
171
+ | `callTool(options)` | `Promise<CallToolResult>` | Call a tool with arguments |
172
+ | `listResources()` | `Promise<Resource[]>` | List available resources |
173
+ | `readResource(uri)` | `Promise<ReadResourceResult>` | Read a resource by URI |
174
+ | `listPrompts()` | `Promise<Prompt[]>` | List available prompts |
175
+ | `getPrompt(name, args?)` | `Promise<GetPromptResult>` | Get a prompt template |
176
+ | `requestSampling(request)` | `Promise<CreateMessageResult>` | Request LLM sampling |
177
+ | `setElicitationHandler(fn)` | `Promise<void>` | Handle server user-input requests |
178
+ | `setNotificationHandlers(handlers)` | `void` | Handle server notifications |
599
179
 
600
- **Returns:** `Promise<SamplingResult>` - Sampling result with generated content
180
+ See [API Reference](./docs/api-reference.md) for full details including types, options, and error classes.
601
181
 
602
- **Throws:** `Error` if client is not started or sampling not supported
182
+ ## Custom Jest Matchers
603
183
 
604
- **Example:**
184
+ Built-in matchers so your assertions read like English:
605
185
 
606
186
  ```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
- ```
187
+ import {
188
+ toHaveTool, toHaveResource, toHavePrompt,
189
+ toReturnText, toReturnError, toReturnJson,
190
+ setupJestMatchers,
191
+ } from '@slbdn/mcp-tester';
620
192
 
621
- ##### `setElicitationHandler(handler: Function): Promise<void>`
193
+ beforeAll(() => setupJestMatchers());
622
194
 
623
- Configure an elicitation handler to respond to server requests for user input.
195
+ // Collection matchers
196
+ expect(tools).toHaveTool('echo');
197
+ expect(resources).toHaveResource('config://settings');
198
+ expect(prompts).toHavePrompt('greet');
199
+ expect(tools).toHaveToolCount(5);
624
200
 
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:**
634
-
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
- });
201
+ // Tool result matchers
202
+ expect(result).toReturnText('Hello World');
203
+ expect(result).toReturnTextContaining('hello');
204
+ expect(result).toReturnError();
205
+ expect(result).toReturnJson({ status: 'ok' });
206
+ expect(result).toReturnContentCount(2);
645
207
  ```
646
208
 
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:**
209
+ ### Vitest Setup
660
210
 
661
211
  ```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
685
-
686
- # Lint code
687
- npm run lint
688
- ```
689
-
690
- ### Test Structure
212
+ import { setupVitestMatchers } from '@slbdn/mcp-tester';
213
+ import { beforeAll } from 'vitest';
214
+ // Add vitest.d.ts to your tsconfig for type safety
215
+ // /// <reference types="@slbdn/mcp-tester/vitest" />
691
216
 
217
+ beforeAll(() => setupVitestMatchers());
692
218
  ```
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
703
- ```
704
-
705
- ### Writing Custom Tests
706
219
 
707
- #### Test Template
220
+ Works identically — same matchers, same `{ pass, message }` format, just `setupVitestMatchers()` instead.
708
221
 
709
- ```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;
222
+ | Matcher | Description |
223
+ |---------|-------------|
224
+ | `toHaveTool(name)` | Assert a tool exists by name |
225
+ | `toHaveToolWithSchema(name)` | Assert a tool exists and has an input schema |
226
+ | `toHaveToolCount(n)` | Assert exact number of tools |
227
+ | `toHaveResource(uri)` | Assert a resource exists by URI |
228
+ | `toHaveResourceByName(name)` | Assert a resource exists by display name |
229
+ | `toHaveResourceCount(n)` | Assert exact number of resources |
230
+ | `toHavePrompt(name)` | Assert a prompt exists by name |
231
+ | `toHavePromptWithArgs(name)` | Assert a prompt exists and has arguments |
232
+ | `toHavePromptCount(n)` | Assert exact number of prompts |
233
+ | `toReturnText(expected?)` | Tool text exact match (or just has text) |
234
+ | `toReturnTextContaining(sub)` | Tool text contains substring |
235
+ | `toReturnError()` | Tool result is an error |
236
+ | `toReturnOk()` | Tool result is successful |
237
+ | `toReturnJson(obj)` | Parse tool text as JSON, deep compare |
238
+ | `toReturnContentCount(n)` | Tool has N content items |
239
+ | `toReturnImage()` | Tool result contains an image |
240
+ | `toReturnResourceText(expected?)` | Resource text exact match (or just has text) |
241
+ | `toReturnResourceTextContaining(sub)` | Resource text contains substring |
242
+ | `toReturnPromptTextContaining(sub)` | Prompt message text contains substring |
243
+ | `toReturnPromptMessageCount(n)` | Prompt has N messages |
244
+
245
+ See [Testing Guide](./docs/testing.md) for full testing patterns.
246
+
247
+ ## Assertion Utilities
248
+
249
+ Not using Jest? Running tests with a custom `runTest()` harness? The built-in `assert` module throws descriptive errors on failure — works with any runner:
250
+
251
+ ```typescript
252
+ import { MCPClient, assert } from '@slbdn/mcp-tester';
715
253
 
716
- beforeEach(() => {
717
- client = new MCPClient({
718
- name: 'test-client',
719
- version: '1.0.0',
720
- });
721
- });
254
+ const client = new MCPClient();
255
+ await client.start({ command: 'node', args: ['./server.js'] });
722
256
 
723
- afterEach(async () => {
724
- if (client.isConnected()) {
725
- await client.stop();
726
- }
257
+ await runTest("Get constant: c (speed of light)", async () => {
258
+ const result = await client.callTool({
259
+ name: "get_constant",
260
+ arguments: { name: "c" },
727
261
  });
262
+ assert.toolNumEquals(result, 299792458);
263
+ });
728
264
 
729
- it('should test feature', async () => {
730
- const result = await client.callTool({
731
- name: 'my-feature',
732
- arguments: {},
733
- });
734
- expect(result).toBeDefined();
265
+ await runTest("Echo returns input", async () => {
266
+ const result = await client.callTool({
267
+ name: "echo",
268
+ arguments: { message: "hello" },
735
269
  });
270
+ assert.toolTextContains(result, "hello");
736
271
  });
737
272
  ```
738
273
 
739
- #### Testing Patterns
740
-
741
- **Async/Await Pattern**
742
-
743
- Always use `async/await` for MCP client operations:
274
+ | Assertion | Description |
275
+ |-----------|-------------|
276
+ | `equal(a, b)` / `notEqual(a, b)` | Strict equality checks |
277
+ | `deepEqual(a, b)` | JSON deep equality |
278
+ | `ok(val)` / `notOk(val)` | Truthy / falsy |
279
+ | `throws(fn)` / `doesNotThrow(fn)` | Async error checks |
280
+ | `greaterThan(a, b)` / `lessThan(a, b)` / `closeTo(a, b, eps?)` | Numeric comparisons |
281
+ | `contains(str, sub)` / `matches(str, regex)` | String checks |
282
+ | `toolTextEquals(r, str)` | Tool text exact match |
283
+ | `toolTextContains(r, str)` | Tool text contains |
284
+ | `toolNumEquals(r, num)` | Parse tool text as number, compare |
285
+ | `toolNumCloseTo(r, num, eps?)` | Numeric approximate match |
286
+ | `toolJsonEquals(r, obj)` | Parse tool text as JSON, deep compare |
287
+ | `toolIsError(r)` / `toolIsOk(r)` | Error/success check |
288
+ | `toolHasImage(r)` | Check for image content |
289
+ | `resourceTextContains(r, str)` | Resource text contains |
290
+ | `promptTextContains(r, str)` | Prompt text contains |
291
+
292
+ ## Documentation
293
+
294
+ | Doc | Description |
295
+ |-----|-------------|
296
+ | [API Reference](./docs/api-reference.md) | Full method docs, types, and error classes |
297
+ | [Testing Guide](./docs/testing.md) | Writing tests, matchers, mock server |
298
+ | [Examples](./docs/examples.md) | Practical code examples |
299
+ | [Advanced Usage](./docs/advanced.md) | Timeouts, retries, concurrency, notifications, logging |
300
+ | [CLI Reference](./docs/cli.md) | CLI commands, options, and output formats |
301
+ | [CI/CD Integration](./docs/cicd.md) | GitHub Actions, CircleCI, Jenkins |
302
+ | [Troubleshooting](./docs/troubleshooting.md) | Common issues and solutions |
303
+ | [Releases](./docs/releases.md) | Release process and commands |
304
+ | [Node.js Compatibility](./docs/nodejs-compatibility.md) | Version matrix and upgrade guide |
305
+ | [AGENTS.md](./AGENTS.md) | Guide for AI agents working with this codebase |
306
+
307
+ ## Project Structure
744
308
 
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
- });
750
309
  ```
751
-
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
- });
310
+ mcp-tester/
311
+ ├── src/
312
+ │ ├── client/MCPClient.ts # Main client class
313
+ │ ├── cli/index.ts # CLI tool
314
+ │ ├── assert.ts # Assertion utilities
315
+ │ ├── matchers.ts # Custom Jest matchers
316
+ │ ├── utils/
317
+ │ │ ├── errors.ts # Error classes
318
+ │ │ ├── logger.ts # Logging
319
+ │ │ └── env.ts # Environment utilities
320
+ │ └── __tests__/ # 227 tests
321
+ │ ├── everything-server.test.ts # Integration tests (server-everything)
322
+ │ ├── real-server.test.ts # Integration tests (stdio transport)
323
+ │ ├── cli.test.ts # CLI tool tests
324
+ │ └── fixtures/mock-server.ts # In-memory mock server
325
+ ├── examples/
326
+ │ ├── basic-test.ts # Basic usage example
327
+ │ ├── full-test.ts # Full capabilities example
328
+ │ ├── jest-matchers-example.ts # Jest matchers usage
329
+ │ ├── assert-example.ts # Assert utilities usage
330
+ │ ├── everything-server-test.ts # Test against real MCP server
331
+ │ └── mock-server.js # Standalone MCP server
332
+ └── docs/ # Documentation
762
333
  ```
763
334
 
764
- **Error Testing**
335
+ ## Development
765
336
 
766
- Use `rejects.toThrow()` for expected errors:
767
-
768
- ```typescript
769
- it('should handle invalid tool', async () => {
770
- await expect(
771
- client.callTool({ name: 'non-existent', arguments: {} })
772
- ).rejects.toThrow();
773
- });
337
+ ```bash
338
+ npm install # Install dependencies
339
+ npm run build # Compile TypeScript
340
+ npm test # Run tests (227 tests)
341
+ npm run lint # Lint code
342
+ npm run format # Format with Prettier
774
343
  ```
775
344
 
776
- ### Test Helpers and Matchers
777
-
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:
783
-
784
- ```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
- });
795
-
796
- afterEach(async () => {
797
- await testSuite.teardown(); // Stops server automatically
798
- });
799
-
800
- it('should test feature', async () => {
801
- const tools = await testSuite.client.listTools();
802
- expect(tools).toBeDefined();
803
- });
804
- });
805
- ```
806
-
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
823
-
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:
345
+ ## Contributing
1519
346
 
1520
347
  1. Fork the repository
1521
348
  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`)
349
+ 3. Make changes and test (`npm run build && npm test`)
350
+ 4. Format and lint (`npm run format && npm run lint`)
1524
351
  5. Open a Pull Request
1525
352
 
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
353
+ ## License
1561
354
 
1562
- MIT License - see [LICENSE](LICENSE) for details.
355
+ MIT — see [LICENSE](LICENSE) for details.