Install
$ agentstack add mcp-gnana997-mcp-dev-kit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
mcp-dev-kit
[](https://www.npmjs.com/package/mcp-dev-kit) [](https://github.com/gnana997/mcp-dev-kit/actions) [](https://opensource.org/licenses/MIT)
Complete testing and debugging toolkit for Model Context Protocol (MCP) servers
Build reliable MCP servers with comprehensive testing utilities, intelligent snapshot testing, and stdio-safe debug logging.
Why mcp-dev-kit?
Developing MCP servers comes with unique challenges:
- ❌ Testing is hard - No built-in test utilities for MCP servers
- ❌ Snapshots break - Timestamps and IDs change on every run
- ❌ Logging breaks stdio -
console.log()corrupts JSON-RPC communication - ❌ Manual assertions - Repetitive boilerplate for common checks
mcp-dev-kit solves all of these:
- ✅ MCPTestClient - Full-featured test client for MCP servers
- ✅ Smart snapshots - Auto-exclude dynamic fields (timestamps, IDs)
- ✅ Custom matchers - Readable assertions for tools, resources, prompts
- ✅ Safe logging - Debug without breaking JSON-RPC protocol
Quick Start
Installation
npm install --save-dev mcp-dev-kit vitest
Basic Test Setup
1. Create vitest.setup.ts:
import { installMCPMatchers } from 'mcp-dev-kit/matchers';
installMCPMatchers();
2. Configure vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
setupFiles: ['./vitest.setup.ts'],
},
});
3. Write tests (server.test.ts):
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { MCPTestClient } from 'mcp-dev-kit/client';
describe('My MCP Server', () => {
let client: MCPTestClient;
beforeAll(async () => {
client = new MCPTestClient({
command: 'node',
args: ['./my-server.js'],
});
await client.connect();
});
afterAll(async () => {
await client.disconnect();
});
it('should list available tools', async () => {
const tools = await client.listTools();
expect(tools).toHaveLength(2);
expect(tools[0]).toHaveToolProperty('name', 'echo');
});
it('should execute tools successfully', async () => {
const result = await client.callTool('echo', { message: 'hello' });
await expect(result).toReturnToolResult('hello');
});
it('should have stable response structure', async () => {
const result = await client.callTool('list_files', { path: '/' });
expect(result).toMatchToolResponseSnapshot();
});
});
4. Add debug logging to your server:
// At the top of your MCP server file
import 'mcp-dev-kit/logger';
// Now console.log works without breaking JSON-RPC!
console.log('Server started');
console.error('Connection error', error);
5. Run tests:
npx vitest
Features
MCP Test Client
Comprehensive test client for spawning and testing MCP servers via stdio.
import { MCPTestClient } from 'mcp-dev-kit/client';
const client = new MCPTestClient({
command: 'node',
args: ['./my-server.js'],
env: { DEBUG: 'true' },
timeout: 30000,
});
await client.connect();
// Test server capabilities
const serverInfo = client.getServerInfo();
const capabilities = client.getServerCapabilities();
// List and call tools
const tools = await client.listTools();
const result = await client.callTool('my-tool', { param: 'value' });
// List and read resources
const resources = await client.listResources();
const content = await client.readResource('file://config.json');
// List and get prompts
const prompts = await client.listPrompts();
const prompt = await client.getPrompt('greeting', { name: 'Alice' });
// Helper methods for common patterns
const toolResult = await client.expectToolCallSuccess('my-tool', { input: 'test' });
const error = await client.expectToolCallError('bad-tool', {});
await client.disconnect();
Key Features:
- Automatic process lifecycle management
- Request/response matching with timeouts
- Server notification handling
- Comprehensive error handling
- TypeScript-first with full type safety
Custom Vitest Matchers
Readable, expressive assertions for MCP-specific testing.
import { installMCPMatchers } from 'mcp-dev-kit/matchers';
installMCPMatchers();
Available Matchers:
// Tool assertions
await expect(client).toHaveTool('echo');
const tools = await client.listTools();
expect(tools[0]).toHaveToolProperty('description', 'Echoes back the message');
expect(tools[0]).toMatchToolSchema({
type: 'object',
required: ['message']
});
// Resource assertions
await expect(client).toHaveResource('config://app.json');
const resources = await client.listResources();
expect(resources[0]).toHaveProperty('uri', 'config://app.json');
// Prompt assertions
await expect(client).toHavePrompt('greeting');
const prompts = await client.listPrompts();
expect(prompts[0]).toHaveProperty('name', 'greeting');
// Tool result assertions
const result = await client.callTool('echo', { message: 'test' });
await expect(result).toReturnToolResult('test');
await expect(client.callTool('unknown', {})).toThrowToolError();
Benefits:
- Clear, self-documenting test code
- Better error messages when tests fail
- Reduces boilerplate in test files
- Type-safe with TypeScript
Snapshot Testing
MCP-aware snapshot testing with intelligent field exclusion.
Why Snapshot Testing for MCP?
MCP server responses often contain dynamic data that changes on every run:
- Timestamps (
2024-11-03T10:30:00.000Z) - Request IDs (
abc123) - Execution times (
42.5ms) - Auto-increment IDs, file inodes, git SHAs
Regular snapshot testing would fail on every run. mcp-dev-kit automatically excludes these fields while capturing the stable response structure.
Quick Example
import { installMCPMatchers } from 'mcp-dev-kit/matchers';
installMCPMatchers();
describe('File System Server', () => {
it('should return consistent file listing structure', async () => {
const result = await client.callTool('list_files', { path: '/project' });
// Timestamps, IDs, and dynamic fields automatically excluded!
expect(result).toMatchToolResponseSnapshot();
});
it('should have stable tool definitions', async () => {
const tools = await client.listTools();
// Captures tool schemas for regression detection
expect(tools).toMatchToolListSnapshot();
});
it('should snapshot custom data structures', async () => {
const data = {
users: [...],
timestamp: new Date().toISOString(), // Auto-excluded
requestId: 'abc123', // Auto-excluded
};
expect(data).toMatchMCPSnapshot();
});
});
Available Snapshot Matchers
toMatchMCPSnapshot(options?) - Generic snapshot matcher for any MCP data
expect(serverResponse).toMatchMCPSnapshot();
expect(data).toMatchMCPSnapshot({ exclude: ['user.id', 'files.*.size'] });
toMatchToolResponseSnapshot(options?) - For tool call results
const result = await client.callTool('query_database', { query: 'SELECT * FROM orders' });
expect(result).toMatchToolResponseSnapshot();
toMatchToolListSnapshot(options?) - For tool definitions
const tools = await client.listTools();
expect(tools).toMatchToolListSnapshot();
toMatchResourceListSnapshot(options?) - For resource listings
const resources = await client.listResources();
expect(resources).toMatchResourceListSnapshot();
toMatchPromptListSnapshot(options?) - For prompt definitions
const prompts = await client.listPrompts();
expect(prompts).toMatchPromptListSnapshot();
Smart Defaults
These fields are automatically excluded from all snapshots:
timestamprequestIdexecutionTimecacheKey_meta.timestampserverInfo.startedAtserverInfo.uptime
Example:
// Original response
{
"users": [...],
"timestamp": "2024-11-03T10:30:00.000Z", // ❌ Excluded
"requestId": "abc123", // ❌ Excluded
"executionTime": 42.5 // ❌ Excluded
}
// Snapshot (only stable data)
{
"users": [...] // ✅ Captured
}
Custom Exclusions
Exclude additional fields using glob patterns:
// Exclude file system-specific fields
expect(result).toMatchToolResponseSnapshot({
exclude: ['files.*.size', 'files.*.inode', 'files.*.modified']
});
// Exclude all auto-increment IDs
expect(data).toMatchMCPSnapshot({
exclude: ['*.id', '*.userId', 'rows.*.orderId']
});
// Exclude nested timestamps with custom names
expect(response).toMatchMCPSnapshot({
exclude: ['data.users.*.createdAt', 'metadata.generatedAt']
});
Pattern Syntax:
field- Excludes top-level fieldnested.field- Excludes nested fieldarray.*.field- Excludes field from all array itemsdata.users.*.createdAt- ExcludescreatedAtfrom all users indata.users
Performance
Snapshot testing with property exclusion adds negligible overhead:
| Data Size | Normalization Overhead | Notes | |-----------|----------------------|-------| | 10-100 items | { const result = await client.callTool('list_users', {}); const parsed = JSON.parse(result.content[0]?.text || '{}');
// Explicit assertions for critical properties expect(parsed.users).toHaveLength(50); expect(parsed.users[0]).toHaveProperty('name'); expect(parsed.users[0]).toHaveProperty('email');
// Snapshot for structure regression detection expect(result).toMatchToolResponseSnapshot(); });
See [examples/snapshot-example/](./examples/snapshot-example/) for complete working examples with benchmarks.
### Debug Logging
Safe debug logging that doesn't break JSON-RPC stdio communication.
#### The Problem
MCP servers communicate via JSON-RPC over stdio. Every message must be a single line of JSON on stdout:
{"jsonrpc":"2.0","method":"tools/list","params":{...}}\n
If you write anything else to stdout (like `console.log()`), it corrupts the stream:
Server starting... ← Breaks protocol! {"jsonrpc":"2.0","method":"tools/list","params":{...}}\n
Result: `SyntaxError: Unexpected token 'S'`
#### The Solution
**mcp-dev-kit redirects all console output to stderr**, keeping stdout clean:
- **stdout** = pure JSON-RPC (protocol)
- **stderr** = all your logs (debugging)
#### Auto-Patch (Recommended)
```typescript
// At the top of your MCP server file
import 'mcp-dev-kit/logger';
// Now console.log works without breaking JSON-RPC!
console.log('Server started', { port: 3000 });
console.info('Configuration loaded');
console.warn('Deprecated feature used');
console.error('Connection failed', error);
Opt-out:
MCP_DEV_KIT_NO_AUTO_PATCH=true node server.js
Manual Logger
For more control, create a custom logger instance:
import { createLogger } from 'mcp-dev-kit';
const logger = createLogger({
timestamps: true,
colors: true,
level: 'info', // Only show info and above
logFile: './server.log', // Optional file output
});
logger.info('Server starting...');
logger.warn('Configuration may need updating');
logger.error('Connection failed', { reason: 'timeout' });
// Cleanup when done
await logger.close();
Logger Features
- Auto-patching - Just import and console.log works
- Colored output - Color-coded log levels (auto-detects TTY)
- Timestamps - ISO8601 timestamps on all logs
- Object formatting - Pretty-print objects with
util.inspect() - File logging - Optional async file output
- Cleanup - Graceful restoration of original console
- Zero overhead - Lightweight, uses picocolors (7 KB)
Configuration
Log Levels:
const logger = createLogger({ level: 'warn' });
logger.debug('Not shown');
logger.info('Not shown');
logger.warn('Shown'); // ✓
logger.error('Shown'); // ✓
Colors:
createLogger({ colors: false }); // Force disable
createLogger({ colors: true }); // Force enable
// Auto-detected by default based on process.stderr.isTTY
Timestamps:
createLogger({ timestamps: false }); // Disable
// ISO8601 format: 2024-11-03T12:34:56.789Z
File Logging:
const logger = createLogger({
logFile: './server.log',
});
logger.info('This goes to both stderr and server.log');
// Flush pending writes
await logger.close();
Testing Guidelines
Test Structure
Organize your tests by MCP capabilities:
describe('My MCP Server', () => {
let client: MCPTestClient;
beforeAll(async () => {
client = new MCPTestClient({ command: 'node', args: ['./server.js'] });
await client.connect();
});
afterAll(async () => {
await client.disconnect();
});
describe('Server Initialization', () => {
it('should expose correct server info', () => {
const info = client.getServerInfo();
expect(info.name).toBe('my-server');
expect(info.version).toBe('1.0.0');
});
it('should declare required capabilities', () => {
const caps = client.getServerCapabilities();
expect(caps.tools).toBeDefined();
});
});
describe('Tools', () => {
it('should list all available tools', async () => {
const tools = await client.listTools();
expect(tools).toHaveLength(3);
expect(tools.map(t => t.name)).toEqual(['echo', 'calculate', 'search']);
});
it('should execute tools successfully', async () => {
const result = await client.callTool('echo', { message: 'test' });
expect(result.content[0]?.text).toBe('test');
});
it('should handle tool errors gracefully', async () => {
const error = await client.expectToolCallError('calculate', { invalid: 'params' });
expect(error.message).toContain('Invalid parameters');
});
it('should have stable tool schemas', async () => {
const tools = await client.listTools();
expect(tools).toMatchToolListSnapshot();
});
});
describe('Resources', () => {
it('should list available resources', async () => {
await expect(client).toHaveResource('config://app.json');
});
it('should read resource content', async () => {
const content = await client.readResource('config://app.json');
expect(content.contents[0]?.text).toContain('version');
});
});
describe('Prompts', () => {
it('should provide defined prompts', async () => {
await expect(client).toHavePrompt('greeting');
});
it('should render prompts with arguments', async () => {
const prompt = await client.getPrompt('greeting', { name: 'Alice' });
expect(prompt.messages[0]?.content.text).toContain('Alice');
});
});
});
Testing Best Practices
✅ DO:
- Test all MCP capabilities - Tools, resources, prompts
- Use descriptive test names - Clearly state what's being tested
- Combine matchers and snapshots - Explicit assertions + structure validation
- Test error cases - Don't just test happy paths
- Clean up resources - Always disconnect client in
afterAll - Use timeouts appropriately - Set reasonable timeouts for slow operations
- Test server lifecycle - Test initialization and shutdown
❌ DON'T:
- Don't share client state - Each test suite should have its own client
- Don't skip error testing - Error handling is critical
- Don't test implementation details - Test public API only
- Don't create flaky tests - Avoid timing-dependent assertions
- Don't ignore snapshots - Review snapshot changes carefully
- Don't hardcode system-specific paths - Use relative paths or env vars
Error Testing
Always test error conditions:
it('should validate tool parameters', async () => {
const error = await client.expectToolCallError('calculate', {
…
## Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [gnana997](https://github.com/gnana997)
- **Source:** [gnana997/mcp-dev-kit](https://github.com/gnana997/mcp-dev-kit)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.