npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@slbdn/mcp-tester

v1.2.0

Published

Minimal MCP client implementation for CI/CD testing of MCP servers

Readme

MCP Tester

This is not your grandpa's MCP Inspector. This is a real testing framework. No GUI, no clicking, no manual verification. Write tests, run them in CI, ship with confidence. 🚀

npm version npm downloads Node.js Version License: MIT Test Status Coverage

A production-ready MCP (Model Context Protocol) client for automated testing of MCP servers with Jest. No more manual clicking through inspectors — write real tests, run them in CI, break builds on regressions.

Capabilities

| Category | What You Can Test | |----------|-------------------| | Tools | List, call with arguments, validate results, test error handling | | Resources | List available resources, read by URI, validate content | | Prompts | List templates, get with arguments, validate generated messages | | Sampling | Request LLM sampling, validate model responses | | Elicitation | Handle server-initiated user input requests | | Notifications | React to resource changes, logging messages, tool list updates | | Error Handling | Custom error classes for timeouts, connection failures, server errors | | Retries | Exponential backoff with configurable attempts and delay | | Benchmarks | Measure latency, throughput, and payload performance with npm run benchmark | | CLI | Smoke-test any server from the terminal in seconds |

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        Your Test Suite                             │
│  ┌──────────┐  ┌───────────┐  ┌────────┐  ┌───────────────────┐  │
│  │   Jest    │  │  Vitest   │  │ assert │  │  Custom Runner   │  │
│  │ matchers │  │ matchers  │  │ module │  │  (any framework)  │  │
│  └────┬─────┘  └─────┬─────┘  └───┬────┘  └────────┬──────────┘  │
│       │              │             │                 │              │
│  ┌────▼──────────────▼─────────────▼─────────────────▼──────────┐    │
│  │                      MCPClient                              │    │
│  │  ┌──────────────────────────────────────────────────────┐  │    │
│  │  │  start() │ stop() │ listTools() │ callTool() │ ...   │  │    │
│  │  └──────────────────────┬───────────────────────────────────┘  │    │
│  │                         │                                      │    │
│  │  ┌──────────────────────▼───────────────────────────────┐    │    │
│  │  │         Retry │ Timeout │ Error Handling              │    │    │
│  │  │    MCPTimeoutError │ MCPConnectionError │ ...        │    │    │
│  │  └──────────────────────┬───────────────────────────────┘    │    │
│  └─────────────────────────┼─────────────────────────────────────┘    │
│                            │ stdio (JSON-RPC)                        │
│  ┌─────────────────────────▼─────────────────────────────────────┐  │
│  │              MCP Server (child process)                       │  │
│  │  ┌────────┐  ┌───────────┐  ┌──────────┐  ┌────────────┐   │  │
│  │  │ Tools  │  │ Resources │  │ Prompts  │  │ Sampling   │   │  │
│  │  └────────┘  └───────────┘  └──────────┘  └────────────┘   │  │
│  └──────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

Data flow: Your test calls MCPClient → which sends JSON-RPC over stdio → to the MCP server process → server responds → MCPClient returns typed results → your assertion validates them.

Key design: MCPClient manages the server lifecycle (spawn/kill) and protocol handling. Your tests never deal with the transport layer directly.

Why MCP Tester?

  • Full protocol support — tools, resources, prompts, sampling, elicitation, notifications
  • Built on the official SDK@modelcontextprotocol/sdk v1.29.0
  • Works out of the box — mock server included, 257 tests passing
  • CLI included — test any server from the command line
  • TypeScript first — strict types, ESM, full IntelliSense
  • Parallel execution — fire multiple tool calls concurrently with Promise.all for blazing-fast test suites
  • Retry with backoff — flaky servers? Configure retries so your CI stays green
  • In-memory mock server — unit test your test logic without spawning real processes
  • Custom Jest matcherstoHaveTool(), toHaveResource(), toHavePrompt() — and Vitest support

Test Coverage

| Metric | Coverage | |--------|----------| | Statements | 68% | | Branches | 61% | | Functions | 60% | | Lines | 68% | | Tests | 306 passing |

