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