bubblemcp-test-kit
v1.0.0
Published
Standalone testing toolkit for Model Context Protocol (MCP) servers — fluent assertions, multi-transport client, JSON Schema validation, and mocking/recording. No account, no backend required.
Downloads
249
Maintainers
Readme
bubblemcp-test-kit
A standalone testing toolkit for Model Context Protocol (MCP) servers. No account, no backend, no instrumentation of your code — install it and start testing.
- Fluent assertions —
expectMcp(result).toBeValidMcpResponse() - One client, any transport — stdio, HTTP, or SSE behind the same API
- JSON Schema validation — automatically check tool results against the tool's declared
outputSchema - Mocking & recording — fabricate tool responses for unit tests, or record real ones once and replay them without a live server
bubblemcp testCLI — point it at a server and get a real-time pass/fail panel, with zero config for a first smoke test
If this is useful, a ⭐️ on the repo helps others find it.
Install
npm install bubblemcp-test-kitQuickstart
import { createMcpTestClient, expectMcp } from 'bubblemcp-test-kit'
const client = await createMcpTestClient({ transport: 'http', url: 'http://localhost:3000/mcp' })
const tools = await client.listTools()
const healthCheck = tools.find(t => t.name === 'health_check')!
const result = await client.callTool('health_check', { service: 'weather' })
expectMcp(result)
.toBeValidMcpResponse()
.toMatchOutputSchema(healthCheck)
await client.close()Works the same way with { transport: 'stdio', command: 'node', args: ['server.js'] } or { transport: 'sse', url: '...' } — swap the config, nothing else changes.
Assertions
expectMcp(result).toBeValidMcpResponse() // well-formed content, no protocol-level malformation
expectMcp(result).toBeError() // isError === true
expectMcp(result).not.toBeError() // negation works on any assertion
expectMcp(result).toMatchOutputSchema(tool) // validates structuredContent against tool.outputSchema
expectMcp(result).toContainText('healthy') // substring match against text content
expectMcp(result).toEqual(value) // deep equality against structuredContent/contentEvery assertion throws a plain Error on failure, so it works in Jest, Vitest, Mocha, node --test, or a bare script — anything that treats a thrown error as a failed test.
Input validation
// Throws before the call is even sent if args don't match the tool's inputSchema
await client.callTool('health_check', { service: 123 }, { validateInput: true })Mocking
Test your own agent/orchestration code's tool-calling logic without a real MCP server:
import { createMockMcpClient, expectMcp } from 'bubblemcp-test-kit'
const mock = createMockMcpClient()
mock.mockTool('health_check').resolves({ status: 'ok', latencyMs: 42 })
const result = await mock.callTool('health_check', { service: 'weather' })
expectMcp(result).toBeValidMcpResponse()
mock.mockTool('flaky_tool').rejects('rate limited')createMockMcpClient implements the same interface as the real client, so any code you write against McpTestClient works with either one.
Recording & replay
Record real responses once, replay them in CI with no live server:
import { createMcpTestClient, withRecording, createReplayClient } from 'bubblemcp-test-kit'
// Record (run this once, locally, against a real server)
const real = await createMcpTestClient({ transport: 'http', url: 'http://localhost:3000/mcp' })
const recording = withRecording(real, './fixtures/health-check.json')
await recording.callTool('health_check', { service: 'weather' })
await real.close()
// Replay (run this in CI — no network, no live server)
const replay = await createReplayClient('./fixtures/health-check.json')
const result = await replay.callTool('health_check', { service: 'weather' })CLI
bubblemcp test connects to a running MCP server and gives you a real-time terminal panel: every discovered tool with no required input args is auto smoke-tested against its declared outputSchema, no config needed.
bubblemcp test --url http://localhost:3000/mcp
bubblemcp test --stdio "node server.js"bubblemcp test — connecting to http://localhost:3000/mcp
✓ health_check 12ms
✓ list_users 8ms
✗ create_user 5ms
→ expected result to match "create_user"'s outputSchema, but it didn't: ...
– delete_user requires input args (id) — add a test case in bubblemcp.config.json
4 tools · 2 passed · 1 failed · 1 skipped · 25ms total
✖ bubblemcp test failedIt exits non-zero on any failure, so it works as a CI gate.
Tools that need arguments
Tools with required input args are skipped by auto-discovery — give them an explicit test case in bubblemcp.config.json (or .js/.mjs/.cjs, or a "bubblemcp" key in package.json):
{
"transport": { "transport": "http", "url": "http://localhost:3000/mcp" },
"timeout": 10000,
"tests": [
{
"tool": "create_user",
"args": { "name": "Ada" },
"expect": { "contains": "created" }
},
{
"tool": "delete_user",
"args": { "id": "does-not-exist" },
"expect": { "error": true }
}
]
}Each test case may set expect.schema (default true, validates structuredContent against outputSchema), expect.error (expect isError: true), expect.contains (substring match), and expect.equals (deep equality). Tools not listed in tests still get the automatic smoke test.
--config overrides auto-discovery of the config file; --stdio/--url/--transport/--header override the config's transport.
Options
--config <path> Path to a config file
--stdio <command> Launch the server over stdio, e.g. --stdio "node server.js"
--url <url> Connect over HTTP/SSE at this URL
--transport <type> stdio | http | sse | auto
--header <k: v> Add an HTTP header (repeatable)
--timeout <ms> Per-tool-call timeout (default: 10000)
--reporter <type> pretty | json — json emits one JSON object per line (NDJSON) plus a final summary
--webhook <url> POST the run summary as JSON when the run finishes (also read from BUBBLEMCP_WEBHOOK_URL)
--bail Stop at the first failing test--reporter json and --webhook exist so a CI pipeline — or a future dashboard — can consume run results as structured data instead of parsing terminal output.
License
MIT
