AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Mcp Testkit

mcp-buildwithabid-mcp-testkit · by BuildWithAbid

Testing framework for MCP (Model Context Protocol) servers. Custom matchers, schema validation, fuzz testing, and snapshot support for vitest and jest.

No reviews yet
0 installs
36 views
0.0% view→install

Install

$ agentstack add mcp-buildwithabid-mcp-testkit

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-buildwithabid-mcp-testkit)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Mcp Testkit? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.