@crashbytes/mcp-test-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +655 -0
- package/dist/client/MCPTestClient.d.ts +62 -0
- package/dist/client/MCPTestClient.d.ts.map +1 -0
- package/dist/client/MCPTestClient.js +197 -0
- package/dist/client/MCPTestClient.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/matchers/index.d.ts +7 -0
- package/dist/matchers/index.d.ts.map +1 -0
- package/dist/matchers/index.js +7 -0
- package/dist/matchers/index.js.map +1 -0
- package/dist/matchers/vitest.d.ts +29 -0
- package/dist/matchers/vitest.d.ts.map +1 -0
- package/dist/matchers/vitest.js +201 -0
- package/dist/matchers/vitest.js.map +1 -0
- package/dist/mocks/MockServer.d.ts +93 -0
- package/dist/mocks/MockServer.d.ts.map +1 -0
- package/dist/mocks/MockServer.js +120 -0
- package/dist/mocks/MockServer.js.map +1 -0
- package/dist/mocks/index.d.ts +5 -0
- package/dist/mocks/index.d.ts.map +1 -0
- package/dist/mocks/index.js +5 -0
- package/dist/mocks/index.js.map +1 -0
- package/dist/types/index.d.ts +193 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Blackhole Software LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
# MCP Test Kit
|
|
2
|
+
|
|
3
|
+
> **A comprehensive testing framework for Model Context Protocol (MCP) servers**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@crashbytes/mcp-test-kit)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## 📖 Table of Contents
|
|
9
|
+
|
|
10
|
+
- [What is MCP?](#what-is-mcp)
|
|
11
|
+
- [What is MCP Test Kit?](#what-is-mcp-test-kit)
|
|
12
|
+
- [Why Use MCP Test Kit?](#why-use-mcp-test-kit)
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Quick Start](#quick-start)
|
|
15
|
+
- [Core Concepts](#core-concepts)
|
|
16
|
+
- [API Reference](#api-reference)
|
|
17
|
+
- [Examples](#examples)
|
|
18
|
+
- [Best Practices](#best-practices)
|
|
19
|
+
- [Contributing](#contributing)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
|
|
22
|
+
## 🤔 What is MCP?
|
|
23
|
+
|
|
24
|
+
**Model Context Protocol (MCP)** is an open protocol developed by Anthropic that enables AI assistants like Claude to securely interact with external tools, data sources, and services. Think of it as a standardized way for AI models to:
|
|
25
|
+
|
|
26
|
+
- **Access tools**: Execute functions like searching databases, calling APIs, or running calculations
|
|
27
|
+
- **Read resources**: Fetch data from files, databases, or external services
|
|
28
|
+
- **Use prompts**: Leverage pre-built prompt templates for common tasks
|
|
29
|
+
|
|
30
|
+
### Real-World MCP Examples
|
|
31
|
+
|
|
32
|
+
Here are some practical MCP servers you might build:
|
|
33
|
+
|
|
34
|
+
1. **Weather Server**: Provides current weather data for any city
|
|
35
|
+
2. **Database Server**: Allows querying a PostgreSQL/MySQL database
|
|
36
|
+
3. **File System Server**: Enables reading/writing files on disk
|
|
37
|
+
4. **API Integration Server**: Connects to third-party APIs (GitHub, Slack, etc.)
|
|
38
|
+
5. **Calculator Server**: Performs complex mathematical operations
|
|
39
|
+
|
|
40
|
+
### How MCP Works
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
44
|
+
│ Claude │ ←─MCP──→│ MCP Server │ ←──────→│ Your Data │
|
|
45
|
+
│ (Client) │ │ (Your Code) │ │ or Service │
|
|
46
|
+
└─────────────┘ └──────────────┘ └──────────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The MCP server acts as a bridge between Claude and your data/services, exposing them through a standardized protocol.
|
|
50
|
+
|
|
51
|
+
## 🎯 What is MCP Test Kit?
|
|
52
|
+
|
|
53
|
+
**MCP Test Kit** is a testing framework that makes it easy to write automated tests for your MCP servers. It provides:
|
|
54
|
+
|
|
55
|
+
### 🔧 Core Features
|
|
56
|
+
|
|
57
|
+
1. **MCPTestClient**: A test client that connects to your MCP server and calls its tools/resources
|
|
58
|
+
2. **Custom Vitest Matchers**: Specialized assertions for validating MCP responses
|
|
59
|
+
3. **Mock Server**: Create fake MCP servers for testing client code
|
|
60
|
+
4. **TypeScript Support**: Full type definitions for better IDE support and type safety
|
|
61
|
+
|
|
62
|
+
### 🎪 The Problem It Solves
|
|
63
|
+
|
|
64
|
+
Without MCP Test Kit, testing an MCP server is challenging:
|
|
65
|
+
|
|
66
|
+
- ❌ You need to manually start the server and interact with it
|
|
67
|
+
- ❌ No standardized way to assert MCP-specific behaviors
|
|
68
|
+
- ❌ Complex setup for integration tests
|
|
69
|
+
- ❌ Hard to test error conditions and edge cases
|
|
70
|
+
|
|
71
|
+
**With MCP Test Kit:**
|
|
72
|
+
|
|
73
|
+
- ✅ Programmatically start and stop your server in tests
|
|
74
|
+
- ✅ Use intuitive matchers like `toBeValidMCPTool()` and `toMatchMCPToolResponse()`
|
|
75
|
+
- ✅ Write comprehensive integration tests in minutes
|
|
76
|
+
- ✅ Easily test error handling and edge cases
|
|
77
|
+
|
|
78
|
+
## 💡 Why Use MCP Test Kit?
|
|
79
|
+
|
|
80
|
+
### For MCP Server Developers
|
|
81
|
+
|
|
82
|
+
If you're building an MCP server, MCP Test Kit helps you:
|
|
83
|
+
|
|
84
|
+
- **Verify correctness**: Ensure your server implements the MCP protocol correctly
|
|
85
|
+
- **Test tools**: Validate that your tools return the expected results
|
|
86
|
+
- **Test resources**: Confirm resources are accessible and return proper data
|
|
87
|
+
- **Test error handling**: Verify error responses follow MCP standards
|
|
88
|
+
- **Regression testing**: Catch bugs before they reach production
|
|
89
|
+
- **Documentation**: Tests serve as living documentation of your server's behavior
|
|
90
|
+
|
|
91
|
+
### Example Use Case
|
|
92
|
+
|
|
93
|
+
Let's say you built a weather MCP server. With MCP Test Kit, you can write tests like:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
it('should return weather data for San Francisco', async () => {
|
|
97
|
+
const result = await client.callTool('get-weather', {
|
|
98
|
+
city: 'San Francisco'
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(result).toMatchMCPToolResponse();
|
|
102
|
+
expect(result.content[0].text).toContain('temperature');
|
|
103
|
+
expect(result.content[0].text).toContain('San Francisco');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle invalid city names', async () => {
|
|
107
|
+
await expect(
|
|
108
|
+
client.callTool('get-weather', { city: '' })
|
|
109
|
+
).rejects.toMatchMCPError({
|
|
110
|
+
code: -32602,
|
|
111
|
+
message: /invalid.*city/i
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 📦 Installation
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Using npm
|
|
120
|
+
npm install --save-dev @crashbytes/mcp-test-kit
|
|
121
|
+
|
|
122
|
+
# Using yarn
|
|
123
|
+
yarn add -D @crashbytes/mcp-test-kit
|
|
124
|
+
|
|
125
|
+
# Using pnpm
|
|
126
|
+
pnpm add -D @crashbytes/mcp-test-kit
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Prerequisites
|
|
130
|
+
|
|
131
|
+
- Node.js 18+
|
|
132
|
+
- A testing framework (Vitest recommended)
|
|
133
|
+
- TypeScript (optional, but recommended)
|
|
134
|
+
|
|
135
|
+
## 🚀 Quick Start
|
|
136
|
+
|
|
137
|
+
### 1. Create Your First Test
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// tests/weather-server.test.ts
|
|
141
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
142
|
+
import { createMCPTestClient } from '@crashbytes/mcp-test-kit';
|
|
143
|
+
import type { MCPTestClient } from '@crashbytes/mcp-test-kit';
|
|
144
|
+
import '@crashbytes/mcp-test-kit/matchers';
|
|
145
|
+
|
|
146
|
+
describe('Weather MCP Server', () => {
|
|
147
|
+
let client: MCPTestClient;
|
|
148
|
+
|
|
149
|
+
beforeAll(async () => {
|
|
150
|
+
// Connect to your MCP server
|
|
151
|
+
client = await createMCPTestClient({
|
|
152
|
+
command: 'node',
|
|
153
|
+
args: ['dist/server.js'],
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
afterAll(async () => {
|
|
158
|
+
await client.disconnect();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should list available tools', async () => {
|
|
162
|
+
const tools = await client.listTools();
|
|
163
|
+
|
|
164
|
+
expect(tools).toHaveLength(1);
|
|
165
|
+
expect(tools[0]).toBeValidMCPTool();
|
|
166
|
+
expect(tools[0].name).toBe('get-weather');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should get weather for a city', async () => {
|
|
170
|
+
const result = await client.callTool('get-weather', {
|
|
171
|
+
city: 'London',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(result).toMatchMCPToolResponse();
|
|
175
|
+
expect(result.content[0].text).toContain('London');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 2. Run Your Tests
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npm test
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
That's it! You're now testing your MCP server.
|
|
187
|
+
|
|
188
|
+
## 🧩 Core Concepts
|
|
189
|
+
|
|
190
|
+
### MCPTestClient
|
|
191
|
+
|
|
192
|
+
The `MCPTestClient` is your main interface for testing MCP servers. It:
|
|
193
|
+
|
|
194
|
+
- Spawns your MCP server as a subprocess
|
|
195
|
+
- Establishes a connection using the MCP protocol
|
|
196
|
+
- Provides methods to call tools, list resources, etc.
|
|
197
|
+
- Handles cleanup when tests complete
|
|
198
|
+
|
|
199
|
+
**Key Methods:**
|
|
200
|
+
|
|
201
|
+
- `listTools()`: Get all available tools from the server
|
|
202
|
+
- `callTool(name, args)`: Execute a tool with arguments
|
|
203
|
+
- `listResources()`: Get all available resources
|
|
204
|
+
- `readResource(uri)`: Read a specific resource
|
|
205
|
+
- `listPrompts()`: Get all available prompts
|
|
206
|
+
- `getPrompt(name, args)`: Get a specific prompt
|
|
207
|
+
|
|
208
|
+
### Custom Matchers
|
|
209
|
+
|
|
210
|
+
MCP Test Kit provides custom Vitest matchers for MCP-specific assertions:
|
|
211
|
+
|
|
212
|
+
#### `toBeValidMCPTool()`
|
|
213
|
+
|
|
214
|
+
Validates that an object is a properly formatted MCP tool:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const tool = {
|
|
218
|
+
name: 'calculate',
|
|
219
|
+
description: 'Performs calculations',
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
expression: { type: 'string' }
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
expect(tool).toBeValidMCPTool();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### `toBeValidMCPResource()`
|
|
232
|
+
|
|
233
|
+
Validates that an object is a properly formatted MCP resource:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const resource = {
|
|
237
|
+
uri: 'file:///data/users.json',
|
|
238
|
+
name: 'Users Database',
|
|
239
|
+
description: 'List of all users'
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
expect(resource).toBeValidMCPResource();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `toMatchMCPToolResponse()`
|
|
246
|
+
|
|
247
|
+
Validates the structure of a tool response:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const response = await client.callTool('get-weather', { city: 'Paris' });
|
|
251
|
+
|
|
252
|
+
expect(response).toMatchMCPToolResponse();
|
|
253
|
+
expect(response.content[0].text).toContain('Paris');
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `toMatchMCPError(error)`
|
|
257
|
+
|
|
258
|
+
Validates MCP error responses:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
await expect(
|
|
262
|
+
client.callTool('invalid-tool', {})
|
|
263
|
+
).rejects.toMatchMCPError({
|
|
264
|
+
code: -32601, // Method not found
|
|
265
|
+
message: /not found/i
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### `toHaveMCPProtocolVersion(version)`
|
|
270
|
+
|
|
271
|
+
Validates the MCP protocol version:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const info = await client.getServerInfo();
|
|
275
|
+
expect(info).toHaveMCPProtocolVersion('2024-11-05');
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## 📚 API Reference
|
|
279
|
+
|
|
280
|
+
### createMCPTestClient(config)
|
|
281
|
+
|
|
282
|
+
Creates and connects to an MCP test client.
|
|
283
|
+
|
|
284
|
+
**Parameters:**
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
interface MCPTestClientConfig {
|
|
288
|
+
command: string; // Command to run (e.g., 'node', 'python')
|
|
289
|
+
args?: string[]; // Arguments for the command
|
|
290
|
+
env?: Record<string, string>; // Environment variables
|
|
291
|
+
timeout?: number; // Timeout in milliseconds (default: 5000)
|
|
292
|
+
transport?: 'stdio'; // Transport type (only stdio supported)
|
|
293
|
+
debug?: boolean; // Enable debug logging
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Returns:** `Promise<MCPTestClient>`
|
|
298
|
+
|
|
299
|
+
**Example:**
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
const client = await createMCPTestClient({
|
|
303
|
+
command: 'node',
|
|
304
|
+
args: ['dist/server.js'],
|
|
305
|
+
env: {
|
|
306
|
+
NODE_ENV: 'test',
|
|
307
|
+
DATABASE_URL: 'sqlite::memory:'
|
|
308
|
+
},
|
|
309
|
+
timeout: 10000,
|
|
310
|
+
debug: true
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### MCPTestClient Methods
|
|
315
|
+
|
|
316
|
+
#### `listTools(): Promise<MCPTool[]>`
|
|
317
|
+
|
|
318
|
+
Lists all tools available on the MCP server.
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
const tools = await client.listTools();
|
|
322
|
+
console.log(tools[0].name); // 'get-weather'
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### `callTool(name: string, args?: object): Promise<MCPToolResult>`
|
|
326
|
+
|
|
327
|
+
Executes a tool with the given arguments.
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
const result = await client.callTool('calculate', {
|
|
331
|
+
expression: '2 + 2'
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### `listResources(): Promise<MCPResource[]>`
|
|
336
|
+
|
|
337
|
+
Lists all resources available on the MCP server.
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const resources = await client.listResources();
|
|
341
|
+
console.log(resources[0].uri); // 'file:///data/users.json'
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### `readResource(uri: string): Promise<MCPResourceContent>`
|
|
345
|
+
|
|
346
|
+
Reads the content of a specific resource.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
const content = await client.readResource('file:///data/users.json');
|
|
350
|
+
console.log(content.text);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### `listPrompts(): Promise<MCPPrompt[]>`
|
|
354
|
+
|
|
355
|
+
Lists all prompts available on the MCP server.
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
const prompts = await client.listPrompts();
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
#### `getPrompt(name: string, args?: Record<string, string>): Promise<unknown>`
|
|
362
|
+
|
|
363
|
+
Gets a specific prompt with arguments.
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
const prompt = await client.getPrompt('code-review', {
|
|
367
|
+
language: 'typescript'
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
#### `disconnect(): Promise<void>`
|
|
372
|
+
|
|
373
|
+
Disconnects from the MCP server and cleans up resources.
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
await client.disconnect();
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## 💻 Examples
|
|
380
|
+
|
|
381
|
+
### Example 1: Testing a Calculator Server
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
385
|
+
import { createMCPTestClient, MCPTestClient } from '@crashbytes/mcp-test-kit';
|
|
386
|
+
import '@crashbytes/mcp-test-kit/matchers';
|
|
387
|
+
|
|
388
|
+
describe('Calculator MCP Server', () => {
|
|
389
|
+
let client: MCPTestClient;
|
|
390
|
+
|
|
391
|
+
beforeAll(async () => {
|
|
392
|
+
client = await createMCPTestClient({
|
|
393
|
+
command: 'node',
|
|
394
|
+
args: ['dist/calculator-server.js'],
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
afterAll(async () => {
|
|
399
|
+
await client.disconnect();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
describe('Basic Operations', () => {
|
|
403
|
+
it('should add two numbers', async () => {
|
|
404
|
+
const result = await client.callTool('calculate', {
|
|
405
|
+
operation: 'add',
|
|
406
|
+
a: 5,
|
|
407
|
+
b: 3
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
expect(result).toMatchMCPToolResponse();
|
|
411
|
+
expect(result.content[0].text).toBe('8');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should handle division by zero', async () => {
|
|
415
|
+
await expect(
|
|
416
|
+
client.callTool('calculate', {
|
|
417
|
+
operation: 'divide',
|
|
418
|
+
a: 10,
|
|
419
|
+
b: 0
|
|
420
|
+
})
|
|
421
|
+
).rejects.toMatchMCPError({
|
|
422
|
+
code: -32602,
|
|
423
|
+
message: /division by zero/i
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe('Tool Validation', () => {
|
|
429
|
+
it('should have valid tool schema', async () => {
|
|
430
|
+
const tools = await client.listTools();
|
|
431
|
+
const calcTool = tools.find(t => t.name === 'calculate');
|
|
432
|
+
|
|
433
|
+
expect(calcTool).toBeValidMCPTool();
|
|
434
|
+
expect(calcTool?.inputSchema.properties).toHaveProperty('operation');
|
|
435
|
+
expect(calcTool?.inputSchema.properties).toHaveProperty('a');
|
|
436
|
+
expect(calcTool?.inputSchema.properties).toHaveProperty('b');
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Example 2: Testing a Database Server
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
446
|
+
import { createMCPTestClient, MCPTestClient } from '@crashbytes/mcp-test-kit';
|
|
447
|
+
import '@crashbytes/mcp-test-kit/matchers';
|
|
448
|
+
|
|
449
|
+
describe('Database MCP Server', () => {
|
|
450
|
+
let client: MCPTestClient;
|
|
451
|
+
|
|
452
|
+
beforeAll(async () => {
|
|
453
|
+
client = await createMCPTestClient({
|
|
454
|
+
command: 'node',
|
|
455
|
+
args: ['dist/db-server.js'],
|
|
456
|
+
env: {
|
|
457
|
+
DATABASE_URL: 'sqlite::memory:',
|
|
458
|
+
NODE_ENV: 'test'
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
afterAll(async () => {
|
|
464
|
+
await client.disconnect();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should query users from database', async () => {
|
|
468
|
+
const result = await client.callTool('query', {
|
|
469
|
+
sql: 'SELECT * FROM users WHERE age > 18'
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(result).toMatchMCPToolResponse();
|
|
473
|
+
const data = JSON.parse(result.content[0].text);
|
|
474
|
+
expect(Array.isArray(data)).toBe(true);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('should list database resources', async () => {
|
|
478
|
+
const resources = await client.listResources();
|
|
479
|
+
|
|
480
|
+
expect(resources.length).toBeGreaterThan(0);
|
|
481
|
+
resources.forEach(resource => {
|
|
482
|
+
expect(resource).toBeValidMCPResource();
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should read table schema', async () => {
|
|
487
|
+
const content = await client.readResource('schema://users');
|
|
488
|
+
|
|
489
|
+
expect(content.text).toContain('id');
|
|
490
|
+
expect(content.text).toContain('name');
|
|
491
|
+
expect(content.text).toContain('email');
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Example 3: Testing with Mocks
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { describe, it, expect } from 'vitest';
|
|
500
|
+
import { createMockMCPServer, mockTool } from '@crashbytes/mcp-test-kit/mocks';
|
|
501
|
+
|
|
502
|
+
describe('Mock MCP Server', () => {
|
|
503
|
+
it('should create a mock server with tools', async () => {
|
|
504
|
+
const server = createMockMCPServer({
|
|
505
|
+
tools: [
|
|
506
|
+
mockTool(
|
|
507
|
+
'greet',
|
|
508
|
+
async (args) => ({
|
|
509
|
+
content: [{ type: 'text', text: `Hello, ${args.name}!` }]
|
|
510
|
+
}),
|
|
511
|
+
{
|
|
512
|
+
description: 'Greets a person',
|
|
513
|
+
inputSchema: {
|
|
514
|
+
type: 'object',
|
|
515
|
+
properties: {
|
|
516
|
+
name: { type: 'string' }
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
]
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const tools = server.getTools();
|
|
525
|
+
expect(tools).toHaveLength(1);
|
|
526
|
+
expect(tools[0].name).toBe('greet');
|
|
527
|
+
|
|
528
|
+
const result = await server.callTool('greet', { name: 'World' });
|
|
529
|
+
expect(result.content[0].text).toBe('Hello, World!');
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## 🎓 Best Practices
|
|
535
|
+
|
|
536
|
+
### 1. Use beforeAll/afterAll for Connection Management
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
let client: MCPTestClient;
|
|
540
|
+
|
|
541
|
+
beforeAll(async () => {
|
|
542
|
+
client = await createMCPTestClient({ /* config */ });
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
afterAll(async () => {
|
|
546
|
+
await client.disconnect();
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### 2. Test Both Success and Failure Cases
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// Test success
|
|
554
|
+
it('should return data for valid input', async () => {
|
|
555
|
+
const result = await client.callTool('tool', { valid: true });
|
|
556
|
+
expect(result).toMatchMCPToolResponse();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Test failure
|
|
560
|
+
it('should reject invalid input', async () => {
|
|
561
|
+
await expect(
|
|
562
|
+
client.callTool('tool', { invalid: true })
|
|
563
|
+
).rejects.toMatchMCPError({ code: -32602 });
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### 3. Validate Tool Schemas
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
it('should have properly defined tools', async () => {
|
|
571
|
+
const tools = await client.listTools();
|
|
572
|
+
|
|
573
|
+
tools.forEach(tool => {
|
|
574
|
+
expect(tool).toBeValidMCPTool();
|
|
575
|
+
expect(tool.description).toBeTruthy();
|
|
576
|
+
expect(tool.inputSchema.properties).toBeDefined();
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 4. Use Descriptive Test Names
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// ❌ Bad
|
|
585
|
+
it('test 1', async () => { /* ... */ });
|
|
586
|
+
|
|
587
|
+
// ✅ Good
|
|
588
|
+
it('should return weather data for valid city names', async () => { /* ... */ });
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### 5. Isolate Tests
|
|
592
|
+
|
|
593
|
+
Each test should be independent and not rely on state from other tests.
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
// ❌ Bad - Tests depend on order
|
|
597
|
+
it('creates a user', async () => { /* ... */ });
|
|
598
|
+
it('updates the user', async () => { /* assumes user exists */ });
|
|
599
|
+
|
|
600
|
+
// ✅ Good - Each test is independent
|
|
601
|
+
beforeEach(async () => {
|
|
602
|
+
await client.callTool('reset-database', {});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('creates a user', async () => { /* ... */ });
|
|
606
|
+
it('updates a user', async () => {
|
|
607
|
+
await client.callTool('create-user', { name: 'Test' });
|
|
608
|
+
await client.callTool('update-user', { name: 'Updated' });
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
## 🤝 Contributing
|
|
613
|
+
|
|
614
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
615
|
+
|
|
616
|
+
### Development Setup
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
# Clone the repository
|
|
620
|
+
git clone https://github.com/crashbytes/mcp-test-kit.git
|
|
621
|
+
cd mcp-test-kit
|
|
622
|
+
|
|
623
|
+
# Install dependencies
|
|
624
|
+
npm install
|
|
625
|
+
|
|
626
|
+
# Build the project
|
|
627
|
+
npm run build
|
|
628
|
+
|
|
629
|
+
# Run tests
|
|
630
|
+
npm test
|
|
631
|
+
|
|
632
|
+
# Run linter
|
|
633
|
+
npm run lint
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## 📄 License
|
|
637
|
+
|
|
638
|
+
MIT © [Blackhole Software LLC](https://crashbytes.com)
|
|
639
|
+
|
|
640
|
+
## 🔗 Links
|
|
641
|
+
|
|
642
|
+
- **NPM Package**: https://www.npmjs.com/package/@crashbytes/mcp-test-kit
|
|
643
|
+
- **GitHub**: https://github.com/crashbytes/mcp-test-kit
|
|
644
|
+
- **MCP Specification**: https://spec.modelcontextprotocol.io/
|
|
645
|
+
- **CrashBytes Blog**: https://crashbytes.com
|
|
646
|
+
|
|
647
|
+
## 🙏 Acknowledgments
|
|
648
|
+
|
|
649
|
+
- [Anthropic](https://anthropic.com) for creating the Model Context Protocol
|
|
650
|
+
- [Vitest](https://vitest.dev) for the excellent testing framework
|
|
651
|
+
- The MCP community for feedback and contributions
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
**Built with ❤️ by [CrashBytes](https://crashbytes.com)**
|