---
name: mcp-testing
description: MCP Testing Strategies with Vitest, Inspector, and Integration Tests
keywords:
[mcp, testing, vitest, inspector, unit-tests, integration-tests, ci-cd]
---
# MCP Testing Skill
Modern testing strategies for Model Context Protocol (MCP) servers developed in VS Code using TypeScript, Bun, and Vitest.
## Unit Testing with Vitest & In-Memory Transport
### Setup Minimal Test Environment
```typescript
import { describe, it, expect, beforeEach } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/testing/inMemoryTransport.js";
import { RaindropMCPService } from "../src/services/raindropmcp.service.js";
describe("RaindropMCPService Tools", () => {
let service: RaindropMCPService;
let transport: InMemoryTransport;
let client: Client;
beforeEach(async () => {
service = new RaindropMCPService();
transport = new InMemoryTransport(service.getServer());
client = new Client(
{
name: "test-client",
version: "1.0.0",
},
{ capabilities: {} },
);
await client.connect(transport);
});
it("should list available tools", async () => {
const tools = await client.listTools();
expect(tools.tools.length).toBeGreaterThan(0);
expect(tools.tools.map((t) => t.name)).toContain("collection_list");
});
it("should call a tool with valid input", async () => {
const result = await client.callTool({
name: "collection_list",
arguments: {},
});
expect(result).toBeDefined();
expect(result.content).toBeDefined();
});
});
```
### Key Testing Patterns
1. **In-Memory Transport**: Use for unit tests—no subprocess overhead, direct method invocation
2. **Server Capabilities**: Verify manifest before running tool tests
3. **Mock Context**: Provide mock `RaindropService` for isolated tool testing
4. **Error Handling**: Test both success and error paths with Zod validation
## Integration Testing with MCP Inspector
### Launch Inspector in VS Code
1. **Terminal > Run Task > "Debug HTTP Server with Inspector"** (or STDIO variant)
- Opens Inspector at `http://localhost:3000` (or via auto-opened browser)
- Live tool/resource exploration with bidirectional message capture
2. **Manual Inspection Flow**:
- Call `diagnostics` tool → view server version and enabled tools
- Call `collection_list` → see resource links to collections
- Read collection resources by URI → validate dynamic resource handling
- Test tool inputs directly with Inspector's form interface
3. **Record & Replay**:
- Inspector logs all requests/responses
- Copy test cases directly into Vitest from Inspector logs
- Validate against snapshot tests
## Snapshot Testing for Tool Schemas
```typescript
import { describe, it, expect } from "vitest";
import { RaindropMCPService } from "../src/services/raindropmcp.service.js";
describe("Tool Schema Snapshots", () => {
let service: RaindropMCPService;
beforeEach(() => {
service = new RaindropMCPService();
});
it("should maintain consistent tool schemas", async () => {
const tools = await service.listTools();
expect(tools).toMatchSnapshot();
});
});
```
### Why Snapshot Testing?
- Catches unintended schema changes (breaking for LLM clients)
- Auto-generates golden files for schema validation
- Reduces boilerplate when verifying complex schemas
## Running Tests in VS Code
### Run Tests with Coverage
```bash
bun run test:coverage
```
Generates coverage reports showing:
- Line & branch coverage for tools
- Uncovered error paths
- Integration test coverage vs unit test coverage
### Watch Mode During Development
```bash
bun test --watch
```
- Re-runs tests on file changes
- Ideal for TDD workflows
- Vitest provides formatted output in terminal
## Load & Performance Testing
### Stress Test Tools with Concurrency
```typescript
it("should handle concurrent tool calls", async () => {
const promises = Array.from({ length: 10 }, () =>
client.callTool({
name: "collection_list",
arguments: {},
}),
);
const results = await Promise.all(promises);
expect(results).toHaveLength(10);
expect(results.every((r) => r.content)).toBe(true);
});
```
### Measure Tool Latency
```typescript
it("should respond within acceptable latency", async () => {
const start = performance.now();
await client.callTool({
name: "bookmark_search",
arguments: { search: "test" },
});
const duration = performance.now() - start;
expect(duration).toBeLessThan(5000); // 5s timeout for API calls
});
```
## Resource Testing
### Test Dynamic Resource Resolution
```typescript
it("should fetch and validate collection resources", async () => {
const resources = await client.listResources();
const collectionPattern = resources.resources.find((r) =>
r.uri.includes("mcp://collection/"),
);
expect(collectionPattern).toBeDefined();
// Read a specific collection
const resourceData = await client.readResource({
uri: "mcp://collection/12345",
});
expect(resourceData).toBeDefined();
expect(Array.isArray(resourceData)).toBe(true);
expect(resourceData[0].text).toBeDefined();
});
```
### Test Error Conditions
```typescript
it("should handle invalid resource URIs gracefully", async () => {
expect(async () => {
await client.readResource({ uri: "mcp://invalid/xyz" });
}).rejects.toThrow();
});
```
## Recommended Dev Dependencies
### Testing & Quality
```json
{
"devDependencies": {
"vitest": "^4.0.0",
"@vitest/coverage-v8": "^1.0.0",
"@vitest/ui": "^1.0.0",
"happy-dom": "^13.0.0"
}
}
```
### Code Quality & Linting
```json
{
"devDependencies": {
"eslint": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint-config-prettier": "^9.0.0",
"prettier": "^3.0.0"
}
}
```
### Debugging & Inspection
```json
{
"devDependencies": {
"@modelcontextprotocol/inspector": "^1.0.0"
}
}
```
## References
- [Vitest Documentation](https://vitest.dev/)
- [MCP Testing Guide](https://modelcontextprotocol.io/)
- [MCP Inspector GitHub](https://github.com/modelcontextprotocol/inspector)