@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/CHANGELOG.md +83 -79
- package/README.md +333 -1406
- package/dist/assert.d.ts +75 -0
- package/dist/assert.d.ts.map +1 -0
- package/dist/assert.js +250 -0
- package/dist/assert.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +311 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/MCPClient.d.ts +316 -0
- package/dist/client/MCPClient.d.ts.map +1 -1
- package/dist/client/MCPClient.js +270 -28
- package/dist/client/MCPClient.js.map +1 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -1
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +262 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/matchers.js +527 -0
- package/dist/matchers.js.map +1 -0
- package/dist/utils/errors.d.ts +80 -4
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +123 -6
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/logger.d.ts +88 -4
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +131 -8
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/masking.d.ts +66 -0
- package/dist/utils/masking.d.ts.map +1 -0
- package/dist/utils/masking.js +171 -0
- package/dist/utils/masking.js.map +1 -0
- package/package.json +34 -5
- package/vitest.d.ts +51 -0
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
|
[](https://www.npmjs.com/package/@slbdn/mcp-tester)
|
|
4
8
|
[](https://www.npmjs.com/package/@slbdn/mcp-tester)
|
|
5
|
-
[](https://nodejs.org)
|
|
6
10
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml)
|
|
12
|
+
[](https://github.com/islobodan/mcp-tester/actions/workflows/test.yml) <!-- statements/branches/functions/lines -->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
315
|
-
arguments: {
|
|
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
|
-
###
|
|
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
|
-
###
|
|
164
|
+
### Test from the Command Line
|
|
361
165
|
|
|
362
166
|
```bash
|
|
363
|
-
#
|
|
364
|
-
npx
|
|
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
|
-
|
|
170
|
+
# List tools (JSON output)
|
|
171
|
+
npx @slbdn/mcp-tester list-tools node ./server.js --json
|
|
380
172
|
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
180
|
+
See [CLI Reference](./docs/cli.md) for all commands and options.
|
|
403
181
|
|
|
404
|
-
|
|
182
|
+
## Speed Up Your Tests with Parallel Execution
|
|
405
183
|
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
+
const client = new MCPClient();
|
|
190
|
+
await client.start({ command: 'node', args: ['./server.js'] });
|
|
434
191
|
|
|
435
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
+
## Core API
|
|
460
213
|
|
|
461
|
-
|
|
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
|
-
|
|
229
|
+
See [API Reference](./docs/api-reference.md) for full details including types, options, and error classes.
|
|
464
230
|
|
|
465
|
-
|
|
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
|
-
|
|
233
|
+
Built-in matchers so your assertions read like English:
|
|
471
234
|
|
|
472
235
|
```typescript
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
236
|
+
import {
|
|
237
|
+
toHaveTool, toHaveResource, toHavePrompt,
|
|
238
|
+
toReturnText, toReturnError, toReturnJson,
|
|
239
|
+
setupJestMatchers,
|
|
240
|
+
} from '@slbdn/mcp-tester';
|
|
478
241
|
|
|
479
|
-
|
|
242
|
+
beforeAll(() => setupJestMatchers());
|
|
480
243
|
|
|
481
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
258
|
+
### Vitest Setup
|
|
494
259
|
|
|
495
260
|
```typescript
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
266
|
+
beforeAll(() => setupVitestMatchers());
|
|
503
267
|
```
|
|
504
268
|
|
|
505
|
-
|
|
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
|
-
|
|
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
|
-
|
|
514
|
-
|
|
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
|
-
|
|
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
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
-
|
|
558
|
-
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
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
|
-
|
|
400
|
+
### "Client not started" Error
|
|
601
401
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
**Example:**
|
|
402
|
+
Calling methods before `start()`:
|
|
605
403
|
|
|
606
404
|
```typescript
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
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
|
-
|
|
663
|
-
|
|
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
|
-
|
|
687
|
-
|
|
421
|
+
// Per-call override
|
|
422
|
+
await client.callTool({ name: 'slow-tool', arguments: {}, timeout: 30000 });
|
|
688
423
|
```
|
|
689
424
|
|
|
690
|
-
###
|
|
425
|
+
### Server Won't Start
|
|
691
426
|
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
432
|
+
Check that the command and args are correct — `MCPConnectionError` includes the command and suggestions.
|
|
706
433
|
|
|
707
|
-
|
|
434
|
+
### ESM Import Errors
|
|
708
435
|
|
|
709
436
|
```typescript
|
|
710
|
-
|
|
711
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
450
|
+
### Debug Mode
|
|
767
451
|
|
|
768
452
|
```typescript
|
|
769
|
-
|
|
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
|
-
|
|
456
|
+
All logs go to stderr — they won't interfere with test output.
|
|
777
457
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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 {
|
|
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
|
-
|
|
797
|
-
|
|
798
|
-
|
|
468
|
+
// Measure operation timing
|
|
469
|
+
const elapsed = startTimer();
|
|
470
|
+
await someOperation();
|
|
471
|
+
console.log(`Took ${elapsed()}ms`);
|
|
799
472
|
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1523
|
-
4.
|
|
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
|
-
|
|
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
|
|
489
|
+
MIT — see [LICENSE](LICENSE) for details.
|