mcp-testing
adeze/raindrop-mcp/.github/skills/mcp-testing/SKILL.md
MCP Testing Strategies with Vitest, Inspector, and Integration Tests
Skill183 starsChanged 7 months ago
---
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)
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

