Install
$ agentstack add mcp-gabry-ts-mcp-harness ✓ 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 Used
- ● Environment & secrets Used
- ✓ 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-harness
[](https://www.npmjs.com/package/mcp-harness) [](https://github.com/gabry-ts/mcp-harness/actions/workflows/ci.yml) [](https://opensource.org/licenses/MIT)
> In-memory testing toolkit for MCP servers in TypeScript — supertest for MCP.
Why
Testing MCP servers today means running them as subprocesses, connecting over stdio, and manually inspecting JSON. It's slow, flaky, and hard to integrate into CI.
mcp-harness gives you a fast, in-memory test harness that connects directly to your McpServer instance — no child processes, no ports, no IO. Just import your server, create a harness, and assert on results. Think supertest but for the Model Context Protocol.
Quick Start
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { createHarness } from 'mcp-harness';
import { hasText, getFirstText } from 'mcp-harness/assertions';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
server.tool('greet', { name: z.string() }, async ({ name }) => ({
content: [{ type: 'text', text: `Hello, ${name}!` }],
}));
const harness = await createHarness(server);
const result = await harness.callTool('greet', { name: 'World' });
console.log(getFirstText(result)); // "Hello, World!"
console.log(hasText(result, 'Hello')); // true
await harness.close();
Installation
npm install --save-dev mcp-harness
Peer dependency: @modelcontextprotocol/sdk must be installed in your project.
Usage
In-Memory Mode
Pass your McpServer instance directly. Zero IO, instant startup:
import { createHarness } from 'mcp-harness';
import { myServer } from './server.js';
const harness = await createHarness(myServer);
// List tools, resources, prompts
const tools = await harness.listTools();
const resources = await harness.listResources();
const templates = await harness.listResourceTemplates();
const prompts = await harness.listPrompts();
// Call a tool
const result = await harness.callTool('my-tool', { arg: 'value' });
// Read a resource
const resource = await harness.readResource('info://version');
// Get a prompt
const prompt = await harness.getPrompt('my-prompt', { name: 'Gab' });
// Server info
const capabilities = harness.getServerCapabilities();
const version = harness.getServerVersion();
// Health check
await harness.ping();
// Always close when done
await harness.close();
Subprocess Mode
Test compiled servers via stdio transport, just like a real MCP client would:
import { createHarness } from 'mcp-harness';
const harness = await createHarness({
command: 'node',
args: ['dist/server.js'],
env: { API_KEY: 'test-key' },
cwd: '/path/to/project',
});
const tools = await harness.listTools();
// Access subprocess stderr for debugging
console.log(harness.stderr);
await harness.close();
Assertion Helpers
Framework-agnostic helpers for inspecting MCP results. Use with Vitest, Jest, node:assert, or any test runner:
import {
hasText,
getTexts,
getFirstText,
hasError,
hasErrorMatching,
toolExists,
findTool,
resourceExists,
promptExists,
} from 'mcp-harness/assertions';
// Inspect tool call results
const result = await harness.callTool('echo', { message: 'hi' });
hasText(result, 'hi'); // true
getFirstText(result); // "hi"
getTexts(result); // ["hi"]
// Check for errors
hasError(result); // false
hasErrorMatching(result, /timeout/); // false
// Inspect server capabilities
const tools = await harness.listTools();
toolExists(tools, 'echo'); // true
findTool(tools, 'echo'); // Tool object or undefined
const resources = await harness.listResources();
resourceExists(resources, 'info://version'); // true
const prompts = await harness.listPrompts();
promptExists(prompts, 'greet'); // true
Options
const harness = await createHarness(server, {
timeout: 5000, // Connection/request timeout in ms
clientName: 'my-test', // Client name for MCP handshake
clientVersion: '1.0.0', // Client version
clientCapabilities: {}, // Additional client capabilities
});
API Reference
createHarness(server, options?)
| Parameter | Type | Description | |-----------|------|-------------| | server | McpServer \| SubprocessConfig | Server instance or subprocess config | | options | HarnessOptions | Optional connection settings | | Returns | Promise | Connected harness instance |
McpHarness
| Method | Signature | Description | |--------|-----------|-------------| | listTools() | () => Promise | List all registered tools | | callTool() | (name, args?) => Promise | Call a tool by name | | listResources() | () => Promise | List all registered resources | | readResource() | (uri) => Promise | Read a resource by URI | | listResourceTemplates() | () => Promise | List resource templates | | listPrompts() | () => Promise | List all registered prompts | | getPrompt() | (name, args?) => Promise | Get a prompt by name | | ping() | () => Promise | Ping the server | | getServerCapabilities() | () => ServerCapabilities \| undefined | Get server capabilities | | getServerVersion() | () => { name, version } \| undefined | Get server name and version | | close() | () => Promise | Close the harness (idempotent) | | client | Client | Raw MCP Client for advanced use | | stderr | string \| undefined | Subprocess stderr output (subprocess mode only) |
HarnessOptions
| Field | Type | Default | Description | |-------|------|---------|-------------| | timeout | number | — | Connection/request timeout in ms | | clientName | string | 'mcp-harness' | Client name for handshake | | clientVersion | string | '0.1.0' | Client version | | clientCapabilities | object | — | Additional capabilities |
SubprocessConfig
| Field | Type | Description | |-------|------|-------------| | command | string | Command to execute ('node', 'npx') | | args | string[] | Command arguments | | env | Record | Environment variables (merged with process.env) | | cwd | string | Working directory |
Assertion Helpers (mcp-harness/assertions)
| Function | Signature | Description | |----------|-----------|-------------| | hasText() | (result, text) => boolean | Check if any text block contains substring | | getTexts() | (result) => string[] | Extract all text strings from result | | getFirstText() | (result) => string \| undefined | Get first text string or undefined | | hasError() | (result) => boolean | Check if result is an error | | hasErrorMatching() | (result, pattern) => boolean | Check error matches string/RegExp | | toolExists() | (tools, name) => boolean | Check if tool exists by name | | findTool() | (tools, name) => Tool \| undefined | Find tool by name | | resourceExists() | (resources, uri) => boolean | Check if resource exists by URI | | promptExists() | (prompts, name) => boolean | Check if prompt exists by name |
Examples
- [Vitest example](./examples/vitest-example/) — Full test suite with Vitest
- [Jest example](./examples/jest-example/) — Same tests with Jest + ESM
- [Standalone example](./examples/standalone-example/) — No test framework, just
node:assert
How It Works
In in-memory mode, mcp-harness uses the MCP SDK's InMemoryTransport to create a linked pair of transports. Your McpServer connects to one side, and a Client connects to the other. Messages flow directly through memory — no serialization, no IO, no child processes.
flowchart LR
client["MCP Client(harness)"]
server["McpServer(your code)"]
client |InMemoryTransportlinked pair| server
In subprocess mode, the harness spawns your server as a child process and connects via StdioClientTransport, the same way a real MCP host would.
Contributing
git clone https://github.com/gabry-ts/mcp-harness.git
cd mcp-harness
npm install
npm test
npm run build
License
[MIT](./LICENSE) © Gabriele Partiti
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: gabry-ts
- Source: gabry-ts/mcp-harness
- License: MIT
- Homepage: https://partiti.dev
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.