Per-file coverage thresholds are set in jest.config.js to catch regressions while avoiding CI noise. Run npm run test:coverage to see the full breakdown by file. PRs automatically receive a coverage comment via GitHub Actions.

Install

npm install @slbdn/mcp-tester --save-dev

Requires Node.js >= 18. See Node.js Compatibility for details.

Quick Start

Connect and Use Tools

import { MCPClient } from '@slbdn/mcp-tester';

const client = new MCPClient({
  name: 'my-test-client',
  version: '1.0.0',
});

// Connect to your MCP server
await client.start({
  command: 'node',
  args: ['./your-mcp-server.js'],
});

// List and call tools
const tools = await client.listTools();
const result = await client.callTool({
  name: 'your-tool',
  arguments: { input: 'test' },
});

await client.stop();

Test with Jest

import { MCPClient } from '@slbdn/mcp-tester';

describe('My MCP Server', () => {
  let client: MCPClient;

  beforeEach(async () => {
    client = new MCPClient();
    await client.start({
      command: 'node',
      args: ['./my-server.js'],
    });
  });

  afterEach(async () => {
    if (client.isConnected()) {
      await client.stop();
    }
  });

  it('should list tools', async () => {
    const tools = await client.listTools();
    expect(tools.length).toBeGreaterThan(0);
  });

  it('should call a tool', async () => {
    const result = await client.callTool({
      name: 'my-tool',
      arguments: { input: 'test' },
    });
    expect(result.content).toBeDefined();
  });
});

Test from the Command Line

# Quick smoke test
npx @slbdn/mcp-tester test node ./server.js

# List tools (JSON output)
npx @slbdn/mcp-tester list-tools node ./server.js --json

# Call a tool
npx @slbdn/mcp-tester call-tool echo node ./server.js --params '{"message":"Hello"}'

# Short aliases: lt, ct, rr, gp
npx @slbdn/mcp-tester lt node ./server.js

See CLI Reference for all commands and options.

Speed Up Your Tests with Parallel Execution

MCP Tester is built for concurrency. Fire off multiple tool calls at once — no waiting in line:

import { MCPClient } from '@slbdn/mcp-tester';

const client = new MCPClient();
await client.start({ command: 'node', args: ['./server.js'] });

// 🔥 Run all calls in parallel — 5x faster than sequential
const [tools, resources, prompts] = await Promise.all([
  client.listTools(),
  client.listResources(),
  client.listPrompts(),
]);

// Parallel tool calls? No problem.
const [echo, sum, env] = await Promise.all([
  client.callTool({ name: 'echo', arguments: { message: 'hello' } }),
  client.callTool({ name: 'get-sum', arguments: { a: 1, b: 2 } }),
  client.callTool({ name: 'get-env', arguments: {} }),
]);

await client.stop();

Works because each MCP call is independent — the client multiplexes requests over a single stdio connection. No extra config needed. Just Promise.all and go.

Core API

| Method | Returns | Description | |--------|---------|-------------| | start(config) | Promise<void> | Connect to an MCP server | | stop() | Promise<void> | Disconnect and clean up | | isConnected() | boolean | Check connection state | | listTools() | Promise<Tool[]> | List available tools | | callTool(options) | Promise<CallToolResult> | Call a tool with arguments | | listResources() | Promise<Resource[]> | List available resources | | readResource(uri) | Promise<ReadResourceResult> | Read a resource by URI | | listPrompts() | Promise<Prompt[]> | List available prompts | | getPrompt(name, args?) | Promise<GetPromptResult> | Get a prompt template | | requestSampling(request) | Promise<CreateMessageResult> | Request LLM sampling | | setElicitationHandler(fn) | Promise<void> | Handle server user-input requests | | setNotificationHandlers(handlers) | void | Handle server notifications |

See API Reference for full details including types, options, and error classes.

Custom Jest Matchers

Built-in matchers so your assertions read like English:

import {
  toHaveTool, toHaveResource, toHavePrompt,
  toReturnText, toReturnError, toReturnJson,
  setupJestMatchers,
} from '@slbdn/mcp-tester';

