Install
$ agentstack add mcp-buildwithabid-mcp-testkit ✓ 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-testkit
The Testing Framework for MCP Servers
Test your Model Context Protocol tools, resources, and prompts with expressive matchers, schema validation, fuzz testing, and snapshot support.
[](https://www.npmjs.com/package/mcp-testkit) [](https://github.com/BuildWithAbid/mcp-testkit/actions/workflows/ci.yml) [](https://opensource.org/licenses/MIT) [](https://nodejs.org) [](https://www.typescriptlang.org)
[Getting Started](#-getting-started) · [Features](#-features) · [API Reference](#-api-reference) · [Contributing](#-contributing)
Why mcp-testkit?
Building MCP servers is straightforward. Knowing they work correctly is the hard part.
| Problem | How mcp-testkit solves it | |:--------|:-------------------------| | No standard testing patterns | One-line harness setup for any MCP server | | Boilerplate transport wiring | createHarness() handles InMemoryTransport or stdio for you | | Manual result.content[0].text checks | Purpose-built matchers like toBeSuccessful() and toHaveTextContent() | | No schema validation | validateToolSchema() catches issues before production | | Edge cases found by users, not tests | Built-in fuzz testing with reproducible seeds |
> Write tests for MCP servers the same way you write tests for REST APIs — fast, expressive, and reliable.
Table of Contents
- [Getting Started](#-getting-started)
- [Installation](#installation)
- [Your First Test](#your-first-test)
- [Auto-Setup (Optional)](#auto-setup-optional)
- [Features](#-features)
- [Test Harness](#-test-harness)
- [Custom Matchers](#-custom-matchers)
- [Schema Validation](#-schema-validation)
- [Input Generation](#-input-generation)
- [Fuzz Testing](#-fuzz-testing)
- [Snapshot Testing](#-snapshot-testing)
- [Text Helpers](#-text-helpers)
- [API Reference](#-api-reference)
- [Harness](#harness)
- [Matchers](#matchers)
- [Schema & Validation](#schema--validation)
- [Fuzz](#fuzz)
- [Snapshot](#snapshot)
- [Helpers](#helpers)
- [Compatibility](#-compatibility)
- [Contributing](#-contributing)
- [License](#-license)
Getting Started
Installation
npm install -D mcp-testkit
Peer dependencies (you likely already have these):
npm install @modelcontextprotocol/sdk zod
Your First Test
import { describe, it, expect } from "vitest";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { createHarness } from "mcp-testkit";
import { mcpMatchers } from "mcp-testkit/matchers";
expect.extend(mcpMatchers);
const server = new McpServer({ name: "my-server", version: "1.0.0" });
server.tool(
"greet",
"Greet a user",
{ name: z.string() },
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
})
);
describe("My MCP Server", () => {
it("greets users", async () => {
const harness = await createHarness(server);
const tools = await harness.listTools();
expect(tools).toHaveToolNamed("greet");
const result = await harness.callTool("greet", { name: "World" });
expect(result).toBeSuccessful();
expect(result).toHaveTextContent("Hello, World!");
await harness.close();
});
});
Auto-Setup (Optional)
Skip the expect.extend() boilerplate by registering matchers automatically:
// vitest.config.ts
export default {
test: {
setupFiles: ["mcp-testkit/setup/vitest"],
},
};
Features
Test Harness
Create a fully wired MCP client-server pair in one line. No transport setup, no boilerplate.
import { createHarness } from "mcp-testkit";
// In-memory harness (unit tests) — pass an McpServer instance
const harness = await createHarness(server);
// Stdio harness (integration tests) — pass a command config
const harness = await createHarness({
command: "node",
args: ["./dist/server.js"],
env: { API_KEY: "test" },
});
// Same API for both modes
const tools = await harness.listTools();
const result = await harness.callTool("my-tool", { input: "test" });
const resources = await harness.listResources();
const prompts = await harness.listPrompts();
await harness.close();
In-memory vs stdio — when to use which?
| | In-memory | Stdio | |---|---|---| | Speed | Instant | Spawns a child process | | Use case | Unit tests, CI | Integration tests, E2E | | Setup | Pass McpServer instance | Pass { command, args } config | | Isolation | Shares process memory | Full process isolation |
Use in-memory for fast iteration during development. Use stdio to test the actual binary your users will run.
Custom Matchers
Expressive assertions purpose-built for MCP testing. Works with Vitest and Jest.
Tool Discovery
expect(tools).toHaveToolNamed("search");
expect(tools).toHaveToolCount(5);
expect(tools).toHaveToolDescription("search", /find items/i);
expect(tools).toHaveInputSchema("search", { query: {}, limit: {} });
Tool Results
expect(result).toBeSuccessful();
expect(result).toBeToolError();
expect(result).toHaveTextContent("expected output");
expect(result).toHaveTextContent(/pattern/);
expect(result).toHaveContentCount(1);
expect(result).toHaveContentType("text");
Schema Validation
Validate that your tool schemas follow MCP best practices before they reach production.
import { validateToolSchema, validateAllToolSchemas } from "mcp-testkit";
const tools = await harness.listTools();
// Validate a single tool
const issues = validateToolSchema(tools[0]);
// [{ path: "properties.query", message: "Missing description", severity: "warning" }]
// Validate all tools at once
const allIssues = validateAllToolSchemas(tools);
// Map
Input Generation
Automatically generate valid and edge-case inputs from any tool's JSON Schema.
import { generateValidInput, generateEdgeCaseInputs } from "mcp-testkit";
const schema = tools[0].inputSchema;
// Generate a minimal valid input
const input = generateValidInput(schema);
// { query: "test" }
// Generate edge cases for thorough testing
const edgeCases = generateEdgeCaseInputs(schema);
// [
// { label: "empty object", input: {} },
// { label: "missing required: query", input: {} },
// { label: "wrong type for: query", input: { query: 42 } },
// { label: "boundary: empty string for query", input: { query: "" } },
// { label: "boundary: very long string for query", input: { query: "a".repeat(10000) } },
// ]
Fuzz Testing
Throw random and adversarial inputs at your tools to surface crashes before your users do.
import { fuzzTool, fuzzAllTools } from "mcp-testkit";
// Fuzz a single tool — with a seed for reproducibility
const result = await fuzzTool(harness, "search", {
runs: 100,
seed: 42,
assert: (toolResult) => {
expect(toolResult.content.length).toBeGreaterThan(0);
},
});
console.log(`${result.passed}/${result.runs} passed`);
console.log("Failures:", result.errors);
// Fuzz every tool on the server
const results = await fuzzAllTools(harness, { runs: 50, seed: 42 });
Snapshot Testing
Custom serializer that strips non-deterministic fields for stable, readable snapshots.
import { toolResultSerializer } from "mcp-testkit";
expect.addSnapshotSerializer(toolResultSerializer);
const result = await harness.callTool("greet", { name: "World" });
expect(result).toMatchSnapshot();
Text Helpers
Utility functions for extracting and checking text content in tool results.
import { getTexts, getFirstText, hasText } from "mcp-testkit";
const result = await harness.callTool("greet", { name: "World" });
getTexts(result); // ["Hello, World!"]
getFirstText(result); // "Hello, World!"
hasText(result, /hello/i); // true
API Reference
Harness
| Function | Description | |:---------|:------------| | createHarness(server) | Create an in-memory test harness from an McpServer instance | | createHarness(config) | Create a stdio test harness from { command, args?, env?, cwd? } |
| Method | Returns | Description | |:-------|:--------|:------------| | harness.listTools() | ToolInfo[] | List all registered tools | | harness.callTool(name, args?) | ToolResult | Call a tool by name | | harness.listResources() | ResourceInfo[] | List all resources | | harness.readResource(uri) | ResourceResult | Read a resource by URI | | harness.listPrompts() | PromptInfo[] | List all prompts | | harness.getPrompt(name, args?) | PromptResult | Get a prompt by name | | harness.getServerCapabilities() | Record | Get server capabilities | | harness.close() | void | Close connection and clean up (idempotent) |
Matchers
| Matcher | Applies to | Description | |:--------|:-----------|:------------| | toHaveToolNamed(name) | ToolInfo[] | Assert a tool with the given name exists | | toHaveToolCount(n) | ToolInfo[] | Assert the exact number of tools | | toHaveToolDescription(name, expected) | ToolInfo[] | Assert a tool's description matches a string or regex | | toHaveInputSchema(name, props) | ToolInfo[] | Assert a tool's schema contains expected properties | | toBeSuccessful() | ToolResult | Assert the result has no error flag | | toBeToolError() | ToolResult | Assert the result has the error flag | | toHaveTextContent(expected) | ToolResult | Assert text content matches a string or regex | | toHaveContentCount(n) | ToolResult | Assert the exact number of content items | | toHaveContentType(type) | ToolResult | Assert at least one content item has the given type |
Schema & Validation
| Function | Description | |:---------|:------------| | validateToolSchema(tool) | Validate a single tool's JSON Schema, returns SchemaIssue[] | | validateAllToolSchemas(tools) | Validate all tools, returns Map | | validateOutput(output, schema) | Validate output data against a JSON Schema | | generateValidInput(schema) | Generate a minimal valid input object from a schema | | generateEdgeCaseInputs(schema) | Generate an array of edge-case inputs for boundary testing |
Fuzz
| Function | Description | |:---------|:------------| | fuzzTool(harness, name, options?) | Fuzz test a single tool with random inputs | | fuzzAllTools(harness, options?) | Fuzz test every tool on the server |
FuzzOptions
| Option | Type | Default | Description | |:-------|:-----|:--------|:------------| | runs | number | 50 | Number of random inputs to generate | | seed | number | Date.now() | Seed for reproducible runs | | assert | (result, input) => void | — | Custom assertion applied to every result |
Snapshot
| Export | Description | |:-------|:------------| | toolResultSerializer | Pre-configured snapshot serializer (strips _meta, _timestamp, etc.) | | createToolResultSerializer(options?) | Create a custom serializer with your own stripFields |
Helpers
| Function | Description | |:---------|:------------| | getTexts(result) | Extract all text strings from a ToolResult | | getFirstText(result) | Extract the first text string, or undefined | | hasText(result, pattern) | Check if any text content matches a string or regex |
Compatibility
| Dependency | Supported Versions | |:-----------|:-------------------| | Node.js | 18, 20, 22+ | | Test Runners | Vitest, Jest | | MCP SDK | @modelcontextprotocol/sdk v1.12+ | | Zod | Zod v3.23+ or v4 | | TypeScript | 5.x |
Contributing
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
git clone https://github.com/BuildWithAbid/mcp-testkit.git
cd mcp-testkit
npm install
npm test
npm run build
License
[MIT](LICENSE) © BuildWithAbid
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: BuildWithAbid
- Source: BuildWithAbid/mcp-testkit
- License: MIT
- Homepage: https://www.npmjs.com/package/mcp-testkit
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.