@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 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
+ [![npm version](https://img.shields.io/npm/v/@crashbytes/mcp-test-kit.svg)](https://www.npmjs.com/package/@crashbytes/mcp-test-kit)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)**