beforeAll(() => setupJestMatchers());

// Collection matchers
expect(tools).toHaveTool('echo');
expect(resources).toHaveResource('config://settings');
expect(prompts).toHavePrompt('greet');
expect(tools).toHaveToolCount(5);

// Tool result matchers
expect(result).toReturnText('Hello World');
expect(result).toReturnTextContaining('hello');
expect(result).toReturnError();
expect(result).toReturnJson({ status: 'ok' });
expect(result).toReturnContentCount(2);

Vitest Setup

import { setupVitestMatchers } from '@slbdn/mcp-tester';
import { beforeAll } from 'vitest';
// Add vitest.d.ts to your tsconfig for type safety
// /// <reference types="@slbdn/mcp-tester/vitest" />

beforeAll(() => setupVitestMatchers());

Works identically — same matchers, same { pass, message } format, just setupVitestMatchers() instead.

| Matcher | Description | |---------|-------------| | toHaveTool(name) | Assert a tool exists by name | | toHaveToolWithSchema(name) | Assert a tool exists and has an input schema | | toHaveToolCount(n) | Assert exact number of tools | | toHaveResource(uri) | Assert a resource exists by URI | | toHaveResourceByName(name) | Assert a resource exists by display name | | toHaveResourceCount(n) | Assert exact number of resources | | toHavePrompt(name) | Assert a prompt exists by name | | toHavePromptWithArgs(name) | Assert a prompt exists and has arguments | | toHavePromptCount(n) | Assert exact number of prompts | | toReturnText(expected?) | Tool text exact match (or just has text) | | toReturnTextContaining(sub) | Tool text contains substring | | toReturnError() | Tool result is an error | | toReturnOk() | Tool result is successful | | toReturnJson(obj) | Parse tool text as JSON, deep compare | | toReturnContentCount(n) | Tool has N content items | | toReturnImage() | Tool result contains an image | | toReturnResourceText(expected?) | Resource text exact match (or just has text) | | toReturnResourceTextContaining(sub) | Resource text contains substring | | toReturnPromptTextContaining(sub) | Prompt message text contains substring | | toReturnPromptMessageCount(n) | Prompt has N messages |

See Testing Guide for full testing patterns.

Assertion Utilities

Not using Jest? Running tests with a custom runTest() harness? The built-in assert module throws descriptive errors on failure — works with any runner:

import { MCPClient, assert } from '@slbdn/mcp-tester';

const client = new MCPClient();
await client.start({ command: 'node', args: ['./server.js'] });

await runTest("Get constant: c (speed of light)", async () => {
  const result = await client.callTool({
    name: "get_constant",
    arguments: { name: "c" },
  });
  assert.toolNumEquals(result, 299792458);
});

await runTest("Echo returns input", async () => {
  const result = await client.callTool({
    name: "echo",
    arguments: { message: "hello" },
  });
  assert.toolTextContains(result, "hello");
});

| Assertion | Description | |-----------|-------------| | equal(a, b) / notEqual(a, b) | Strict equality checks | | deepEqual(a, b) | JSON deep equality | | ok(val) / notOk(val) | Truthy / falsy | | throws(fn) / doesNotThrow(fn) | Async error checks | | greaterThan(a, b) / lessThan(a, b) / closeTo(a, b, eps?) | Numeric comparisons | | contains(str, sub) / matches(str, regex) | String checks | | toolTextEquals(r, str) | Tool text exact match | | toolTextContains(r, str) | Tool text contains | | toolNumEquals(r, num) | Parse tool text as number, compare | | toolNumCloseTo(r, num, eps?) | Numeric approximate match | | toolJsonEquals(r, obj) | Parse tool text as JSON, deep compare | | toolIsError(r) / toolIsOk(r) | Error/success check | | toolHasImage(r) | Check for image content | | resourceTextContains(r, str) | Resource text contains | | promptTextContains(r, str) | Prompt text contains |

Documentation

| Doc | Description | |-----|-------------| | API Reference | Full method docs, types, and error classes | | Testing Guide | Writing tests, matchers, mock server | | Examples | Practical code examples | | Advanced Usage | Timeouts, retries, concurrency, notifications, logging | | CLI Reference | CLI commands, options, and output formats | | CI/CD Integration | GitHub Actions, CircleCI, Jenkins | | Troubleshooting | Common issues and solutions | | Releases | Release process and commands | | Node.js Compatibility | Version matrix and upgrade guide | | AGENTS.md | Guide for AI agents working with this codebase |

Project Structure

mcp-tester/
├── src/
│   ├── client/MCPClient.ts      # Main client class
│   ├── cli/index.ts             # CLI tool
│   ├── assert.ts               # Assertion utilities
│   ├── matchers.ts             # Custom Jest matchers
│   ├── utils/
│   │   ├── errors.ts            # Error classes
│   │   ├── logger.ts            # Logging
│   │   └── env.ts               # Environment utilities
│   └── __tests__/               # 306 tests
│       ├── everything-server.test.ts  # Integration tests (server-everything)
│       ├── real-server.test.ts        # Integration tests (stdio transport)
│       ├── cli.test.ts                # CLI tool tests
│       └── fixtures/mock-server.ts    # In-memory mock server
├── examples/
│   ├── basic-test.ts            # Basic usage example
│   ├── full-test.ts             # Full capabilities example
│   ├── jest-matchers-example.ts # Jest matchers usage
│   ├── assert-example.ts        # Assert utilities usage
│   ├── everything-server-test.ts # Test against real MCP server
│   ├── stateful-server-test.ts  # Stateful server testing pattern
│   ├── mock-server.js           # Basic standalone MCP server
│   ├── simple-server.ts         # Minimal TypeScript MCP server
│   └── stateful-server.ts        # Stateful MCP server (counter + todos)
└── docs/                        # Documentation

Development

npm install          # Install dependencies
npm run build        # Compile TypeScript
npm run dev          # Watch mode — auto-rebuild on source changes
npm test             # Run tests (306 tests)
npm run lint         # Lint code
npm run format       # Format with Prettier

Troubleshooting

"Client not started" Error

Calling methods before start():

// ✗ Wrong
const client = new MCPClient();
const tools = await client.listTools(); // throws MCPNotStartedError

// ✓ Correct
const client = new MCPClient();
await client.start({ command: 'node', args: ['./server.js'] });
const tools = await client.listTools();

Timeout Errors

// Global timeout
const client = new MCPClient({ timeout: 60000 });

// Per-call override
await client.callTool({ name: 'slow-tool', arguments: {}, timeout: 30000 });

Server Won't Start

# Verify the server runs standalone first
node ./my-server.js

Check that the command and args are correct — MCPConnectionError includes the command and suggestions.

ESM Import Errors

// ✗ Wrong — missing .js extension
import { MCPClient } from './client/MCPClient';

// ✓ Correct
import { MCPClient } from './client/MCPClient.js';

Tests Fail in CI

  • Set maxWorkers: 1 in Jest config (parallel workers can crash)
  • Verify Node.js >= 18
  • Check that server paths are correct

Debug Mode

const client = new MCPClient({ logLevel: 'debug', enableProtocolLogging: true });

All logs go to stderr — they won't interfere with test output.

New logging features:

  • Timing: operations log elapsed time (e.g., Listed 4 tools in 12ms)
  • Secret masking: API keys, tokens, and passwords are auto-masked (e.g., sk-ab...789)
  • Pretty-printed JSON: protocol messages are formatted and masked
  • Timestamps: enable with { timestamps: true }
  • Colors: auto-enabled in terminals (cyan=debug, green=info, yellow=warn, red=error)
import { startTimer, prettyPrint } from '@slbdn/mcp-tester';

// Measure operation timing
const elapsed = startTimer();
await someOperation();
console.log(`Took ${elapsed()}ms`);

// Pretty-print objects with secret masking
prettyPrint({ apiKey: 'sk-proj-abc123...', count: 42 });

See Troubleshooting Guide for more details.

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make changes and test (npm run build && npm test)
  4. Format and lint (npm run format && npm run lint)
  5. Open a Pull Request

License

MIT — see LICENSE for details.